Compare commits

..

17 Commits

Author SHA1 Message Date
fengmk2
b1d7011047 chore(server): use jemalloc to reduce RSS 2025-07-10 11:22:37 +08:00
L-Sun
1fe07410c0 feat(editor): can highlight resolved comment (#13122)
#### PR Dependency Tree


* **PR #13122** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Inline comments now visually distinguish between unresolved, resolved,
and deleted states.
* Only unresolved inline comments are interactive and highlighted in the
editor.

* **Bug Fixes**
* Improved accuracy in fetching and displaying all comments, including
resolved ones, during initialization.

* **Refactor**
* Enhanced handling of comment resolution and deletion to provide
clearer differentiation in both behavior and appearance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-10 03:06:05 +00:00
DarkSky
0f3066f7d0 fix(server): batch size in gemini embedding (#13120)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved reliability of embedding generation for multiple messages,
allowing partial results even if some embeddings fail.
* Enhanced error handling to ensure only valid embeddings are returned.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 15:56:10 +00:00
DarkSky
c4c11da976 feat(server): use faster model in ci test (#13038)
fix AI-329
2025-07-09 22:21:30 +08:00
Peng Xiao
38537bf310 fix(core): code block artifact styles (#13116)
fix AI-314

#### PR Dependency Tree


* **PR #13116** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved theme support for AI artifact tools, with banners and UI
adapting to light or dark mode.
* Enhanced notification handling for user actions like copying or saving
content.

* **Refactor**
* Streamlined the structure of AI artifact tools for better
maintainability and a more consistent user experience.
* Unified and modernized preview and control panels for code and
document compose tools.
* Updated component integrations to consistently pass theme and
notification services.

* **Style**
  * Updated hover effects and visual feedback for artifact tool cards.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


#### PR Dependency Tree


* **PR #13116** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
2025-07-09 13:26:06 +00:00
Wu Yue
1f87cd8752 feat(core): add onOpenDoc handler for AFFiNE Intelligence page (#13118)
Close [AI-240](https://linear.app/affine-design/issue/AI-240)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Enabled opening specific documents directly from the chat toolbar,
automatically displaying the document in the workbench and focusing the
chat tab in the sidebar.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 12:55:31 +00:00
EYHN
f54cb5c296 fix(android): fix android build error (#13117)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Improved chat session and history retrieval with support for paginated
results in the chat interface.

* **Bug Fixes**
* Enhanced reliability when loading large numbers of chat messages and
histories.

* **Refactor**
* Updated chat data handling to align with the latest backend schema and
pagination model.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 12:52:05 +00:00
fengmk2
45c016af8b fix(server): add user id to comment-attachment model (#13113)
close AF-2723



#### PR Dependency Tree


* **PR #13113** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Comment attachments now track and display the user who uploaded them.

* **Tests**
* Updated tests to verify that the uploader’s information is correctly
stored and retrieved with comment attachments.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 12:16:09 +00:00
Peng Xiao
d4c905600b feat(core): support normal attachments (#13112)
fix AF-2722


![image](https://github.com/user-attachments/assets/376a0119-ae8e-4cb4-a31c-2eb6bb56c868)


#### PR Dependency Tree


* **PR #13112** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Expanded comment editor attachment support to include any file type,
not just images.
* Added file preview and download functionality for non-image
attachments.
* Introduced notifications for attachment upload failures and downloads.
* Added a new AI artifact tool component for enhanced AI tool
integrations.

* **Style**
* Added new styles for generic file previews, including icons, file
info, and delete button.

* **Bug Fixes**
  * Improved error handling and user feedback for attachment uploads.

* **Refactor**
* Unified attachment UI rendering and handling for both images and other
file types.

* **Chores**
* Removed obsolete editor state attribute from comment preview sidebar.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


#### PR Dependency Tree


* **PR #13112** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
2025-07-09 11:22:04 +00:00
Peng Xiao
f839e5c136 fix(core): should use sonnet 4 for make it real (#13106)
#### PR Dependency Tree


* **PR #13106** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved code highlighting performance and resource management for
AI-generated code artifacts, resulting in smoother user experience and
more efficient updates.
* **Chores**
* Updated underlying AI model for "Make it real" features, which may
affect AI-generated outputs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 11:10:53 +00:00
L-Sun
39abd1bbb8 fix(editor): can not create surface block comment (#13115)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved comment handling to ensure elements from all selections are
considered, regardless of surface ID.
* Enhanced preview generation for comments to include all relevant
selections without surface-based filtering.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 11:05:03 +00:00
Lakr
ecea7bd825 fix: 🚑 compiler issue (#13114) 2025-07-09 10:18:04 +00:00
Wu Yue
d10e5ee92f feat(core): completely remove the dependence on EditorHost (#13110)
Close [AI-260](https://linear.app/affine-design/issue/AI-260)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added theme support to AI chat and message components, enabling
dynamic theming based on the current app theme.
* Introduced a reactive theme signal to the theme service for improved
theme handling.
* Integrated notification and theme services across various AI chat,
playground, and message components for consistent user experience.

* **Refactor**
* Simplified component APIs by removing dependencies on editor host and
related properties across AI chat, message, and tool components.
* Centralized and streamlined clipboard and markdown conversion
utilities, reducing external dependencies.
* Standardized the interface for context file addition and improved type
usage for better consistency.
* Reworked notification service to a class-based implementation for
improved encapsulation.
* Updated AI chat components to use injected notification and theme
services instead of host-based retrieval.

* **Bug Fixes**
* Improved reliability of copy and notification actions by decoupling
them from editor host dependencies.

* **Chores**
* Updated and cleaned up internal imports and removed unused properties
to enhance maintainability.
  * Added test IDs for sidebar close button to improve test reliability.
  * Updated test prompts in end-to-end tests for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 10:16:55 +00:00
Cats Juice
dace1d1738 fix(core): should show delete permanently for trash page multi-select (#13111)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a confirmation modal before permanently deleting pages from the
trash, ensuring users must confirm before deletion.
* Permanent deletion now displays a toast notification upon completion.

* **Improvements**
* Enhanced deletion actions with callbacks for handling completion,
cancellation, or errors.
* Permanent delete option is now conditionally available based on user
permissions (admin or owner).

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 08:45:26 +00:00
fengmk2
ae74f4ae51 fix(server): should use signed url first (#13109)
#### PR Dependency Tree


* **PR #13109** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Updated internal handling of comment attachments to improve processing
logic. No visible changes to end-user features or workflows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 08:12:16 +00:00
Peng Xiao
9071c5032d fix(core): should not be able to commit comments when uploading images (#13108)
#### PR Dependency Tree


* **PR #13108** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* The commit button in the comment editor is now properly disabled while
attachments are uploading or when the editor is empty without
attachments, preventing accidental or premature submissions.
* **New Features**
* Attachment delete button now shows a loading state during uploads for
clearer user feedback.
* **Style**
* Updated comment editor attachment button styles for a cleaner and more
consistent appearance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 07:56:34 +00:00
DarkSky
8236ecf486 fix(server): chunk session in migration (#13107)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Improved the process for updating session records to handle them in
smaller batches, enhancing reliability and performance during data
updates. No changes to user-facing features.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-09 07:32:02 +00:00
140 changed files with 3595 additions and 1193 deletions

View File

@@ -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"]

View File

@@ -43,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)
@@ -64,15 +68,16 @@ 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._handleDeleteAndResolve(comment);
this.std.get(BlockElementCommentManager).handleDeleteAndResolve(comment);
this.std
.get(BlockElementCommentManager)
.handleDeleteAndResolve(comment, 'delete');
});
}
@@ -162,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;
@@ -176,7 +184,7 @@ export class InlineCommentManager extends LifeCycleWatcher {
inlineEditor?.formatText(
selection.from,
{
[`comment-${id}`]: null,
[`comment-${id}`]: type === 'delete' ? null : false,
},
{
withoutTransact: true,

View File

@@ -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() {

View File

@@ -21,19 +21,25 @@ 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,
});
@@ -47,3 +53,7 @@ export const NullCommentInlineSpecExtension =
match: () => false,
renderer: () => html``,
});
// reuse the same identifier
NullCommentInlineSpecExtension.identifier =
CommentInlineSpecExtension.identifier;

View File

@@ -57,10 +57,12 @@ export class BlockElementCommentManager 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)
@@ -123,8 +125,7 @@ export class BlockElementCommentManager extends LifeCycleWatcher {
const gfx = this.std.get(GfxControllerIdentifier);
const elementsFromSurfaceSelection = selections
.filter(s => s instanceof SurfaceSelection)
.flatMap(({ blockId, elements }) => {
if (blockId !== gfx.surface?.id) return [];
.flatMap(({ elements }) => {
return elements
.map(id => gfx.getElementById<GfxModel>(id))
.filter(m => m !== null);
@@ -147,18 +148,29 @@ export class BlockElementCommentManager extends LifeCycleWatcher {
}
};
readonly handleDeleteAndResolve = (id: CommentId) => {
readonly handleDeleteAndResolve = (
id: CommentId,
type: 'delete' | 'resolve'
) => {
const commentedBlocks = findCommentedBlocks(this.std.store, id);
this.std.store.withoutTransact(() => {
commentedBlocks.forEach(block => {
delete block.props.comments[id];
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 => {
delete element.comments[id];
if (type === 'delete') {
delete element.comments[id];
} else {
element.comments[id] = false;
}
});
});
};

View File

@@ -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,

View File

@@ -31,7 +31,7 @@ export interface BlockStdOptions {
extensions: ExtensionType[];
}
const internalExtensions = [
export const internalExtensions = [
ServiceManager,
CommandManager,
UIEventDispatcher,

View File

@@ -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)}`
);
});
},

View File

@@ -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(

View File

@@ -361,7 +361,8 @@ export class CommentResolver {
docId,
key,
attachment.filename ?? key,
buffer
buffer,
me.id
);
return this.commentAttachmentStorage.getUrl(workspaceId, docId, key);
}

View File

@@ -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');

View File

@@ -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,
});
}

View File

@@ -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);

View File

@@ -1,4 +1,5 @@
import { PrismaClient } from '@prisma/client';
import { chunk } from 'lodash-es';
type SessionTime = {
sessionId: string;
@@ -17,16 +18,19 @@ export class CorrectSessionUpdateTime1751966744168 {
},
});
await Promise.all(
sessionTime
.filter((s): s is SessionTime => !!s._max.createdAt)
.map(s =>
db.aiSession.update({
where: { id: s.sessionId },
data: { updatedAt: s._max.createdAt },
})
)
);
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

View File

@@ -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);

View File

@@ -32,6 +32,7 @@ export class CommentAttachmentModel extends BaseModel {
name: input.name,
mime: input.mime,
size: input.size,
createdBy: input.createdBy,
},
});
}

View File

@@ -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) {

View File

@@ -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',
@@ -1677,7 +1677,7 @@ This sentence contains information from the first source[^1]. This sentence refe
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>

View File

@@ -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: {

View File

@@ -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);

View File

@@ -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');
}
}

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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()

View File

@@ -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,
)
}
}
}

View File

@@ -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()
}

View File

@@ -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";

View File

@@ -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
}
}

View File

@@ -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"] }
}
}

View File

@@ -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
}
}
}

View File

@@ -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"] }
}
}
}
}
}

View File

@@ -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"] }
}
}
}
}

View File

@@ -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"] }
}
}

View File

@@ -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"] }
}
}

View File

@@ -0,0 +1,27 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class ReadAllNotificationsMutation: GraphQLMutation {
public static let operationName: String = "readAllNotifications"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"mutation readAllNotifications { readAllNotifications }"#
))
public init() {}
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("readAllNotifications", Bool.self),
] }
/// mark all notifications as read
public var readAllNotifications: Bool { __data["readAllNotifications"] }
}
}

View File

@@ -0,0 +1,33 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class ResolveCommentMutation: GraphQLMutation {
public static let operationName: String = "resolveComment"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"mutation resolveComment($input: CommentResolveInput!) { resolveComment(input: $input) }"#
))
public var input: CommentResolveInput
public init(input: CommentResolveInput) {
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("resolveComment", Bool.self, arguments: ["input": .variable("input")]),
] }
/// Resolve a comment or not
public var resolveComment: Bool { __data["resolveComment"] }
}
}

View File

@@ -0,0 +1,33 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class UpdateCommentMutation: GraphQLMutation {
public static let operationName: String = "updateComment"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"mutation updateComment($input: CommentUpdateInput!) { updateComment(input: $input) }"#
))
public var input: CommentUpdateInput
public init(input: CommentUpdateInput) {
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("updateComment", Bool.self, arguments: ["input": .variable("input")]),
] }
/// Update a comment content
public var updateComment: Bool { __data["updateComment"] }
}
}

View File

@@ -0,0 +1,33 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class UpdateReplyMutation: GraphQLMutation {
public static let operationName: String = "updateReply"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"mutation updateReply($input: ReplyUpdateInput!) { updateReply(input: $input) }"#
))
public var input: ReplyUpdateInput
public init(input: ReplyUpdateInput) {
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("updateReply", Bool.self, arguments: ["input": .variable("input")]),
] }
/// Update a reply content
public var updateReply: Bool { __data["updateReply"] }
}
}

View File

@@ -0,0 +1,49 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class UploadCommentAttachmentMutation: GraphQLMutation {
public static let operationName: String = "uploadCommentAttachment"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"mutation uploadCommentAttachment($workspaceId: String!, $docId: String!, $attachment: Upload!) { uploadCommentAttachment( workspaceId: $workspaceId docId: $docId attachment: $attachment ) }"#
))
public var workspaceId: String
public var docId: String
public var attachment: Upload
public init(
workspaceId: String,
docId: String,
attachment: Upload
) {
self.workspaceId = workspaceId
self.docId = docId
self.attachment = attachment
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"docId": docId,
"attachment": attachment
] }
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("uploadCommentAttachment", String.self, arguments: [
"workspaceId": .variable("workspaceId"),
"docId": .variable("docId"),
"attachment": .variable("attachment")
]),
] }
/// Upload a comment attachment and return the access url
public var uploadCommentAttachment: String { __data["uploadCommentAttachment"] }
}
}

View File

@@ -0,0 +1,114 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class GetCopilotDocSessionsQuery: GraphQLQuery {
public static let operationName: String = "getCopilotDocSessions"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotDocSessions($workspaceId: String!, $docId: String!, $pagination: PaginationInput!, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats(pagination: $pagination, docId: $docId, options: $options) { __typename ...PaginatedCopilotChats } } } }"#,
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
))
public var workspaceId: String
public var docId: String
public var pagination: PaginationInput
public var options: GraphQLNullable<QueryChatHistoriesInput>
public init(
workspaceId: String,
docId: String,
pagination: PaginationInput,
options: GraphQLNullable<QueryChatHistoriesInput>
) {
self.workspaceId = workspaceId
self.docId = docId
self.pagination = pagination
self.options = options
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"docId": docId,
"pagination": pagination,
"options": options
] }
public struct Data: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
public static var __selections: [ApolloAPI.Selection] { [
.field("currentUser", CurrentUser?.self),
] }
/// Get current user
public var currentUser: CurrentUser? { __data["currentUser"] }
/// CurrentUser
///
/// Parent Type: `UserType`
public struct CurrentUser: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("copilot", Copilot.self, arguments: ["workspaceId": .variable("workspaceId")]),
] }
public var copilot: Copilot { __data["copilot"] }
/// CurrentUser.Copilot
///
/// Parent Type: `Copilot`
public struct Copilot: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("chats", Chats.self, arguments: [
"pagination": .variable("pagination"),
"docId": .variable("docId"),
"options": .variable("options")
]),
] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: AffineGraphQL.SelectionSet {
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),
.fragment(PaginatedCopilotChats.self),
] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
public struct Fragments: FragmentContainer {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public var paginatedCopilotChats: PaginatedCopilotChats { _toFragment() }
}
public typealias PageInfo = PaginatedCopilotChats.PageInfo
public typealias Edge = PaginatedCopilotChats.Edge
}
}
}
}
}

View File

@@ -7,25 +7,30 @@ public class GetCopilotHistoriesQuery: GraphQLQuery {
public static let operationName: String = "getCopilotHistories"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotHistories($workspaceId: String!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename histories(docId: $docId, options: $options) { __typename sessionId pinned tokens action createdAt messages { __typename id role content streamObjects { __typename type textDelta toolCallId toolName args result } attachments createdAt } } } } }"#
#"query getCopilotHistories($workspaceId: String!, $pagination: PaginationInput!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats(pagination: $pagination, docId: $docId, options: $options) { __typename ...PaginatedCopilotChats } } } }"#,
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
))
public var workspaceId: String
public var pagination: PaginationInput
public var docId: GraphQLNullable<String>
public var options: GraphQLNullable<QueryChatHistoriesInput>
public init(
workspaceId: String,
pagination: PaginationInput,
docId: GraphQLNullable<String>,
options: GraphQLNullable<QueryChatHistoriesInput>
) {
self.workspaceId = workspaceId
self.pagination = pagination
self.docId = docId
self.options = options
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"pagination": pagination,
"docId": docId,
"options": options
] }
@@ -67,92 +72,41 @@ public class GetCopilotHistoriesQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("histories", [History].self, arguments: [
.field("chats", Chats.self, arguments: [
"pagination": .variable("pagination"),
"docId": .variable("docId"),
"options": .variable("options")
]),
] }
public var histories: [History] { __data["histories"] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.History
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `CopilotHistories`
public struct History: AffineGraphQL.SelectionSet {
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: 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 __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCopilotHistoriesType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("sessionId", String.self),
.field("pinned", Bool.self),
.field("tokens", Int.self),
.field("action", String?.self),
.field("createdAt", AffineGraphQL.DateTime.self),
.field("messages", [Message].self),
.fragment(PaginatedCopilotChats.self),
] }
public var sessionId: String { __data["sessionId"] }
public var pinned: Bool { __data["pinned"] }
/// The number of tokens used in the session
public var tokens: Int { __data["tokens"] }
/// An mark identifying which view to use to display the session
public var action: String? { __data["action"] }
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
public var messages: [Message] { __data["messages"] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
/// CurrentUser.Copilot.History.Message
///
/// Parent Type: `ChatMessage`
public struct Message: AffineGraphQL.SelectionSet {
public struct Fragments: FragmentContainer {
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("streamObjects", [StreamObject]?.self),
.field("attachments", [String]?.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 streamObjects: [StreamObject]? { __data["streamObjects"] }
public var attachments: [String]? { __data["attachments"] }
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
/// CurrentUser.Copilot.History.Message.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"] }
}
public var paginatedCopilotChats: PaginatedCopilotChats { _toFragment() }
}
public typealias PageInfo = PaginatedCopilotChats.PageInfo
public typealias Edge = PaginatedCopilotChats.Edge
}
}
}

View File

@@ -7,25 +7,29 @@ public class GetCopilotHistoryIdsQuery: GraphQLQuery {
public static let operationName: String = "getCopilotHistoryIds"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotHistoryIds($workspaceId: String!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename histories(docId: $docId, options: $options) { __typename sessionId pinned messages { __typename id role createdAt } } } } }"#
#"query getCopilotHistoryIds($workspaceId: String!, $pagination: PaginationInput!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats(pagination: $pagination, docId: $docId, options: $options) { __typename pageInfo { __typename hasNextPage hasPreviousPage startCursor endCursor } edges { __typename cursor node { __typename sessionId pinned messages { __typename id role createdAt } } } } } } }"#
))
public var workspaceId: String
public var pagination: PaginationInput
public var docId: GraphQLNullable<String>
public var options: GraphQLNullable<QueryChatHistoriesInput>
public init(
workspaceId: String,
pagination: PaginationInput,
docId: GraphQLNullable<String>,
options: GraphQLNullable<QueryChatHistoriesInput>
) {
self.workspaceId = workspaceId
self.pagination = pagination
self.docId = docId
self.options = options
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"pagination": pagination,
"docId": docId,
"options": options
] }
@@ -67,51 +71,110 @@ public class GetCopilotHistoryIdsQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("histories", [History].self, arguments: [
.field("chats", Chats.self, arguments: [
"pagination": .variable("pagination"),
"docId": .variable("docId"),
"options": .variable("options")
]),
] }
public var histories: [History] { __data["histories"] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.History
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `CopilotHistories`
public struct History: AffineGraphQL.SelectionSet {
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: 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 __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCopilotHistoriesType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("sessionId", String.self),
.field("pinned", Bool.self),
.field("messages", [Message].self),
.field("pageInfo", PageInfo.self),
.field("edges", [Edge].self),
] }
public var sessionId: String { __data["sessionId"] }
public var pinned: Bool { __data["pinned"] }
public var messages: [Message] { __data["messages"] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
/// CurrentUser.Copilot.History.Message
/// CurrentUser.Copilot.Chats.PageInfo
///
/// Parent Type: `ChatMessage`
public struct Message: AffineGraphQL.SelectionSet {
/// 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.ChatMessage }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PageInfo }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("id", AffineGraphQL.ID?.self),
.field("role", String.self),
.field("createdAt", AffineGraphQL.DateTime.self),
.field("hasNextPage", Bool.self),
.field("hasPreviousPage", Bool.self),
.field("startCursor", String?.self),
.field("endCursor", String?.self),
] }
public var id: AffineGraphQL.ID? { __data["id"] }
public var role: String { __data["role"] }
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
public var hasNextPage: Bool { __data["hasNextPage"] }
public var hasPreviousPage: Bool { __data["hasPreviousPage"] }
public var startCursor: String? { __data["startCursor"] }
public var endCursor: String? { __data["endCursor"] }
}
/// CurrentUser.Copilot.Chats.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"] }
/// CurrentUser.Copilot.Chats.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),
.field("sessionId", String.self),
.field("pinned", Bool.self),
.field("messages", [Message].self),
] }
public var sessionId: String { __data["sessionId"] }
public var pinned: Bool { __data["pinned"] }
public var messages: [Message] { __data["messages"] }
/// CurrentUser.Copilot.Chats.Edge.Node.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),
.field("id", AffineGraphQL.ID?.self),
.field("role", String.self),
.field("createdAt", AffineGraphQL.DateTime.self),
] }
public var id: AffineGraphQL.ID? { __data["id"] }
public var role: String { __data["role"] }
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
}
}
}
}
}

View File

@@ -7,7 +7,8 @@ public class GetCopilotLatestDocSessionQuery: GraphQLQuery {
public static let operationName: String = "getCopilotLatestDocSession"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotLatestDocSession($workspaceId: String!, $docId: String!) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename histories( docId: $docId options: { limit: 1, sessionOrder: desc, action: false, fork: false } ) { __typename sessionId workspaceId docId pinned action tokens createdAt updatedAt messages { __typename id role content attachments params createdAt } } } } }"#
#"query getCopilotLatestDocSession($workspaceId: String!, $docId: String!) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats( pagination: { first: 1 } docId: $docId options: { sessionOrder: desc, action: false, fork: false, withMessages: true } ) { __typename ...PaginatedCopilotChats } } } }"#,
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
))
public var workspaceId: String
@@ -63,77 +64,46 @@ public class GetCopilotLatestDocSessionQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("histories", [History].self, arguments: [
.field("chats", Chats.self, arguments: [
"pagination": ["first": 1],
"docId": .variable("docId"),
"options": [
"limit": 1,
"sessionOrder": "desc",
"action": false,
"fork": false
"fork": false,
"withMessages": true
]
]),
] }
public var histories: [History] { __data["histories"] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.History
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `CopilotHistories`
public struct History: AffineGraphQL.SelectionSet {
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: 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 __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCopilotHistoriesType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("sessionId", String.self),
.field("workspaceId", String.self),
.field("docId", String?.self),
.field("pinned", Bool.self),
.field("action", String?.self),
.field("tokens", Int.self),
.field("createdAt", AffineGraphQL.DateTime.self),
.field("updatedAt", AffineGraphQL.DateTime.self),
.field("messages", [Message].self),
.fragment(PaginatedCopilotChats.self),
] }
public var sessionId: String { __data["sessionId"] }
public var workspaceId: String { __data["workspaceId"] }
public var docId: String? { __data["docId"] }
public var pinned: Bool { __data["pinned"] }
/// An mark identifying which view to use to display the session
public var action: String? { __data["action"] }
/// The number of tokens used in the session
public var tokens: Int { __data["tokens"] }
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] }
public var messages: [Message] { __data["messages"] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
/// CurrentUser.Copilot.History.Message
///
/// Parent Type: `ChatMessage`
public struct Message: AffineGraphQL.SelectionSet {
public struct Fragments: FragmentContainer {
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("params", AffineGraphQL.JSON?.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 params: AffineGraphQL.JSON? { __data["params"] }
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
public var paginatedCopilotChats: PaginatedCopilotChats { _toFragment() }
}
public typealias PageInfo = PaginatedCopilotChats.PageInfo
public typealias Edge = PaginatedCopilotChats.Edge
}
}
}

View File

@@ -0,0 +1,118 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class GetCopilotPinnedSessionsQuery: GraphQLQuery {
public static let operationName: String = "getCopilotPinnedSessions"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotPinnedSessions($workspaceId: String!, $docId: String, $messageOrder: ChatHistoryOrder, $withPrompt: Boolean) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats( pagination: { first: 1 } docId: $docId options: { pinned: true, messageOrder: $messageOrder, withPrompt: $withPrompt } ) { __typename ...PaginatedCopilotChats } } } }"#,
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
))
public var workspaceId: String
public var docId: GraphQLNullable<String>
public var messageOrder: GraphQLNullable<GraphQLEnum<ChatHistoryOrder>>
public var withPrompt: GraphQLNullable<Bool>
public init(
workspaceId: String,
docId: GraphQLNullable<String>,
messageOrder: GraphQLNullable<GraphQLEnum<ChatHistoryOrder>>,
withPrompt: GraphQLNullable<Bool>
) {
self.workspaceId = workspaceId
self.docId = docId
self.messageOrder = messageOrder
self.withPrompt = withPrompt
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"docId": docId,
"messageOrder": messageOrder,
"withPrompt": withPrompt
] }
public struct Data: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
public static var __selections: [ApolloAPI.Selection] { [
.field("currentUser", CurrentUser?.self),
] }
/// Get current user
public var currentUser: CurrentUser? { __data["currentUser"] }
/// CurrentUser
///
/// Parent Type: `UserType`
public struct CurrentUser: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("copilot", Copilot.self, arguments: ["workspaceId": .variable("workspaceId")]),
] }
public var copilot: Copilot { __data["copilot"] }
/// CurrentUser.Copilot
///
/// Parent Type: `Copilot`
public struct Copilot: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("chats", Chats.self, arguments: [
"pagination": ["first": 1],
"docId": .variable("docId"),
"options": [
"pinned": true,
"messageOrder": .variable("messageOrder"),
"withPrompt": .variable("withPrompt")
]
]),
] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: AffineGraphQL.SelectionSet {
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),
.fragment(PaginatedCopilotChats.self),
] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
public struct Fragments: FragmentContainer {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public var paginatedCopilotChats: PaginatedCopilotChats { _toFragment() }
}
public typealias PageInfo = PaginatedCopilotChats.PageInfo
public typealias Edge = PaginatedCopilotChats.Edge
}
}
}
}
}

View File

@@ -7,7 +7,8 @@ public class GetCopilotRecentSessionsQuery: GraphQLQuery {
public static let operationName: String = "getCopilotRecentSessions"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotRecentSessions($workspaceId: String!, $limit: Int = 10) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename histories(options: { limit: $limit, sessionOrder: desc }) { __typename sessionId workspaceId docId pinned action tokens createdAt updatedAt } } } }"#
#"query getCopilotRecentSessions($workspaceId: String!, $limit: Int = 10) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats( pagination: { first: $limit } options: { fork: false, sessionOrder: desc, withMessages: true } ) { __typename ...PaginatedCopilotChats } } } }"#,
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
))
public var workspaceId: String
@@ -63,44 +64,44 @@ public class GetCopilotRecentSessionsQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("histories", [History].self, arguments: ["options": [
"limit": .variable("limit"),
"sessionOrder": "desc"
]]),
.field("chats", Chats.self, arguments: [
"pagination": ["first": .variable("limit")],
"options": [
"fork": false,
"sessionOrder": "desc",
"withMessages": true
]
]),
] }
public var histories: [History] { __data["histories"] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.History
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `CopilotHistories`
public struct History: AffineGraphQL.SelectionSet {
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: 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 __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCopilotHistoriesType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("sessionId", String.self),
.field("workspaceId", String.self),
.field("docId", String?.self),
.field("pinned", Bool.self),
.field("action", String?.self),
.field("tokens", Int.self),
.field("createdAt", AffineGraphQL.DateTime.self),
.field("updatedAt", AffineGraphQL.DateTime.self),
.fragment(PaginatedCopilotChats.self),
] }
public var sessionId: String { __data["sessionId"] }
public var workspaceId: String { __data["workspaceId"] }
public var docId: String? { __data["docId"] }
public var pinned: Bool { __data["pinned"] }
/// An mark identifying which view to use to display the session
public var action: String? { __data["action"] }
/// The number of tokens used in the session
public var tokens: Int { __data["tokens"] }
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
public struct Fragments: FragmentContainer {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public var paginatedCopilotChats: PaginatedCopilotChats { _toFragment() }
}
public typealias PageInfo = PaginatedCopilotChats.PageInfo
public typealias Edge = PaginatedCopilotChats.Edge
}
}
}

View File

@@ -7,7 +7,8 @@ public class GetCopilotSessionQuery: GraphQLQuery {
public static let operationName: String = "getCopilotSession"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotSession($workspaceId: String!, $sessionId: String!) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename session(sessionId: $sessionId) { __typename id parentSessionId docId pinned promptName model optionalModels } } } }"#
#"query getCopilotSession($workspaceId: String!, $sessionId: String!) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats(pagination: { first: 1 }, options: { sessionId: $sessionId }) { __typename ...PaginatedCopilotChats } } } }"#,
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
))
public var workspaceId: String
@@ -63,38 +64,40 @@ public class GetCopilotSessionQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("session", Session.self, arguments: ["sessionId": .variable("sessionId")]),
.field("chats", Chats.self, arguments: [
"pagination": ["first": 1],
"options": ["sessionId": .variable("sessionId")]
]),
] }
/// Get the session by id
public var session: Session { __data["session"] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.Session
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `CopilotSessionType`
public struct Session: AffineGraphQL.SelectionSet {
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotSessionType }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCopilotHistoriesType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("id", AffineGraphQL.ID.self),
.field("parentSessionId", AffineGraphQL.ID?.self),
.field("docId", String?.self),
.field("pinned", Bool.self),
.field("promptName", String.self),
.field("model", String.self),
.field("optionalModels", [String].self),
.fragment(PaginatedCopilotChats.self),
] }
public var id: AffineGraphQL.ID { __data["id"] }
public var parentSessionId: AffineGraphQL.ID? { __data["parentSessionId"] }
public var docId: String? { __data["docId"] }
public var pinned: Bool { __data["pinned"] }
public var promptName: String { __data["promptName"] }
public var model: String { __data["model"] }
public var optionalModels: [String] { __data["optionalModels"] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
public struct Fragments: FragmentContainer {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public var paginatedCopilotChats: PaginatedCopilotChats { _toFragment() }
}
public typealias PageInfo = PaginatedCopilotChats.PageInfo
public typealias Edge = PaginatedCopilotChats.Edge
}
}
}

View File

@@ -7,25 +7,30 @@ public class GetCopilotSessionsQuery: GraphQLQuery {
public static let operationName: String = "getCopilotSessions"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotSessions($workspaceId: String!, $docId: String, $options: QueryChatSessionsInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename sessions(docId: $docId, options: $options) { __typename id parentSessionId docId pinned promptName model optionalModels } } } }"#
#"query getCopilotSessions($workspaceId: String!, $pagination: PaginationInput!, $docId: String, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats(pagination: $pagination, docId: $docId, options: $options) { __typename ...PaginatedCopilotChats } } } }"#,
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
))
public var workspaceId: String
public var pagination: PaginationInput
public var docId: GraphQLNullable<String>
public var options: GraphQLNullable<QueryChatSessionsInput>
public var options: GraphQLNullable<QueryChatHistoriesInput>
public init(
workspaceId: String,
pagination: PaginationInput,
docId: GraphQLNullable<String>,
options: GraphQLNullable<QueryChatSessionsInput>
options: GraphQLNullable<QueryChatHistoriesInput>
) {
self.workspaceId = workspaceId
self.pagination = pagination
self.docId = docId
self.options = options
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"pagination": pagination,
"docId": docId,
"options": options
] }
@@ -67,41 +72,41 @@ public class GetCopilotSessionsQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("sessions", [Session].self, arguments: [
.field("chats", Chats.self, arguments: [
"pagination": .variable("pagination"),
"docId": .variable("docId"),
"options": .variable("options")
]),
] }
/// Get the session list in the workspace
public var sessions: [Session] { __data["sessions"] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.Session
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `CopilotSessionType`
public struct Session: AffineGraphQL.SelectionSet {
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotSessionType }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCopilotHistoriesType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("id", AffineGraphQL.ID.self),
.field("parentSessionId", AffineGraphQL.ID?.self),
.field("docId", String?.self),
.field("pinned", Bool.self),
.field("promptName", String.self),
.field("model", String.self),
.field("optionalModels", [String].self),
.fragment(PaginatedCopilotChats.self),
] }
public var id: AffineGraphQL.ID { __data["id"] }
public var parentSessionId: AffineGraphQL.ID? { __data["parentSessionId"] }
public var docId: String? { __data["docId"] }
public var pinned: Bool { __data["pinned"] }
public var promptName: String { __data["promptName"] }
public var model: String { __data["model"] }
public var optionalModels: [String] { __data["optionalModels"] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
public struct Fragments: FragmentContainer {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public var paginatedCopilotChats: PaginatedCopilotChats { _toFragment() }
}
public typealias PageInfo = PaginatedCopilotChats.PageInfo
public typealias Edge = PaginatedCopilotChats.Edge
}
}
}

View File

@@ -0,0 +1,110 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class GetCopilotWorkspaceSessionsQuery: GraphQLQuery {
public static let operationName: String = "getCopilotWorkspaceSessions"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getCopilotWorkspaceSessions($workspaceId: String!, $pagination: PaginationInput!, $options: QueryChatHistoriesInput) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats(pagination: $pagination, docId: null, options: $options) { __typename ...PaginatedCopilotChats } } } }"#,
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
))
public var workspaceId: String
public var pagination: PaginationInput
public var options: GraphQLNullable<QueryChatHistoriesInput>
public init(
workspaceId: String,
pagination: PaginationInput,
options: GraphQLNullable<QueryChatHistoriesInput>
) {
self.workspaceId = workspaceId
self.pagination = pagination
self.options = options
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"pagination": pagination,
"options": options
] }
public struct Data: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
public static var __selections: [ApolloAPI.Selection] { [
.field("currentUser", CurrentUser?.self),
] }
/// Get current user
public var currentUser: CurrentUser? { __data["currentUser"] }
/// CurrentUser
///
/// Parent Type: `UserType`
public struct CurrentUser: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("copilot", Copilot.self, arguments: ["workspaceId": .variable("workspaceId")]),
] }
public var copilot: Copilot { __data["copilot"] }
/// CurrentUser.Copilot
///
/// Parent Type: `Copilot`
public struct Copilot: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("chats", Chats.self, arguments: [
"pagination": .variable("pagination"),
"docId": .null,
"options": .variable("options")
]),
] }
public var chats: Chats { __data["chats"] }
/// CurrentUser.Copilot.Chats
///
/// Parent Type: `PaginatedCopilotHistoriesType`
public struct Chats: AffineGraphQL.SelectionSet {
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),
.fragment(PaginatedCopilotChats.self),
] }
public var pageInfo: PageInfo { __data["pageInfo"] }
public var edges: [Edge] { __data["edges"] }
public struct Fragments: FragmentContainer {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public var paginatedCopilotChats: PaginatedCopilotChats { _toFragment() }
}
public typealias PageInfo = PaginatedCopilotChats.PageInfo
public typealias Edge = PaginatedCopilotChats.Edge
}
}
}
}
}

View File

@@ -7,7 +7,7 @@ public class GetDocRolePermissionsQuery: GraphQLQuery {
public static let operationName: String = "getDocRolePermissions"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getDocRolePermissions($workspaceId: String!, $docId: String!) { workspace(id: $workspaceId) { __typename doc(docId: $docId) { __typename permissions { __typename Doc_Copy Doc_Delete Doc_Duplicate Doc_Properties_Read Doc_Properties_Update Doc_Publish Doc_Read Doc_Restore Doc_TransferOwner Doc_Trash Doc_Update Doc_Users_Manage Doc_Users_Read } } } }"#
#"query getDocRolePermissions($workspaceId: String!, $docId: String!) { workspace(id: $workspaceId) { __typename doc(docId: $docId) { __typename permissions { __typename Doc_Copy Doc_Delete Doc_Duplicate Doc_Properties_Read Doc_Properties_Update Doc_Publish Doc_Read Doc_Restore Doc_TransferOwner Doc_Trash Doc_Update Doc_Users_Manage Doc_Users_Read Doc_Comments_Create Doc_Comments_Delete Doc_Comments_Read Doc_Comments_Resolve } } } }"#
))
public var workspaceId: String
@@ -92,6 +92,10 @@ public class GetDocRolePermissionsQuery: GraphQLQuery {
.field("Doc_Update", Bool.self),
.field("Doc_Users_Manage", Bool.self),
.field("Doc_Users_Read", Bool.self),
.field("Doc_Comments_Create", Bool.self),
.field("Doc_Comments_Delete", Bool.self),
.field("Doc_Comments_Read", Bool.self),
.field("Doc_Comments_Resolve", Bool.self),
] }
public var doc_Copy: Bool { __data["Doc_Copy"] }
@@ -107,6 +111,10 @@ public class GetDocRolePermissionsQuery: GraphQLQuery {
public var doc_Update: Bool { __data["Doc_Update"] }
public var doc_Users_Manage: Bool { __data["Doc_Users_Manage"] }
public var doc_Users_Read: Bool { __data["Doc_Users_Read"] }
public var doc_Comments_Create: Bool { __data["Doc_Comments_Create"] }
public var doc_Comments_Delete: Bool { __data["Doc_Comments_Delete"] }
public var doc_Comments_Read: Bool { __data["Doc_Comments_Read"] }
public var doc_Comments_Resolve: Bool { __data["Doc_Comments_Resolve"] }
}
}
}

View File

@@ -7,7 +7,7 @@ public class GetUserSettingsQuery: GraphQLQuery {
public static let operationName: String = "getUserSettings"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getUserSettings { currentUser { __typename settings { __typename receiveInvitationEmail receiveMentionEmail } } }"#
#"query getUserSettings { currentUser { __typename settings { __typename receiveInvitationEmail receiveMentionEmail receiveCommentEmail } } }"#
))
public init() {}
@@ -52,12 +52,15 @@ public class GetUserSettingsQuery: GraphQLQuery {
.field("__typename", String.self),
.field("receiveInvitationEmail", Bool.self),
.field("receiveMentionEmail", Bool.self),
.field("receiveCommentEmail", Bool.self),
] }
/// Receive invitation email
public var receiveInvitationEmail: Bool { __data["receiveInvitationEmail"] }
/// Receive mention email
public var receiveMentionEmail: Bool { __data["receiveMentionEmail"] }
/// Receive comment email
public var receiveCommentEmail: Bool { __data["receiveCommentEmail"] }
}
}
}

View File

@@ -0,0 +1,149 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class ListCommentChangesQuery: GraphQLQuery {
public static let operationName: String = "listCommentChanges"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query listCommentChanges($workspaceId: String!, $docId: String!, $pagination: PaginationInput!) { workspace(id: $workspaceId) { __typename commentChanges(docId: $docId, pagination: $pagination) { __typename totalCount edges { __typename cursor node { __typename action id commentId item } } pageInfo { __typename startCursor endCursor hasNextPage hasPreviousPage } } } }"#
))
public var workspaceId: String
public var docId: String
public var pagination: PaginationInput
public init(
workspaceId: String,
docId: String,
pagination: PaginationInput
) {
self.workspaceId = workspaceId
self.docId = docId
self.pagination = pagination
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"docId": docId,
"pagination": pagination
] }
public struct Data: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
public static var __selections: [ApolloAPI.Selection] { [
.field("workspace", Workspace.self, arguments: ["id": .variable("workspaceId")]),
] }
/// Get workspace by id
public var workspace: Workspace { __data["workspace"] }
/// Workspace
///
/// Parent Type: `WorkspaceType`
public struct Workspace: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("commentChanges", CommentChanges.self, arguments: [
"docId": .variable("docId"),
"pagination": .variable("pagination")
]),
] }
/// Get comment changes of a doc
public var commentChanges: CommentChanges { __data["commentChanges"] }
/// Workspace.CommentChanges
///
/// Parent Type: `PaginatedCommentChangeObjectType`
public struct CommentChanges: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCommentChangeObjectType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("totalCount", Int.self),
.field("edges", [Edge].self),
.field("pageInfo", PageInfo.self),
] }
public var totalCount: Int { __data["totalCount"] }
public var edges: [Edge] { __data["edges"] }
public var pageInfo: PageInfo { __data["pageInfo"] }
/// Workspace.CommentChanges.Edge
///
/// Parent Type: `CommentChangeObjectTypeEdge`
public struct Edge: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CommentChangeObjectTypeEdge }
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"] }
/// Workspace.CommentChanges.Edge.Node
///
/// Parent Type: `CommentChangeObjectType`
public struct Node: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CommentChangeObjectType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("action", GraphQLEnum<AffineGraphQL.CommentChangeAction>.self),
.field("id", AffineGraphQL.ID.self),
.field("commentId", AffineGraphQL.ID?.self),
.field("item", AffineGraphQL.JSONObject.self),
] }
/// The action of the comment change
public var action: GraphQLEnum<AffineGraphQL.CommentChangeAction> { __data["action"] }
public var id: AffineGraphQL.ID { __data["id"] }
public var commentId: AffineGraphQL.ID? { __data["commentId"] }
/// The item of the comment or reply, different types have different fields, see UnionCommentObjectType
public var item: AffineGraphQL.JSONObject { __data["item"] }
}
}
/// Workspace.CommentChanges.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("startCursor", String?.self),
.field("endCursor", String?.self),
.field("hasNextPage", Bool.self),
.field("hasPreviousPage", Bool.self),
] }
public var startCursor: String? { __data["startCursor"] }
public var endCursor: String? { __data["endCursor"] }
public var hasNextPage: Bool { __data["hasNextPage"] }
public var hasPreviousPage: Bool { __data["hasPreviousPage"] }
}
}
}
}
}

View File

@@ -0,0 +1,229 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class ListCommentsQuery: GraphQLQuery {
public static let operationName: String = "listComments"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query listComments($workspaceId: String!, $docId: String!, $pagination: PaginationInput) { workspace(id: $workspaceId) { __typename comments(docId: $docId, pagination: $pagination) { __typename totalCount edges { __typename cursor node { __typename id content resolved createdAt updatedAt user { __typename id name avatarUrl } replies { __typename commentId id content createdAt updatedAt user { __typename id name avatarUrl } } } } pageInfo { __typename startCursor endCursor hasNextPage hasPreviousPage } } } }"#
))
public var workspaceId: String
public var docId: String
public var pagination: GraphQLNullable<PaginationInput>
public init(
workspaceId: String,
docId: String,
pagination: GraphQLNullable<PaginationInput>
) {
self.workspaceId = workspaceId
self.docId = docId
self.pagination = pagination
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"docId": docId,
"pagination": pagination
] }
public struct Data: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
public static var __selections: [ApolloAPI.Selection] { [
.field("workspace", Workspace.self, arguments: ["id": .variable("workspaceId")]),
] }
/// Get workspace by id
public var workspace: Workspace { __data["workspace"] }
/// Workspace
///
/// Parent Type: `WorkspaceType`
public struct Workspace: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("comments", Comments.self, arguments: [
"docId": .variable("docId"),
"pagination": .variable("pagination")
]),
] }
/// Get comments of a doc
public var comments: Comments { __data["comments"] }
/// Workspace.Comments
///
/// Parent Type: `PaginatedCommentObjectType`
public struct Comments: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedCommentObjectType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("totalCount", Int.self),
.field("edges", [Edge].self),
.field("pageInfo", PageInfo.self),
] }
public var totalCount: Int { __data["totalCount"] }
public var edges: [Edge] { __data["edges"] }
public var pageInfo: PageInfo { __data["pageInfo"] }
/// Workspace.Comments.Edge
///
/// Parent Type: `CommentObjectTypeEdge`
public struct Edge: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CommentObjectTypeEdge }
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"] }
/// Workspace.Comments.Edge.Node
///
/// Parent Type: `CommentObjectType`
public struct Node: 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"] }
/// Workspace.Comments.Edge.Node.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"] }
}
/// Workspace.Comments.Edge.Node.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"] }
/// Workspace.Comments.Edge.Node.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"] }
}
}
}
}
/// Workspace.Comments.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("startCursor", String?.self),
.field("endCursor", String?.self),
.field("hasNextPage", Bool.self),
.field("hasPreviousPage", Bool.self),
] }
public var startCursor: String? { __data["startCursor"] }
public var endCursor: String? { __data["endCursor"] }
public var hasNextPage: Bool { __data["hasNextPage"] }
public var hasPreviousPage: Bool { __data["hasPreviousPage"] }
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
/// Comment change action
public enum CommentChangeAction: String, EnumType {
case delete = "delete"
case update = "update"
}

View File

@@ -5,6 +5,7 @@ import ApolloAPI
/// User permission in doc
public enum DocRole: String, EnumType {
case commenter = "Commenter"
case editor = "Editor"
case external = "External"
case manager = "Manager"

View File

@@ -5,6 +5,8 @@ import ApolloAPI
/// Notification type
public enum NotificationType: String, EnumType {
case comment = "Comment"
case commentMention = "CommentMention"
case invitation = "Invitation"
case invitationAccepted = "InvitationAccepted"
case invitationBlocked = "InvitationBlocked"

View File

@@ -5,9 +5,11 @@ import ApolloAPI
public enum ServerFeature: String, EnumType {
case captcha = "Captcha"
case comment = "Comment"
case copilot = "Copilot"
case copilotEmbedding = "CopilotEmbedding"
case indexer = "Indexer"
case localWorkspace = "LocalWorkspace"
case oAuth = "OAuth"
case payment = "Payment"
}

View File

@@ -11,7 +11,7 @@ public struct AddContextFileInput: InputObject {
}
public init(
blobId: String,
blobId: GraphQLNullable<String> = nil,
contextId: String
) {
__data = InputDict([
@@ -20,7 +20,7 @@ public struct AddContextFileInput: InputObject {
])
}
public var blobId: String {
public var blobId: GraphQLNullable<String> {
get { __data["blobId"] }
set { __data["blobId"] = newValue }
}

View File

@@ -0,0 +1,61 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public struct CommentCreateInput: InputObject {
public private(set) var __data: InputDict
public init(_ data: InputDict) {
__data = data
}
public init(
content: JSONObject,
docId: ID,
docMode: GraphQLEnum<DocMode>,
docTitle: String,
mentions: GraphQLNullable<[String]> = nil,
workspaceId: ID
) {
__data = InputDict([
"content": content,
"docId": docId,
"docMode": docMode,
"docTitle": docTitle,
"mentions": mentions,
"workspaceId": workspaceId
])
}
public var content: JSONObject {
get { __data["content"] }
set { __data["content"] = newValue }
}
public var docId: ID {
get { __data["docId"] }
set { __data["docId"] = newValue }
}
public var docMode: GraphQLEnum<DocMode> {
get { __data["docMode"] }
set { __data["docMode"] = newValue }
}
public var docTitle: String {
get { __data["docTitle"] }
set { __data["docTitle"] = newValue }
}
/// The mention user ids, if not provided, the comment will not be mentioned
public var mentions: GraphQLNullable<[String]> {
get { __data["mentions"] }
set { __data["mentions"] = newValue }
}
public var workspaceId: ID {
get { __data["workspaceId"] }
set { __data["workspaceId"] = newValue }
}
}

View File

@@ -0,0 +1,33 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public struct CommentResolveInput: InputObject {
public private(set) var __data: InputDict
public init(_ data: InputDict) {
__data = data
}
public init(
id: ID,
resolved: Bool
) {
__data = InputDict([
"id": id,
"resolved": resolved
])
}
public var id: ID {
get { __data["id"] }
set { __data["id"] = newValue }
}
/// Whether the comment is resolved
public var resolved: Bool {
get { __data["resolved"] }
set { __data["resolved"] = newValue }
}
}

View File

@@ -0,0 +1,32 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public struct CommentUpdateInput: InputObject {
public private(set) var __data: InputDict
public init(_ data: InputDict) {
__data = data
}
public init(
content: JSONObject,
id: ID
) {
__data = InputDict([
"content": content,
"id": id
])
}
public var content: JSONObject {
get { __data["content"] }
set { __data["content"] = newValue }
}
public var id: ID {
get { __data["id"] }
set { __data["id"] = newValue }
}
}

View File

@@ -14,12 +14,14 @@ public struct CreateChatSessionInput: InputObject {
docId: GraphQLNullable<String> = nil,
pinned: GraphQLNullable<Bool> = nil,
promptName: String,
reuseLatestChat: GraphQLNullable<Bool> = nil,
workspaceId: String
) {
__data = InputDict([
"docId": docId,
"pinned": pinned,
"promptName": promptName,
"reuseLatestChat": reuseLatestChat,
"workspaceId": workspaceId
])
}
@@ -40,6 +42,12 @@ public struct CreateChatSessionInput: InputObject {
set { __data["promptName"] = newValue }
}
/// true by default, compliant for old version
public var reuseLatestChat: GraphQLNullable<Bool> {
get { __data["reuseLatestChat"] }
set { __data["reuseLatestChat"] = newValue }
}
public var workspaceId: String {
get { __data["workspaceId"] }
set { __data["workspaceId"] = newValue }

View File

@@ -19,6 +19,7 @@ public struct QueryChatHistoriesInput: InputObject {
sessionId: GraphQLNullable<String> = nil,
sessionOrder: GraphQLNullable<GraphQLEnum<ChatHistoryOrder>> = nil,
skip: GraphQLNullable<Int> = nil,
withMessages: GraphQLNullable<Bool> = nil,
withPrompt: GraphQLNullable<Bool> = nil
) {
__data = InputDict([
@@ -30,6 +31,7 @@ public struct QueryChatHistoriesInput: InputObject {
"sessionId": sessionId,
"sessionOrder": sessionOrder,
"skip": skip,
"withMessages": withMessages,
"withPrompt": withPrompt
])
}
@@ -74,6 +76,11 @@ public struct QueryChatHistoriesInput: InputObject {
set { __data["skip"] = newValue }
}
public var withMessages: GraphQLNullable<Bool> {
get { __data["withMessages"] }
set { __data["withMessages"] = newValue }
}
public var withPrompt: GraphQLNullable<Bool> {
get { __data["withPrompt"] }
set { __data["withPrompt"] = newValue }

View File

@@ -1,53 +0,0 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public struct QueryChatSessionsInput: InputObject {
public private(set) var __data: InputDict
public init(_ data: InputDict) {
__data = data
}
public init(
action: GraphQLNullable<Bool> = nil,
fork: GraphQLNullable<Bool> = nil,
limit: GraphQLNullable<Int> = nil,
pinned: GraphQLNullable<Bool> = nil,
skip: GraphQLNullable<Int> = nil
) {
__data = InputDict([
"action": action,
"fork": fork,
"limit": limit,
"pinned": pinned,
"skip": skip
])
}
public var action: GraphQLNullable<Bool> {
get { __data["action"] }
set { __data["action"] = newValue }
}
public var fork: GraphQLNullable<Bool> {
get { __data["fork"] }
set { __data["fork"] = newValue }
}
public var limit: GraphQLNullable<Int> {
get { __data["limit"] }
set { __data["limit"] = newValue }
}
public var pinned: GraphQLNullable<Bool> {
get { __data["pinned"] }
set { __data["pinned"] = newValue }
}
public var skip: GraphQLNullable<Int> {
get { __data["skip"] }
set { __data["skip"] = newValue }
}
}

View File

@@ -0,0 +1,54 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public struct ReplyCreateInput: InputObject {
public private(set) var __data: InputDict
public init(_ data: InputDict) {
__data = data
}
public init(
commentId: ID,
content: JSONObject,
docMode: GraphQLEnum<DocMode>,
docTitle: String,
mentions: GraphQLNullable<[String]> = nil
) {
__data = InputDict([
"commentId": commentId,
"content": content,
"docMode": docMode,
"docTitle": docTitle,
"mentions": mentions
])
}
public var commentId: ID {
get { __data["commentId"] }
set { __data["commentId"] = newValue }
}
public var content: JSONObject {
get { __data["content"] }
set { __data["content"] = newValue }
}
public var docMode: GraphQLEnum<DocMode> {
get { __data["docMode"] }
set { __data["docMode"] = newValue }
}
public var docTitle: String {
get { __data["docTitle"] }
set { __data["docTitle"] = newValue }
}
/// The mention user ids, if not provided, the comment reply will not be mentioned
public var mentions: GraphQLNullable<[String]> {
get { __data["mentions"] }
set { __data["mentions"] = newValue }
}
}

View File

@@ -0,0 +1,32 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public struct ReplyUpdateInput: InputObject {
public private(set) var __data: InputDict
public init(_ data: InputDict) {
__data = data
}
public init(
content: JSONObject,
id: ID
) {
__data = InputDict([
"content": content,
"id": id
])
}
public var content: JSONObject {
get { __data["content"] }
set { __data["content"] = newValue }
}
public var id: ID {
get { __data["id"] }
set { __data["id"] = newValue }
}
}

View File

@@ -11,15 +11,23 @@ public struct UpdateUserSettingsInput: InputObject {
}
public init(
receiveCommentEmail: GraphQLNullable<Bool> = nil,
receiveInvitationEmail: GraphQLNullable<Bool> = nil,
receiveMentionEmail: GraphQLNullable<Bool> = nil
) {
__data = InputDict([
"receiveCommentEmail": receiveCommentEmail,
"receiveInvitationEmail": receiveInvitationEmail,
"receiveMentionEmail": receiveMentionEmail
])
}
/// Receive comment email
public var receiveCommentEmail: GraphQLNullable<Bool> {
get { __data["receiveCommentEmail"] }
set { __data["receiveCommentEmail"] = newValue }
}
/// Receive invitation email
public var receiveInvitationEmail: GraphQLNullable<Bool> {
get { __data["receiveInvitationEmail"] }

View File

@@ -0,0 +1,12 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let CommentChangeObjectType = ApolloAPI.Object(
typename: "CommentChangeObjectType",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -0,0 +1,12 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let CommentChangeObjectTypeEdge = ApolloAPI.Object(
typename: "CommentChangeObjectTypeEdge",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -4,8 +4,8 @@
import ApolloAPI
public extension Objects {
static let CopilotSessionType = ApolloAPI.Object(
typename: "CopilotSessionType",
static let CommentObjectType = ApolloAPI.Object(
typename: "CommentObjectType",
implementedInterfaces: [],
keyFields: nil
)

View File

@@ -0,0 +1,12 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let CommentObjectTypeEdge = ApolloAPI.Object(
typename: "CommentObjectTypeEdge",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -0,0 +1,12 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let CopilotHistoriesTypeEdge = ApolloAPI.Object(
typename: "CopilotHistoriesTypeEdge",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -0,0 +1,12 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let PaginatedCommentChangeObjectType = ApolloAPI.Object(
typename: "PaginatedCommentChangeObjectType",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -0,0 +1,12 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let PaginatedCommentObjectType = ApolloAPI.Object(
typename: "PaginatedCommentObjectType",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -0,0 +1,12 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let PaginatedCopilotHistoriesType = ApolloAPI.Object(
typename: "PaginatedCopilotHistoriesType",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -0,0 +1,12 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let ReplyObjectType = ApolloAPI.Object(
typename: "ReplyObjectType",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -25,6 +25,10 @@ public enum SchemaMetadata: ApolloAPI.SchemaMetadata {
case "AggregateResultObjectType": return AffineGraphQL.Objects.AggregateResultObjectType
case "AppConfigValidateResult": return AffineGraphQL.Objects.AppConfigValidateResult
case "ChatMessage": return AffineGraphQL.Objects.ChatMessage
case "CommentChangeObjectType": return AffineGraphQL.Objects.CommentChangeObjectType
case "CommentChangeObjectTypeEdge": return AffineGraphQL.Objects.CommentChangeObjectTypeEdge
case "CommentObjectType": return AffineGraphQL.Objects.CommentObjectType
case "CommentObjectTypeEdge": return AffineGraphQL.Objects.CommentObjectTypeEdge
case "ContextMatchedDocChunk": return AffineGraphQL.Objects.ContextMatchedDocChunk
case "ContextMatchedFileChunk": return AffineGraphQL.Objects.ContextMatchedFileChunk
case "ContextWorkspaceEmbeddingStatus": return AffineGraphQL.Objects.ContextWorkspaceEmbeddingStatus
@@ -35,11 +39,11 @@ public enum SchemaMetadata: ApolloAPI.SchemaMetadata {
case "CopilotContextFile": return AffineGraphQL.Objects.CopilotContextFile
case "CopilotDocType": return AffineGraphQL.Objects.CopilotDocType
case "CopilotHistories": return AffineGraphQL.Objects.CopilotHistories
case "CopilotHistoriesTypeEdge": return AffineGraphQL.Objects.CopilotHistoriesTypeEdge
case "CopilotPromptConfigType": return AffineGraphQL.Objects.CopilotPromptConfigType
case "CopilotPromptMessageType": return AffineGraphQL.Objects.CopilotPromptMessageType
case "CopilotPromptType": return AffineGraphQL.Objects.CopilotPromptType
case "CopilotQuota": return AffineGraphQL.Objects.CopilotQuota
case "CopilotSessionType": return AffineGraphQL.Objects.CopilotSessionType
case "CopilotWorkspaceConfig": return AffineGraphQL.Objects.CopilotWorkspaceConfig
case "CopilotWorkspaceFile": return AffineGraphQL.Objects.CopilotWorkspaceFile
case "CopilotWorkspaceFileTypeEdge": return AffineGraphQL.Objects.CopilotWorkspaceFileTypeEdge
@@ -67,6 +71,9 @@ public enum SchemaMetadata: ApolloAPI.SchemaMetadata {
case "NotificationObjectType": return AffineGraphQL.Objects.NotificationObjectType
case "NotificationObjectTypeEdge": return AffineGraphQL.Objects.NotificationObjectTypeEdge
case "PageInfo": return AffineGraphQL.Objects.PageInfo
case "PaginatedCommentChangeObjectType": return AffineGraphQL.Objects.PaginatedCommentChangeObjectType
case "PaginatedCommentObjectType": return AffineGraphQL.Objects.PaginatedCommentObjectType
case "PaginatedCopilotHistoriesType": return AffineGraphQL.Objects.PaginatedCopilotHistoriesType
case "PaginatedCopilotWorkspaceFileType": return AffineGraphQL.Objects.PaginatedCopilotWorkspaceFileType
case "PaginatedDocType": return AffineGraphQL.Objects.PaginatedDocType
case "PaginatedGrantedDocUserType": return AffineGraphQL.Objects.PaginatedGrantedDocUserType
@@ -77,6 +84,7 @@ public enum SchemaMetadata: ApolloAPI.SchemaMetadata {
case "Query": return AffineGraphQL.Objects.Query
case "ReleaseVersionType": return AffineGraphQL.Objects.ReleaseVersionType
case "RemoveAvatar": return AffineGraphQL.Objects.RemoveAvatar
case "ReplyObjectType": return AffineGraphQL.Objects.ReplyObjectType
case "SearchDocObjectType": return AffineGraphQL.Objects.SearchDocObjectType
case "SearchNodeObjectType": return AffineGraphQL.Objects.SearchNodeObjectType
case "SearchResultObjectType": return AffineGraphQL.Objects.SearchResultObjectType

View File

@@ -33,7 +33,7 @@ extension ChatManager {
append(sessionId: sessionId, UserMessageCellViewModel(
id: .init(),
content: inputBoxData.text,
timestamp: .init(),
timestamp: .init()
))
append(sessionId: sessionId, UserHintCellViewModel(
id: .init(),

View File

@@ -55,7 +55,7 @@ class AssistantMessageCell: ChatBaseCell {
)
markdownViewForSizeCalculation.setMarkdown(
vm.documentBlocks,
renderedContent: vm.documentRenderedContent,
renderedContent: vm.documentRenderedContent
)
let boundingSize = markdownViewForSizeCalculation.boundingSize(for: width)
return ceil(boundingSize.height)

View File

@@ -1,4 +1,5 @@
import type {
AddContextFileInput,
ContextMatchedDocChunk,
ContextMatchedFileChunk,
ContextWorkspaceEmbeddingStatus,
@@ -295,10 +296,7 @@ declare global {
}) => Promise<boolean>;
addContextFile: (
file: File,
options: {
contextId: string;
blobId: string;
}
options: AddContextFileInput
) => Promise<CopilotContextFile>;
removeContextFile: (options: {
contextId: string;

View File

@@ -1,5 +1,9 @@
import type { TextRendererOptions } from '@affine/core/blocksuite/ai/components/text-renderer';
import type { EditorHost } from '@blocksuite/affine/std';
import {
NotificationProvider,
ThemeProvider,
} from '@blocksuite/affine-shared/services';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
@@ -65,6 +69,7 @@ export class AIChatBlockMessage extends LitElement {
}
private renderStreamObjects(answer: StreamObject[]) {
const notificationService = this.host.std.get(NotificationProvider);
return html`<chat-content-stream-objects
.answer=${answer}
.host=${this.host}
@@ -72,17 +77,19 @@ export class AIChatBlockMessage extends LitElement {
.extensions=${this.textRendererOptions.extensions}
.affineFeatureFlagService=${this.textRendererOptions
.affineFeatureFlagService}
.notificationService=${notificationService}
.theme=${this.host.std.get(ThemeProvider).app$}
></chat-content-stream-objects>`;
}
private renderRichText(text: string) {
return html`<chat-content-rich-text
.host=${this.host}
.text=${text}
.state=${this.state}
.extensions=${this.textRendererOptions.extensions}
.affineFeatureFlagService=${this.textRendererOptions
.affineFeatureFlagService}
.theme=${this.host.std.get(ThemeProvider).app$}
></chat-content-rich-text>`;
}

View File

@@ -1,6 +1,7 @@
import { WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import type { EditorHost } from '@blocksuite/affine/std';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import {
ArrowDownBigIcon as ArrowDownIcon,
ArrowUpBigIcon as ArrowUpIcon,
@@ -163,16 +164,18 @@ export class ActionWrapper extends WithDisposable(LitElement) {
></chat-content-images>`
: nothing}
${answer
? createTextRenderer(this.host, {
? createTextRenderer({
customHeading: true,
testId: 'chat-message-action-answer',
theme: this.host.std.get(ThemeProvider).app$,
})(answer)
: nothing}
${originalText
? html`<div class="subtitle prompt">Prompt</div>
${createTextRenderer(this.host, {
${createTextRenderer({
customHeading: true,
testId: 'chat-message-action-prompt',
theme: this.host.std.get(ThemeProvider).app$,
})(item.messages[0].content + originalText)}`
: nothing}
</div>

View File

@@ -3,6 +3,7 @@ import './action-wrapper';
import { WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVar } from '@blocksuite/affine/shared/theme';
import type { EditorHost } from '@blocksuite/affine/std';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
@@ -57,8 +58,9 @@ export class ActionText extends WithDisposable(LitElement) {
class="original-text"
data-testid="original-text"
>
${createTextRenderer(this.host, {
${createTextRenderer({
customHeading: true,
theme: this.host.std.get(ThemeProvider).app$,
})(originalText)}
</div>
</action-wrapper>`;

View File

@@ -1,5 +1,6 @@
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import type { AppThemeService } from '@affine/core/modules/theme';
import type { WorkbenchService } from '@affine/core/modules/workbench';
import type {
ContextEmbedStatus,
@@ -7,7 +8,7 @@ import type {
UpdateChatSessionInput,
} from '@affine/graphql';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { NotificationProvider } from '@blocksuite/affine/shared/services';
import { type NotificationService } from '@blocksuite/affine/shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
@@ -125,6 +126,12 @@ export class ChatPanel extends SignalWatcher(
@property({ attribute: false })
accessor affineWorkbenchService!: WorkbenchService;
@property({ attribute: false })
accessor affineThemeService!: AppThemeService;
@property({ attribute: false })
accessor notificationService!: NotificationService;
@state()
accessor session: CopilotChatHistoryFragment | null | undefined;
@@ -144,7 +151,6 @@ export class ChatPanel extends SignalWatcher(
private get chatTitle() {
const [done, total] = this.embeddingProgress;
const isEmbedding = total > 0 && done < total;
const notification = this.host.std.getOptional(NotificationProvider);
return html`
<div class="chat-panel-title-text">
@@ -170,7 +176,7 @@ export class ChatPanel extends SignalWatcher(
.onOpenSession=${this.openSession}
.onOpenDoc=${this.openDoc}
.docDisplayConfig=${this.docDisplayConfig}
.notification=${notification}
.notificationService=${this.notificationService}
></ai-chat-toolbar>
`;
}
@@ -371,6 +377,8 @@ export class ChatPanel extends SignalWatcher(
.docDisplayConfig=${this.docDisplayConfig}
.extensions=${this.extensions}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.affineThemeService=${this.affineThemeService}
.notificationService=${this.notificationService}
></playground-content>
`;
@@ -444,6 +452,8 @@ export class ChatPanel extends SignalWatcher(
.extensions=${this.extensions}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
.affineThemeService=${this.affineThemeService}
.notificationService=${this.notificationService}
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
.onContextChange=${this.onContextChange}
.width=${this.sidebarWidth}

View File

@@ -1,10 +1,12 @@
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import type { AppThemeService } from '@affine/core/modules/theme';
import type { CopilotChatHistoryFragment } from '@affine/graphql';
import { WithDisposable } from '@blocksuite/affine/global/lit';
import { isInsidePageEditor } from '@blocksuite/affine/shared/utils';
import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store';
import type { NotificationService } from '@blocksuite/affine-shared/services';
import type { Signal } from '@preact/signals-core';
import { css, html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
@@ -35,9 +37,6 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor host: EditorHost | null | undefined;
@property({ attribute: false })
accessor docId: string | undefined;
@property({ attribute: false })
accessor item!: ChatMessage;
@@ -56,6 +55,9 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor affineFeatureFlagService!: FeatureFlagService;
@property({ attribute: false })
accessor affineThemeService!: AppThemeService;
@property({ attribute: false })
accessor session!: CopilotChatHistoryFragment | null | undefined;
@@ -68,6 +70,9 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor width: Signal<number | undefined> | undefined;
@property({ attribute: false })
accessor notificationService!: NotificationService;
get state() {
const { isLast, status } = this;
return isLast
@@ -118,27 +123,29 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
private renderStreamObjects(answer: StreamObject[]) {
return html`<chat-content-stream-objects
.answer=${answer}
.host=${this.host}
.answer=${answer}
.state=${this.state}
.width=${this.width}
.extensions=${this.extensions}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.notificationService=${this.notificationService}
.theme=${this.affineThemeService.appTheme.themeSignal}
></chat-content-stream-objects>`;
}
private renderRichText(text: string) {
return html`<chat-content-rich-text
.host=${this.host}
.text=${text}
.state=${this.state}
.extensions=${this.extensions}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.theme=${this.affineThemeService.appTheme.themeSignal}
></chat-content-rich-text>`;
}
private renderEditorActions() {
const { item, isLast, status, host, session, docId } = this;
const { item, isLast, status, host, session } = this;
if (!isChatMessage(item) || item.role !== 'assistant') return nothing;
@@ -161,7 +168,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
: EdgelessEditorActions
: null;
const showActions = host && docId && !!markdown;
const showActions = host && !!markdown;
return html`
<chat-copy-more
@@ -173,6 +180,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
.messageId=${messageId}
.withMargin=${true}
.retry=${() => this.retry()}
.notificationService=${this.notificationService}
></chat-copy-more>
${isLast && showActions
? html`<chat-action-list
@@ -182,6 +190,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
.content=${markdown}
.messageId=${messageId ?? undefined}
.withMargin=${true}
.notificationService=${this.notificationService}
></chat-action-list>`
: nothing}
`;

View File

@@ -2,7 +2,7 @@ import type { TagMeta } from '@affine/core/components/page-list';
import { createLitPortal } from '@blocksuite/affine/components/portal';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
import { MoreVerticalIcon, PlusIcon } from '@blocksuite/icons/lit';
import { flip, offset } from '@floating-ui/dom';
import { computed, type Signal, signal } from '@preact/signals-core';
@@ -82,9 +82,6 @@ export class ChatPanelChips extends SignalWatcher(
private _abortController: AbortController | null = null;
@property({ attribute: false })
accessor host: EditorHost | null | undefined;
@property({ attribute: false })
accessor chips!: ChatChip[];
@@ -167,7 +164,6 @@ export class ChatPanelChips extends SignalWatcher(
.removeChip=${this._removeChip}
.checkTokenLimit=${this._checkTokenLimit}
.docDisplayConfig=${this.docDisplayConfig}
.host=${this.host}
></chat-panel-doc-chip>`;
}
if (isFileChip(chip)) {
@@ -407,13 +403,8 @@ export class ChatPanelChips extends SignalWatcher(
if (!contextId || !AIProvider.context) {
throw new Error('Context not found');
}
if (!this.host) {
throw new Error('Host not found');
}
const blobId = await this.host.store.blobSync.set(chip.file);
const contextFile = await AIProvider.context.addContextFile(chip.file, {
contextId,
blobId,
});
this._updateChip(chip, {
state: contextFile.status,

View File

@@ -1,6 +1,6 @@
import track from '@affine/track';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
import { Signal } from '@preact/signals-core';
import { html, type PropertyValues } from 'lit';
import { property } from 'lit/decorators.js';
@@ -36,9 +36,6 @@ export class ChatPanelDocChip extends SignalWatcher(
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@property({ attribute: false })
accessor host: EditorHost | null | undefined;
private chipName = new Signal<string>('');
override connectedCallback() {
@@ -103,9 +100,6 @@ export class ChatPanelDocChip extends SignalWatcher(
};
private readonly processDocChip = async () => {
if (!this.host) {
return;
}
try {
const doc = this.docDisplayConfig.getDoc(this.chip.docId);
if (!doc) {
@@ -114,10 +108,7 @@ export class ChatPanelDocChip extends SignalWatcher(
if (!doc.ready) {
doc.load();
}
const value = await extractMarkdownFromDoc(
doc,
this.host.std.store.provider
);
const value = await extractMarkdownFromDoc(doc);
const tokenCount = estimateTokenCount(value);
if (this.checkTokenLimit(this.chip, tokenCount)) {
const markdown = this.chip.markdown ?? new Signal<string>('');

View File

@@ -120,7 +120,6 @@ export class AIChatComposer extends SignalWatcher(
override render() {
return html`
<chat-panel-chips
.host=${this.host}
.chips=${this.chips}
.createContextId=${this._createContextId}
.updateChips=${this.updateChips}

View File

@@ -1,5 +1,6 @@
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import type { AppThemeService } from '@affine/core/modules/theme';
import type {
ContextEmbedStatus,
CopilotChatHistoryFragment,
@@ -8,6 +9,7 @@ import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store';
import type { NotificationService } from '@blocksuite/affine-shared/services';
import { type Signal } from '@preact/signals-core';
import {
css,
@@ -160,6 +162,12 @@ export class AIChatContent extends SignalWatcher(
@property({ attribute: false })
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
@property({ attribute: false })
accessor affineThemeService!: AppThemeService;
@property({ attribute: false })
accessor notificationService!: NotificationService;
@property({ attribute: false })
accessor onEmbeddingProgressChange!: (
count: Record<ContextEmbedStatus, number>
@@ -401,6 +409,8 @@ export class AIChatContent extends SignalWatcher(
.isHistoryLoading=${this.isHistoryLoading}
.extensions=${this.extensions}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.affineThemeService=${this.affineThemeService}
.notificationService=${this.notificationService}
.networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig}
.width=${this.width}

View File

@@ -1,8 +1,10 @@
import type { AppThemeService } from '@affine/core/modules/theme';
import type { CopilotChatHistoryFragment } from '@affine/graphql';
import { WithDisposable } from '@blocksuite/affine/global/lit';
import {
DocModeProvider,
FeatureFlagService,
type FeatureFlagService,
type NotificationService,
} from '@blocksuite/affine/shared/services';
import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
@@ -187,6 +189,12 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor affineFeatureFlagService!: FeatureFlagService;
@property({ attribute: false })
accessor affineThemeService!: AppThemeService;
@property({ attribute: false })
accessor notificationService!: NotificationService;
@property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig;
@@ -222,8 +230,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
}
private _renderAIOnboarding() {
return this.isHistoryLoading ||
!this.host?.store.get(FeatureFlagService).getFlag('enable_ai_onboarding')
return this.isHistoryLoading
? nothing
: html`<div class="onboarding-wrapper" data-testid="ai-onboarding">
${repeat(
@@ -311,7 +318,6 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
} else if (isChatMessage(item) && item.role === 'assistant') {
return html`<chat-message-assistant
.host=${this.host}
.docId=${this.docId}
.session=${this.session}
.item=${item}
.isLast=${isLast}
@@ -319,6 +325,8 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
.error=${isLast ? error : null}
.extensions=${this.extensions}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.affineThemeService=${this.affineThemeService}
.notificationService=${this.notificationService}
.retry=${() => this.retry()}
.width=${this.width}
></chat-message-assistant>`;

View File

@@ -42,7 +42,7 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
accessor docDisplayConfig!: DocDisplayConfig;
@property({ attribute: false })
accessor notification: NotificationService | null | undefined;
accessor notificationService!: NotificationService;
@query('.history-button')
accessor historyButton!: HTMLDivElement;
@@ -104,21 +104,19 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
private readonly unpinConfirm = async () => {
if (this.session && this.session.pinned) {
try {
const confirm = this.notification
? await this.notification.confirm({
title: 'Switch Chat? Current chat is pinned',
message:
'Switching will unpinned the current chat. This will change the active chat panel, allowing you to navigate between different conversation histories.',
confirmText: 'Switch Chat',
cancelText: 'Cancel',
})
: true;
const confirm = await this.notificationService.confirm({
title: 'Switch Chat? Current chat is pinned',
message:
'Switching will unpinned the current chat. This will change the active chat panel, allowing you to navigate between different conversation histories.',
confirmText: 'Switch Chat',
cancelText: 'Cancel',
});
if (!confirm) {
return false;
}
await this.onTogglePin();
} catch {
this.notification?.toast('Failed to unpin the chat');
this.notificationService.toast('Failed to unpin the chat');
}
}
return true;
@@ -133,7 +131,7 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
private readonly onSessionClick = async (sessionId: string) => {
if (this.session?.sessionId === sessionId) {
this.notification?.toast('You are already in this chat');
this.notificationService.toast('You are already in this chat');
return;
}
const confirm = await this.unpinConfirm();
@@ -144,7 +142,7 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
private readonly onDocClick = async (docId: string, sessionId: string) => {
if (this.docId === docId && this.session?.sessionId === sessionId) {
this.notification?.toast('You are already in this chat');
this.notificationService.toast('You are already in this chat');
return;
}
this.onOpenDoc(docId, sessionId);
@@ -169,7 +167,6 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
.docDisplayConfig=${this.docDisplayConfig}
.onSessionClick=${this.onSessionClick}
.onDocClick=${this.onDocClick}
.notification=${this.notification}
></ai-session-history>
`,
portalStyles: {

View File

@@ -1,6 +1,5 @@
import type { CopilotSessionType } from '@affine/graphql';
import { WithDisposable } from '@blocksuite/affine/global/lit';
import type { NotificationService } from '@blocksuite/affine/shared/services';
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { ShadowlessElement } from '@blocksuite/affine/std';
@@ -134,9 +133,6 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor onDocClick!: (docId: string, sessionId: string) => void;
@property({ attribute: false })
accessor notification: NotificationService | null | undefined;
@state()
private accessor sessions: BlockSuitePresets.AIRecentSession[] = [];

View File

@@ -18,7 +18,7 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
accessor session!: CopilotChatHistoryFragment | null | undefined;
@property({ attribute: false })
accessor notification: NotificationService | null | undefined;
accessor notificationService!: NotificationService;
@property({ attribute: false })
accessor doc!: Store;
@@ -52,15 +52,13 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
}
const sessionId = this.session.sessionId;
try {
const confirm = this.notification
? await this.notification.confirm({
title: 'Clear History',
message:
'Are you sure you want to clear all history? This action will permanently delete all content, including all chat logs and data, and cannot be undone.',
confirmText: 'Confirm',
cancelText: 'Cancel',
})
: true;
const confirm = await this.notificationService.confirm({
title: 'Clear History',
message:
'Are you sure you want to clear all history? This action will permanently delete all content, including all chat logs and data, and cannot be undone.',
confirmText: 'Confirm',
cancelText: 'Cancel',
});
if (confirm) {
const actionIds = this.chatContextValue.messages
@@ -71,11 +69,11 @@ export class AIHistoryClear extends WithDisposable(ShadowlessElement) {
this.doc.id,
[...(sessionId ? [sessionId] : []), ...(actionIds || [])]
);
this.notification?.toast('History cleared');
this.notificationService.toast('History cleared');
this.onHistoryCleared?.();
}
} catch {
this.notification?.toast('Failed to clear history');
this.notificationService.toast('Failed to clear history');
}
};

View File

@@ -1,17 +1,15 @@
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { WithDisposable } from '@blocksuite/affine/global/lit';
import type { EditorHost } from '@blocksuite/affine/std';
import type { ColorScheme } from '@blocksuite/affine/model';
import { ShadowlessElement } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store';
import type { Signal } from '@preact/signals-core';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import { createTextRenderer } from '../../components/text-renderer';
export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor host: EditorHost | null | undefined;
@property({ attribute: false })
accessor text!: string;
@@ -24,12 +22,16 @@ export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor affineFeatureFlagService!: FeatureFlagService;
@property({ attribute: false })
accessor theme!: Signal<ColorScheme>;
protected override render() {
const { text, host } = this;
return html`${createTextRenderer(host, {
const { text } = this;
return html`${createTextRenderer({
customHeading: true,
extensions: this.extensions,
affineFeatureFlagService: this.affineFeatureFlagService,
theme: this.theme,
})(text, this.state)}`;
}
}

View File

@@ -1,14 +1,13 @@
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { WithDisposable } from '@blocksuite/affine/global/lit';
import { ImageProxyService } from '@blocksuite/affine/shared/adapters';
import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
import type { ColorScheme } from '@blocksuite/affine/model';
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store';
import type { NotificationService } from '@blocksuite/affine-shared/services';
import type { Signal } from '@preact/signals-core';
import { css, html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { BlockDiffProvider } from '../../services/block-diff';
import type { AffineAIPanelState } from '../../widgets/ai-panel/type';
import type { StreamObject } from '../ai-chat-messages';
@@ -42,12 +41,16 @@ export class ChatContentStreamObjects extends WithDisposable(
@property({ attribute: false })
accessor affineFeatureFlagService!: FeatureFlagService;
@property({ attribute: false })
accessor theme!: Signal<ColorScheme>;
@property({ attribute: false })
accessor notificationService!: NotificationService;
private renderToolCall(streamObject: StreamObject) {
if (streamObject.type !== 'tool-call') {
return nothing;
}
const imageProxyService = this.host?.store.get(ImageProxyService);
const blockDiffService = this.host?.view.std.getOptional(BlockDiffProvider);
switch (streamObject.toolName) {
case 'web_crawl_exa':
@@ -55,7 +58,6 @@ export class ChatContentStreamObjects extends WithDisposable(
<web-crawl-tool
.data=${streamObject}
.width=${this.width}
.imageProxyService=${imageProxyService}
></web-crawl-tool>
`;
case 'web_search_exa':
@@ -63,7 +65,6 @@ export class ChatContentStreamObjects extends WithDisposable(
<web-search-tool
.data=${streamObject}
.width=${this.width}
.imageProxyService=${imageProxyService}
></web-search-tool>
`;
case 'doc_compose':
@@ -72,7 +73,8 @@ export class ChatContentStreamObjects extends WithDisposable(
.std=${this.host?.std}
.data=${streamObject}
.width=${this.width}
.imageProxyService=${imageProxyService}
.theme=${this.theme}
.notificationService=${this.notificationService}
></doc-compose-tool>
`;
case 'code_artifact':
@@ -81,7 +83,6 @@ export class ChatContentStreamObjects extends WithDisposable(
.std=${this.host?.std}
.data=${streamObject}
.width=${this.width}
.imageProxyService=${imageProxyService}
></code-artifact-tool>
`;
case 'doc_edit':
@@ -89,7 +90,7 @@ export class ChatContentStreamObjects extends WithDisposable(
<doc-edit-tool
.data=${streamObject}
.doc=${this.host?.store}
.blockDiffService=${blockDiffService}
.notificationService=${this.notificationService}
></doc-edit-tool>
`;
default: {
@@ -105,8 +106,6 @@ export class ChatContentStreamObjects extends WithDisposable(
if (streamObject.type !== 'tool-result') {
return nothing;
}
const imageProxyService = this.host?.store.get(ImageProxyService);
const blockDiffService = this.host?.view.std.getOptional(BlockDiffProvider);
switch (streamObject.toolName) {
case 'web_crawl_exa':
@@ -114,7 +113,6 @@ export class ChatContentStreamObjects extends WithDisposable(
<web-crawl-tool
.data=${streamObject}
.width=${this.width}
.imageProxyService=${imageProxyService}
></web-crawl-tool>
`;
case 'web_search_exa':
@@ -122,7 +120,6 @@ export class ChatContentStreamObjects extends WithDisposable(
<web-search-tool
.data=${streamObject}
.width=${this.width}
.imageProxyService=${imageProxyService}
></web-search-tool>
`;
case 'doc_compose':
@@ -131,7 +128,8 @@ export class ChatContentStreamObjects extends WithDisposable(
.std=${this.host?.std}
.data=${streamObject}
.width=${this.width}
.imageProxyService=${imageProxyService}
.theme=${this.theme}
.notificationService=${this.notificationService}
></doc-compose-tool>
`;
case 'code_artifact':
@@ -140,7 +138,8 @@ export class ChatContentStreamObjects extends WithDisposable(
.std=${this.host?.std}
.data=${streamObject}
.width=${this.width}
.imageProxyService=${imageProxyService}
.theme=${this.theme}
.notificationService=${this.notificationService}
></code-artifact-tool>
`;
case 'doc_edit':
@@ -148,8 +147,8 @@ export class ChatContentStreamObjects extends WithDisposable(
<doc-edit-tool
.data=${streamObject}
.host=${this.host}
.blockDiffService=${blockDiffService}
.renderRichText=${this.renderRichText.bind(this)}
.notificationService=${this.notificationService}
></doc-edit-tool>
`;
default: {
@@ -158,7 +157,6 @@ export class ChatContentStreamObjects extends WithDisposable(
<tool-result-card
.name=${name}
.width=${this.width}
.imageProxyService=${imageProxyService}
></tool-result-card>
`;
}
@@ -167,11 +165,11 @@ export class ChatContentStreamObjects extends WithDisposable(
private renderRichText(text: string) {
return html`<chat-content-rich-text
.host=${this.host}
.text=${text}
.state=${this.state}
.extensions=${this.extensions}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.theme=${this.theme}
></chat-content-rich-text>`;
}

View File

@@ -62,7 +62,7 @@ export class AIScrollableTextRenderer extends WithDisposable(
}
override render() {
const { host, answer, state, textRendererOptions } = this;
const { answer, state, textRendererOptions } = this;
return html` <style>
.ai-scrollable-text-renderer {
@@ -71,7 +71,6 @@ export class AIScrollableTextRenderer extends WithDisposable(
</style>
<div class="ai-scrollable-text-renderer" @wheel=${this._onWheel}>
<text-renderer
.host=${host}
.answer=${answer}
.state=${state}
.options=${textRendererOptions}
@@ -83,7 +82,7 @@ export class AIScrollableTextRenderer extends WithDisposable(
accessor answer!: string;
@property({ attribute: false })
accessor host: EditorHost | null | undefined;
accessor host!: EditorHost;
@property({ attribute: false })
accessor state: AffineAIPanelState | undefined;

View File

@@ -0,0 +1,157 @@
import { LoadingIcon } from '@blocksuite/affine/components/icons';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import type { ColorScheme } from '@blocksuite/affine/model';
import { ShadowlessElement } from '@blocksuite/affine/std';
import { type NotificationService } from '@blocksuite/affine-shared/services';
import type { Signal } from '@preact/signals-core';
import {
css,
html,
nothing,
type PropertyValues,
type TemplateResult,
} from 'lit';
import { property } from 'lit/decorators.js';
import {
isPreviewPanelOpen,
renderPreviewPanel,
} from './artifacts-preview-panel';
/**
* Base web-component for AI artifact tools.
* It encapsulates common reactive properties (data/std/width/…)
* and automatically calls `updatePreviewPanel()` when the `data`
* property changes while the preview panel is open.
*/
export abstract class ArtifactTool<
TData extends { type: 'tool-result' | 'tool-call' },
> extends SignalWatcher(WithDisposable(ShadowlessElement)) {
static override styles = css`
.artifact-tool-card {
cursor: pointer;
margin: 8px 0;
}
.artifact-tool-card:hover {
opacity: 0.8;
}
`;
/** Tool data coming from ChatGPT (tool-call / tool-result). */
@property({ attribute: false })
accessor data!: TData;
@property({ attribute: false })
accessor width: Signal<number | undefined> | undefined;
@property({ attribute: false })
accessor notificationService!: NotificationService;
@property({ attribute: false })
accessor theme!: Signal<ColorScheme>;
/* -------------------------- Card meta hooks -------------------------- */
/**
* Sub-class must provide primary information for the card.
*/
protected abstract getCardMeta(): {
title: string;
/** Page / file icon shown when not loading */
icon: TemplateResult | HTMLElement | string | null;
/** Whether the spinner should be displayed */
loading: boolean;
/** Extra css class appended to card root */
className?: string;
};
/** Banner shown on the right side of the card (can be undefined). */
protected abstract getBanner(
theme: ColorScheme
): TemplateResult | HTMLElement | string | null | undefined;
/**
* Provide the main TemplateResult shown in the preview panel.
* Called each time the panel opens or the tool data updates.
*/
protected abstract getPreviewContent(): TemplateResult<1>;
/** Provide the action controls (right-side buttons) for the panel. */
protected getPreviewControls(): TemplateResult<1> | undefined {
return undefined;
}
/** Open or refresh the preview panel. */
private openOrUpdatePreviewPanel() {
renderPreviewPanel(
this,
this.getPreviewContent(),
this.getPreviewControls()
);
}
protected refreshPreviewPanel() {
if (isPreviewPanelOpen(this)) {
this.openOrUpdatePreviewPanel();
}
}
/** Optionally override to show an error card. Return null if no error. */
protected getErrorTemplate(): TemplateResult | null {
return null;
}
private readonly onCardClick = (_e: Event) => {
this.openOrUpdatePreviewPanel();
};
protected renderCard() {
const { title, icon, loading, className } = this.getCardMeta();
const resolvedIcon = loading
? LoadingIcon({
size: '20px',
})
: icon;
const banner = this.getBanner(this.theme.value);
return html`
<div
class="affine-embed-linked-doc-block artifact-tool-card ${className ??
''} horizontal"
@click=${this.onCardClick}
>
<div class="affine-embed-linked-doc-content">
<div class="affine-embed-linked-doc-content-title">
<div class="affine-embed-linked-doc-content-title-icon">
${resolvedIcon}
</div>
<div class="affine-embed-linked-doc-content-title-text">
${title}
</div>
</div>
</div>
${banner
? html`<div class="affine-embed-linked-doc-banner">${banner}</div>`
: nothing}
</div>
`;
}
override render() {
const err = this.getErrorTemplate();
if (err) {
return err;
}
return this.renderCard();
}
override updated(changed: PropertyValues<this>) {
super.updated(changed);
if (changed.has('data') && isPreviewPanelOpen(this)) {
this.openOrUpdatePreviewPanel();
}
}
}

View File

@@ -1,10 +1,14 @@
import { CodeBlockHighlighter } from '@blocksuite/affine/blocks/code';
import { toast } from '@blocksuite/affine/components/toast';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import type { ImageProxyService } from '@blocksuite/affine/shared/adapters';
import { ColorScheme } from '@blocksuite/affine/model';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/affine/std';
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
import { type BlockStdScope } from '@blocksuite/affine/std';
import {
CodeBlockIcon,
CopyIcon,
PageIcon,
ToolIcon,
} from '@blocksuite/icons/lit';
import type { Signal } from '@preact/signals-core';
import { effect, signal } from '@preact/signals-core';
import { css, html, LitElement, nothing } from 'lit';
@@ -12,7 +16,7 @@ import { property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { bundledLanguagesInfo, type ThemedToken } from 'shiki';
import { renderPreviewPanel } from './artifacts-preview-panel';
import { ArtifactTool } from './artifact-tool';
import type { ToolError } from './type';
interface CodeArtifactToolCall {
@@ -104,6 +108,8 @@ export class CodeHighlighter extends SignalWatcher(WithDisposable(LitElement)) {
override connectedCallback() {
super.connectedCallback();
this.highlighter.mounted();
// recompute highlight when code / language changes
this.disposables.add(
effect(() => {
@@ -112,17 +118,25 @@ export class CodeHighlighter extends SignalWatcher(WithDisposable(LitElement)) {
);
}
override disconnectedCallback() {
super.disconnectedCallback();
this.highlighter.unmounted();
}
private _updateHighlightTokens() {
let cancelled = false;
const language = this.language;
const highlighter = this.highlighter.highlighter$.value;
if (!highlighter) return;
const updateTokens = () => {
if (cancelled) return;
this.highlightTokens.value = highlighter.codeToTokensBase(this.code, {
lang: language,
theme: this.highlighter.themeKey,
requestIdleCallback(() => {
this.highlightTokens.value = highlighter.codeToTokensBase(this.code, {
lang: language,
theme: this.highlighter.themeKey,
});
});
};
@@ -200,20 +214,143 @@ export class CodeHighlighter extends SignalWatcher(WithDisposable(LitElement)) {
}
}
const CodeBlockBanner = html`<svg
width="204"
height="102"
viewBox="0 0 204 102"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_3371_100809)">
<g filter="url(#filter0_d_3371_100809)">
<rect
x="53.5054"
width="111.999"
height="99.5543"
rx="12.4443"
transform="rotate(8.37805 53.5054 0)"
fill="white"
/>
</g>
<path
d="M89.7547 40.6581C90.8629 39.8345 92.4285 40.065 93.2522 41.1732C94.0758 42.2813 93.8452 43.847 92.7371 44.6706L79.7618 54.3146L89.4058 67.2899L89.5482 67.5024C90.1977 68.5905 89.9295 70.0161 88.8906 70.7883C87.8516 71.56 86.4104 71.4044 85.5558 70.4689L85.3932 70.2732L74.2581 55.2907C73.4345 54.1826 73.6653 52.617 74.7732 51.7933L89.7547 40.6581ZM114.378 44.2845C115.486 43.4608 117.052 43.6914 117.875 44.7996L129.011 59.7812C129.834 60.8892 129.604 62.4551 128.496 63.2787L113.514 74.4147L113.301 74.5552C112.213 75.2046 110.789 74.9382 110.016 73.8996C109.244 72.8606 109.399 71.4184 110.335 70.5637L110.531 70.4012L123.507 60.7572L113.863 47.7819C113.039 46.6738 113.27 45.1081 114.378 44.2845Z"
fill="#F3F3F3"
/>
</g>
<defs>
<filter
id="filter0_d_3371_100809"
x="35.6787"
y="-3.32129"
width="131.951"
height="121.453"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="2.5" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.258824 0 0 0 0 0.254902 0 0 0 0 0.286275 0 0 0 0.17 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_3371_100809"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_3371_100809"
result="shape"
/>
</filter>
<clipPath id="clip0_3371_100809">
<rect width="204" height="102" fill="white" />
</clipPath>
</defs>
</svg>`;
const CodeBlockBannerDark = html`<svg
width="204"
height="102"
viewBox="0 0 204 102"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_3371_101118)">
<g filter="url(#filter0_d_3371_101118)">
<rect
x="53.5055"
width="111.999"
height="99.5543"
rx="12.4443"
transform="rotate(8.37805 53.5055 0)"
fill="#252525"
/>
</g>
<path
d="M89.7551 40.6574C90.8631 39.8342 92.429 40.0647 93.2525 41.1725C94.0762 42.2806 93.8455 43.8472 92.7373 44.6709L79.762 54.3149L89.406 67.2902L89.5475 67.5025C90.197 68.5907 89.9298 70.0163 88.8908 70.7886C87.8519 71.5603 86.4106 71.4047 85.5561 70.4692L85.3934 70.2735L74.2574 55.2908C73.4341 54.1829 73.6649 52.6171 74.7725 51.7934L89.7551 40.6574ZM114.378 44.2838C115.486 43.4606 117.052 43.6911 117.876 44.7988L129.011 59.7814C129.834 60.8895 129.604 62.4552 128.496 63.2788L113.514 74.4149L113.301 74.5553C112.213 75.2045 110.789 74.9381 110.016 73.8998C109.244 72.8609 109.398 71.4186 110.334 70.5638L110.532 70.4014L123.507 60.7574L113.863 47.7822C113.039 46.674 113.27 45.1074 114.378 44.2838Z"
fill="#565656"
/>
</g>
<defs>
<filter
id="filter0_d_3371_101118"
x="35.6787"
y="-3.32129"
width="131.951"
height="121.453"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="2.5" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.258824 0 0 0 0 0.254902 0 0 0 0 0.286275 0 0 0 0.17 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_3371_101118"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_3371_101118"
result="shape"
/>
</filter>
<clipPath id="clip0_3371_101118">
<rect width="204" height="102" fill="white" />
</clipPath>
</defs>
</svg> `;
/**
* Component to render code artifact tool call/result inside chat.
*/
export class CodeArtifactTool extends WithDisposable(ShadowlessElement) {
export class CodeArtifactTool extends ArtifactTool<
CodeArtifactToolCall | CodeArtifactToolResult
> {
static override styles = css`
.code-artifact-result {
cursor: pointer;
margin: 8px 0;
}
.code-artifact-result:hover {
background-color: var(--affine-hover-color);
}
.code-artifact-preview {
padding: 0;
width: 100%;
@@ -290,168 +427,148 @@ export class CodeArtifactTool extends WithDisposable(ShadowlessElement) {
}
`;
@property({ attribute: false })
accessor data!: CodeArtifactToolCall | CodeArtifactToolResult;
@property({ attribute: false })
accessor width: Signal<number | undefined> | undefined;
@property({ attribute: false })
accessor imageProxyService: ImageProxyService | null | undefined;
@property({ attribute: false })
accessor std: BlockStdScope | undefined;
@state()
private accessor mode: 'preview' | 'code' = 'code';
private renderToolCall() {
const { args } = this.data as CodeArtifactToolCall;
const name = `Generating HTML artifact "${args.title}"`;
return html`<tool-call-card
.name=${name}
.icon=${ToolIcon()}
></tool-call-card>`;
/* ---------------- ArtifactTool hooks ---------------- */
protected getBanner(theme: ColorScheme) {
return theme === ColorScheme.Dark ? CodeBlockBannerDark : CodeBlockBanner;
}
private renderToolResult() {
if (!this.std) return nothing;
if (this.data.type !== 'tool-result') return nothing;
const resultData = this.data as CodeArtifactToolResult;
const result = resultData.result;
protected getCardMeta() {
const loading = this.data.type === 'tool-call';
return {
title: this.data.args.title,
icon: CodeBlockIcon({ width: '20', height: '20' }),
loading,
className: 'code-artifact-result',
};
}
if (result && typeof result === 'object' && 'title' in result) {
const { title, html: htmlContent } = result as {
title: string;
html: string;
};
const onClick = () => {
const copyHTML = async () => {
if (this.std) {
await navigator.clipboard
.writeText(htmlContent)
.catch(console.error);
toast(this.std.host, 'Copied HTML to clipboard');
}
};
const downloadHTML = () => {
try {
const blob = new Blob([htmlContent], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${title || 'artifact'}.html`;
document.body.append(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
} catch (e) {
console.error(e);
}
};
const setCodeMode = () => {
if (this.mode !== 'code') {
this.mode = 'code';
renderPreview();
}
};
const setPreviewMode = () => {
if (this.mode !== 'preview') {
this.mode = 'preview';
renderPreview();
}
};
const renderPreview = () => {
const controls = html`
<div class="code-artifact-toggle-container">
<div
class=${classMap({
'toggle-button': true,
active: this.mode === 'code',
})}
@click=${setCodeMode}
>
Code
</div>
<div
class=${classMap({
'toggle-button': true,
active: this.mode === 'preview',
})}
@click=${setPreviewMode}
>
Preview
</div>
</div>
<div style="flex: 1"></div>
<button class="code-artifact-control-btn" @click=${downloadHTML}>
${PageIcon({
width: '20',
height: '20',
style: `color: ${unsafeCSSVarV2('icon/primary')}`,
})}
Download
</button>
<icon-button @click=${copyHTML} title="Copy HTML">
${CopyIcon({ width: '20', height: '20' })}
</icon-button>
`;
renderPreviewPanel(
this,
html`<div class="code-artifact-preview">
${this.mode === 'preview'
? html`<html-preview .html=${htmlContent}></html-preview>`
: html`<code-highlighter
.std=${this.std}
.code=${htmlContent}
.language=${'html'}
.showLineNumbers=${true}
></code-highlighter>`}
</div>`,
controls
);
};
renderPreview();
};
return html`
protected override getPreviewContent() {
if (this.data.type !== 'tool-result' || !this.data.result) {
// loading state
return html`<div class="code-artifact-preview">
<div
class="affine-embed-linked-doc-block code-artifact-result horizontal"
@click=${onClick}
style="display:flex;justify-content:center;align-items:center;height:100%"
>
<div class="affine-embed-linked-doc-content">
<div class="affine-embed-linked-doc-content-title">
<div class="affine-embed-linked-doc-content-title-icon">
${PageIcon({ width: '20', height: '20' })}
</div>
<div class="affine-embed-linked-doc-content-title-text">
${title}
</div>
</div>
</div>
${CodeBlockIcon({ width: '24', height: '24' })}
</div>
`;
</div>`;
}
return html`<tool-call-failed
.name=${'Code artifact failed'}
.icon=${ToolIcon()}
></tool-call-failed>`;
const result = this.data.result;
if (typeof result !== 'object' || !('html' in result)) return html``;
const { html: htmlContent } = result as { html: string };
return html`<div class="code-artifact-preview">
${this.mode === 'preview'
? html`<html-preview .html=${htmlContent}></html-preview>`
: html`<code-highlighter
.std=${this.std}
.code=${htmlContent}
.language=${'html'}
.showLineNumbers=${true}
></code-highlighter>`}
</div>`;
}
protected override render() {
if (this.data.type === 'tool-call') {
return this.renderToolCall();
protected override getPreviewControls() {
if (this.data.type !== 'tool-result' || !this.std || !this.data.result) {
return undefined;
}
if (this.data.type === 'tool-result') {
return this.renderToolResult();
const result = this.data.result as { html: string; title: string };
const htmlContent = result.html;
const title = result.title;
const copyHTML = async () => {
await navigator.clipboard.writeText(htmlContent).catch(console.error);
this.notificationService.toast('Copied HTML to clipboard');
};
const downloadHTML = () => {
try {
const blob = new Blob([htmlContent], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${title || 'artifact'}.html`;
document.body.append(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
} catch (e) {
console.error(e);
}
};
const setCodeMode = () => {
if (this.mode !== 'code') {
this.mode = 'code';
this.refreshPreviewPanel();
}
};
const setPreviewMode = () => {
if (this.mode !== 'preview') {
this.mode = 'preview';
this.refreshPreviewPanel();
}
};
return html`
<div class="code-artifact-toggle-container">
<div
class=${classMap({
'toggle-button': true,
active: this.mode === 'code',
})}
@click=${setCodeMode}
>
Code
</div>
<div
class=${classMap({
'toggle-button': true,
active: this.mode === 'preview',
})}
@click=${setPreviewMode}
>
Preview
</div>
</div>
<div style="flex: 1"></div>
<button class="code-artifact-control-btn" @click=${downloadHTML}>
${PageIcon({
width: '20',
height: '20',
style: `color: ${unsafeCSSVarV2('icon/primary')}`,
})}
Download
</button>
<icon-button @click=${copyHTML} title="Copy HTML">
${CopyIcon({ width: '20', height: '20' })}
</icon-button>
`;
}
protected override getErrorTemplate() {
if (
this.data.type === 'tool-result' &&
this.data.result &&
(this.data.result as any).type === 'error'
) {
return html`<tool-call-failed
.name=${'Code artifact failed'}
.icon=${ToolIcon()}
></tool-call-failed>`;
}
return nothing;
return null;
}
}

View File

@@ -2,27 +2,18 @@ import { getStoreManager } from '@affine/core/blocksuite/manager/store';
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
import { getEmbedLinkedDocIcons } from '@blocksuite/affine/blocks/embed-doc';
import { LoadingIcon } from '@blocksuite/affine/components/icons';
import { toast } from '@blocksuite/affine/components/toast';
import { WithDisposable } from '@blocksuite/affine/global/lit';
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
import type { ImageProxyService } from '@blocksuite/affine/shared/adapters';
import {
NotificationProvider,
ThemeProvider,
} from '@blocksuite/affine/shared/services';
import type { ColorScheme } from '@blocksuite/affine/model';
import { NotificationProvider } from '@blocksuite/affine/shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/affine/std';
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
import { type Signal } from '@preact/signals-core';
import { css, html, nothing, type PropertyValues } from 'lit';
import type { BlockStdScope } from '@blocksuite/std';
import { css, html } from 'lit';
import { property } from 'lit/decorators.js';
import { getCustomPageEditorBlockSpecs } from '../text-renderer';
import {
isPreviewPanelOpen,
renderPreviewPanel,
} from './artifacts-preview-panel';
import { ArtifactTool } from './artifact-tool';
import type { ToolError } from './type';
interface DocComposeToolCall {
@@ -50,17 +41,10 @@ interface DocComposeToolResult {
/**
* Component to render doc compose tool call/result inside chat.
*/
export class DocComposeTool extends WithDisposable(ShadowlessElement) {
export class DocComposeTool extends ArtifactTool<
DocComposeToolCall | DocComposeToolResult
> {
static override styles = css`
.doc-compose-result {
cursor: pointer;
margin: 8px 0;
}
.doc-compose-result:hover {
background-color: var(--affine-hover-color);
}
.doc-compose-result-preview {
padding: 24px;
height: 100%;
@@ -103,30 +87,60 @@ export class DocComposeTool extends WithDisposable(ShadowlessElement) {
}
`;
@property({ attribute: false })
accessor data!: DocComposeToolCall | DocComposeToolResult;
@property({ attribute: false })
accessor width: Signal<number | undefined> | undefined;
@property({ attribute: false })
accessor imageProxyService: ImageProxyService | null | undefined;
@property({ attribute: false })
accessor std: BlockStdScope | undefined;
override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has('data') && isPreviewPanelOpen(this)) {
this.updatePreviewPanel();
}
protected getBanner(theme: ColorScheme) {
const { LinkedDocEmptyBanner } = getEmbedLinkedDocIcons(
theme,
'page',
'horizontal'
);
return LinkedDocEmptyBanner;
}
private updatePreviewPanel() {
protected getCardMeta() {
const composing = this.data.type === 'tool-call';
return {
title: this.data.args.title,
icon: PageIcon(),
loading: composing,
className: 'doc-compose-result',
};
}
protected override getPreviewContent() {
if (!this.std) return html``;
const std = this.std;
const resultData = this.data;
const title = this.data.args.title;
const result = resultData.type === 'tool-result' ? resultData.result : null;
const successResult = result && 'markdown' in result ? result : null;
return html`<div class="doc-compose-result-preview">
<div class="doc-compose-result-preview-title">${title}</div>
${successResult
? html`<text-renderer
.answer=${successResult.markdown}
.host=${std.host}
.schema=${std.store.schema}
.options=${{
customHeading: true,
extensions: getCustomPageEditorBlockSpecs(),
}}
></text-renderer>`
: html`<div class="doc-compose-result-preview-loading">
${LoadingIcon({
size: '32px',
})}
</div>`}
</div>`;
}
protected override getPreviewControls() {
if (!this.std) return;
const std = this.std;
const resultData = this.data;
const composing = resultData.type === 'tool-call';
const title = this.data.args.title;
const result = resultData.type === 'tool-result' ? resultData.result : null;
const successResult = result && 'markdown' in result ? result : null;
@@ -138,7 +152,7 @@ export class DocComposeTool extends WithDisposable(ShadowlessElement) {
await navigator.clipboard
.writeText(successResult.markdown)
.catch(console.error);
toast(std.host, 'Copied markdown to clipboard');
this.notificationService.toast('Copied markdown to clipboard');
};
const saveAsDoc = async () => {
@@ -171,103 +185,43 @@ export class DocComposeTool extends WithDisposable(ShadowlessElement) {
});
}
} else {
toast(std.host, 'Failed to create document');
this.notificationService.toast('Failed to create document');
}
} catch (e) {
console.error(e);
toast(std.host, 'Failed to create document');
this.notificationService.toast('Failed to create document');
}
};
const controls = html`
<button class="doc-compose-result-save-as-doc" @click=${saveAsDoc}>
${PageIcon({
width: '20',
height: '20',
style: `color: ${unsafeCSSVarV2('icon/primary')}`,
})}
Save as doc
</button>
<icon-button @click=${copyMarkdown} title="Copy markdown">
${CopyIcon({ width: '20', height: '20' })}
</icon-button>
`;
renderPreviewPanel(
this,
html`<div class="doc-compose-result-preview">
<div class="doc-compose-result-preview-title">${title}</div>
${successResult
? html`<text-renderer
.answer=${successResult.markdown}
.host=${std.host}
.schema=${std.store.schema}
.options=${{
customHeading: true,
extensions: getCustomPageEditorBlockSpecs(),
}}
></text-renderer>`
: html`<div class="doc-compose-result-preview-loading">
${LoadingIcon({
size: '32px',
})}
</div>`}
</div>`,
composing ? undefined : controls
);
return this.data.type === 'tool-call'
? undefined
: html`
<button class="doc-compose-result-save-as-doc" @click=${saveAsDoc}>
${PageIcon({
width: '20',
height: '20',
style: `color: ${unsafeCSSVarV2('icon/primary')}`,
})}
Save as doc
</button>
<icon-button @click=${copyMarkdown} title="Copy markdown">
${CopyIcon({ width: '20', height: '20' })}
</icon-button>
`;
}
protected override render() {
if (!this.std) return nothing;
const resultData = this.data;
const composing = resultData.type === 'tool-call';
const title = this.data.args.title;
protected override getErrorTemplate() {
if (
resultData.type === 'tool-result' &&
resultData.result &&
'type' in resultData.result &&
resultData.result.type === 'error'
this.data.type === 'tool-result' &&
this.data.result &&
'type' in this.data.result &&
(this.data.result as any).type === 'error'
) {
// failed
return html`<tool-call-failed
.name=${'Doc compose failed'}
.icon=${ToolIcon()}
></tool-call-failed>`;
}
const theme = this.std.get(ThemeProvider).theme;
const { LinkedDocEmptyBanner } = getEmbedLinkedDocIcons(
theme,
'page',
'horizontal'
);
return html`
<div
class="affine-embed-linked-doc-block doc-compose-result horizontal"
@click=${this.updatePreviewPanel}
>
<div class="affine-embed-linked-doc-content">
<div class="affine-embed-linked-doc-content-title">
<div class="affine-embed-linked-doc-content-title-icon">
${composing
? LoadingIcon({
size: '20px',
})
: PageIcon()}
</div>
<div class="affine-embed-linked-doc-content-title-text">
${title}
</div>
</div>
</div>
<div class="affine-embed-linked-doc-banner">
${LinkedDocEmptyBanner}
</div>
</div>
`;
return null;
}
}

View File

@@ -1,7 +1,7 @@
import { WithDisposable } from '@blocksuite/affine/global/lit';
import { NotificationProvider } from '@blocksuite/affine/shared/services';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
import type { NotificationService } from '@blocksuite/affine-shared/services';
import {
CloseIcon,
CopyIcon,
@@ -14,7 +14,7 @@ import {
import { css, html, nothing } from 'lit';
import { property, state } from 'lit/decorators.js';
import type { BlockDiffService } from '../../services/block-diff';
import { BlockDiffProvider } from '../../services/block-diff';
import { diffMarkdown } from '../../utils/apply-model/markdown-diff';
import { copyText } from '../../utils/editor-actions';
import type { ToolError } from './type';
@@ -190,14 +190,18 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
accessor data!: DocEditToolCall | DocEditToolResult;
@property({ attribute: false })
accessor blockDiffService: BlockDiffService | undefined;
accessor renderRichText!: (text: string) => string;
@property({ attribute: false })
accessor renderRichText!: (text: string) => string;
accessor notificationService!: NotificationService;
@state()
accessor isCollapsed = false;
get blockDiffService() {
return this.host?.std.getOptional(BlockDiffProvider);
}
private async _handleApply(markdown: string) {
if (!this.host) {
return;
@@ -229,14 +233,9 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
if (!this.host) {
return;
}
const success = await copyText(
this.host,
removeMarkdownComments(changedMarkdown)
);
const success = await copyText(removeMarkdownComments(changedMarkdown));
if (success) {
const notificationService =
this.host?.std.getOptional(NotificationProvider);
notificationService?.notify({
this.notificationService.notify({
title: 'Copied to clipboard',
accent: 'success',
onClose: function (): void {},

View File

@@ -1,7 +1,7 @@
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { type ImageProxyService } from '@blocksuite/affine/shared/adapters';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { ShadowlessElement } from '@blocksuite/affine/std';
import { DEFAULT_IMAGE_PROXY_ENDPOINT } from '@blocksuite/affine-shared/consts';
import { ToggleDownIcon, ToolIcon } from '@blocksuite/icons/lit';
import { type Signal } from '@preact/signals-core';
import { css, html, nothing, type TemplateResult } from 'lit';
@@ -205,12 +205,11 @@ export class ToolResultCard extends SignalWatcher(
@property({ attribute: false })
accessor width: Signal<number | undefined> | undefined;
@property({ attribute: false })
accessor imageProxyService: ImageProxyService | null | undefined;
@state()
private accessor isCollapsed = true;
private readonly imageProxyURL = DEFAULT_IMAGE_PROXY_ENDPOINT;
protected override render() {
return html`
<div class="ai-tool-result-wrapper">
@@ -272,15 +271,20 @@ export class ToolResultCard extends SignalWatcher(
`;
}
buildUrl(imageUrl: string) {
if (imageUrl.startsWith(this.imageProxyURL)) {
return imageUrl;
}
return `${this.imageProxyURL}?url=${encodeURIComponent(imageUrl)}`;
}
private renderIcon(icon: string | TemplateResult<1> | undefined) {
if (!icon) {
return nothing;
}
if (typeof icon === 'string') {
if (this.imageProxyService) {
return html`<img src=${this.imageProxyService.buildUrl(icon)} />`;
}
return html`<img src=${icon} />`;
return html`<img src=${this.buildUrl(icon)} />`;
}
return html`${icon}`;
}

Some files were not shown because too many files have changed in this diff Show More