mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-02 02:00:49 +08:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1d7011047 | |||
| 1fe07410c0 | |||
| 0f3066f7d0 | |||
| c4c11da976 | |||
| 38537bf310 | |||
| 1f87cd8752 | |||
| f54cb5c296 | |||
| 45c016af8b | |||
| d4c905600b | |||
| f839e5c136 | |||
| 39abd1bbb8 | |||
| ecea7bd825 | |||
| d10e5ee92f | |||
| dace1d1738 | |||
| ae74f4ae51 | |||
| 9071c5032d | |||
| 8236ecf486 | |||
| a50270fc03 | |||
| ce7fffda08 | |||
| 3cc33bd40f | |||
| ee878e8f27 | |||
| 95f88c378c | |||
| 15db657b1c | |||
| e04d407b2f | |||
| 0bd1f10498 | |||
| 072fff1460 | |||
| 81a76634f2 | |||
| 1d865f16fe | |||
| e027564d2a | |||
| 3226a0a3fe | |||
| d5c959a83f | |||
| d2f016c628 | |||
| 839706cf65 | |||
| 6dac94d90a | |||
| 8c49a45162 | |||
| f6a45ae20b | |||
| afb3907efa | |||
| db79c00ea7 |
@@ -25,7 +25,7 @@ services:
|
||||
image: redis
|
||||
|
||||
indexer:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-10.1.0}
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
|
||||
@@ -12,4 +12,4 @@ DB_DATABASE_NAME=affine
|
||||
# ELASTIC_PLATFORM=linux/arm64
|
||||
|
||||
# manticoresearch
|
||||
MANTICORE_VERSION=9.3.2
|
||||
MANTICORE_VERSION=10.1.0
|
||||
|
||||
@@ -26,7 +26,7 @@ services:
|
||||
|
||||
# https://manual.manticoresearch.com/Starting_the_server/Docker
|
||||
manticoresearch:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-10.1.0}
|
||||
ports:
|
||||
- 9308:9308
|
||||
ulimits:
|
||||
|
||||
@@ -7,7 +7,10 @@ COPY ./packages/frontend/apps/mobile/dist /app/static/mobile
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends openssl && \
|
||||
apt-get install -y --no-install-recommends openssl libjemalloc2 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Enable jemalloc by preloading the library
|
||||
ENV LD_PRELOAD=libjemalloc.so.2
|
||||
|
||||
CMD ["node", "./dist/main.js"]
|
||||
|
||||
@@ -1064,24 +1064,36 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tests:
|
||||
- name: 'Cloud E2E Test 1/6'
|
||||
- name: 'Cloud E2E Test 1/10'
|
||||
shard: 1
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=1/6
|
||||
- name: 'Cloud E2E Test 2/6'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=1/10
|
||||
- name: 'Cloud E2E Test 2/10'
|
||||
shard: 2
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=2/6
|
||||
- name: 'Cloud E2E Test 3/6'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=2/10
|
||||
- name: 'Cloud E2E Test 3/10'
|
||||
shard: 3
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=3/6
|
||||
- name: 'Cloud E2E Test 4/6'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=3/10
|
||||
- name: 'Cloud E2E Test 4/10'
|
||||
shard: 4
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=4/6
|
||||
- name: 'Cloud E2E Test 5/6'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=4/10
|
||||
- name: 'Cloud E2E Test 5/10'
|
||||
shard: 5
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=5/6
|
||||
- name: 'Cloud E2E Test 6/6'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=5/10
|
||||
- name: 'Cloud E2E Test 6/10'
|
||||
shard: 6
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=6/6
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=6/10
|
||||
- name: 'Cloud E2E Test 7/10'
|
||||
shard: 7
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=7/10
|
||||
- name: 'Cloud E2E Test 8/10'
|
||||
shard: 8
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=8/10
|
||||
- name: 'Cloud E2E Test 9/10'
|
||||
shard: 9
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=9/10
|
||||
- name: 'Cloud E2E Test 10/10'
|
||||
shard: 10
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=10/10
|
||||
- name: 'Cloud Desktop E2E Test'
|
||||
shard: desktop
|
||||
script: |
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
AttachmentBlockStyles,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
CitationProvider,
|
||||
DocModeProvider,
|
||||
FileSizeLimitProvider,
|
||||
@@ -96,7 +96,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
type ToolbarAction,
|
||||
type ToolbarActionGroup,
|
||||
type ToolbarModuleConfig,
|
||||
@@ -241,10 +240,6 @@ const builtinToolbarConfig = {
|
||||
replaceAction,
|
||||
downloadAction,
|
||||
captionAction,
|
||||
{
|
||||
id: 'f.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
} from '@blocksuite/affine-model';
|
||||
import { ImageProxyService } from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
CitationProvider,
|
||||
DocModeProvider,
|
||||
LinkPreviewServiceIdentifier,
|
||||
@@ -132,7 +132,7 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
EmbedIframeService,
|
||||
EmbedOptionProvider,
|
||||
type LinkEventType,
|
||||
@@ -289,10 +288,6 @@ const builtinToolbarConfig = {
|
||||
},
|
||||
} satisfies ToolbarActionGroup<ToolbarAction>,
|
||||
captionAction,
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -19,8 +19,12 @@ import {
|
||||
export class CodeBlockHighlighter extends LifeCycleWatcher {
|
||||
static override key = 'code-block-highlighter';
|
||||
|
||||
private _darkThemeKey: string | undefined;
|
||||
// Singleton highlighter instance
|
||||
private static _sharedHighlighter: HighlighterCore | null = null;
|
||||
private static _highlighterPromise: Promise<HighlighterCore> | null = null;
|
||||
private static _refCount = 0;
|
||||
|
||||
private _darkThemeKey: string | undefined;
|
||||
private _lightThemeKey: string | undefined;
|
||||
|
||||
highlighter$: Signal<HighlighterCore | null> = signal(null);
|
||||
@@ -44,18 +48,45 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
|
||||
this.highlighter$.value = highlighter;
|
||||
};
|
||||
|
||||
private static async _getOrCreateHighlighter(): Promise<HighlighterCore> {
|
||||
if (CodeBlockHighlighter._sharedHighlighter) {
|
||||
return CodeBlockHighlighter._sharedHighlighter;
|
||||
}
|
||||
|
||||
if (!CodeBlockHighlighter._highlighterPromise) {
|
||||
CodeBlockHighlighter._highlighterPromise = createHighlighterCore({
|
||||
engine: createOnigurumaEngine(() => getWasm),
|
||||
}).then(highlighter => {
|
||||
CodeBlockHighlighter._sharedHighlighter = highlighter;
|
||||
return highlighter;
|
||||
});
|
||||
}
|
||||
|
||||
return CodeBlockHighlighter._highlighterPromise;
|
||||
}
|
||||
|
||||
override mounted(): void {
|
||||
super.mounted();
|
||||
|
||||
createHighlighterCore({
|
||||
engine: createOnigurumaEngine(() => getWasm),
|
||||
})
|
||||
CodeBlockHighlighter._refCount++;
|
||||
|
||||
CodeBlockHighlighter._getOrCreateHighlighter()
|
||||
.then(this._loadTheme)
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
override unmounted(): void {
|
||||
this.highlighter$.value?.dispose();
|
||||
CodeBlockHighlighter._refCount--;
|
||||
|
||||
// Only dispose the shared highlighter when no instances are using it
|
||||
if (
|
||||
CodeBlockHighlighter._refCount === 0 &&
|
||||
CodeBlockHighlighter._sharedHighlighter
|
||||
) {
|
||||
CodeBlockHighlighter._sharedHighlighter.dispose();
|
||||
CodeBlockHighlighter._sharedHighlighter = null;
|
||||
CodeBlockHighlighter._highlighterPromise = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
EDGELESS_TOP_CONTENTEDITABLE_SELECTOR,
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
DocModeProvider,
|
||||
NotificationProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
@@ -394,7 +394,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { toast } from '@blocksuite/affine-components/toast';
|
||||
import type { DatabaseBlockModel } from '@blocksuite/affine-model';
|
||||
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
CommentProviderIdentifier,
|
||||
DocModeProvider,
|
||||
NotificationProvider,
|
||||
@@ -316,7 +316,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
DocDisplayMetaProvider,
|
||||
EditorSettingProvider,
|
||||
type LinkEventType,
|
||||
@@ -306,10 +305,6 @@ const builtinToolbarConfig = {
|
||||
},
|
||||
} satisfies ToolbarActionGroup<ToolbarAction>,
|
||||
captionAction,
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
EditorSettingProvider,
|
||||
type LinkEventType,
|
||||
type OpenDocMode,
|
||||
@@ -226,10 +225,6 @@ const builtinToolbarConfig = {
|
||||
openDocActionGroup,
|
||||
conversionsActionGroup,
|
||||
captionAction,
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
EMBED_CARD_WIDTH,
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
DocModeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
@@ -65,7 +65,7 @@ export class EmbedBlockComponent<
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
EmbedOptionProvider,
|
||||
type LinkEventType,
|
||||
type ToolbarAction,
|
||||
@@ -349,10 +348,6 @@ function createBuiltinToolbarConfigForExternal(
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ImageBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
type ToolbarModuleConfig,
|
||||
ToolbarModuleExtension,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
@@ -50,10 +49,6 @@ const builtinToolbarConfig = {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'c.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
@@ -146,10 +141,6 @@ const builtinSurfaceToolbarConfig = {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'c.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
],
|
||||
|
||||
when: ctx => ctx.getSurfaceModelsByType(ImageBlockModel).length === 1,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ResourceController } from '@blocksuite/affine-components/resource';
|
||||
import type { ImageBlockModel } from '@blocksuite/affine-model';
|
||||
import { ImageSelection } from '@blocksuite/affine-shared/selection';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
ToolbarRegistryIdentifier,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { formatSize } from '@blocksuite/affine-shared/utils';
|
||||
@@ -71,7 +71,7 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
EDGELESS_TOP_CONTENTEDITABLE_SELECTOR,
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
CitationProvider,
|
||||
DocModeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
@@ -112,7 +112,7 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
type ElementLockEvent,
|
||||
type ToolbarAction,
|
||||
type ToolbarContext,
|
||||
@@ -305,6 +306,12 @@ export const builtinMiscToolbarConfig = {
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
placement: ActionPlacement.End,
|
||||
id: 'c.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
|
||||
// More actions
|
||||
...moreActions.map(action => ({
|
||||
...action,
|
||||
|
||||
@@ -305,7 +305,10 @@ export class PageRootBlockComponent extends BlockComponent<RootBlockModel> {
|
||||
);
|
||||
|
||||
// make sure there is a block can be focused
|
||||
if (notes.length === 0 || notes[notes.length - 1].children.length === 0) {
|
||||
if (
|
||||
!this.store.readonly$.value &&
|
||||
(notes.length === 0 || notes[notes.length - 1].children.length === 0)
|
||||
) {
|
||||
this.std.command.exec(appendParagraphCommand);
|
||||
return;
|
||||
}
|
||||
@@ -322,7 +325,7 @@ export class PageRootBlockComponent extends BlockComponent<RootBlockModel> {
|
||||
parseFloat(paddingLeft),
|
||||
parseFloat(paddingRight)
|
||||
);
|
||||
if (!isClickOnBlankArea) {
|
||||
if (!isClickOnBlankArea && !this.store.readonly$.value) {
|
||||
const lastBlock = notes[notes.length - 1].lastChild();
|
||||
if (
|
||||
!lastBlock ||
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
type ToolbarModuleConfig,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { CaptionIcon, CopyIcon, DeleteIcon } from '@blocksuite/icons/lit';
|
||||
@@ -62,10 +61,6 @@ export const surfaceRefToolbarModuleConfig: ToolbarModuleConfig = {
|
||||
surfaceRefBlock.captionElement.show();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
id: 'a.clipboard',
|
||||
placement: ActionPlacement.More,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
type SurfaceRefBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
DocModeProvider,
|
||||
EditPropsStore,
|
||||
type OpenDocMode,
|
||||
@@ -145,7 +145,7 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '@blocksuite/affine-ext-loader';
|
||||
import {
|
||||
AutoClearSelectionService,
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
CitationService,
|
||||
DefaultOpenDocExtension,
|
||||
DNDAPIExtension,
|
||||
@@ -79,7 +79,7 @@ export class FoundationViewExtension extends ViewExtensionProvider<FoundationVie
|
||||
LinkPreviewCache,
|
||||
LinkPreviewService,
|
||||
CitationService,
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
]);
|
||||
context.register(clipboardConfigs);
|
||||
if (this.isEdgeless(context.scope)) {
|
||||
|
||||
@@ -17,9 +17,9 @@ import {
|
||||
TextAlign,
|
||||
type TextStyleProps,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type {
|
||||
ToolbarActions,
|
||||
ToolbarContext,
|
||||
import {
|
||||
type ToolbarActions,
|
||||
type ToolbarContext,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
getMostCommonResolvedValue,
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export { InlineCommentManager } from './inline-comment-manager';
|
||||
export * from './inline-spec';
|
||||
export * from './utils';
|
||||
export * from './view';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { getInlineEditorByModel } from '@blocksuite/affine-rich-text';
|
||||
import { getSelectedBlocksCommand } from '@blocksuite/affine-shared/commands';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
BlockElementCommentManager,
|
||||
type CommentId,
|
||||
CommentProviderIdentifier,
|
||||
findAllCommentedBlocks,
|
||||
findAllCommentedElements,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { AffineInlineEditor } from '@blocksuite/affine-shared/types';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
@@ -42,10 +43,14 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
||||
|
||||
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
||||
this._disposables.add(
|
||||
provider.onCommentDeleted(this._handleDeleteAndResolve)
|
||||
provider.onCommentDeleted(id =>
|
||||
this._handleDeleteAndResolve(id, 'delete')
|
||||
)
|
||||
);
|
||||
this._disposables.add(
|
||||
provider.onCommentResolved(this._handleDeleteAndResolve)
|
||||
provider.onCommentResolved(id =>
|
||||
this._handleDeleteAndResolve(id, 'resolve')
|
||||
)
|
||||
);
|
||||
this._disposables.add(
|
||||
provider.onCommentHighlighted(this._handleHighlightComment)
|
||||
@@ -63,23 +68,35 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
||||
const provider = this._provider;
|
||||
if (!provider) return;
|
||||
|
||||
const commentsInProvider = await provider.getComments('unresolved');
|
||||
const commentsInProvider = await provider.getComments('all');
|
||||
|
||||
const commentsInEditor = this.getCommentsInEditor();
|
||||
|
||||
// remove comments that are in editor but not in provider
|
||||
// which means the comment may be removed or resolved in provider side
|
||||
difference(commentsInEditor, commentsInProvider).forEach(comment => {
|
||||
this.std
|
||||
.get(BlockElementCommentManager)
|
||||
.handleDeleteAndResolve(comment, 'delete');
|
||||
});
|
||||
}
|
||||
|
||||
getCommentsInEditor() {
|
||||
const inlineComments = [...findAllCommentedTexts(this.std.store).values()];
|
||||
|
||||
const blockComments = findAllCommentedBlocks(this.std.store).flatMap(
|
||||
block => Object.keys(block.props.comments)
|
||||
);
|
||||
|
||||
const surfaceComments = findAllCommentedElements(this.std.store).flatMap(
|
||||
element => Object.keys(element.comments)
|
||||
);
|
||||
|
||||
const commentsInEditor = [
|
||||
...new Set([...inlineComments, ...blockComments]),
|
||||
...new Set([...inlineComments, ...blockComments, ...surfaceComments]),
|
||||
];
|
||||
|
||||
// remove comments that are in editor but not in provider
|
||||
// which means the comment may be removed or resolved in provider side
|
||||
difference(commentsInEditor, commentsInProvider).forEach(comment => {
|
||||
this._handleDeleteAndResolve(comment);
|
||||
this.std.get(BlockCommentManager).handleDeleteAndResolve(comment);
|
||||
});
|
||||
return commentsInEditor;
|
||||
}
|
||||
|
||||
private readonly _handleAddComment = (
|
||||
@@ -150,7 +167,10 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _handleDeleteAndResolve = (id: CommentId) => {
|
||||
private readonly _handleDeleteAndResolve = (
|
||||
id: CommentId,
|
||||
type: 'delete' | 'resolve'
|
||||
) => {
|
||||
const commentedTexts = findCommentedTexts(this.std.store, id);
|
||||
if (commentedTexts.length === 0) return;
|
||||
|
||||
@@ -164,7 +184,7 @@ export class InlineCommentManager extends LifeCycleWatcher {
|
||||
inlineEditor?.formatText(
|
||||
selection.from,
|
||||
{
|
||||
[`comment-${id}`]: null,
|
||||
[`comment-${id}`]: type === 'delete' ? null : false,
|
||||
},
|
||||
{
|
||||
withoutTransact: true,
|
||||
|
||||
@@ -22,7 +22,7 @@ import { isEqual } from 'lodash-es';
|
||||
})
|
||||
export class InlineComment extends WithDisposable(ShadowlessElement) {
|
||||
static override styles = css`
|
||||
inline-comment {
|
||||
inline-comment.unresolved {
|
||||
display: inline-block;
|
||||
background-color: ${unsafeCSSVarV2('block/comment/highlightDefault')};
|
||||
border-bottom: 2px solid
|
||||
@@ -41,6 +41,9 @@ export class InlineComment extends WithDisposable(ShadowlessElement) {
|
||||
})
|
||||
accessor commentIds!: string[];
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor unresolved = false;
|
||||
|
||||
private _index: number = 0;
|
||||
|
||||
@consume({ context: stdContext })
|
||||
@@ -54,8 +57,10 @@ export class InlineComment extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
|
||||
private readonly _handleClick = () => {
|
||||
this._provider?.highlightComment(this.commentIds[this._index]);
|
||||
this._index = (this._index + 1) % this.commentIds.length;
|
||||
if (this.unresolved) {
|
||||
this._provider?.highlightComment(this.commentIds[this._index]);
|
||||
this._index = (this._index + 1) % this.commentIds.length;
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _handleHighlight = (id: CommentId | null) => {
|
||||
@@ -89,6 +94,13 @@ export class InlineComment extends WithDisposable(ShadowlessElement) {
|
||||
this.classList.remove('highlighted');
|
||||
}
|
||||
}
|
||||
if (_changedProperties.has('unresolved')) {
|
||||
if (this.unresolved) {
|
||||
this.classList.add('unresolved');
|
||||
} else {
|
||||
this.classList.remove('unresolved');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override render() {
|
||||
|
||||
@@ -21,18 +21,39 @@ export const CommentInlineSpecExtension =
|
||||
),
|
||||
match: delta => {
|
||||
if (!delta.attributes) return false;
|
||||
const comments = Object.entries(delta.attributes).filter(
|
||||
([key, value]) => isInlineCommendId(key) && value === true
|
||||
);
|
||||
const comments = Object.keys(delta.attributes).filter(isInlineCommendId);
|
||||
return comments.length > 0;
|
||||
},
|
||||
renderer: ({ delta, children }) =>
|
||||
html`<inline-comment .commentIds=${extractCommentIdFromDelta(delta)}
|
||||
renderer: ({ delta, children }) => {
|
||||
if (!delta.attributes) return html`${nothing}`;
|
||||
|
||||
const unresolved = Object.entries(delta.attributes).some(
|
||||
([key, value]) => isInlineCommendId(key) && value === true
|
||||
);
|
||||
return html`<inline-comment
|
||||
.unresolved=${unresolved}
|
||||
.commentIds=${extractCommentIdFromDelta(delta)}
|
||||
>${when(
|
||||
children,
|
||||
() => html`${children}`,
|
||||
() => nothing
|
||||
)}</inline-comment
|
||||
>`,
|
||||
>`;
|
||||
},
|
||||
wrapper: true,
|
||||
});
|
||||
|
||||
export const NullCommentInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'comment',
|
||||
schema: dynamicSchema(
|
||||
isInlineCommendId,
|
||||
z.boolean().optional().nullable().catch(undefined)
|
||||
),
|
||||
match: () => false,
|
||||
renderer: () => html``,
|
||||
});
|
||||
|
||||
// reuse the same identifier
|
||||
NullCommentInlineSpecExtension.identifier =
|
||||
CommentInlineSpecExtension.identifier;
|
||||
|
||||
@@ -2,21 +2,41 @@ import {
|
||||
type ViewExtensionContext,
|
||||
ViewExtensionProvider,
|
||||
} from '@blocksuite/affine-ext-loader';
|
||||
import z from 'zod';
|
||||
|
||||
import { effects } from './effects';
|
||||
import { InlineCommentManager } from './inline-comment-manager';
|
||||
import { CommentInlineSpecExtension } from './inline-spec';
|
||||
import {
|
||||
CommentInlineSpecExtension,
|
||||
NullCommentInlineSpecExtension,
|
||||
} from './inline-spec';
|
||||
|
||||
export class InlineCommentViewExtension extends ViewExtensionProvider {
|
||||
const optionsSchema = z.object({
|
||||
enabled: z.boolean().optional().default(true),
|
||||
});
|
||||
|
||||
export class InlineCommentViewExtension extends ViewExtensionProvider<
|
||||
z.infer<typeof optionsSchema>
|
||||
> {
|
||||
override name = 'affine-inline-comment';
|
||||
|
||||
override schema = optionsSchema;
|
||||
|
||||
override effect(): void {
|
||||
super.effect();
|
||||
effects();
|
||||
}
|
||||
|
||||
override setup(context: ViewExtensionContext) {
|
||||
super.setup(context);
|
||||
context.register([CommentInlineSpecExtension, InlineCommentManager]);
|
||||
override setup(
|
||||
context: ViewExtensionContext,
|
||||
options?: z.infer<typeof optionsSchema>
|
||||
) {
|
||||
super.setup(context, options);
|
||||
context.register([
|
||||
options?.enabled
|
||||
? CommentInlineSpecExtension
|
||||
: NullCommentInlineSpecExtension,
|
||||
InlineCommentManager,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import { DividerBlockModel } from '@blocksuite/affine-model';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import {
|
||||
BlockSelection,
|
||||
LifeCycleWatcher,
|
||||
TextSelection,
|
||||
} from '@blocksuite/std';
|
||||
import type { BaseSelection, BlockModel } from '@blocksuite/store';
|
||||
import { signal } from '@preact/signals-core';
|
||||
|
||||
import { getSelectedBlocksCommand } from '../../commands';
|
||||
import { ImageSelection } from '../../selection';
|
||||
import { matchModels } from '../../utils';
|
||||
import { type CommentId, CommentProviderIdentifier } from './comment-provider';
|
||||
import { findCommentedBlocks } from './utils';
|
||||
|
||||
export class BlockCommentManager extends LifeCycleWatcher {
|
||||
static override key = 'block-comment-manager';
|
||||
|
||||
private readonly _highlightedCommentId$ = signal<CommentId | null>(null);
|
||||
|
||||
private readonly _disposables = new DisposableGroup();
|
||||
|
||||
private get _provider() {
|
||||
return this.std.getOptional(CommentProviderIdentifier);
|
||||
}
|
||||
|
||||
isBlockCommentHighlighted(
|
||||
block: BlockModel<{ comments?: Record<CommentId, boolean> }>
|
||||
) {
|
||||
const comments = block.props.comments;
|
||||
if (!comments) return false;
|
||||
return (
|
||||
this._highlightedCommentId$.value !== null &&
|
||||
Object.keys(comments).includes(this._highlightedCommentId$.value)
|
||||
);
|
||||
}
|
||||
|
||||
override mounted() {
|
||||
const provider = this._provider;
|
||||
if (!provider) return;
|
||||
|
||||
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
||||
this._disposables.add(
|
||||
provider.onCommentDeleted(this.handleDeleteAndResolve)
|
||||
);
|
||||
this._disposables.add(
|
||||
provider.onCommentResolved(this.handleDeleteAndResolve)
|
||||
);
|
||||
this._disposables.add(
|
||||
provider.onCommentHighlighted(this._handleHighlightComment)
|
||||
);
|
||||
}
|
||||
|
||||
override unmounted() {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
private readonly _handleAddComment = (
|
||||
id: CommentId,
|
||||
selections: BaseSelection[]
|
||||
) => {
|
||||
const blocksFromTextRange = selections
|
||||
.filter((s): s is TextSelection => s.is(TextSelection))
|
||||
.map(s => {
|
||||
const [_, { selectedBlocks }] = this.std.command.exec(
|
||||
getSelectedBlocksCommand,
|
||||
{
|
||||
textSelection: s,
|
||||
}
|
||||
);
|
||||
if (!selectedBlocks) return [];
|
||||
return selectedBlocks.map(b => b.model).filter(m => !m.text);
|
||||
});
|
||||
|
||||
const needCommentBlocks = [
|
||||
...blocksFromTextRange.flat(),
|
||||
...selections
|
||||
.filter(s => s instanceof BlockSelection || s instanceof ImageSelection)
|
||||
.map(({ blockId }) => this.std.store.getModelById(blockId))
|
||||
.filter(
|
||||
(m): m is BlockModel =>
|
||||
m !== null && !matchModels(m, [DividerBlockModel])
|
||||
),
|
||||
];
|
||||
|
||||
if (needCommentBlocks.length === 0) return;
|
||||
|
||||
this.std.store.withoutTransact(() => {
|
||||
needCommentBlocks.forEach(block => {
|
||||
const comments = (
|
||||
'comments' in block.props &&
|
||||
typeof block.props.comments === 'object' &&
|
||||
block.props.comments !== null
|
||||
? block.props.comments
|
||||
: {}
|
||||
) as Record<CommentId, boolean>;
|
||||
|
||||
this.std.store.updateBlock(block, {
|
||||
comments: { [id]: true, ...comments },
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
readonly handleDeleteAndResolve = (id: CommentId) => {
|
||||
const commentedBlocks = findCommentedBlocks(this.std.store, id);
|
||||
this.std.store.withoutTransact(() => {
|
||||
commentedBlocks.forEach(block => {
|
||||
delete block.props.comments[id];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _handleHighlightComment = (id: CommentId | null) => {
|
||||
this._highlightedCommentId$.value = id;
|
||||
};
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
import { DividerBlockModel } from '@blocksuite/affine-model';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import {
|
||||
BlockSelection,
|
||||
LifeCycleWatcher,
|
||||
SurfaceSelection,
|
||||
TextSelection,
|
||||
} from '@blocksuite/std';
|
||||
import {
|
||||
GfxControllerIdentifier,
|
||||
type GfxModel,
|
||||
type GfxPrimitiveElementModel,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import type { BaseSelection, BlockModel } from '@blocksuite/store';
|
||||
import { signal } from '@preact/signals-core';
|
||||
|
||||
import { getSelectedBlocksCommand } from '../../commands';
|
||||
import { ImageSelection } from '../../selection';
|
||||
import { matchModels } from '../../utils';
|
||||
import { type CommentId, CommentProviderIdentifier } from './comment-provider';
|
||||
import { findCommentedBlocks, findCommentedElements } from './utils';
|
||||
|
||||
export class BlockElementCommentManager extends LifeCycleWatcher {
|
||||
static override key = 'block-element-comment-manager';
|
||||
|
||||
private readonly _highlightedCommentId$ = signal<CommentId | null>(null);
|
||||
|
||||
private readonly _disposables = new DisposableGroup();
|
||||
|
||||
private get _provider() {
|
||||
return this.std.getOptional(CommentProviderIdentifier);
|
||||
}
|
||||
|
||||
isBlockCommentHighlighted(
|
||||
block: BlockModel<{ comments?: Record<CommentId, boolean> }>
|
||||
) {
|
||||
const comments = block.props.comments;
|
||||
if (!comments) return false;
|
||||
return (
|
||||
this._highlightedCommentId$.value !== null &&
|
||||
Object.keys(comments).includes(this._highlightedCommentId$.value)
|
||||
);
|
||||
}
|
||||
|
||||
isElementCommentHighlighted(element: GfxPrimitiveElementModel) {
|
||||
const comments = element.comments;
|
||||
if (!comments) return false;
|
||||
return (
|
||||
this._highlightedCommentId$.value !== null &&
|
||||
Object.keys(comments).includes(this._highlightedCommentId$.value)
|
||||
);
|
||||
}
|
||||
|
||||
override mounted() {
|
||||
const provider = this._provider;
|
||||
if (!provider) return;
|
||||
|
||||
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
|
||||
this._disposables.add(
|
||||
provider.onCommentDeleted(id => this.handleDeleteAndResolve(id, 'delete'))
|
||||
);
|
||||
this._disposables.add(
|
||||
provider.onCommentResolved(id =>
|
||||
this.handleDeleteAndResolve(id, 'resolve')
|
||||
)
|
||||
);
|
||||
this._disposables.add(
|
||||
provider.onCommentHighlighted(this._handleHighlightComment)
|
||||
);
|
||||
}
|
||||
|
||||
override unmounted() {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
private readonly _handleAddComment = (
|
||||
id: CommentId,
|
||||
selections: BaseSelection[]
|
||||
) => {
|
||||
// get blocks from text range that some no-text blocks are selected such as image, bookmark, etc.
|
||||
const noTextBlocksFromTextRange = selections
|
||||
.filter((s): s is TextSelection => s.is(TextSelection))
|
||||
.flatMap(s => {
|
||||
const [_, { selectedBlocks }] = this.std.command.exec(
|
||||
getSelectedBlocksCommand,
|
||||
{
|
||||
textSelection: s,
|
||||
}
|
||||
);
|
||||
if (!selectedBlocks) return [];
|
||||
return selectedBlocks.map(b => b.model).filter(m => !m.text);
|
||||
});
|
||||
|
||||
const blocksFromBlockSelection = selections
|
||||
.filter(s => s instanceof BlockSelection || s instanceof ImageSelection)
|
||||
.map(({ blockId }) => this.std.store.getModelById(blockId))
|
||||
.filter(
|
||||
(m): m is BlockModel =>
|
||||
m !== null && !matchModels(m, [DividerBlockModel])
|
||||
);
|
||||
|
||||
const needCommentBlocks = [
|
||||
...noTextBlocksFromTextRange,
|
||||
...blocksFromBlockSelection,
|
||||
];
|
||||
|
||||
if (needCommentBlocks.length !== 0) {
|
||||
this.std.store.withoutTransact(() => {
|
||||
needCommentBlocks.forEach(block => {
|
||||
const comments = (
|
||||
'comments' in block.props &&
|
||||
typeof block.props.comments === 'object' &&
|
||||
block.props.comments !== null
|
||||
? block.props.comments
|
||||
: {}
|
||||
) as Record<CommentId, boolean>;
|
||||
|
||||
this.std.store.updateBlock(block, {
|
||||
comments: { [id]: true, ...comments },
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const gfx = this.std.get(GfxControllerIdentifier);
|
||||
const elementsFromSurfaceSelection = selections
|
||||
.filter(s => s instanceof SurfaceSelection)
|
||||
.flatMap(({ elements }) => {
|
||||
return elements
|
||||
.map(id => gfx.getElementById<GfxModel>(id))
|
||||
.filter(m => m !== null);
|
||||
});
|
||||
if (elementsFromSurfaceSelection.length !== 0) {
|
||||
this.std.store.withoutTransact(() => {
|
||||
elementsFromSurfaceSelection.forEach(element => {
|
||||
const comments =
|
||||
'comments' in element &&
|
||||
typeof element.comments === 'object' &&
|
||||
element.comments !== null
|
||||
? element.comments
|
||||
: {};
|
||||
|
||||
gfx.updateElement(element, {
|
||||
comments: { [id]: true, ...comments },
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
readonly handleDeleteAndResolve = (
|
||||
id: CommentId,
|
||||
type: 'delete' | 'resolve'
|
||||
) => {
|
||||
const commentedBlocks = findCommentedBlocks(this.std.store, id);
|
||||
this.std.store.withoutTransact(() => {
|
||||
commentedBlocks.forEach(block => {
|
||||
if (type === 'delete') {
|
||||
delete block.props.comments[id];
|
||||
} else {
|
||||
block.props.comments[id] = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const commentedElements = findCommentedElements(this.std.store, id);
|
||||
this.std.store.withoutTransact(() => {
|
||||
commentedElements.forEach(element => {
|
||||
if (type === 'delete') {
|
||||
delete element.comments[id];
|
||||
} else {
|
||||
element.comments[id] = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _handleHighlightComment = (id: CommentId | null) => {
|
||||
this._highlightedCommentId$.value = id;
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './block-comment-manager';
|
||||
export * from './block-element-comment-manager';
|
||||
export * from './comment-provider';
|
||||
export * from './utils';
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { CommentIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import type { BlockModel, Store } from '@blocksuite/store';
|
||||
import { BlockSelection, SurfaceSelection } from '@blocksuite/std';
|
||||
import type {
|
||||
GfxPrimitiveElementModel,
|
||||
SurfaceBlockModel,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import { BlockModel, type Store } from '@blocksuite/store';
|
||||
|
||||
import type { ToolbarAction } from '../toolbar-service';
|
||||
import { type CommentId, CommentProviderIdentifier } from './comment-provider';
|
||||
@@ -22,6 +26,31 @@ export function findCommentedBlocks(store: Store, commentId: CommentId) {
|
||||
});
|
||||
}
|
||||
|
||||
export function findAllCommentedElements(store: Store) {
|
||||
type CommentedElement = GfxPrimitiveElementModel & {
|
||||
comments: Record<CommentId, boolean>;
|
||||
};
|
||||
const surface = store.getModelsByFlavour('affine:surface')[0] as
|
||||
| SurfaceBlockModel
|
||||
| undefined;
|
||||
if (!surface) return [];
|
||||
|
||||
return surface.elementModels.filter(
|
||||
(element): element is CommentedElement => {
|
||||
return (
|
||||
element.comments !== undefined &&
|
||||
Object.keys(element.comments).length > 0
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function findCommentedElements(store: Store, commentId: CommentId) {
|
||||
return findAllCommentedElements(store).filter(element => {
|
||||
return element.comments[commentId];
|
||||
});
|
||||
}
|
||||
|
||||
export const blockCommentToolbarButton: Omit<ToolbarAction, 'id'> = {
|
||||
tooltip: 'Comment',
|
||||
when: ({ std }) => !!std.getOptional(CommentProviderIdentifier),
|
||||
@@ -29,22 +58,26 @@ export const blockCommentToolbarButton: Omit<ToolbarAction, 'id'> = {
|
||||
run: ctx => {
|
||||
const commentProvider = ctx.std.getOptional(CommentProviderIdentifier);
|
||||
if (!commentProvider) return;
|
||||
const selections = ctx.selection.value;
|
||||
|
||||
const selections = ctx.selection.value;
|
||||
const model = ctx.getCurrentModel();
|
||||
|
||||
if (selections.length > 1) {
|
||||
// may be hover on a block or element, in this case
|
||||
// the selection is empty, so we need to get the current model
|
||||
if (model && selections.length === 0) {
|
||||
if (model instanceof BlockModel) {
|
||||
commentProvider.addComment([
|
||||
new BlockSelection({
|
||||
blockId: model.id,
|
||||
}),
|
||||
]);
|
||||
} else if (ctx.gfx.surface?.id) {
|
||||
commentProvider.addComment([
|
||||
new SurfaceSelection(ctx.gfx.surface.id, [model.id], false),
|
||||
]);
|
||||
}
|
||||
} else if (selections.length > 0) {
|
||||
commentProvider.addComment(selections);
|
||||
} else if (model) {
|
||||
commentProvider.addComment([
|
||||
new BlockSelection({
|
||||
blockId: model.id,
|
||||
}),
|
||||
]);
|
||||
} else if (selections.length === 1) {
|
||||
commentProvider.addComment(selections);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,7 +40,6 @@ export interface NotificationService {
|
||||
}[];
|
||||
onClose?: () => void;
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* Notify with undo action, it is a helper function to notify with undo action.
|
||||
* And the notification card will be closed when undo action is triggered by shortcut key or other ways.
|
||||
@@ -55,13 +54,16 @@ export const NotificationProvider = createIdentifier<NotificationService>(
|
||||
);
|
||||
|
||||
export function NotificationExtension(
|
||||
notificationService: Omit<NotificationService, 'notifyWithUndoAction'>
|
||||
notificationService: NotificationService
|
||||
): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(NotificationProvider, provider => {
|
||||
return {
|
||||
...notificationService,
|
||||
notify: notificationService.notify,
|
||||
toast: notificationService.toast,
|
||||
confirm: notificationService.confirm,
|
||||
prompt: notificationService.prompt,
|
||||
notifyWithUndoAction: options => {
|
||||
notifyWithUndoActionImpl(
|
||||
provider,
|
||||
|
||||
@@ -52,6 +52,7 @@ export type BaseElementProps = {
|
||||
index: string;
|
||||
seed: number;
|
||||
lockedBySelf?: boolean;
|
||||
comments?: Record<string, boolean>;
|
||||
};
|
||||
|
||||
export type SerializedElement = Record<string, unknown> & {
|
||||
@@ -60,6 +61,7 @@ export type SerializedElement = Record<string, unknown> & {
|
||||
id: string;
|
||||
index: string;
|
||||
lockedBySelf?: boolean;
|
||||
comments?: Record<string, boolean>;
|
||||
props: Record<string, unknown>;
|
||||
};
|
||||
export abstract class GfxPrimitiveElementModel<
|
||||
@@ -372,6 +374,9 @@ export abstract class GfxPrimitiveElementModel<
|
||||
|
||||
@field()
|
||||
accessor seed!: number;
|
||||
|
||||
@field()
|
||||
accessor comments: Record<string, boolean> | undefined = undefined;
|
||||
}
|
||||
|
||||
export abstract class GfxGroupLikeElementModel<
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface BlockStdOptions {
|
||||
extensions: ExtensionType[];
|
||||
}
|
||||
|
||||
const internalExtensions = [
|
||||
export const internalExtensions = [
|
||||
ServiceManager,
|
||||
CommandManager,
|
||||
UIEventDispatcher,
|
||||
|
||||
@@ -33,6 +33,7 @@ export function getTestCommonExtensions(
|
||||
di.override(DocModeProvider, mockDocModeService(editor));
|
||||
},
|
||||
},
|
||||
// CommentProviderExtension(mockCommentProvider()),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -1,5 +1,9 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "ai_sessions_metadata" ADD COLUMN "updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
ALTER TABLE ai_sessions_metadata ADD COLUMN updated_at TIMESTAMPTZ(3);
|
||||
|
||||
UPDATE ai_sessions_metadata SET updated_at = created_at;
|
||||
|
||||
ALTER TABLE ai_sessions_metadata ALTER COLUMN updated_at SET NOT NULL, ALTER COLUMN updated_at SET DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX IF EXISTS "ai_session_unique_doc_session_idx";
|
||||
@@ -369,7 +369,7 @@ The term **“CRDT”** was first introduced by Marc Shapiro, Nuno Preguiça, Ca
|
||||
.map(c => JSON.parse(c.citationJson).type)
|
||||
.filter(type => ['attachment', 'doc'].includes(type)).length ===
|
||||
0,
|
||||
'should not have citation'
|
||||
`should not have citation: ${JSON.stringify(c, null, 2)}`
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1891,7 +1891,7 @@ test('should handle generateSessionTitle correctly under various conditions', as
|
||||
await session.generateSessionTitle({ sessionId });
|
||||
|
||||
if (testCase.expectSnapshot) {
|
||||
const sessionState = await session.getSession(sessionId);
|
||||
const sessionState = await session.getSessionInfo(sessionId);
|
||||
t.snapshot(
|
||||
{
|
||||
chatWithPromptCalled: testCase.expectNotCalled
|
||||
|
||||
@@ -21,3 +21,13 @@ e2e('should comment feature enabled by default', async t => {
|
||||
JSON.stringify(serverConfig, null, 2)
|
||||
);
|
||||
});
|
||||
|
||||
e2e('should enable local workspace feature by default', async t => {
|
||||
const { serverConfig } = await app.gql({ query: serverConfigQuery });
|
||||
|
||||
t.is(
|
||||
serverConfig.features.includes(ServerFeature.LocalWorkspace),
|
||||
true,
|
||||
JSON.stringify(serverConfig, null, 2)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
notificationCountQuery,
|
||||
NotificationObjectType,
|
||||
NotificationType,
|
||||
readAllNotificationsMutation,
|
||||
readNotificationMutation,
|
||||
} from '@affine/graphql';
|
||||
|
||||
@@ -677,3 +678,41 @@ e2e('should list and count notifications', async t => {
|
||||
t.is(result3.currentUser!.notifications.edges.length, 0);
|
||||
}
|
||||
});
|
||||
|
||||
e2e('should mark all notifications as read', async t => {
|
||||
const { member, owner, workspace } = await init();
|
||||
await app.login(owner);
|
||||
|
||||
await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-1',
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await app.login(member);
|
||||
|
||||
await app.gql({
|
||||
query: readAllNotificationsMutation,
|
||||
});
|
||||
|
||||
const result = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 10,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
t.is(result.currentUser!.notifications.totalCount, 0);
|
||||
});
|
||||
|
||||
@@ -73,7 +73,8 @@ e2e('should get comment attachment body', async t => {
|
||||
docId,
|
||||
key,
|
||||
'test.txt',
|
||||
Buffer.from('test')
|
||||
Buffer.from('test'),
|
||||
owner.id
|
||||
);
|
||||
|
||||
const res = await app.GET(
|
||||
|
||||
@@ -361,7 +361,8 @@ export class CommentResolver {
|
||||
docId,
|
||||
key,
|
||||
attachment.filename ?? key,
|
||||
buffer
|
||||
buffer,
|
||||
me.id
|
||||
);
|
||||
return this.commentAttachmentStorage.getUrl(workspaceId, docId, key);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ export class ServerConfigResolver {
|
||||
baseUrl: this.url.requestBaseUrl,
|
||||
type: env.DEPLOYMENT_TYPE,
|
||||
features: this.server.features,
|
||||
// TODO(@fengmk2): remove this field after the feature 0.25.0 is released
|
||||
allowGuestDemoWorkspace: this.config.flags.allowGuestDemoWorkspace,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,6 +110,13 @@ export class ServerService implements OnApplicationBootstrap {
|
||||
this.event.emit('config.changed', event);
|
||||
}
|
||||
|
||||
@OnEvent('config.changed')
|
||||
onConfigChanged(event: Events['config.changed']) {
|
||||
if ('flags' in event.updates) {
|
||||
this.onFlagsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
async revalidateConfig() {
|
||||
const overrides = await this.loadDbOverrides();
|
||||
this.configFactory.override(overrides);
|
||||
@@ -122,6 +129,7 @@ export class ServerService implements OnApplicationBootstrap {
|
||||
await this.event.emitAsync('config.init', {
|
||||
config: this.configFactory.config,
|
||||
});
|
||||
this.onFlagsChanged();
|
||||
}
|
||||
|
||||
private async loadDbOverrides() {
|
||||
@@ -134,4 +142,13 @@ export class ServerService implements OnApplicationBootstrap {
|
||||
|
||||
return overrides;
|
||||
}
|
||||
|
||||
private onFlagsChanged() {
|
||||
const flags = this.configFactory.config.flags;
|
||||
if (flags.allowGuestDemoWorkspace) {
|
||||
this.enableFeature(ServerFeature.LocalWorkspace);
|
||||
} else {
|
||||
this.disableFeature(ServerFeature.LocalWorkspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export enum ServerFeature {
|
||||
OAuth = 'oauth',
|
||||
Indexer = 'indexer',
|
||||
Comment = 'comment',
|
||||
LocalWorkspace = 'local_workspace',
|
||||
}
|
||||
|
||||
registerEnumType(ServerFeature, {
|
||||
@@ -42,6 +43,8 @@ export class ServerConfigType {
|
||||
|
||||
@Field(() => Boolean, {
|
||||
description: 'Whether allow guest users to create demo workspaces.',
|
||||
deprecationReason:
|
||||
'This field is deprecated, please use `features` instead. Will be removed in 0.25.0',
|
||||
})
|
||||
allowGuestDemoWorkspace!: boolean;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,14 @@ export class UserNotificationResolver {
|
||||
await this.service.markAsRead(me.id, notificationId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, {
|
||||
description: 'mark all notifications as read',
|
||||
})
|
||||
async readAllNotifications(@CurrentUser() me: UserType) {
|
||||
await this.service.markAllAsRead(me.id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Resolver(() => NotificationObjectType)
|
||||
|
||||
@@ -399,6 +399,10 @@ export class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
async markAllAsRead(userId: string) {
|
||||
await this.models.notification.markAllAsRead(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find notifications by user id, order by createdAt desc
|
||||
*/
|
||||
|
||||
@@ -24,11 +24,12 @@ test.after.always(async () => {
|
||||
|
||||
test('should put comment attachment', async t => {
|
||||
const workspace = await module.create(Mockers.Workspace);
|
||||
const user = await module.create(Mockers.User);
|
||||
const docId = randomUUID();
|
||||
const key = randomUUID();
|
||||
const blob = Buffer.from('test');
|
||||
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob);
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob, user.id);
|
||||
|
||||
const item = await models.commentAttachment.get(workspace.id, docId, key);
|
||||
|
||||
@@ -39,15 +40,17 @@ test('should put comment attachment', async t => {
|
||||
t.is(item?.mime, 'text/plain');
|
||||
t.is(item?.size, blob.length);
|
||||
t.is(item?.name, 'test.txt');
|
||||
t.is(item?.createdBy, user.id);
|
||||
});
|
||||
|
||||
test('should get comment attachment', async t => {
|
||||
const workspace = await module.create(Mockers.Workspace);
|
||||
const user = await module.create(Mockers.User);
|
||||
const docId = randomUUID();
|
||||
const key = randomUUID();
|
||||
const blob = Buffer.from('test');
|
||||
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob);
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob, user.id);
|
||||
|
||||
const item = await storage.get(workspace.id, docId, key);
|
||||
|
||||
@@ -62,11 +65,12 @@ test('should get comment attachment', async t => {
|
||||
|
||||
test('should get comment attachment with access url', async t => {
|
||||
const workspace = await module.create(Mockers.Workspace);
|
||||
const user = await module.create(Mockers.User);
|
||||
const docId = randomUUID();
|
||||
const key = randomUUID();
|
||||
const blob = Buffer.from('test');
|
||||
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob);
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob, user.id);
|
||||
|
||||
const url = storage.getUrl(workspace.id, docId, key);
|
||||
|
||||
@@ -79,11 +83,12 @@ test('should get comment attachment with access url', async t => {
|
||||
|
||||
test('should delete comment attachment', async t => {
|
||||
const workspace = await module.create(Mockers.Workspace);
|
||||
const user = await module.create(Mockers.User);
|
||||
const docId = randomUUID();
|
||||
const key = randomUUID();
|
||||
const blob = Buffer.from('test');
|
||||
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob);
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob, user.id);
|
||||
|
||||
await storage.delete(workspace.id, docId, key);
|
||||
|
||||
@@ -94,11 +99,12 @@ test('should delete comment attachment', async t => {
|
||||
|
||||
test('should handle comment.attachment.delete event', async t => {
|
||||
const workspace = await module.create(Mockers.Workspace);
|
||||
const user = await module.create(Mockers.User);
|
||||
const docId = randomUUID();
|
||||
const key = randomUUID();
|
||||
const blob = Buffer.from('test');
|
||||
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob);
|
||||
await storage.put(workspace.id, docId, key, 'test.txt', blob, user.id);
|
||||
|
||||
await storage.onCommentAttachmentDelete({
|
||||
workspaceId: workspace.id,
|
||||
@@ -113,14 +119,15 @@ test('should handle comment.attachment.delete event', async t => {
|
||||
|
||||
test('should handle workspace.deleted event', async t => {
|
||||
const workspace = await module.create(Mockers.Workspace);
|
||||
const user = await module.create(Mockers.User);
|
||||
const docId = randomUUID();
|
||||
const key1 = randomUUID();
|
||||
const key2 = randomUUID();
|
||||
const blob1 = Buffer.from('test');
|
||||
const blob2 = Buffer.from('test2');
|
||||
|
||||
await storage.put(workspace.id, docId, key1, 'test.txt', blob1);
|
||||
await storage.put(workspace.id, docId, key2, 'test.txt', blob2);
|
||||
await storage.put(workspace.id, docId, key1, 'test.txt', blob1, user.id);
|
||||
await storage.put(workspace.id, docId, key2, 'test.txt', blob2, user.id);
|
||||
|
||||
const count = module.event.count('comment.attachment.delete');
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ export class CommentAttachmentStorage {
|
||||
docId: string,
|
||||
key: string,
|
||||
name: string,
|
||||
blob: Buffer
|
||||
blob: Buffer,
|
||||
userId: string
|
||||
) {
|
||||
const meta = autoMetadata(blob);
|
||||
|
||||
@@ -75,6 +76,7 @@ export class CommentAttachmentStorage {
|
||||
name,
|
||||
mime: meta.contentType ?? 'application/octet-stream',
|
||||
size: blob.length,
|
||||
createdBy: userId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ export class WorkspacesController {
|
||||
await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Read');
|
||||
|
||||
const { body, metadata, redirectUrl } =
|
||||
await this.commentAttachmentStorage.get(workspaceId, docId, key);
|
||||
await this.commentAttachmentStorage.get(workspaceId, docId, key, true);
|
||||
|
||||
if (redirectUrl) {
|
||||
return res.redirect(redirectUrl);
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { chunk } from 'lodash-es';
|
||||
|
||||
type SessionTime = {
|
||||
sessionId: string;
|
||||
_max: {
|
||||
createdAt: Date;
|
||||
};
|
||||
};
|
||||
|
||||
export class CorrectSessionUpdateTime1751966744168 {
|
||||
// do the migration
|
||||
static async up(db: PrismaClient) {
|
||||
const sessionTime = await db.aiSessionMessage.groupBy({
|
||||
by: ['sessionId'],
|
||||
_max: {
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const s of chunk(sessionTime, 100)) {
|
||||
const sessions = s.filter((s): s is SessionTime => !!s._max.createdAt);
|
||||
await db.$transaction(async tx => {
|
||||
await Promise.all(
|
||||
sessions.map(s =>
|
||||
tx.aiSession.update({
|
||||
where: { id: s.sessionId },
|
||||
data: { updatedAt: s._max.createdAt },
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// revert the migration
|
||||
static async down(_db: PrismaClient) {}
|
||||
}
|
||||
@@ -6,3 +6,4 @@ export * from './1732861452428-migrate-invite-status';
|
||||
export * from './1733125339942-universal-subscription';
|
||||
export * from './1738590347632-feature-redundant';
|
||||
export * from './1745211351719-create-indexer-tables';
|
||||
export * from './1751966744168-correct-session-update-time';
|
||||
|
||||
@@ -13,6 +13,7 @@ test.after.always(async () => {
|
||||
|
||||
test('should upsert comment attachment', async t => {
|
||||
const workspace = await module.create(Mockers.Workspace);
|
||||
const user = await module.create(Mockers.User);
|
||||
|
||||
// add
|
||||
const item = await models.commentAttachment.upsert({
|
||||
@@ -22,6 +23,7 @@ test('should upsert comment attachment', async t => {
|
||||
name: 'test-name',
|
||||
mime: 'text/plain',
|
||||
size: 100,
|
||||
createdBy: user.id,
|
||||
});
|
||||
|
||||
t.is(item.workspaceId, workspace.id);
|
||||
@@ -30,6 +32,7 @@ test('should upsert comment attachment', async t => {
|
||||
t.is(item.mime, 'text/plain');
|
||||
t.is(item.size, 100);
|
||||
t.truthy(item.createdAt);
|
||||
t.is(item.createdBy, user.id);
|
||||
|
||||
// update
|
||||
const item2 = await models.commentAttachment.upsert({
|
||||
@@ -46,6 +49,7 @@ test('should upsert comment attachment', async t => {
|
||||
t.is(item2.key, 'test-key');
|
||||
t.is(item2.mime, 'text/html');
|
||||
t.is(item2.size, 200);
|
||||
t.is(item2.createdBy, user.id);
|
||||
|
||||
// make sure only one blob is created
|
||||
const items = await models.commentAttachment.list(workspace.id);
|
||||
|
||||
@@ -430,3 +430,32 @@ test('should create a comment mention notification', async t => {
|
||||
t.is(notification.body.commentId, commentId);
|
||||
t.is(notification.body.replyId, replyId);
|
||||
});
|
||||
|
||||
test('should mark all notifications as read', async t => {
|
||||
await models.notification.createMention({
|
||||
userId: user.id,
|
||||
body: {
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: docId,
|
||||
title: 'doc-title',
|
||||
blockId: 'blockId',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
createdByUserId: createdBy.id,
|
||||
},
|
||||
});
|
||||
await models.notification.createInvitation({
|
||||
userId: user.id,
|
||||
body: {
|
||||
workspaceId: workspace.id,
|
||||
createdByUserId: createdBy.id,
|
||||
inviteId: randomUUID(),
|
||||
},
|
||||
});
|
||||
|
||||
await models.notification.markAllAsRead(user.id);
|
||||
|
||||
const notifications = await models.notification.findManyByUserId(user.id);
|
||||
t.is(notifications.length, 0);
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ export class CommentAttachmentModel extends BaseModel {
|
||||
name: input.name,
|
||||
mime: input.mime,
|
||||
size: input.size,
|
||||
createdBy: input.createdBy,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -265,26 +265,31 @@ export class CopilotSessionModel extends BaseModel {
|
||||
userId: true,
|
||||
workspaceId: true,
|
||||
docId: true,
|
||||
pinned: true,
|
||||
parentSessionId: true,
|
||||
pinned: true,
|
||||
title: true,
|
||||
promptName: true,
|
||||
tokenCost: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
messages: {
|
||||
select: {
|
||||
id: true,
|
||||
role: true,
|
||||
content: true,
|
||||
streamObjects: true,
|
||||
attachments: true,
|
||||
streamObjects: true,
|
||||
params: true,
|
||||
createdAt: true,
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
},
|
||||
promptName: true,
|
||||
});
|
||||
}
|
||||
|
||||
async list(options: ListSessionOptions) {
|
||||
private getListConditions(
|
||||
options: ListSessionOptions
|
||||
): Prisma.AiSessionWhereInput {
|
||||
const { userId, sessionId, workspaceId, docId, action, fork } = options;
|
||||
|
||||
function getNullCond<T>(
|
||||
@@ -330,8 +335,18 @@ export class CopilotSessionModel extends BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
return { OR: conditions };
|
||||
}
|
||||
|
||||
async count(options: ListSessionOptions) {
|
||||
return await this.db.aiSession.count({
|
||||
where: this.getListConditions(options),
|
||||
});
|
||||
}
|
||||
|
||||
async list(options: ListSessionOptions) {
|
||||
return await this.db.aiSession.findMany({
|
||||
where: { OR: conditions },
|
||||
where: this.getListConditions(options),
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
@@ -351,8 +366,8 @@ export class CopilotSessionModel extends BaseModel {
|
||||
role: true,
|
||||
content: true,
|
||||
attachments: true,
|
||||
params: true,
|
||||
streamObjects: true,
|
||||
params: true,
|
||||
createdAt: true,
|
||||
},
|
||||
orderBy: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Transactional } from '@nestjs-cls/transactional';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
|
||||
import { PaginationInput } from '../base';
|
||||
import { BaseModel } from './base';
|
||||
@@ -16,6 +16,10 @@ import type {
|
||||
|
||||
@Injectable()
|
||||
export class CopilotWorkspaceConfigModel extends BaseModel {
|
||||
constructor(private readonly database: PrismaClient) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Transactional()
|
||||
private async listIgnoredDocIds(
|
||||
workspaceId: string,
|
||||
@@ -41,28 +45,25 @@ export class CopilotWorkspaceConfigModel extends BaseModel {
|
||||
* @param workspaceId id of the workspace
|
||||
* @returns docIds
|
||||
*/
|
||||
@Transactional()
|
||||
async findDocsToEmbed(workspaceId: string): Promise<string[]> {
|
||||
const ignoredDocIds = (await this.listIgnoredDocIds(workspaceId)).map(
|
||||
d => d.docId
|
||||
);
|
||||
// NOTE: for unknown reason, the transaction will timeout if call from event handler
|
||||
// so we use an independent client here
|
||||
const docIds = await this.database.$queryRaw<{ id: string }[]>`
|
||||
SELECT s.guid as id
|
||||
FROM snapshots AS s
|
||||
LEFT JOIN ai_workspace_embeddings e
|
||||
ON e.workspace_id = s.workspace_id
|
||||
AND e.doc_id = s.guid
|
||||
LEFT JOIN ai_workspace_ignored_docs id
|
||||
ON id.workspace_id = s.workspace_id
|
||||
AND id.doc_id = s.guid
|
||||
WHERE s.workspace_id = ${workspaceId}
|
||||
AND s.guid != s.workspace_id
|
||||
AND s.guid NOT LIKE '%$%'
|
||||
AND e.doc_id IS NULL
|
||||
AND id.doc_id IS NULL;`;
|
||||
|
||||
const docIds = await this.db.snapshot
|
||||
.findMany({
|
||||
where: {
|
||||
workspaceId,
|
||||
AND: [
|
||||
{ id: { notIn: ignoredDocIds } },
|
||||
{ id: { not: workspaceId } },
|
||||
{ id: { not: { contains: '$' } } },
|
||||
],
|
||||
embedding: { none: {} },
|
||||
},
|
||||
select: { id: true },
|
||||
})
|
||||
.then(r => r.map(doc => doc.id));
|
||||
|
||||
return docIds;
|
||||
return docIds.map(r => r.id);
|
||||
}
|
||||
|
||||
@Transactional()
|
||||
|
||||
@@ -261,6 +261,18 @@ export class NotificationModel extends BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
async markAllAsRead(userId: string) {
|
||||
const { count } = await this.db.notification.updateMany({
|
||||
where: { userId },
|
||||
data: {
|
||||
read: true,
|
||||
},
|
||||
});
|
||||
this.logger.log(
|
||||
`Marked all notifications as read for user ${userId}, count: ${count}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find many notifications by user id, exclude read notifications by default
|
||||
*/
|
||||
|
||||
@@ -112,11 +112,14 @@ class ProductionEmbeddingClient extends EmbeddingClient {
|
||||
);
|
||||
|
||||
try {
|
||||
return ranks.map((score, chunk) => ({
|
||||
chunk,
|
||||
targetId: this.getTargetId(embeddings[chunk]),
|
||||
score,
|
||||
}));
|
||||
return ranks.map((score, i) => {
|
||||
const chunk = embeddings[i];
|
||||
return {
|
||||
chunk: chunk.chunk,
|
||||
targetId: this.getTargetId(chunk),
|
||||
score: Math.max(score, 1 - (chunk.distance || -Infinity)),
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to parse rerank results', error);
|
||||
// silent error, will fallback to default sorting in parent method
|
||||
@@ -148,7 +151,7 @@ class ProductionEmbeddingClient extends EmbeddingClient {
|
||||
|
||||
const chunks = sortedEmbeddings.reduce(
|
||||
(acc, e) => {
|
||||
const targetId = 'docId' in e ? e.docId : 'fileId' in e ? e.fileId : '';
|
||||
const targetId = this.getTargetId(e);
|
||||
const key = `${targetId}:${e.chunk}`;
|
||||
acc[key] = e;
|
||||
return acc;
|
||||
@@ -179,7 +182,10 @@ class ProductionEmbeddingClient extends EmbeddingClient {
|
||||
.filter(Boolean);
|
||||
|
||||
this.logger.verbose(
|
||||
`ReRank completed: ${highConfidenceChunks.length} high-confidence results found`
|
||||
`ReRank completed: ${highConfidenceChunks.length} high-confidence results found, total ${sortedEmbeddings.length} embeddings`,
|
||||
highConfidenceChunks.length !== sortedEmbeddings.length
|
||||
? JSON.stringify(ranks)
|
||||
: undefined
|
||||
);
|
||||
return highConfidenceChunks.slice(0, topK);
|
||||
} catch (error) {
|
||||
|
||||
@@ -338,7 +338,7 @@ Convert a multi-speaker audio recording into a structured JSON format by transcr
|
||||
{
|
||||
name: 'Rerank results',
|
||||
action: 'Rerank results',
|
||||
model: 'gpt-4.1-mini',
|
||||
model: 'gpt-4.1',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1286,7 +1286,7 @@ If there are items in the content that can be used as to-do tasks, please refer
|
||||
{
|
||||
name: 'Make it real',
|
||||
action: 'Make it real',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'claude-sonnet-4@20250514',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1327,7 +1327,7 @@ When sent new wireframes, respond ONLY with the contents of the html file.`,
|
||||
{
|
||||
name: 'Make it real with text',
|
||||
action: 'Make it real with text',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
model: 'claude-sonnet-4@20250514',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -1675,8 +1675,9 @@ This sentence contains information from the first source[^1]. This sentence refe
|
||||
|
||||
<tool-calling-guidelines>
|
||||
Before starting Tool calling, you need to follow:
|
||||
- DO NOT explain what operation you will perform.
|
||||
- DO NOT embed a tool call mid-sentence.
|
||||
- When searching for unknown information or keyword, prioritize searching the user's workspace.
|
||||
- When searching for unknown information, personal information or keyword, prioritize searching the user's workspace rather than the web.
|
||||
- Depending on the complexity of the question and the information returned by the search tools, you can call different tools multiple times to search.
|
||||
</tool-calling-guidelines>
|
||||
|
||||
|
||||
@@ -53,8 +53,11 @@ export class PromptService implements OnApplicationBootstrap {
|
||||
* @returns prompt messages
|
||||
*/
|
||||
async get(name: string): Promise<ChatPrompt | null> {
|
||||
const cached = this.cache.get(name);
|
||||
if (cached) return cached;
|
||||
// skip cache in dev mode to ensure the latest prompt is always fetched
|
||||
if (!env.dev) {
|
||||
const cached = this.cache.get(name);
|
||||
if (cached) return cached;
|
||||
}
|
||||
|
||||
const prompt = await this.db.aiPrompt.findUnique({
|
||||
where: {
|
||||
|
||||
@@ -62,6 +62,7 @@ export abstract class AnthropicProvider<T> extends CopilotProvider<T> {
|
||||
|
||||
try {
|
||||
metrics.ai.counter('chat_text_calls').add(1, { model: model.id });
|
||||
|
||||
const [system, msgs] = await chatToGPTMessage(messages, true, true);
|
||||
|
||||
const modelInstance = this.instance(model.id);
|
||||
|
||||
@@ -88,6 +88,12 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
||||
system,
|
||||
messages: msgs,
|
||||
abortSignal: options.signal,
|
||||
providerOptions: {
|
||||
google: this.getGeminiOptions(options, model.id),
|
||||
},
|
||||
tools: await this.getTools(options, model.id),
|
||||
maxSteps: this.MAX_STEPS,
|
||||
experimental_continueSteps: true,
|
||||
});
|
||||
|
||||
if (!text) throw new Error('Failed to generate text');
|
||||
@@ -233,12 +239,16 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
||||
taskType: 'RETRIEVAL_DOCUMENT',
|
||||
});
|
||||
|
||||
const { embeddings } = await embedMany({
|
||||
model: modelInstance,
|
||||
values: messages,
|
||||
});
|
||||
const embeddings = await Promise.allSettled(
|
||||
messages.map(m =>
|
||||
embedMany({ model: modelInstance, values: [m], maxRetries: 3 })
|
||||
)
|
||||
);
|
||||
|
||||
return embeddings.filter(v => v && Array.isArray(v));
|
||||
return embeddings
|
||||
.map(e => (e.status === 'fulfilled' ? e.value.embeddings : null))
|
||||
.flat()
|
||||
.filter((v): v is number[] => !!v && Array.isArray(v));
|
||||
} catch (e: any) {
|
||||
metrics.ai
|
||||
.counter('generate_embedding_errors')
|
||||
@@ -254,16 +264,16 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
||||
) {
|
||||
const [system, msgs] = await chatToGPTMessage(messages);
|
||||
const { fullStream } = streamText({
|
||||
model: this.instance(model.id, {
|
||||
useSearchGrounding: this.useSearchGrounding(options),
|
||||
}),
|
||||
model: this.instance(model.id),
|
||||
system,
|
||||
messages: msgs,
|
||||
abortSignal: options.signal,
|
||||
maxSteps: this.MAX_STEPS,
|
||||
providerOptions: {
|
||||
google: this.getGeminiOptions(options, model.id),
|
||||
},
|
||||
tools: await this.getTools(options, model.id),
|
||||
maxSteps: this.MAX_STEPS,
|
||||
experimental_continueSteps: true,
|
||||
});
|
||||
return fullStream;
|
||||
}
|
||||
@@ -282,8 +292,4 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
||||
private isReasoningModel(model: string) {
|
||||
return model.startsWith('gemini-2.5');
|
||||
}
|
||||
|
||||
private useSearchGrounding(options: CopilotChatOptions) {
|
||||
return options?.tools?.includes('webSearch');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,9 +274,11 @@ export class OpenAIProvider extends CopilotProvider<OpenAIConfig> {
|
||||
override getProviderSpecificTools(
|
||||
toolName: CopilotChatTools,
|
||||
model: string
|
||||
): [string, Tool] | undefined {
|
||||
): [string, Tool?] | undefined {
|
||||
if (toolName === 'webSearch' && !this.isReasoningModel(model)) {
|
||||
return ['web_search_preview', openai.tools.webSearchPreview()];
|
||||
} else if (toolName === 'docEdit') {
|
||||
return ['doc_edit', undefined];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ export abstract class CopilotProvider<C = any> {
|
||||
protected getProviderSpecificTools(
|
||||
_toolName: CopilotChatTools,
|
||||
_model: string
|
||||
): [string, Tool] | undefined {
|
||||
): [string, Tool?] | undefined {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,10 @@ export abstract class CopilotProvider<C = any> {
|
||||
for (const tool of options.tools) {
|
||||
const toolDef = this.getProviderSpecificTools(tool, model);
|
||||
if (toolDef) {
|
||||
tools[toolDef[0]] = toolDef[1];
|
||||
// allow provider prevent tool creation
|
||||
if (toolDef[1]) {
|
||||
tools[toolDef[0]] = toolDef[1];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
switch (tool) {
|
||||
|
||||
@@ -25,6 +25,9 @@ import {
|
||||
CopilotFailedToCreateMessage,
|
||||
CopilotSessionNotFound,
|
||||
type FileUpload,
|
||||
paginate,
|
||||
Paginated,
|
||||
PaginationInput,
|
||||
RequestMutex,
|
||||
Throttle,
|
||||
TooManyRequest,
|
||||
@@ -38,12 +41,7 @@ import { PromptService } from './prompt';
|
||||
import { PromptMessage, StreamObject } from './providers';
|
||||
import { ChatSessionService } from './session';
|
||||
import { CopilotStorage } from './storage';
|
||||
import {
|
||||
type ChatHistory,
|
||||
type ChatMessage,
|
||||
type ChatSessionState,
|
||||
SubmittedMessage,
|
||||
} from './types';
|
||||
import { type ChatHistory, type ChatMessage, SubmittedMessage } from './types';
|
||||
|
||||
export const COPILOT_LOCKER = 'copilot';
|
||||
|
||||
@@ -186,6 +184,9 @@ class QueryChatHistoriesInput
|
||||
@Field(() => String, { nullable: true })
|
||||
sessionId: string | undefined;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
withMessages: boolean | undefined;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
withPrompt: boolean | undefined;
|
||||
}
|
||||
@@ -239,7 +240,7 @@ class ChatMessageType implements Partial<ChatMessage> {
|
||||
}
|
||||
|
||||
@ObjectType('CopilotHistories')
|
||||
class CopilotHistoriesType implements Partial<ChatHistory> {
|
||||
class CopilotHistoriesType implements Omit<ChatHistory, 'userId'> {
|
||||
@Field(() => String)
|
||||
sessionId!: string;
|
||||
|
||||
@@ -249,8 +250,17 @@ class CopilotHistoriesType implements Partial<ChatHistory> {
|
||||
@Field(() => String, { nullable: true })
|
||||
docId!: string | null;
|
||||
|
||||
@Field(() => Boolean)
|
||||
pinned!: boolean;
|
||||
@Field(() => String, { nullable: true })
|
||||
parentSessionId!: string | null;
|
||||
|
||||
@Field(() => String)
|
||||
promptName!: string;
|
||||
|
||||
@Field(() => String)
|
||||
model!: string;
|
||||
|
||||
@Field(() => [String])
|
||||
optionalModels!: string[];
|
||||
|
||||
@Field(() => String, {
|
||||
description: 'An mark identifying which view to use to display the session',
|
||||
@@ -258,6 +268,12 @@ class CopilotHistoriesType implements Partial<ChatHistory> {
|
||||
})
|
||||
action!: string | null;
|
||||
|
||||
@Field(() => Boolean)
|
||||
pinned!: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
title!: string | null;
|
||||
|
||||
@Field(() => Number, {
|
||||
description: 'The number of tokens used in the session',
|
||||
})
|
||||
@@ -273,6 +289,11 @@ class CopilotHistoriesType implements Partial<ChatHistory> {
|
||||
updatedAt!: Date;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class PaginatedCopilotHistoriesType extends Paginated(
|
||||
CopilotHistoriesType
|
||||
) {}
|
||||
|
||||
@ObjectType('CopilotQuota')
|
||||
class CopilotQuotaType {
|
||||
@Field(() => SafeIntResolver, { nullable: true })
|
||||
@@ -421,7 +442,7 @@ export class CopilotResolver {
|
||||
@Args('sessionId') sessionId: string
|
||||
): Promise<CopilotSessionType> {
|
||||
await this.assertPermission(user, copilot);
|
||||
const session = await this.chatSession.getSession(sessionId);
|
||||
const session = await this.chatSession.getSessionInfo(sessionId);
|
||||
if (!session) {
|
||||
throw new NotFoundException('Session not found');
|
||||
}
|
||||
@@ -430,6 +451,7 @@ export class CopilotResolver {
|
||||
|
||||
@ResolveField(() => [CopilotSessionType], {
|
||||
description: 'Get the session list in the workspace',
|
||||
deprecationReason: 'use `chats` instead',
|
||||
complexity: 2,
|
||||
})
|
||||
async sessions(
|
||||
@@ -447,11 +469,12 @@ export class CopilotResolver {
|
||||
Object.assign({}, copilot, { docId: maybeDocId })
|
||||
);
|
||||
|
||||
const sessions = await this.chatSession.listSessions(
|
||||
Object.assign({}, options, appendOptions)
|
||||
const sessions = await this.chatSession.list(
|
||||
Object.assign({}, options, appendOptions),
|
||||
false
|
||||
);
|
||||
if (appendOptions.docId) {
|
||||
type Session = Omit<ChatSessionState, 'messages'> & { docId: string };
|
||||
type Session = ChatHistory & { docId: string };
|
||||
const filtered = sessions.filter((s): s is Session => !!s.docId);
|
||||
const accessible = await this.ac
|
||||
.user(user.id)
|
||||
@@ -463,7 +486,9 @@ export class CopilotResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@ResolveField(() => [CopilotHistoriesType], {})
|
||||
@ResolveField(() => [CopilotHistoriesType], {
|
||||
deprecationReason: 'use `chats` instead',
|
||||
})
|
||||
@CallMetric('ai', 'histories')
|
||||
async histories(
|
||||
@Parent() copilot: CopilotType,
|
||||
@@ -478,8 +503,9 @@ export class CopilotResolver {
|
||||
await this.assertPermission(user, { workspaceId, docId });
|
||||
}
|
||||
|
||||
const histories = await this.chatSession.listHistories(
|
||||
Object.assign({}, options, { userId: user.id, workspaceId, docId })
|
||||
const histories = await this.chatSession.list(
|
||||
Object.assign({}, options, { userId: user.id, workspaceId, docId }),
|
||||
true
|
||||
);
|
||||
|
||||
return histories.map(h => ({
|
||||
@@ -491,6 +517,48 @@ export class CopilotResolver {
|
||||
}));
|
||||
}
|
||||
|
||||
@ResolveField(() => PaginatedCopilotHistoriesType, {})
|
||||
@CallMetric('ai', 'histories')
|
||||
async chats(
|
||||
@Parent() copilot: CopilotType,
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('pagination', PaginationInput.decode) pagination: PaginationInput,
|
||||
@Args('docId', { nullable: true }) docId?: string,
|
||||
@Args('options', { nullable: true }) options?: QueryChatHistoriesInput
|
||||
): Promise<PaginatedCopilotHistoriesType> {
|
||||
const workspaceId = copilot.workspaceId;
|
||||
if (!workspaceId) {
|
||||
return paginate([], 'updatedAt', pagination, 0);
|
||||
} else {
|
||||
await this.assertPermission(user, { workspaceId, docId });
|
||||
}
|
||||
|
||||
const finalOptions = Object.assign(
|
||||
{},
|
||||
options,
|
||||
{ userId: user.id, workspaceId, docId },
|
||||
{ skip: pagination.offset, limit: pagination.first }
|
||||
);
|
||||
const totalCount = await this.chatSession.count(finalOptions);
|
||||
const histories = await this.chatSession.list(
|
||||
finalOptions,
|
||||
!!options?.withMessages
|
||||
);
|
||||
|
||||
return paginate(
|
||||
histories.map(h => ({
|
||||
...h,
|
||||
// filter out empty messages
|
||||
messages: h.messages?.filter(
|
||||
m => m.content || m.attachments?.length
|
||||
) as ChatMessageType[],
|
||||
})),
|
||||
'updatedAt',
|
||||
pagination,
|
||||
totalCount
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => String, {
|
||||
description: 'Create a chat session',
|
||||
})
|
||||
@@ -657,18 +725,9 @@ export class CopilotResolver {
|
||||
}
|
||||
|
||||
private transformToSessionType(
|
||||
session: Omit<ChatSessionState, 'messages'>
|
||||
session: Omit<ChatHistory, 'messages'>
|
||||
): CopilotSessionType {
|
||||
return {
|
||||
id: session.sessionId,
|
||||
parentSessionId: session.parentSessionId,
|
||||
docId: session.docId,
|
||||
pinned: session.pinned,
|
||||
title: session.title,
|
||||
promptName: session.prompt.name,
|
||||
model: session.prompt.model,
|
||||
optionalModels: session.prompt.optionalModels,
|
||||
};
|
||||
return { id: session.sessionId, ...session };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { Transactional } from '@nestjs-cls/transactional';
|
||||
import { AiPromptRole } from '@prisma/client';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
import {
|
||||
CopilotActionTaken,
|
||||
@@ -25,7 +26,7 @@ import {
|
||||
UpdateChatSessionOptions,
|
||||
} from '../../models';
|
||||
import { ChatMessageCache } from './message';
|
||||
import { PromptService } from './prompt';
|
||||
import { ChatPrompt, PromptService } from './prompt';
|
||||
import {
|
||||
CopilotProviderFactory,
|
||||
ModelOutputType,
|
||||
@@ -240,6 +241,14 @@ export class ChatSession implements AsyncDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
type Session = NonNullable<
|
||||
Awaited<ReturnType<Models['copilotSession']['get']>>
|
||||
>;
|
||||
|
||||
type SessionHistory = ChatHistory & {
|
||||
prompt: ChatPrompt;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ChatSessionService {
|
||||
private readonly logger = new Logger(ChatSessionService.name);
|
||||
@@ -253,27 +262,55 @@ export class ChatSessionService {
|
||||
private readonly prompt: PromptService
|
||||
) {}
|
||||
|
||||
async getSession(sessionId: string): Promise<ChatSessionState | undefined> {
|
||||
const session = await this.models.copilotSession.get(sessionId);
|
||||
if (!session) return;
|
||||
private getMessage(session: Session): ChatMessage[] {
|
||||
if (!Array.isArray(session.messages) || !session.messages.length) {
|
||||
return [];
|
||||
}
|
||||
const messages = ChatMessageSchema.array().safeParse(session.messages);
|
||||
if (!messages.success) {
|
||||
this.logger.error(
|
||||
`Unexpected message schema: ${JSON.stringify(messages.error)}`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
return messages.data;
|
||||
}
|
||||
|
||||
private async getHistory(session: Session): Promise<SessionHistory> {
|
||||
const prompt = await this.prompt.get(session.promptName);
|
||||
if (!prompt) throw new CopilotPromptNotFound({ name: session.promptName });
|
||||
|
||||
const messages = ChatMessageSchema.array().safeParse(session.messages);
|
||||
|
||||
return {
|
||||
...pick(session, [
|
||||
'userId',
|
||||
'workspaceId',
|
||||
'docId',
|
||||
'parentSessionId',
|
||||
'pinned',
|
||||
'title',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
]),
|
||||
sessionId: session.id,
|
||||
userId: session.userId,
|
||||
workspaceId: session.workspaceId,
|
||||
docId: session.docId,
|
||||
pinned: session.pinned,
|
||||
title: session.title,
|
||||
parentSessionId: session.parentSessionId,
|
||||
tokens: session.tokenCost,
|
||||
messages: this.getMessage(session),
|
||||
|
||||
// prompt info
|
||||
prompt,
|
||||
messages: messages.success ? messages.data : [],
|
||||
action: prompt.action || null,
|
||||
model: prompt.model,
|
||||
optionalModels: prompt.optionalModels || null,
|
||||
promptName: prompt.name,
|
||||
};
|
||||
}
|
||||
|
||||
async getSessionInfo(sessionId: string): Promise<SessionHistory | undefined> {
|
||||
const session = await this.models.copilotSession.get(sessionId);
|
||||
if (!session) return;
|
||||
|
||||
return await this.getHistory(session);
|
||||
}
|
||||
|
||||
// revert the latest messages not generate by user
|
||||
// after revert, we can retry the action
|
||||
async revertLatestMessage(
|
||||
@@ -286,116 +323,70 @@ export class ChatSessionService {
|
||||
);
|
||||
}
|
||||
|
||||
async listSessions(
|
||||
options: ListSessionOptions
|
||||
): Promise<Omit<ChatSessionState, 'messages'>[]> {
|
||||
const sessions = await this.models.copilotSession.list({
|
||||
...options,
|
||||
withMessages: false,
|
||||
});
|
||||
|
||||
return Promise.all(
|
||||
sessions.map(async session => {
|
||||
const prompt = await this.prompt.get(session.promptName);
|
||||
if (!prompt)
|
||||
throw new CopilotPromptNotFound({ name: session.promptName });
|
||||
|
||||
return {
|
||||
sessionId: session.id,
|
||||
userId: session.userId,
|
||||
workspaceId: session.workspaceId,
|
||||
docId: session.docId,
|
||||
pinned: session.pinned,
|
||||
title: session.title,
|
||||
parentSessionId: session.parentSessionId,
|
||||
prompt,
|
||||
};
|
||||
})
|
||||
);
|
||||
async count(options: ListSessionOptions): Promise<number> {
|
||||
return await this.models.copilotSession.count(options);
|
||||
}
|
||||
|
||||
async listHistories(options: ListSessionOptions): Promise<ChatHistory[]> {
|
||||
const { userId } = options;
|
||||
async list(
|
||||
options: ListSessionOptions,
|
||||
withMessages: boolean
|
||||
): Promise<ChatHistory[]> {
|
||||
const { userId: reqUserId } = options;
|
||||
const sessions = await this.models.copilotSession.list({
|
||||
...options,
|
||||
withMessages: true,
|
||||
withMessages,
|
||||
});
|
||||
const histories = await Promise.all(
|
||||
sessions.map(
|
||||
async ({
|
||||
userId: uid,
|
||||
id,
|
||||
workspaceId,
|
||||
docId,
|
||||
pinned,
|
||||
title,
|
||||
promptName,
|
||||
tokenCost,
|
||||
messages,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
}) => {
|
||||
try {
|
||||
const prompt = await this.prompt.get(promptName);
|
||||
if (!prompt) {
|
||||
throw new CopilotPromptNotFound({ name: promptName });
|
||||
}
|
||||
sessions.map(async session => {
|
||||
const { userId, id: sessionId, createdAt } = session;
|
||||
try {
|
||||
const { prompt, messages, ...baseHistory } =
|
||||
await this.getHistory(session);
|
||||
|
||||
if (withMessages) {
|
||||
if (
|
||||
// filter out the user's session that not match the action option
|
||||
(uid === userId && !!options?.action !== !!prompt.action) ||
|
||||
(userId === reqUserId && !!options?.action !== !!prompt.action) ||
|
||||
// filter out the non chat session from other user
|
||||
(uid !== userId && !!prompt.action)
|
||||
(userId !== reqUserId && !!prompt.action)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const ret = ChatMessageSchema.array().safeParse(messages);
|
||||
if (ret.success) {
|
||||
// render system prompt
|
||||
const preload = (
|
||||
options?.withPrompt
|
||||
? prompt
|
||||
.finish(ret.data[0]?.params || {}, id)
|
||||
.filter(({ role }) => role !== 'system')
|
||||
: []
|
||||
) as ChatMessage[];
|
||||
// render system prompt
|
||||
const preload = (
|
||||
options?.withPrompt
|
||||
? prompt
|
||||
.finish(messages[0]?.params || {}, sessionId)
|
||||
.filter(({ role }) => role !== 'system')
|
||||
: []
|
||||
) as ChatMessage[];
|
||||
|
||||
// `createdAt` is required for history sorting in frontend
|
||||
// let's fake the creating time of prompt messages
|
||||
preload.forEach((msg, i) => {
|
||||
msg.createdAt = new Date(
|
||||
createdAt.getTime() - preload.length - i - 1
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
sessionId: id,
|
||||
workspaceId,
|
||||
docId,
|
||||
pinned,
|
||||
title,
|
||||
action: prompt.action || null,
|
||||
tokens: tokenCost,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
messages: preload.concat(ret.data).map(m => ({
|
||||
...m,
|
||||
attachments: m.attachments
|
||||
?.map(a => (typeof a === 'string' ? a : a.attachment))
|
||||
.filter(a => !!a),
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
this.logger.error(
|
||||
`Unexpected message schema: ${JSON.stringify(ret.error)}`
|
||||
// `createdAt` is required for history sorting in frontend
|
||||
// let's fake the creating time of prompt messages
|
||||
preload.forEach((msg, i) => {
|
||||
msg.createdAt = new Date(
|
||||
createdAt.getTime() - preload.length - i - 1
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error('Unexpected error in listHistories', e);
|
||||
});
|
||||
|
||||
return {
|
||||
...baseHistory,
|
||||
messages: preload.concat(messages).map(m => ({
|
||||
...m,
|
||||
attachments: m.attachments
|
||||
?.map(a => (typeof a === 'string' ? a : a.attachment))
|
||||
.filter(a => !!a),
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
return { ...baseHistory, messages: [] };
|
||||
}
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
this.logger.error('Unexpected error in list ChatHistories', e);
|
||||
}
|
||||
)
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
|
||||
return histories.filter((v): v is NonNullable<typeof v> => !!v);
|
||||
@@ -461,7 +452,7 @@ export class ChatSessionService {
|
||||
|
||||
@Transactional()
|
||||
async update(options: UpdateChatSession): Promise<string> {
|
||||
const session = await this.getSession(options.sessionId);
|
||||
const session = await this.getSessionInfo(options.sessionId);
|
||||
if (!session) {
|
||||
throw new CopilotSessionNotFound();
|
||||
}
|
||||
@@ -494,14 +485,14 @@ export class ChatSessionService {
|
||||
|
||||
@Transactional()
|
||||
async fork(options: ChatSessionForkOptions): Promise<string> {
|
||||
const state = await this.getSession(options.sessionId);
|
||||
if (!state) {
|
||||
const session = await this.getSessionInfo(options.sessionId);
|
||||
if (!session) {
|
||||
throw new CopilotSessionNotFound();
|
||||
}
|
||||
|
||||
let messages = state.messages.map(m => ({ ...m, id: undefined }));
|
||||
let messages = session.messages.map(m => ({ ...m, id: undefined }));
|
||||
if (options.latestMessageId) {
|
||||
const lastMessageIdx = state.messages.findLastIndex(
|
||||
const lastMessageIdx = session.messages.findLastIndex(
|
||||
({ id, role }) =>
|
||||
role === AiPromptRole.assistant && id === options.latestMessageId
|
||||
);
|
||||
@@ -514,7 +505,7 @@ export class ChatSessionService {
|
||||
}
|
||||
|
||||
return await this.models.copilotSession.fork({
|
||||
...state,
|
||||
...session,
|
||||
userId: options.userId,
|
||||
sessionId: randomUUID(),
|
||||
parentSessionId: options.sessionId,
|
||||
@@ -544,7 +535,7 @@ export class ChatSessionService {
|
||||
* @returns
|
||||
*/
|
||||
async get(sessionId: string): Promise<ChatSession | null> {
|
||||
const state = await this.getSession(sessionId);
|
||||
const state = await this.getSessionInfo(sessionId);
|
||||
if (state) {
|
||||
return new ChatSession(this.messageCache, state, async state => {
|
||||
await this.models.copilotSession.updateMessages(state);
|
||||
|
||||
@@ -167,7 +167,7 @@ You should specify the following arguments before the others: [doc_id], [origin_
|
||||
},
|
||||
]);
|
||||
|
||||
return { result };
|
||||
return { result, content };
|
||||
} catch {
|
||||
return 'Failed to apply edit to the doc';
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export const createDocSemanticSearchTool = (
|
||||
) => {
|
||||
return tool({
|
||||
description:
|
||||
'Retrieve conceptually related passages by performing vector-based semantic similarity search across embedded documents; use this tool only when exact keyword search fails or the user explicitly needs meaning-level matches (e.g., paraphrases, synonyms, broader concepts).',
|
||||
'Retrieve conceptually related passages by performing vector-based semantic similarity search across embedded documents; use this tool only when exact keyword search fails or the user explicitly needs meaning-level matches (e.g., paraphrases, synonyms, broader concepts, recent documents).',
|
||||
parameters: z.object({
|
||||
query: z
|
||||
.string()
|
||||
|
||||
@@ -46,12 +46,19 @@ export type ChatMessage = z.infer<typeof ChatMessageSchema>;
|
||||
|
||||
export const ChatHistorySchema = z
|
||||
.object({
|
||||
userId: z.string(),
|
||||
sessionId: z.string(),
|
||||
workspaceId: z.string(),
|
||||
docId: z.string().nullable(),
|
||||
parentSessionId: z.string().nullable(),
|
||||
pinned: z.boolean(),
|
||||
title: z.string().nullable(),
|
||||
|
||||
action: z.string().nullable(),
|
||||
model: z.string(),
|
||||
optionalModels: z.array(z.string()),
|
||||
promptName: z.string(),
|
||||
|
||||
tokens: z.number(),
|
||||
messages: z.array(ChatMessageSchema),
|
||||
createdAt: z.date(),
|
||||
@@ -69,32 +76,26 @@ export type SubmittedMessage = z.infer<typeof SubmittedMessageSchema>;
|
||||
|
||||
// ======== Chat Session ========
|
||||
|
||||
export interface ChatSessionOptions {
|
||||
// connect ids
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
promptName: string;
|
||||
pinned: boolean;
|
||||
export type ChatSessionOptions = Pick<
|
||||
ChatHistory,
|
||||
'userId' | 'workspaceId' | 'docId' | 'promptName' | 'pinned'
|
||||
> & {
|
||||
reuseLatestChat?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export interface ChatSessionForkOptions
|
||||
extends Omit<ChatSessionOptions, 'pinned' | 'promptName'> {
|
||||
sessionId: string;
|
||||
export type ChatSessionForkOptions = Pick<
|
||||
ChatHistory,
|
||||
'userId' | 'sessionId' | 'workspaceId' | 'docId'
|
||||
> & {
|
||||
latestMessageId?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface ChatSessionState
|
||||
extends Omit<ChatSessionOptions, 'promptName'> {
|
||||
title: string | null;
|
||||
// connect ids
|
||||
sessionId: string;
|
||||
parentSessionId: string | null;
|
||||
// states
|
||||
export type ChatSessionState = Pick<
|
||||
ChatHistory,
|
||||
'userId' | 'sessionId' | 'workspaceId' | 'docId' | 'messages'
|
||||
> & {
|
||||
prompt: ChatPrompt;
|
||||
messages: ChatMessage[];
|
||||
}
|
||||
};
|
||||
|
||||
export type CopilotContextFile = {
|
||||
id: string; // fileId
|
||||
|
||||
@@ -21,16 +21,19 @@ type SignalReturnType = {
|
||||
export function getSignal(req: Request): SignalReturnType {
|
||||
const controller = new AbortController();
|
||||
|
||||
let isAborted = true;
|
||||
let hasEnded = false;
|
||||
let callback: ((isAborted: boolean) => void) | undefined = undefined;
|
||||
|
||||
const onSocketEnd = () => {
|
||||
isAborted = false;
|
||||
hasEnded = true;
|
||||
};
|
||||
const onSocketClose = (hadError: boolean) => {
|
||||
req.socket.off('end', onSocketEnd);
|
||||
req.socket.off('close', onSocketClose);
|
||||
const aborted = hadError || isAborted;
|
||||
// NOTE: the connection is considered abnormally interrupted:
|
||||
// 1. there is an error when the socket is closed.
|
||||
// 2. the connection is closed directly without going through the normal end process (the client disconnects actively).
|
||||
const aborted = hadError || !hasEnded;
|
||||
if (aborted) {
|
||||
controller.abort();
|
||||
}
|
||||
|
||||
@@ -208,10 +208,11 @@ type ContextWorkspaceEmbeddingStatus {
|
||||
|
||||
type Copilot {
|
||||
audioTranscription(blobId: String, jobId: String): TranscriptionResultType
|
||||
chats(docId: String, options: QueryChatHistoriesInput, pagination: PaginationInput!): PaginatedCopilotHistoriesType!
|
||||
|
||||
"""Get the context list of a session"""
|
||||
contexts(contextId: String, sessionId: String): [CopilotContext!]!
|
||||
histories(docId: String, options: QueryChatHistoriesInput): [CopilotHistories!]!
|
||||
histories(docId: String, options: QueryChatHistoriesInput): [CopilotHistories!]! @deprecated(reason: "use `chats` instead")
|
||||
|
||||
"""Get the quota of the user in the workspace"""
|
||||
quota: CopilotQuota!
|
||||
@@ -220,7 +221,7 @@ type Copilot {
|
||||
session(sessionId: String!): CopilotSessionType!
|
||||
|
||||
"""Get the session list in the workspace"""
|
||||
sessions(docId: String, options: QueryChatSessionsInput): [CopilotSessionType!]!
|
||||
sessions(docId: String, options: QueryChatSessionsInput): [CopilotSessionType!]! @deprecated(reason: "use `chats` instead")
|
||||
workspaceId: ID
|
||||
}
|
||||
|
||||
@@ -313,8 +314,13 @@ type CopilotHistories {
|
||||
createdAt: DateTime!
|
||||
docId: String
|
||||
messages: [ChatMessage!]!
|
||||
model: String!
|
||||
optionalModels: [String!]!
|
||||
parentSessionId: String
|
||||
pinned: Boolean!
|
||||
promptName: String!
|
||||
sessionId: String!
|
||||
title: String
|
||||
|
||||
"""The number of tokens used in the session"""
|
||||
tokens: Int!
|
||||
@@ -322,6 +328,11 @@ type CopilotHistories {
|
||||
workspaceId: String!
|
||||
}
|
||||
|
||||
type CopilotHistoriesTypeEdge {
|
||||
cursor: String!
|
||||
node: CopilotHistories!
|
||||
}
|
||||
|
||||
type CopilotInvalidContextDataType {
|
||||
contextId: String!
|
||||
}
|
||||
@@ -1236,6 +1247,9 @@ type Mutation {
|
||||
"""queue workspace doc embedding"""
|
||||
queueWorkspaceEmbedding(docId: [String!]!, workspaceId: String!): Boolean!
|
||||
|
||||
"""mark all notifications as read"""
|
||||
readAllNotifications: Boolean!
|
||||
|
||||
"""mark notification as read"""
|
||||
readNotification(id: String!): Boolean!
|
||||
recoverDoc(guid: String!, timestamp: DateTime!, workspaceId: String!): DateTime!
|
||||
@@ -1418,6 +1432,12 @@ type PaginatedCommentObjectType {
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
type PaginatedCopilotHistoriesType {
|
||||
edges: [CopilotHistoriesTypeEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
type PaginatedCopilotWorkspaceFileType {
|
||||
edges: [CopilotWorkspaceFileTypeEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
@@ -1549,6 +1569,7 @@ input QueryChatHistoriesInput {
|
||||
sessionId: String
|
||||
sessionOrder: ChatHistoryOrder
|
||||
skip: Int
|
||||
withMessages: Boolean
|
||||
withPrompt: Boolean
|
||||
}
|
||||
|
||||
@@ -1739,7 +1760,7 @@ enum SearchTable {
|
||||
|
||||
type ServerConfigType {
|
||||
"""Whether allow guest users to create demo workspaces."""
|
||||
allowGuestDemoWorkspace: Boolean!
|
||||
allowGuestDemoWorkspace: Boolean! @deprecated(reason: "This field is deprecated, please use `features` instead. Will be removed in 0.25.0")
|
||||
|
||||
"""fetch latest available upgradable release of server"""
|
||||
availableUpgrade: ReleaseVersionType
|
||||
@@ -1781,6 +1802,7 @@ enum ServerFeature {
|
||||
Copilot
|
||||
CopilotEmbedding
|
||||
Indexer
|
||||
LocalWorkspace
|
||||
OAuth
|
||||
Payment
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ query adminServerConfig {
|
||||
baseUrl
|
||||
name
|
||||
features
|
||||
allowGuestDemoWorkspace
|
||||
type
|
||||
initialized
|
||||
credentialsRequirement {
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
query getCopilotHistoryIds(
|
||||
$workspaceId: String!
|
||||
$pagination: PaginationInput!
|
||||
$docId: String
|
||||
$options: QueryChatHistoriesInput
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: $options) {
|
||||
sessionId
|
||||
pinned
|
||||
messages {
|
||||
id
|
||||
role
|
||||
createdAt
|
||||
chats(pagination: $pagination, docId: $docId, options: $options) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
sessionId
|
||||
pinned
|
||||
messages {
|
||||
id
|
||||
role
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
#import "./fragments/copilot.gql"
|
||||
|
||||
query getCopilotDocSessions(
|
||||
$workspaceId: String!
|
||||
$docId: String!
|
||||
$pagination: PaginationInput!
|
||||
$options: QueryChatHistoriesInput
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: $options) {
|
||||
sessionId
|
||||
pinned
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
chats(pagination: $pagination, docId: $docId, options: $options) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#import "./fragments/copilot.gql"
|
||||
|
||||
query getCopilotPinnedSessions(
|
||||
$workspaceId: String!
|
||||
$docId: String
|
||||
@@ -6,32 +8,12 @@ query getCopilotPinnedSessions(
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: {
|
||||
limit: 1,
|
||||
chats(pagination: { first: 1 }, docId: $docId, options: {
|
||||
pinned: true,
|
||||
messageOrder: $messageOrder,
|
||||
withPrompt: $withPrompt
|
||||
}) {
|
||||
sessionId
|
||||
pinned
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
#import "./fragments/copilot.gql"
|
||||
|
||||
query getCopilotWorkspaceSessions(
|
||||
$workspaceId: String!
|
||||
$pagination: PaginationInput!
|
||||
$options: QueryChatHistoriesInput
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: null, options: $options) {
|
||||
sessionId
|
||||
pinned
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
chats(pagination: $pagination, docId: null, options: $options) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
#import "./fragments/copilot.gql"
|
||||
|
||||
query getCopilotHistories(
|
||||
$workspaceId: String!
|
||||
$pagination: PaginationInput!
|
||||
$docId: String
|
||||
$options: QueryChatHistoriesInput
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: $options) {
|
||||
sessionId
|
||||
pinned
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
chats(pagination: $pagination, docId: $docId, options: $options) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
#import "./fragments/copilot.gql"
|
||||
|
||||
query getCopilotLatestDocSession(
|
||||
$workspaceId: String!
|
||||
$docId: String!
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(
|
||||
chats(
|
||||
pagination: { first: 1 }
|
||||
docId: $docId
|
||||
options: {
|
||||
limit: 1
|
||||
sessionOrder: desc
|
||||
action: false
|
||||
fork: false
|
||||
withMessages: true
|
||||
}
|
||||
) {
|
||||
sessionId
|
||||
workspaceId
|
||||
docId
|
||||
pinned
|
||||
action
|
||||
tokens
|
||||
createdAt
|
||||
updatedAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
attachments
|
||||
params
|
||||
createdAt
|
||||
}
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
#import "./fragments/copilot.gql"
|
||||
|
||||
query getCopilotSession(
|
||||
$workspaceId: String!
|
||||
$sessionId: String!
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
session(sessionId: $sessionId) {
|
||||
id
|
||||
parentSessionId
|
||||
docId
|
||||
pinned
|
||||
title
|
||||
promptName
|
||||
model
|
||||
optionalModels
|
||||
chats(
|
||||
pagination: { first: 1 }
|
||||
options: { sessionId: $sessionId }
|
||||
) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
#import "./fragments/copilot.gql"
|
||||
|
||||
query getCopilotRecentSessions(
|
||||
$workspaceId: String!
|
||||
$limit: Int = 10
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(
|
||||
chats(
|
||||
pagination: { first: $limit }
|
||||
options: {
|
||||
limit: $limit
|
||||
fork: false
|
||||
sessionOrder: desc
|
||||
withMessages: true
|
||||
}
|
||||
) {
|
||||
sessionId
|
||||
workspaceId
|
||||
docId
|
||||
pinned
|
||||
action
|
||||
tokens
|
||||
createdAt
|
||||
updatedAt
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#import "./fragments/copilot.gql"
|
||||
|
||||
query getCopilotSessions(
|
||||
$workspaceId: String!
|
||||
$pagination: PaginationInput!
|
||||
$docId: String
|
||||
$options: QueryChatSessionsInput
|
||||
$options: QueryChatHistoriesInput
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
sessions(docId: $docId, options: $options) {
|
||||
id
|
||||
parentSessionId
|
||||
docId
|
||||
pinned
|
||||
title
|
||||
promptName
|
||||
model
|
||||
optionalModels
|
||||
chats(pagination: $pagination, docId: $docId, options: $options) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
fragment CopilotChatMessage on ChatMessage {
|
||||
id
|
||||
role
|
||||
content
|
||||
attachments
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
createdAt
|
||||
}
|
||||
|
||||
fragment CopilotChatHistory on CopilotHistories {
|
||||
sessionId
|
||||
workspaceId
|
||||
docId
|
||||
parentSessionId
|
||||
promptName
|
||||
model
|
||||
optionalModels
|
||||
action
|
||||
pinned
|
||||
title
|
||||
tokens
|
||||
messages {
|
||||
...CopilotChatMessage
|
||||
}
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
|
||||
fragment PaginatedCopilotChats on PaginatedCopilotHistoriesType {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
...CopilotChatHistory
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,53 @@ export interface GraphQLQuery {
|
||||
file?: boolean;
|
||||
deprecations?: string[];
|
||||
}
|
||||
export const copilotChatMessageFragment = `fragment CopilotChatMessage on ChatMessage {
|
||||
id
|
||||
role
|
||||
content
|
||||
attachments
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
createdAt
|
||||
}`;
|
||||
export const copilotChatHistoryFragment = `fragment CopilotChatHistory on CopilotHistories {
|
||||
sessionId
|
||||
workspaceId
|
||||
docId
|
||||
parentSessionId
|
||||
promptName
|
||||
model
|
||||
optionalModels
|
||||
action
|
||||
pinned
|
||||
title
|
||||
tokens
|
||||
messages {
|
||||
...CopilotChatMessage
|
||||
}
|
||||
createdAt
|
||||
updatedAt
|
||||
}`;
|
||||
export const paginatedCopilotChatsFragment = `fragment PaginatedCopilotChats on PaginatedCopilotHistoriesType {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
...CopilotChatHistory
|
||||
}
|
||||
}
|
||||
}`;
|
||||
export const credentialsRequirementsFragment = `fragment CredentialsRequirements on CredentialsRequirementType {
|
||||
password {
|
||||
...PasswordLimits
|
||||
@@ -32,7 +79,6 @@ export const adminServerConfigQuery = {
|
||||
baseUrl
|
||||
name
|
||||
features
|
||||
allowGuestDemoWorkspace
|
||||
type
|
||||
initialized
|
||||
credentialsRequirement {
|
||||
@@ -762,16 +808,27 @@ export const queueWorkspaceEmbeddingMutation = {
|
||||
export const getCopilotHistoryIdsQuery = {
|
||||
id: 'getCopilotHistoryIdsQuery' as const,
|
||||
op: 'getCopilotHistoryIds',
|
||||
query: `query getCopilotHistoryIds($workspaceId: String!, $docId: String, $options: QueryChatHistoriesInput) {
|
||||
query: `query getCopilotHistoryIds($workspaceId: String!, $pagination: PaginationInput!, $docId: String, $options: QueryChatHistoriesInput) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: $options) {
|
||||
sessionId
|
||||
pinned
|
||||
messages {
|
||||
id
|
||||
role
|
||||
createdAt
|
||||
chats(pagination: $pagination, docId: $docId, options: $options) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
sessionId
|
||||
pinned
|
||||
messages {
|
||||
id
|
||||
role
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -782,34 +839,18 @@ export const getCopilotHistoryIdsQuery = {
|
||||
export const getCopilotDocSessionsQuery = {
|
||||
id: 'getCopilotDocSessionsQuery' as const,
|
||||
op: 'getCopilotDocSessions',
|
||||
query: `query getCopilotDocSessions($workspaceId: String!, $docId: String!, $options: QueryChatHistoriesInput) {
|
||||
query: `query getCopilotDocSessions($workspaceId: String!, $docId: String!, $pagination: PaginationInput!, $options: QueryChatHistoriesInput) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: $options) {
|
||||
sessionId
|
||||
pinned
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
chats(pagination: $pagination, docId: $docId, options: $options) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
${copilotChatMessageFragment}
|
||||
${copilotChatHistoryFragment}
|
||||
${paginatedCopilotChatsFragment}`,
|
||||
};
|
||||
|
||||
export const getCopilotPinnedSessionsQuery = {
|
||||
@@ -818,100 +859,53 @@ export const getCopilotPinnedSessionsQuery = {
|
||||
query: `query getCopilotPinnedSessions($workspaceId: String!, $docId: String, $messageOrder: ChatHistoryOrder, $withPrompt: Boolean) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(
|
||||
chats(
|
||||
pagination: {first: 1}
|
||||
docId: $docId
|
||||
options: {limit: 1, pinned: true, messageOrder: $messageOrder, withPrompt: $withPrompt}
|
||||
options: {pinned: true, messageOrder: $messageOrder, withPrompt: $withPrompt}
|
||||
) {
|
||||
sessionId
|
||||
pinned
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
${copilotChatMessageFragment}
|
||||
${copilotChatHistoryFragment}
|
||||
${paginatedCopilotChatsFragment}`,
|
||||
};
|
||||
|
||||
export const getCopilotWorkspaceSessionsQuery = {
|
||||
id: 'getCopilotWorkspaceSessionsQuery' as const,
|
||||
op: 'getCopilotWorkspaceSessions',
|
||||
query: `query getCopilotWorkspaceSessions($workspaceId: String!, $options: QueryChatHistoriesInput) {
|
||||
query: `query getCopilotWorkspaceSessions($workspaceId: String!, $pagination: PaginationInput!, $options: QueryChatHistoriesInput) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: null, options: $options) {
|
||||
sessionId
|
||||
pinned
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
chats(pagination: $pagination, docId: null, options: $options) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
${copilotChatMessageFragment}
|
||||
${copilotChatHistoryFragment}
|
||||
${paginatedCopilotChatsFragment}`,
|
||||
};
|
||||
|
||||
export const getCopilotHistoriesQuery = {
|
||||
id: 'getCopilotHistoriesQuery' as const,
|
||||
op: 'getCopilotHistories',
|
||||
query: `query getCopilotHistories($workspaceId: String!, $docId: String, $options: QueryChatHistoriesInput) {
|
||||
query: `query getCopilotHistories($workspaceId: String!, $pagination: PaginationInput!, $docId: String, $options: QueryChatHistoriesInput) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: $options) {
|
||||
sessionId
|
||||
pinned
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
streamObjects {
|
||||
type
|
||||
textDelta
|
||||
toolCallId
|
||||
toolName
|
||||
args
|
||||
result
|
||||
}
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
chats(pagination: $pagination, docId: $docId, options: $options) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
${copilotChatMessageFragment}
|
||||
${copilotChatHistoryFragment}
|
||||
${paginatedCopilotChatsFragment}`,
|
||||
};
|
||||
|
||||
export const submitAudioTranscriptionMutation = {
|
||||
@@ -1039,30 +1033,19 @@ export const getCopilotLatestDocSessionQuery = {
|
||||
query: `query getCopilotLatestDocSession($workspaceId: String!, $docId: String!) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(
|
||||
chats(
|
||||
pagination: {first: 1}
|
||||
docId: $docId
|
||||
options: {limit: 1, sessionOrder: desc, action: false, fork: false}
|
||||
options: {sessionOrder: desc, action: false, fork: false, withMessages: true}
|
||||
) {
|
||||
sessionId
|
||||
workspaceId
|
||||
docId
|
||||
pinned
|
||||
action
|
||||
tokens
|
||||
createdAt
|
||||
updatedAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
attachments
|
||||
params
|
||||
createdAt
|
||||
}
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
${copilotChatMessageFragment}
|
||||
${copilotChatHistoryFragment}
|
||||
${paginatedCopilotChatsFragment}`,
|
||||
};
|
||||
|
||||
export const getCopilotSessionQuery = {
|
||||
@@ -1071,19 +1054,15 @@ export const getCopilotSessionQuery = {
|
||||
query: `query getCopilotSession($workspaceId: String!, $sessionId: String!) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
session(sessionId: $sessionId) {
|
||||
id
|
||||
parentSessionId
|
||||
docId
|
||||
pinned
|
||||
title
|
||||
promptName
|
||||
model
|
||||
optionalModels
|
||||
chats(pagination: {first: 1}, options: {sessionId: $sessionId}) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
${copilotChatMessageFragment}
|
||||
${copilotChatHistoryFragment}
|
||||
${paginatedCopilotChatsFragment}`,
|
||||
};
|
||||
|
||||
export const getCopilotRecentSessionsQuery = {
|
||||
@@ -1092,19 +1071,18 @@ export const getCopilotRecentSessionsQuery = {
|
||||
query: `query getCopilotRecentSessions($workspaceId: String!, $limit: Int = 10) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(options: {limit: $limit, sessionOrder: desc}) {
|
||||
sessionId
|
||||
workspaceId
|
||||
docId
|
||||
pinned
|
||||
action
|
||||
tokens
|
||||
createdAt
|
||||
updatedAt
|
||||
chats(
|
||||
pagination: {first: $limit}
|
||||
options: {fork: false, sessionOrder: desc, withMessages: true}
|
||||
) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
${copilotChatMessageFragment}
|
||||
${copilotChatHistoryFragment}
|
||||
${paginatedCopilotChatsFragment}`,
|
||||
};
|
||||
|
||||
export const updateCopilotSessionMutation = {
|
||||
@@ -1118,22 +1096,18 @@ export const updateCopilotSessionMutation = {
|
||||
export const getCopilotSessionsQuery = {
|
||||
id: 'getCopilotSessionsQuery' as const,
|
||||
op: 'getCopilotSessions',
|
||||
query: `query getCopilotSessions($workspaceId: String!, $docId: String, $options: QueryChatSessionsInput) {
|
||||
query: `query getCopilotSessions($workspaceId: String!, $pagination: PaginationInput!, $docId: String, $options: QueryChatHistoriesInput) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
sessions(docId: $docId, options: $options) {
|
||||
id
|
||||
parentSessionId
|
||||
docId
|
||||
pinned
|
||||
title
|
||||
promptName
|
||||
model
|
||||
optionalModels
|
||||
chats(pagination: $pagination, docId: $docId, options: $options) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
${copilotChatMessageFragment}
|
||||
${copilotChatHistoryFragment}
|
||||
${paginatedCopilotChatsFragment}`,
|
||||
};
|
||||
|
||||
export const addWorkspaceEmbeddingFilesMutation = {
|
||||
@@ -1989,6 +1963,14 @@ export const quotaQuery = {
|
||||
deprecations: ["'storageQuota' is deprecated: use `UserQuotaType['usedStorageQuota']` instead"],
|
||||
};
|
||||
|
||||
export const readAllNotificationsMutation = {
|
||||
id: 'readAllNotificationsMutation' as const,
|
||||
op: 'readAllNotifications',
|
||||
query: `mutation readAllNotifications {
|
||||
readAllNotifications
|
||||
}`,
|
||||
};
|
||||
|
||||
export const readNotificationMutation = {
|
||||
id: 'readNotificationMutation' as const,
|
||||
op: 'readNotification',
|
||||
@@ -2107,7 +2089,6 @@ export const serverConfigQuery = {
|
||||
baseUrl
|
||||
name
|
||||
features
|
||||
allowGuestDemoWorkspace
|
||||
type
|
||||
initialized
|
||||
credentialsRequirement {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mutation readAllNotifications {
|
||||
readAllNotifications
|
||||
}
|
||||
@@ -7,7 +7,6 @@ query serverConfig {
|
||||
baseUrl
|
||||
name
|
||||
features
|
||||
allowGuestDemoWorkspace
|
||||
type
|
||||
initialized
|
||||
credentialsRequirement {
|
||||
|
||||
@@ -245,14 +245,19 @@ export interface ContextWorkspaceEmbeddingStatus {
|
||||
export interface Copilot {
|
||||
__typename?: 'Copilot';
|
||||
audioTranscription: Maybe<TranscriptionResultType>;
|
||||
chats: PaginatedCopilotHistoriesType;
|
||||
/** Get the context list of a session */
|
||||
contexts: Array<CopilotContext>;
|
||||
/** @deprecated use `chats` instead */
|
||||
histories: Array<CopilotHistories>;
|
||||
/** Get the quota of the user in the workspace */
|
||||
quota: CopilotQuota;
|
||||
/** Get the session by id */
|
||||
session: CopilotSessionType;
|
||||
/** Get the session list in the workspace */
|
||||
/**
|
||||
* Get the session list in the workspace
|
||||
* @deprecated use `chats` instead
|
||||
*/
|
||||
sessions: Array<CopilotSessionType>;
|
||||
workspaceId: Maybe<Scalars['ID']['output']>;
|
||||
}
|
||||
@@ -262,6 +267,12 @@ export interface CopilotAudioTranscriptionArgs {
|
||||
jobId?: InputMaybe<Scalars['String']['input']>;
|
||||
}
|
||||
|
||||
export interface CopilotChatsArgs {
|
||||
docId?: InputMaybe<Scalars['String']['input']>;
|
||||
options?: InputMaybe<QueryChatHistoriesInput>;
|
||||
pagination: PaginationInput;
|
||||
}
|
||||
|
||||
export interface CopilotContextsArgs {
|
||||
contextId?: InputMaybe<Scalars['String']['input']>;
|
||||
sessionId?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -391,14 +402,25 @@ export interface CopilotHistories {
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
docId: Maybe<Scalars['String']['output']>;
|
||||
messages: Array<ChatMessage>;
|
||||
model: Scalars['String']['output'];
|
||||
optionalModels: Array<Scalars['String']['output']>;
|
||||
parentSessionId: Maybe<Scalars['String']['output']>;
|
||||
pinned: Scalars['Boolean']['output'];
|
||||
promptName: Scalars['String']['output'];
|
||||
sessionId: Scalars['String']['output'];
|
||||
title: Maybe<Scalars['String']['output']>;
|
||||
/** The number of tokens used in the session */
|
||||
tokens: Scalars['Int']['output'];
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
workspaceId: Scalars['String']['output'];
|
||||
}
|
||||
|
||||
export interface CopilotHistoriesTypeEdge {
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: Scalars['String']['output'];
|
||||
node: CopilotHistories;
|
||||
}
|
||||
|
||||
export interface CopilotInvalidContextDataType {
|
||||
__typename?: 'CopilotInvalidContextDataType';
|
||||
contextId: Scalars['String']['output'];
|
||||
@@ -1371,6 +1393,8 @@ export interface Mutation {
|
||||
publishPage: DocType;
|
||||
/** queue workspace doc embedding */
|
||||
queueWorkspaceEmbedding: Scalars['Boolean']['output'];
|
||||
/** mark all notifications as read */
|
||||
readAllNotifications: Scalars['Boolean']['output'];
|
||||
/** mark notification as read */
|
||||
readNotification: Scalars['Boolean']['output'];
|
||||
recoverDoc: Scalars['DateTime']['output'];
|
||||
@@ -1952,6 +1976,13 @@ export interface PaginatedCommentObjectType {
|
||||
totalCount: Scalars['Int']['output'];
|
||||
}
|
||||
|
||||
export interface PaginatedCopilotHistoriesType {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
edges: Array<CopilotHistoriesTypeEdge>;
|
||||
pageInfo: PageInfo;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
}
|
||||
|
||||
export interface PaginatedCopilotWorkspaceFileType {
|
||||
__typename?: 'PaginatedCopilotWorkspaceFileType';
|
||||
edges: Array<CopilotWorkspaceFileTypeEdge>;
|
||||
@@ -2131,6 +2162,7 @@ export interface QueryChatHistoriesInput {
|
||||
sessionId?: InputMaybe<Scalars['String']['input']>;
|
||||
sessionOrder?: InputMaybe<ChatHistoryOrder>;
|
||||
skip?: InputMaybe<Scalars['Int']['input']>;
|
||||
withMessages?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
withPrompt?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
}
|
||||
|
||||
@@ -2317,7 +2349,10 @@ export enum SearchTable {
|
||||
|
||||
export interface ServerConfigType {
|
||||
__typename?: 'ServerConfigType';
|
||||
/** Whether allow guest users to create demo workspaces. */
|
||||
/**
|
||||
* Whether allow guest users to create demo workspaces.
|
||||
* @deprecated This field is deprecated, please use `features` instead. Will be removed in 0.25.0
|
||||
*/
|
||||
allowGuestDemoWorkspace: Scalars['Boolean']['output'];
|
||||
/** fetch latest available upgradable release of server */
|
||||
availableUpgrade: Maybe<ReleaseVersionType>;
|
||||
@@ -2351,6 +2386,7 @@ export enum ServerFeature {
|
||||
Copilot = 'Copilot',
|
||||
CopilotEmbedding = 'CopilotEmbedding',
|
||||
Indexer = 'Indexer',
|
||||
LocalWorkspace = 'LocalWorkspace',
|
||||
OAuth = 'OAuth',
|
||||
Payment = 'Payment',
|
||||
}
|
||||
@@ -2932,7 +2968,6 @@ export type AdminServerConfigQuery = {
|
||||
baseUrl: string;
|
||||
name: string;
|
||||
features: Array<ServerFeature>;
|
||||
allowGuestDemoWorkspace: boolean;
|
||||
type: ServerDeploymentType;
|
||||
initialized: boolean;
|
||||
availableUserFeatures: Array<FeatureType>;
|
||||
@@ -3755,6 +3790,7 @@ export type QueueWorkspaceEmbeddingMutation = {
|
||||
|
||||
export type GetCopilotHistoryIdsQueryVariables = Exact<{
|
||||
workspaceId: Scalars['String']['input'];
|
||||
pagination: PaginationInput;
|
||||
docId?: InputMaybe<Scalars['String']['input']>;
|
||||
options?: InputMaybe<QueryChatHistoriesInput>;
|
||||
}>;
|
||||
@@ -3765,17 +3801,31 @@ export type GetCopilotHistoryIdsQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
histories: Array<{
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
pinned: boolean;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
createdAt: string;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
pinned: boolean;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
createdAt: string;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
@@ -3783,6 +3833,7 @@ export type GetCopilotHistoryIdsQuery = {
|
||||
export type GetCopilotDocSessionsQueryVariables = Exact<{
|
||||
workspaceId: Scalars['String']['input'];
|
||||
docId: Scalars['String']['input'];
|
||||
pagination: PaginationInput;
|
||||
options?: InputMaybe<QueryChatHistoriesInput>;
|
||||
}>;
|
||||
|
||||
@@ -3792,31 +3843,53 @@ export type GetCopilotDocSessionsQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
histories: Array<{
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
pinned: boolean;
|
||||
tokens: number;
|
||||
action: string | null;
|
||||
createdAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
@@ -3834,37 +3907,60 @@ export type GetCopilotPinnedSessionsQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
histories: Array<{
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
pinned: boolean;
|
||||
tokens: number;
|
||||
action: string | null;
|
||||
createdAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type GetCopilotWorkspaceSessionsQueryVariables = Exact<{
|
||||
workspaceId: Scalars['String']['input'];
|
||||
pagination: PaginationInput;
|
||||
options?: InputMaybe<QueryChatHistoriesInput>;
|
||||
}>;
|
||||
|
||||
@@ -3874,37 +3970,60 @@ export type GetCopilotWorkspaceSessionsQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
histories: Array<{
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
pinned: boolean;
|
||||
tokens: number;
|
||||
action: string | null;
|
||||
createdAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type GetCopilotHistoriesQueryVariables = Exact<{
|
||||
workspaceId: Scalars['String']['input'];
|
||||
pagination: PaginationInput;
|
||||
docId?: InputMaybe<Scalars['String']['input']>;
|
||||
options?: InputMaybe<QueryChatHistoriesInput>;
|
||||
}>;
|
||||
@@ -3915,31 +4034,53 @@ export type GetCopilotHistoriesQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
histories: Array<{
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
pinned: boolean;
|
||||
tokens: number;
|
||||
action: string | null;
|
||||
createdAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
@@ -4093,26 +4234,53 @@ export type GetCopilotLatestDocSessionQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
histories: Array<{
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
pinned: boolean;
|
||||
action: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
params: Record<string, string> | null;
|
||||
createdAt: string;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
@@ -4128,16 +4296,52 @@ export type GetCopilotSessionQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
session: {
|
||||
__typename?: 'CopilotSessionType';
|
||||
id: string;
|
||||
parentSessionId: string | null;
|
||||
docId: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
@@ -4154,17 +4358,53 @@ export type GetCopilotRecentSessionsQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
histories: Array<{
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
pinned: boolean;
|
||||
action: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}>;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
@@ -4180,8 +4420,9 @@ export type UpdateCopilotSessionMutation = {
|
||||
|
||||
export type GetCopilotSessionsQueryVariables = Exact<{
|
||||
workspaceId: Scalars['String']['input'];
|
||||
pagination: PaginationInput;
|
||||
docId?: InputMaybe<Scalars['String']['input']>;
|
||||
options?: InputMaybe<QueryChatSessionsInput>;
|
||||
options?: InputMaybe<QueryChatHistoriesInput>;
|
||||
}>;
|
||||
|
||||
export type GetCopilotSessionsQuery = {
|
||||
@@ -4190,17 +4431,53 @@ export type GetCopilotSessionsQuery = {
|
||||
__typename?: 'UserType';
|
||||
copilot: {
|
||||
__typename?: 'Copilot';
|
||||
sessions: Array<{
|
||||
__typename?: 'CopilotSessionType';
|
||||
id: string;
|
||||
parentSessionId: string | null;
|
||||
docId: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
}>;
|
||||
chats: {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
@@ -4436,6 +4713,106 @@ export type GetDocRolePermissionsQuery = {
|
||||
};
|
||||
};
|
||||
|
||||
export type CopilotChatMessageFragment = {
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
};
|
||||
|
||||
export type CopilotChatHistoryFragment = {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type PaginatedCopilotChatsFragment = {
|
||||
__typename?: 'PaginatedCopilotHistoriesType';
|
||||
pageInfo: {
|
||||
__typename?: 'PageInfo';
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
edges: Array<{
|
||||
__typename?: 'CopilotHistoriesTypeEdge';
|
||||
cursor: string;
|
||||
node: {
|
||||
__typename?: 'CopilotHistories';
|
||||
sessionId: string;
|
||||
workspaceId: string;
|
||||
docId: string | null;
|
||||
parentSessionId: string | null;
|
||||
promptName: string;
|
||||
model: string;
|
||||
optionalModels: Array<string>;
|
||||
action: string | null;
|
||||
pinned: boolean;
|
||||
title: string | null;
|
||||
tokens: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: Array<{
|
||||
__typename?: 'ChatMessage';
|
||||
id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
attachments: Array<string> | null;
|
||||
createdAt: string;
|
||||
streamObjects: Array<{
|
||||
__typename?: 'StreamObject';
|
||||
type: string;
|
||||
textDelta: string | null;
|
||||
toolCallId: string | null;
|
||||
toolName: string | null;
|
||||
args: Record<string, string> | null;
|
||||
result: Record<string, string> | null;
|
||||
}> | null;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
|
||||
export type CredentialsRequirementsFragment = {
|
||||
__typename?: 'CredentialsRequirementType';
|
||||
password: {
|
||||
@@ -5218,6 +5595,15 @@ export type QuotaQuery = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type ReadAllNotificationsMutationVariables = Exact<{
|
||||
[key: string]: never;
|
||||
}>;
|
||||
|
||||
export type ReadAllNotificationsMutation = {
|
||||
__typename?: 'Mutation';
|
||||
readAllNotifications: boolean;
|
||||
};
|
||||
|
||||
export type ReadNotificationMutationVariables = Exact<{
|
||||
id: Scalars['String']['input'];
|
||||
}>;
|
||||
@@ -5353,7 +5739,6 @@ export type ServerConfigQuery = {
|
||||
baseUrl: string;
|
||||
name: string;
|
||||
features: Array<ServerFeature>;
|
||||
allowGuestDemoWorkspace: boolean;
|
||||
type: ServerDeploymentType;
|
||||
initialized: boolean;
|
||||
credentialsRequirement: {
|
||||
@@ -6352,6 +6737,11 @@ export type Mutations =
|
||||
variables: PublishPageMutationVariables;
|
||||
response: PublishPageMutation;
|
||||
}
|
||||
| {
|
||||
name: 'readAllNotificationsMutation';
|
||||
variables: ReadAllNotificationsMutationVariables;
|
||||
response: ReadAllNotificationsMutation;
|
||||
}
|
||||
| {
|
||||
name: 'readNotificationMutation';
|
||||
variables: ReadNotificationMutationVariables;
|
||||
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
package app.affine.pro.ai.chat
|
||||
|
||||
import com.affine.pro.graphql.GetCopilotHistoriesQuery
|
||||
import com.affine.pro.graphql.fragment.CopilotChatHistory
|
||||
import com.affine.pro.graphql.fragment.CopilotChatMessage
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
@@ -51,11 +53,11 @@ data class ChatMessage(
|
||||
createAt = Clock.System.now(),
|
||||
)
|
||||
|
||||
fun from(message: GetCopilotHistoriesQuery.Message) = ChatMessage(
|
||||
fun from(message: CopilotChatMessage) = ChatMessage(
|
||||
id = message.id,
|
||||
role = Role.fromValue(message.role),
|
||||
content = message.content,
|
||||
createAt = message.createdAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-8
@@ -9,7 +9,8 @@ import com.affine.pro.graphql.GetCopilotHistoryIdsQuery
|
||||
import com.affine.pro.graphql.GetCopilotSessionsQuery
|
||||
import com.affine.pro.graphql.type.CreateChatMessageInput
|
||||
import com.affine.pro.graphql.type.CreateChatSessionInput
|
||||
import com.affine.pro.graphql.type.QueryChatSessionsInput
|
||||
import com.affine.pro.graphql.type.PaginationInput
|
||||
import com.affine.pro.graphql.type.QueryChatHistoriesInput
|
||||
import com.apollographql.apollo.ApolloClient
|
||||
import com.apollographql.apollo.api.Mutation
|
||||
import com.apollographql.apollo.api.Optional
|
||||
@@ -29,12 +30,15 @@ class GraphQLService @Inject constructor() {
|
||||
GetCopilotSessionsQuery(
|
||||
workspaceId = workspaceId,
|
||||
docId = Optional.present(docId),
|
||||
options = Optional.present(QueryChatSessionsInput(action = Optional.present(false)))
|
||||
pagination = PaginationInput(
|
||||
first = Optional.present(100)
|
||||
),
|
||||
options = Optional.present(QueryChatHistoriesInput(action = Optional.present(false)))
|
||||
)
|
||||
).mapCatching { data ->
|
||||
data.currentUser?.copilot?.sessions?.find {
|
||||
data.currentUser?.copilot?.chats?.paginatedCopilotChats?.edges?.map { item -> item.node.copilotChatHistory }?.find {
|
||||
it.parentSessionId == null
|
||||
}?.id ?: error(ERROR_NULL_SESSION_ID)
|
||||
}?.sessionId ?: error(ERROR_NULL_SESSION_ID)
|
||||
}
|
||||
|
||||
suspend fun createCopilotSession(
|
||||
@@ -60,12 +64,15 @@ class GraphQLService @Inject constructor() {
|
||||
) = query(
|
||||
GetCopilotHistoriesQuery(
|
||||
workspaceId = workspaceId,
|
||||
pagination = PaginationInput(
|
||||
first = Optional.present(100)
|
||||
),
|
||||
docId = Optional.present(docId),
|
||||
)
|
||||
).mapCatching { data ->
|
||||
data.currentUser?.copilot?.histories?.firstOrNull { history ->
|
||||
history.sessionId == sessionId
|
||||
}?.messages ?: emptyList()
|
||||
data.currentUser?.copilot?.chats?.paginatedCopilotChats?.edges?.map { item -> item.node.copilotChatHistory }?.firstOrNull { history ->
|
||||
history.sessionId == sessionId
|
||||
}?.messages?.map { msg -> msg.copilotChatMessage } ?: emptyList()
|
||||
}
|
||||
|
||||
suspend fun getCopilotHistoryIds(
|
||||
@@ -76,9 +83,12 @@ class GraphQLService @Inject constructor() {
|
||||
GetCopilotHistoryIdsQuery(
|
||||
workspaceId = workspaceId,
|
||||
docId = Optional.present(docId),
|
||||
pagination = PaginationInput(
|
||||
first = Optional.present(100)
|
||||
),
|
||||
)
|
||||
).mapCatching { data ->
|
||||
data.currentUser?.copilot?.histories?.firstOrNull { history ->
|
||||
data.currentUser?.copilot?.chats?.edges?.map { item -> item.node }?.firstOrNull { history ->
|
||||
history.sessionId == sessionId
|
||||
}?.messages ?: emptyList()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -90,6 +90,8 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
C45499AB2D140B5000E21978 /* NBStore */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = NBStore;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -337,13 +339,9 @@
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AFFiNE/Pods-AFFiNE-frameworks.sh\"\n";
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public struct CopilotChatHistory: AffineGraphQL.SelectionSet, Fragment {
|
||||
public static var fragmentDefinition: StaticString {
|
||||
#"fragment CopilotChatHistory on CopilotHistories { __typename sessionId workspaceId docId parentSessionId promptName model optionalModels action pinned title tokens messages { __typename ...CopilotChatMessage } createdAt updatedAt }"#
|
||||
}
|
||||
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotHistories }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("sessionId", String.self),
|
||||
.field("workspaceId", String.self),
|
||||
.field("docId", String?.self),
|
||||
.field("parentSessionId", String?.self),
|
||||
.field("promptName", String.self),
|
||||
.field("model", String.self),
|
||||
.field("optionalModels", [String].self),
|
||||
.field("action", String?.self),
|
||||
.field("pinned", Bool.self),
|
||||
.field("title", String?.self),
|
||||
.field("tokens", Int.self),
|
||||
.field("messages", [Message].self),
|
||||
.field("createdAt", AffineGraphQL.DateTime.self),
|
||||
.field("updatedAt", AffineGraphQL.DateTime.self),
|
||||
] }
|
||||
|
||||
public var sessionId: String { __data["sessionId"] }
|
||||
public var workspaceId: String { __data["workspaceId"] }
|
||||
public var docId: String? { __data["docId"] }
|
||||
public var parentSessionId: String? { __data["parentSessionId"] }
|
||||
public var promptName: String { __data["promptName"] }
|
||||
public var model: String { __data["model"] }
|
||||
public var optionalModels: [String] { __data["optionalModels"] }
|
||||
/// An mark identifying which view to use to display the session
|
||||
public var action: String? { __data["action"] }
|
||||
public var pinned: Bool { __data["pinned"] }
|
||||
public var title: String? { __data["title"] }
|
||||
/// The number of tokens used in the session
|
||||
public var tokens: Int { __data["tokens"] }
|
||||
public var messages: [Message] { __data["messages"] }
|
||||
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
|
||||
public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] }
|
||||
|
||||
/// Message
|
||||
///
|
||||
/// Parent Type: `ChatMessage`
|
||||
public struct Message: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ChatMessage }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.fragment(CopilotChatMessage.self),
|
||||
] }
|
||||
|
||||
public var id: AffineGraphQL.ID? { __data["id"] }
|
||||
public var role: String { __data["role"] }
|
||||
public var content: String { __data["content"] }
|
||||
public var attachments: [String]? { __data["attachments"] }
|
||||
public var streamObjects: [StreamObject]? { __data["streamObjects"] }
|
||||
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
|
||||
|
||||
public struct Fragments: FragmentContainer {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public var copilotChatMessage: CopilotChatMessage { _toFragment() }
|
||||
}
|
||||
|
||||
public typealias StreamObject = CopilotChatMessage.StreamObject
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public struct CopilotChatMessage: AffineGraphQL.SelectionSet, Fragment {
|
||||
public static var fragmentDefinition: StaticString {
|
||||
#"fragment CopilotChatMessage on ChatMessage { __typename id role content attachments streamObjects { __typename type textDelta toolCallId toolName args result } createdAt }"#
|
||||
}
|
||||
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ChatMessage }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", AffineGraphQL.ID?.self),
|
||||
.field("role", String.self),
|
||||
.field("content", String.self),
|
||||
.field("attachments", [String]?.self),
|
||||
.field("streamObjects", [StreamObject]?.self),
|
||||
.field("createdAt", AffineGraphQL.DateTime.self),
|
||||
] }
|
||||
|
||||
public var id: AffineGraphQL.ID? { __data["id"] }
|
||||
public var role: String { __data["role"] }
|
||||
public var content: String { __data["content"] }
|
||||
public var attachments: [String]? { __data["attachments"] }
|
||||
public var streamObjects: [StreamObject]? { __data["streamObjects"] }
|
||||
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
|
||||
|
||||
/// StreamObject
|
||||
///
|
||||
/// Parent Type: `StreamObject`
|
||||
public struct StreamObject: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.StreamObject }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("type", String.self),
|
||||
.field("textDelta", String?.self),
|
||||
.field("toolCallId", String?.self),
|
||||
.field("toolName", String?.self),
|
||||
.field("args", AffineGraphQL.JSON?.self),
|
||||
.field("result", AffineGraphQL.JSON?.self),
|
||||
] }
|
||||
|
||||
public var type: String { __data["type"] }
|
||||
public var textDelta: String? { __data["textDelta"] }
|
||||
public var toolCallId: String? { __data["toolCallId"] }
|
||||
public var toolName: String? { __data["toolName"] }
|
||||
public var args: AffineGraphQL.JSON? { __data["args"] }
|
||||
public var result: AffineGraphQL.JSON? { __data["result"] }
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public struct PaginatedCopilotChats: AffineGraphQL.SelectionSet, Fragment {
|
||||
public static var fragmentDefinition: StaticString {
|
||||
#"fragment PaginatedCopilotChats on PaginatedCopilotHistoriesType { __typename pageInfo { __typename hasNextPage hasPreviousPage startCursor endCursor } edges { __typename cursor node { __typename ...CopilotChatHistory } } }"#
|
||||
}
|
||||
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCopilotHistoriesType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("pageInfo", PageInfo.self),
|
||||
.field("edges", [Edge].self),
|
||||
] }
|
||||
|
||||
public var pageInfo: PageInfo { __data["pageInfo"] }
|
||||
public var edges: [Edge] { __data["edges"] }
|
||||
|
||||
/// PageInfo
|
||||
///
|
||||
/// Parent Type: `PageInfo`
|
||||
public struct PageInfo: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PageInfo }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("hasNextPage", Bool.self),
|
||||
.field("hasPreviousPage", Bool.self),
|
||||
.field("startCursor", String?.self),
|
||||
.field("endCursor", String?.self),
|
||||
] }
|
||||
|
||||
public var hasNextPage: Bool { __data["hasNextPage"] }
|
||||
public var hasPreviousPage: Bool { __data["hasPreviousPage"] }
|
||||
public var startCursor: String? { __data["startCursor"] }
|
||||
public var endCursor: String? { __data["endCursor"] }
|
||||
}
|
||||
|
||||
/// Edge
|
||||
///
|
||||
/// Parent Type: `CopilotHistoriesTypeEdge`
|
||||
public struct Edge: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotHistoriesTypeEdge }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("cursor", String.self),
|
||||
.field("node", Node.self),
|
||||
] }
|
||||
|
||||
public var cursor: String { __data["cursor"] }
|
||||
public var node: Node { __data["node"] }
|
||||
|
||||
/// Edge.Node
|
||||
///
|
||||
/// Parent Type: `CopilotHistories`
|
||||
public struct Node: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotHistories }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.fragment(CopilotChatHistory.self),
|
||||
] }
|
||||
|
||||
public var sessionId: String { __data["sessionId"] }
|
||||
public var workspaceId: String { __data["workspaceId"] }
|
||||
public var docId: String? { __data["docId"] }
|
||||
public var parentSessionId: String? { __data["parentSessionId"] }
|
||||
public var promptName: String { __data["promptName"] }
|
||||
public var model: String { __data["model"] }
|
||||
public var optionalModels: [String] { __data["optionalModels"] }
|
||||
/// An mark identifying which view to use to display the session
|
||||
public var action: String? { __data["action"] }
|
||||
public var pinned: Bool { __data["pinned"] }
|
||||
public var title: String? { __data["title"] }
|
||||
/// The number of tokens used in the session
|
||||
public var tokens: Int { __data["tokens"] }
|
||||
public var messages: [Message] { __data["messages"] }
|
||||
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
|
||||
public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] }
|
||||
|
||||
public struct Fragments: FragmentContainer {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public var copilotChatHistory: CopilotChatHistory { _toFragment() }
|
||||
}
|
||||
|
||||
public typealias Message = CopilotChatHistory.Message
|
||||
}
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class CreateCommentMutation: GraphQLMutation {
|
||||
public static let operationName: String = "createComment"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"mutation createComment($input: CommentCreateInput!) { createComment(input: $input) { __typename id content resolved createdAt updatedAt user { __typename id name avatarUrl } replies { __typename commentId id content createdAt updatedAt user { __typename id name avatarUrl } } } }"#
|
||||
))
|
||||
|
||||
public var input: CommentCreateInput
|
||||
|
||||
public init(input: CommentCreateInput) {
|
||||
self.input = input
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["input": input] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("createComment", CreateComment.self, arguments: ["input": .variable("input")]),
|
||||
] }
|
||||
|
||||
public var createComment: CreateComment { __data["createComment"] }
|
||||
|
||||
/// CreateComment
|
||||
///
|
||||
/// Parent Type: `CommentObjectType`
|
||||
public struct CreateComment: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CommentObjectType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", AffineGraphQL.ID.self),
|
||||
.field("content", AffineGraphQL.JSONObject.self),
|
||||
.field("resolved", Bool.self),
|
||||
.field("createdAt", AffineGraphQL.DateTime.self),
|
||||
.field("updatedAt", AffineGraphQL.DateTime.self),
|
||||
.field("user", User.self),
|
||||
.field("replies", [Reply].self),
|
||||
] }
|
||||
|
||||
public var id: AffineGraphQL.ID { __data["id"] }
|
||||
/// The content of the comment
|
||||
public var content: AffineGraphQL.JSONObject { __data["content"] }
|
||||
/// Whether the comment is resolved
|
||||
public var resolved: Bool { __data["resolved"] }
|
||||
/// The created at time of the comment
|
||||
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
|
||||
/// The updated at time of the comment
|
||||
public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] }
|
||||
/// The user who created the comment
|
||||
public var user: User { __data["user"] }
|
||||
/// The replies of the comment
|
||||
public var replies: [Reply] { __data["replies"] }
|
||||
|
||||
/// CreateComment.User
|
||||
///
|
||||
/// Parent Type: `PublicUserType`
|
||||
public struct User: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PublicUserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", String.self),
|
||||
.field("name", String.self),
|
||||
.field("avatarUrl", String?.self),
|
||||
] }
|
||||
|
||||
public var id: String { __data["id"] }
|
||||
public var name: String { __data["name"] }
|
||||
public var avatarUrl: String? { __data["avatarUrl"] }
|
||||
}
|
||||
|
||||
/// CreateComment.Reply
|
||||
///
|
||||
/// Parent Type: `ReplyObjectType`
|
||||
public struct Reply: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ReplyObjectType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("commentId", AffineGraphQL.ID.self),
|
||||
.field("id", AffineGraphQL.ID.self),
|
||||
.field("content", AffineGraphQL.JSONObject.self),
|
||||
.field("createdAt", AffineGraphQL.DateTime.self),
|
||||
.field("updatedAt", AffineGraphQL.DateTime.self),
|
||||
.field("user", User.self),
|
||||
] }
|
||||
|
||||
public var commentId: AffineGraphQL.ID { __data["commentId"] }
|
||||
public var id: AffineGraphQL.ID { __data["id"] }
|
||||
/// The content of the reply
|
||||
public var content: AffineGraphQL.JSONObject { __data["content"] }
|
||||
/// The created at time of the reply
|
||||
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
|
||||
/// The updated at time of the reply
|
||||
public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] }
|
||||
/// The user who created the reply
|
||||
public var user: User { __data["user"] }
|
||||
|
||||
/// CreateComment.Reply.User
|
||||
///
|
||||
/// Parent Type: `PublicUserType`
|
||||
public struct User: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PublicUserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", String.self),
|
||||
.field("name", String.self),
|
||||
.field("avatarUrl", String?.self),
|
||||
] }
|
||||
|
||||
public var id: String { __data["id"] }
|
||||
public var name: String { __data["name"] }
|
||||
public var avatarUrl: String? { __data["avatarUrl"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class CreateReplyMutation: GraphQLMutation {
|
||||
public static let operationName: String = "createReply"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"mutation createReply($input: ReplyCreateInput!) { createReply(input: $input) { __typename commentId id content createdAt updatedAt user { __typename id name avatarUrl } } }"#
|
||||
))
|
||||
|
||||
public var input: ReplyCreateInput
|
||||
|
||||
public init(input: ReplyCreateInput) {
|
||||
self.input = input
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["input": input] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("createReply", CreateReply.self, arguments: ["input": .variable("input")]),
|
||||
] }
|
||||
|
||||
public var createReply: CreateReply { __data["createReply"] }
|
||||
|
||||
/// CreateReply
|
||||
///
|
||||
/// Parent Type: `ReplyObjectType`
|
||||
public struct CreateReply: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ReplyObjectType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("commentId", AffineGraphQL.ID.self),
|
||||
.field("id", AffineGraphQL.ID.self),
|
||||
.field("content", AffineGraphQL.JSONObject.self),
|
||||
.field("createdAt", AffineGraphQL.DateTime.self),
|
||||
.field("updatedAt", AffineGraphQL.DateTime.self),
|
||||
.field("user", User.self),
|
||||
] }
|
||||
|
||||
public var commentId: AffineGraphQL.ID { __data["commentId"] }
|
||||
public var id: AffineGraphQL.ID { __data["id"] }
|
||||
/// The content of the reply
|
||||
public var content: AffineGraphQL.JSONObject { __data["content"] }
|
||||
/// The created at time of the reply
|
||||
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
|
||||
/// The updated at time of the reply
|
||||
public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] }
|
||||
/// The user who created the reply
|
||||
public var user: User { __data["user"] }
|
||||
|
||||
/// CreateReply.User
|
||||
///
|
||||
/// Parent Type: `PublicUserType`
|
||||
public struct User: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PublicUserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", String.self),
|
||||
.field("name", String.self),
|
||||
.field("avatarUrl", String?.self),
|
||||
] }
|
||||
|
||||
public var id: String { __data["id"] }
|
||||
public var name: String { __data["name"] }
|
||||
public var avatarUrl: String? { __data["avatarUrl"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class DeleteCommentMutation: GraphQLMutation {
|
||||
public static let operationName: String = "deleteComment"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"mutation deleteComment($id: String!) { deleteComment(id: $id) }"#
|
||||
))
|
||||
|
||||
public var id: String
|
||||
|
||||
public init(id: String) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["id": id] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("deleteComment", Bool.self, arguments: ["id": .variable("id")]),
|
||||
] }
|
||||
|
||||
/// Delete a comment
|
||||
public var deleteComment: Bool { __data["deleteComment"] }
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class DeleteReplyMutation: GraphQLMutation {
|
||||
public static let operationName: String = "deleteReply"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"mutation deleteReply($id: String!) { deleteReply(id: $id) }"#
|
||||
))
|
||||
|
||||
public var id: String
|
||||
|
||||
public init(id: String) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["id": id] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("deleteReply", Bool.self, arguments: ["id": .variable("id")]),
|
||||
] }
|
||||
|
||||
/// Delete a reply
|
||||
public var deleteReply: Bool { __data["deleteReply"] }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user