mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-03-25 00:30:08 +08:00
Compare commits
1 Commits
v0.23.0-be
...
use-jemall
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1d7011047 |
6
.github/actions/setup-version/action.yml
vendored
6
.github/actions/setup-version/action.yml
vendored
@@ -4,15 +4,9 @@ inputs:
|
||||
app-version:
|
||||
description: 'App Version'
|
||||
required: true
|
||||
ios-app-version:
|
||||
description: 'iOS App Store Version (Optional, use App version if empty)'
|
||||
required: false
|
||||
type: string
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 'Write Version'
|
||||
shell: bash
|
||||
env:
|
||||
IOS_APP_VERSION: ${{ inputs.ios-app-version }}
|
||||
run: ./scripts/set-version.sh ${{ inputs.app-version }}
|
||||
|
||||
8
.github/workflows/release-mobile.yml
vendored
8
.github/workflows/release-mobile.yml
vendored
@@ -12,9 +12,6 @@ on:
|
||||
build-type:
|
||||
type: string
|
||||
required: true
|
||||
ios-app-version:
|
||||
type: string
|
||||
required: false
|
||||
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
@@ -81,7 +78,7 @@ jobs:
|
||||
path: packages/frontend/apps/android/dist
|
||||
|
||||
ios:
|
||||
runs-on: 'macos-15'
|
||||
runs-on: ${{ github.ref_name == 'canary' && 'macos-latest' || 'blaze/macos-14' }}
|
||||
needs:
|
||||
- build-ios-web
|
||||
steps:
|
||||
@@ -90,7 +87,6 @@ jobs:
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
ios-app-version: ${{ inputs.ios-app-version }}
|
||||
- name: 'Update Code Sign Identity'
|
||||
shell: bash
|
||||
run: ./packages/frontend/apps/ios/update_code_sign_identity.sh
|
||||
@@ -110,7 +106,7 @@ jobs:
|
||||
enableScripts: false
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: 16.4
|
||||
xcode-version: 16.2
|
||||
- name: Install Swiftformat
|
||||
run: brew install swiftformat
|
||||
- name: Cap sync
|
||||
|
||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -21,10 +21,6 @@ on:
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
ios-app-version:
|
||||
description: 'iOS App Store Version (Optional, use tag version if empty)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -121,4 +117,3 @@ jobs:
|
||||
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
|
||||
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
|
||||
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
|
||||
ios-app-version: ${{ inputs.ios-app-version }}
|
||||
|
||||
@@ -266,7 +266,6 @@
|
||||
"./components/toggle-button": "./src/components/toggle-button.ts",
|
||||
"./components/toggle-switch": "./src/components/toggle-switch.ts",
|
||||
"./components/toolbar": "./src/components/toolbar.ts",
|
||||
"./components/tooltip": "./src/components/tooltip.ts",
|
||||
"./components/view-dropdown-menu": "./src/components/view-dropdown-menu.ts",
|
||||
"./components/tooltip-content-with-shortcut": "./src/components/tooltip-content-with-shortcut.ts",
|
||||
"./components/resource": "./src/components/resource.ts",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-components/tooltip';
|
||||
@@ -73,8 +73,7 @@
|
||||
"./edgeless-line-styles-panel": "./src/edgeless-line-styles-panel/index.ts",
|
||||
"./edgeless-shape-color-picker": "./src/edgeless-shape-color-picker/index.ts",
|
||||
"./open-doc-dropdown-menu": "./src/open-doc-dropdown-menu/index.ts",
|
||||
"./slider": "./src/slider/index.ts",
|
||||
"./tooltip": "./src/tooltip/index.ts"
|
||||
"./slider": "./src/slider/index.ts"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { effects as tooltipEffects } from '../tooltip/effect.js';
|
||||
import { EditorIconButton } from './icon-button.js';
|
||||
import {
|
||||
EditorMenuAction,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
} from './menu-button.js';
|
||||
import { EditorToolbarSeparator } from './separator.js';
|
||||
import { EditorToolbar } from './toolbar.js';
|
||||
import { Tooltip } from './tooltip.js';
|
||||
|
||||
export { EditorChevronDown } from './chevron-down.js';
|
||||
export { ToolbarMoreMenuConfigExtension } from './config.js';
|
||||
@@ -20,6 +20,7 @@ export { MenuContext } from './menu-context.js';
|
||||
export { EditorToolbarSeparator } from './separator.js';
|
||||
export { darkToolbarStyles, lightToolbarStyles } from './styles.js';
|
||||
export { EditorToolbar } from './toolbar.js';
|
||||
export { Tooltip } from './tooltip.js';
|
||||
export type {
|
||||
AdvancedMenuItem,
|
||||
FatMenuItems,
|
||||
@@ -37,12 +38,11 @@ export {
|
||||
} from './utils.js';
|
||||
|
||||
export function effects() {
|
||||
tooltipEffects();
|
||||
|
||||
customElements.define('editor-toolbar-separator', EditorToolbarSeparator);
|
||||
customElements.define('editor-toolbar', EditorToolbar);
|
||||
customElements.define('editor-icon-button', EditorIconButton);
|
||||
customElements.define('editor-menu-button', EditorMenuButton);
|
||||
customElements.define('editor-menu-content', EditorMenuContent);
|
||||
customElements.define('editor-menu-action', EditorMenuAction);
|
||||
customElements.define('affine-tooltip', Tooltip);
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Tooltip } from './tooltip.js';
|
||||
|
||||
export function effects() {
|
||||
if (!customElements.get('affine-tooltip')) {
|
||||
customElements.define('affine-tooltip', Tooltip);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { effects } from './effect.js';
|
||||
export { Tooltip } from './tooltip.js';
|
||||
@@ -30,9 +30,9 @@ function inlineTextStyles(
|
||||
}
|
||||
|
||||
return styleMap({
|
||||
'font-weight': props.bold ? 'bold' : 'inherit',
|
||||
'font-style': props.italic ? 'italic' : 'inherit',
|
||||
'text-decoration': textDecorations.length > 0 ? textDecorations : 'inherit',
|
||||
'font-weight': props.bold ? 'bold' : 'normal',
|
||||
'font-style': props.italic ? 'italic' : 'normal',
|
||||
'text-decoration': textDecorations.length > 0 ? textDecorations : 'none',
|
||||
...inlineCodeStyle,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,7 +207,6 @@ const retry = async (
|
||||
try {
|
||||
await callback(t);
|
||||
} catch (e) {
|
||||
console.error(`Error during ${action}:`, e);
|
||||
t.log(`Error during ${action}:`, e);
|
||||
throw e;
|
||||
}
|
||||
@@ -484,34 +483,6 @@ The term **“CRDT”** was first introduced by Marc Shapiro, Nuno Preguiça, Ca
|
||||
type: 'structured' as const,
|
||||
prefer: CopilotProviderType.Gemini,
|
||||
},
|
||||
{
|
||||
promptName: ['Conversation Summary'],
|
||||
messages: [
|
||||
{
|
||||
role: 'user' as const,
|
||||
content: '',
|
||||
params: {
|
||||
messages: [
|
||||
{ role: 'user', content: 'what is single source of truth?' },
|
||||
{ role: 'assistant', content: TestAssets.SSOT },
|
||||
],
|
||||
focus: 'technical decisions',
|
||||
length: 'comprehensive',
|
||||
},
|
||||
},
|
||||
],
|
||||
verifier: (t: ExecutionContext<Tester>, result: string) => {
|
||||
assertNotWrappedInCodeBlock(t, result);
|
||||
const cleared = result.toLowerCase();
|
||||
t.assert(
|
||||
cleared.includes('single source of truth') ||
|
||||
/single.*source/.test(cleared) ||
|
||||
cleared.includes('ssot'),
|
||||
'should include original keyword'
|
||||
);
|
||||
},
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
promptName: [
|
||||
'Summary',
|
||||
|
||||
@@ -99,56 +99,3 @@ e2e(
|
||||
t.is(result2.workspace.doc.public, true);
|
||||
}
|
||||
);
|
||||
|
||||
e2e('should get doc with title and summary', async t => {
|
||||
const owner = await app.signup();
|
||||
|
||||
const workspace = await app.create(Mockers.Workspace, {
|
||||
owner: { id: owner.id },
|
||||
});
|
||||
|
||||
const docSnapshot = await app.create(Mockers.DocSnapshot, {
|
||||
workspaceId: workspace.id,
|
||||
user: owner,
|
||||
});
|
||||
const doc = await app.create(Mockers.DocMeta, {
|
||||
workspaceId: workspace.id,
|
||||
docId: docSnapshot.id,
|
||||
title: 'doc1',
|
||||
summary: 'summary1',
|
||||
});
|
||||
|
||||
const result = await app.gql({
|
||||
query: getWorkspacePageByIdQuery,
|
||||
variables: { workspaceId: workspace.id, pageId: doc.docId },
|
||||
});
|
||||
|
||||
t.is(result.workspace.doc.title, doc.title);
|
||||
t.is(result.workspace.doc.summary, doc.summary);
|
||||
});
|
||||
|
||||
e2e('should get doc with title and null summary', async t => {
|
||||
const owner = await app.signup();
|
||||
|
||||
const workspace = await app.create(Mockers.Workspace, {
|
||||
owner: { id: owner.id },
|
||||
});
|
||||
|
||||
const docSnapshot = await app.create(Mockers.DocSnapshot, {
|
||||
workspaceId: workspace.id,
|
||||
user: owner,
|
||||
});
|
||||
const doc = await app.create(Mockers.DocMeta, {
|
||||
workspaceId: workspace.id,
|
||||
docId: docSnapshot.id,
|
||||
title: 'doc1',
|
||||
});
|
||||
|
||||
const result = await app.gql({
|
||||
query: getWorkspacePageByIdQuery,
|
||||
variables: { workspaceId: workspace.id, pageId: doc.docId },
|
||||
});
|
||||
|
||||
t.is(result.workspace.doc.title, doc.title);
|
||||
t.is(result.workspace.doc.summary, null);
|
||||
});
|
||||
|
||||
@@ -669,10 +669,7 @@ test('should get doc info', async t => {
|
||||
};
|
||||
|
||||
await t.context.doc.upsert(snapshot);
|
||||
await t.context.doc.upsertMeta(workspace.id, docId, {
|
||||
title: 'test title',
|
||||
summary: 'test summary',
|
||||
});
|
||||
await t.context.doc.upsertMeta(workspace.id, docId);
|
||||
|
||||
const docInfo = await t.context.doc.getDocInfo(workspace.id, docId);
|
||||
|
||||
@@ -682,8 +679,6 @@ test('should get doc info', async t => {
|
||||
updatedAt: new Date(snapshot.timestamp),
|
||||
creatorId: user.id,
|
||||
lastUpdaterId: user.id,
|
||||
title: 'test title',
|
||||
summary: 'test summary',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import { DocRendererModule } from './core/doc-renderer';
|
||||
import { DocServiceModule } from './core/doc-service';
|
||||
import { FeatureModule } from './core/features';
|
||||
import { MailModule } from './core/mail';
|
||||
import { MonitorModule } from './core/monitor';
|
||||
import { NotificationModule } from './core/notification';
|
||||
import { PermissionModule } from './core/permission';
|
||||
import { QuotaModule } from './core/quota';
|
||||
@@ -113,8 +112,6 @@ export const FunctionalityModules = [
|
||||
WebSocketModule,
|
||||
JobModule.forRoot(),
|
||||
ModelsModule,
|
||||
ScheduleModule.forRoot(),
|
||||
MonitorModule,
|
||||
];
|
||||
|
||||
export class AppModuleBuilder {
|
||||
@@ -154,8 +151,12 @@ export function buildAppModule(env: Env) {
|
||||
// basic
|
||||
.use(...FunctionalityModules)
|
||||
|
||||
// enable indexer module on graphql server and doc service
|
||||
.useIf(() => env.flavors.graphql || env.flavors.doc, IndexerModule)
|
||||
// enable schedule module on graphql server and doc service
|
||||
.useIf(
|
||||
() => env.flavors.graphql || env.flavors.doc,
|
||||
ScheduleModule.forRoot(),
|
||||
IndexerModule
|
||||
)
|
||||
|
||||
// auth
|
||||
.use(UserModule, AuthModule, PermissionModule)
|
||||
|
||||
@@ -653,19 +653,12 @@ export const USER_FRIENDLY_ERRORS = {
|
||||
},
|
||||
no_copilot_provider_available: {
|
||||
type: 'internal_server_error',
|
||||
args: { modelId: 'string' },
|
||||
message: ({ modelId }) => `No copilot provider available: ${modelId}`,
|
||||
message: `No copilot provider available.`,
|
||||
},
|
||||
copilot_failed_to_generate_text: {
|
||||
type: 'internal_server_error',
|
||||
message: `Failed to generate text.`,
|
||||
},
|
||||
copilot_failed_to_generate_embedding: {
|
||||
type: 'internal_server_error',
|
||||
args: { provider: 'string', message: 'string' },
|
||||
message: ({ provider, message }) =>
|
||||
`Failed to generate embedding with ${provider}: ${message}`,
|
||||
},
|
||||
copilot_failed_to_create_message: {
|
||||
type: 'internal_server_error',
|
||||
message: `Failed to create chat message.`,
|
||||
|
||||
@@ -668,14 +668,10 @@ export class CopilotSessionDeleted extends UserFriendlyError {
|
||||
super('action_forbidden', 'copilot_session_deleted', message);
|
||||
}
|
||||
}
|
||||
@ObjectType()
|
||||
class NoCopilotProviderAvailableDataType {
|
||||
@Field() modelId!: string
|
||||
}
|
||||
|
||||
export class NoCopilotProviderAvailable extends UserFriendlyError {
|
||||
constructor(args: NoCopilotProviderAvailableDataType, message?: string | ((args: NoCopilotProviderAvailableDataType) => string)) {
|
||||
super('internal_server_error', 'no_copilot_provider_available', message, args);
|
||||
constructor(message?: string) {
|
||||
super('internal_server_error', 'no_copilot_provider_available', message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,17 +680,6 @@ export class CopilotFailedToGenerateText extends UserFriendlyError {
|
||||
super('internal_server_error', 'copilot_failed_to_generate_text', message);
|
||||
}
|
||||
}
|
||||
@ObjectType()
|
||||
class CopilotFailedToGenerateEmbeddingDataType {
|
||||
@Field() provider!: string
|
||||
@Field() message!: string
|
||||
}
|
||||
|
||||
export class CopilotFailedToGenerateEmbedding extends UserFriendlyError {
|
||||
constructor(args: CopilotFailedToGenerateEmbeddingDataType, message?: string | ((args: CopilotFailedToGenerateEmbeddingDataType) => string)) {
|
||||
super('internal_server_error', 'copilot_failed_to_generate_embedding', message, args);
|
||||
}
|
||||
}
|
||||
|
||||
export class CopilotFailedToCreateMessage extends UserFriendlyError {
|
||||
constructor(message?: string) {
|
||||
@@ -1194,7 +1179,6 @@ export enum ErrorNames {
|
||||
COPILOT_SESSION_DELETED,
|
||||
NO_COPILOT_PROVIDER_AVAILABLE,
|
||||
COPILOT_FAILED_TO_GENERATE_TEXT,
|
||||
COPILOT_FAILED_TO_GENERATE_EMBEDDING,
|
||||
COPILOT_FAILED_TO_CREATE_MESSAGE,
|
||||
UNSPLASH_IS_NOT_CONFIGURED,
|
||||
COPILOT_ACTION_TAKEN,
|
||||
@@ -1255,5 +1239,5 @@ registerEnumType(ErrorNames, {
|
||||
export const ErrorDataUnionType = createUnionType({
|
||||
name: 'ErrorDataUnion',
|
||||
types: () =>
|
||||
[GraphqlBadRequestDataType, HttpRequestErrorDataType, QueryTooLongDataType, ValidationErrorDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidOauthResponseDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, DocUpdateBlockedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, NoMoreSeatDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, NoCopilotProviderAvailableDataType, CopilotFailedToGenerateEmbeddingDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderNotSupportedDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, CopilotFailedToMatchGlobalContextDataType, CopilotFailedToAddWorkspaceFileEmbeddingDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseToActivateDataType, InvalidLicenseUpdateParamsDataType, UnsupportedClientVersionDataType, MentionUserDocAccessDeniedDataType, InvalidAppConfigDataType, InvalidAppConfigInputDataType, InvalidSearchProviderRequestDataType, InvalidIndexerInputDataType] as const,
|
||||
[GraphqlBadRequestDataType, HttpRequestErrorDataType, QueryTooLongDataType, ValidationErrorDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidOauthResponseDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, DocUpdateBlockedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, NoMoreSeatDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderNotSupportedDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, CopilotFailedToMatchGlobalContextDataType, CopilotFailedToAddWorkspaceFileEmbeddingDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseToActivateDataType, InvalidLicenseUpdateParamsDataType, UnsupportedClientVersionDataType, MentionUserDocAccessDeniedDataType, InvalidAppConfigDataType, InvalidAppConfigInputDataType, InvalidSearchProviderRequestDataType, InvalidIndexerInputDataType] as const,
|
||||
});
|
||||
|
||||
@@ -59,9 +59,7 @@ export type KnownMetricScopes =
|
||||
| 'mail'
|
||||
| 'ai'
|
||||
| 'event'
|
||||
| 'queue'
|
||||
| 'storage'
|
||||
| 'process';
|
||||
| 'queue';
|
||||
|
||||
const metricCreators: MetricCreators = {
|
||||
counter(meter: Meter, name: string, opts?: MetricOptions) {
|
||||
|
||||
@@ -100,7 +100,7 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter {
|
||||
{
|
||||
// keep it simple to let all update merged in one job
|
||||
jobId: `doc:merge-pending-updates:${workspaceId}:${docId}`,
|
||||
delay: 5 * 1000 /* 5s */,
|
||||
delay: 30 * 1000 /* 30s */,
|
||||
priority: 100,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { MonitorService } from './service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [MonitorService],
|
||||
})
|
||||
export class MonitorModule {}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
|
||||
import { metrics } from '../../base';
|
||||
|
||||
@Injectable()
|
||||
export class MonitorService {
|
||||
protected logger = new Logger(MonitorService.name);
|
||||
|
||||
@Cron(CronExpression.EVERY_MINUTE)
|
||||
async monitor() {
|
||||
const memoryUsage = process.memoryUsage();
|
||||
this.logger.log(
|
||||
`memory usage: rss: ${memoryUsage.rss}, heapTotal: ${memoryUsage.heapTotal}, heapUsed: ${memoryUsage.heapUsed}, external: ${memoryUsage.external}, arrayBuffers: ${memoryUsage.arrayBuffers}`
|
||||
);
|
||||
metrics.process.gauge('node_process_rss').record(memoryUsage.rss);
|
||||
metrics.process
|
||||
.gauge('node_process_heap_total')
|
||||
.record(memoryUsage.heapTotal);
|
||||
metrics.process
|
||||
.gauge('node_process_heap_used')
|
||||
.record(memoryUsage.heapUsed);
|
||||
metrics.process.gauge('node_process_external').record(memoryUsage.external);
|
||||
metrics.process
|
||||
.gauge('node_process_array_buffers')
|
||||
.record(memoryUsage.arrayBuffers);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
autoMetadata,
|
||||
Config,
|
||||
EventBus,
|
||||
metrics,
|
||||
OnEvent,
|
||||
type StorageProvider,
|
||||
StorageProviderFactory,
|
||||
@@ -70,23 +69,15 @@ export class CommentAttachmentStorage {
|
||||
blob,
|
||||
meta
|
||||
);
|
||||
const mime = meta.contentType ?? 'application/octet-stream';
|
||||
const size = blob.length;
|
||||
await this.models.commentAttachment.upsert({
|
||||
workspaceId,
|
||||
docId,
|
||||
key,
|
||||
name,
|
||||
mime,
|
||||
size,
|
||||
mime: meta.contentType ?? 'application/octet-stream',
|
||||
size: blob.length,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
metrics.storage.histogram('comment_attachment_size').record(size, { mime });
|
||||
metrics.storage.counter('comment_attachment_total').add(1, { mime });
|
||||
this.logger.log(
|
||||
`uploaded comment attachment ${workspaceId}/${docId}/${key} with size ${size}, mime: ${mime}, name: ${name}, user: ${userId}`
|
||||
);
|
||||
}
|
||||
|
||||
async get(
|
||||
|
||||
@@ -79,9 +79,6 @@ class DocType {
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
title?: string | null;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
summary?: string | null;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
@@ -253,11 +250,10 @@ export class WorkspaceDocResolver {
|
||||
deprecationReason: 'use [WorkspaceType.doc] instead',
|
||||
})
|
||||
async publicPage(
|
||||
@CurrentUser() me: CurrentUser,
|
||||
@Parent() workspace: WorkspaceType,
|
||||
@Args('pageId') pageId: string
|
||||
) {
|
||||
return this.doc(me, workspace, pageId);
|
||||
return this.doc(workspace, pageId);
|
||||
}
|
||||
|
||||
@ResolveField(() => PaginatedDocType)
|
||||
@@ -298,14 +294,11 @@ export class WorkspaceDocResolver {
|
||||
complexity: 2,
|
||||
})
|
||||
async doc(
|
||||
@CurrentUser() me: CurrentUser,
|
||||
@Parent() workspace: WorkspaceType,
|
||||
@Args('docId') docId: string
|
||||
): Promise<DocType> {
|
||||
const doc = await this.models.doc.getDocInfo(workspace.id, docId);
|
||||
if (doc) {
|
||||
// check if doc is readable
|
||||
await this.ac.user(me.id).doc(workspace.id, docId).assert('Doc.Read');
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
@@ -165,13 +165,6 @@ export class CopilotContextModel extends BaseModel {
|
||||
fileId: string,
|
||||
embeddings: Embedding[]
|
||||
) {
|
||||
if (embeddings.length === 0) {
|
||||
this.logger.warn(
|
||||
`No embeddings provided for contextId: ${contextId}, fileId: ${fileId}. Skipping insertion.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const values = this.processEmbeddings(contextId, fileId, embeddings);
|
||||
|
||||
await this.db.$executeRaw`
|
||||
@@ -211,13 +204,6 @@ export class CopilotContextModel extends BaseModel {
|
||||
docId: string,
|
||||
embeddings: Embedding[]
|
||||
) {
|
||||
if (embeddings.length === 0) {
|
||||
this.logger.warn(
|
||||
`No embeddings provided for workspaceId: ${workspaceId}, docId: ${docId}. Skipping insertion.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const values = this.processEmbeddings(
|
||||
workspaceId,
|
||||
docId,
|
||||
|
||||
@@ -283,13 +283,6 @@ export class CopilotWorkspaceConfigModel extends BaseModel {
|
||||
fileId: string,
|
||||
embeddings: Embedding[]
|
||||
) {
|
||||
if (embeddings.length === 0) {
|
||||
this.logger.warn(
|
||||
`No embeddings provided for workspaceId: ${workspaceId}, fileId: ${fileId}. Skipping insertion.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const values = this.processEmbeddings(workspaceId, fileId, embeddings);
|
||||
await this.db.$executeRaw`
|
||||
INSERT INTO "ai_workspace_file_embeddings"
|
||||
|
||||
@@ -558,8 +558,6 @@ export class DocModel extends BaseModel {
|
||||
mode: PublicDocMode;
|
||||
public: boolean;
|
||||
defaultRole: DocRole;
|
||||
title: string | null;
|
||||
summary: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
creatorId?: string;
|
||||
@@ -572,8 +570,6 @@ export class DocModel extends BaseModel {
|
||||
"workspace_pages"."mode" as "mode",
|
||||
"workspace_pages"."public" as "public",
|
||||
"workspace_pages"."defaultRole" as "defaultRole",
|
||||
"workspace_pages"."title" as "title",
|
||||
"workspace_pages"."summary" as "summary",
|
||||
"snapshots"."created_at" as "createdAt",
|
||||
"snapshots"."updated_at" as "updatedAt",
|
||||
"snapshots"."created_by" as "creatorId",
|
||||
|
||||
@@ -125,10 +125,7 @@ export class CopilotContextService implements OnApplicationBootstrap {
|
||||
|
||||
async get(id: string): Promise<ContextSession> {
|
||||
if (!this.embeddingClient) {
|
||||
throw new NoCopilotProviderAvailable(
|
||||
{ modelId: 'embedding' },
|
||||
'embedding client not configured'
|
||||
);
|
||||
throw new NoCopilotProviderAvailable('embedding client not configured');
|
||||
}
|
||||
|
||||
const context = await this.getCachedSession(id);
|
||||
|
||||
@@ -124,7 +124,7 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
modelId: model,
|
||||
});
|
||||
if (!provider) {
|
||||
throw new NoCopilotProviderAvailable({ modelId: model });
|
||||
throw new NoCopilotProviderAvailable();
|
||||
}
|
||||
|
||||
return { provider, model, hasAttachment };
|
||||
@@ -299,13 +299,6 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
this.ongoingStreamCount$.next(this.ongoingStreamCount$.value + 1);
|
||||
|
||||
const { signal, onConnectionClosed } = getSignal(req);
|
||||
let endBeforePromiseResolve = false;
|
||||
onConnectionClosed(isAborted => {
|
||||
if (isAborted) {
|
||||
endBeforePromiseResolve = true;
|
||||
}
|
||||
});
|
||||
|
||||
const { messageId, reasoning, webSearch } = ChatQuerySchema.parse(query);
|
||||
|
||||
const source$ = from(
|
||||
@@ -329,21 +322,21 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
shared$.pipe(
|
||||
reduce((acc, chunk) => acc + chunk, ''),
|
||||
tap(buffer => {
|
||||
session.push({
|
||||
role: 'assistant',
|
||||
content: endBeforePromiseResolve
|
||||
? '> Request aborted'
|
||||
: buffer,
|
||||
createdAt: new Date(),
|
||||
onConnectionClosed(isAborted => {
|
||||
session.push({
|
||||
role: 'assistant',
|
||||
content: isAborted ? '> Request aborted' : buffer,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
void session
|
||||
.save()
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Failed to save session in sse stream',
|
||||
err
|
||||
)
|
||||
);
|
||||
});
|
||||
void session
|
||||
.save()
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Failed to save session in sse stream',
|
||||
err
|
||||
)
|
||||
);
|
||||
}),
|
||||
ignoreElements()
|
||||
)
|
||||
@@ -391,13 +384,6 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
this.ongoingStreamCount$.next(this.ongoingStreamCount$.value + 1);
|
||||
|
||||
const { signal, onConnectionClosed } = getSignal(req);
|
||||
let endBeforePromiseResolve = false;
|
||||
onConnectionClosed(isAborted => {
|
||||
if (isAborted) {
|
||||
endBeforePromiseResolve = true;
|
||||
}
|
||||
});
|
||||
|
||||
const { messageId, reasoning, webSearch } = ChatQuerySchema.parse(query);
|
||||
|
||||
const source$ = from(
|
||||
@@ -421,25 +407,25 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
shared$.pipe(
|
||||
reduce((acc, chunk) => acc.concat([chunk]), [] as StreamObject[]),
|
||||
tap(result => {
|
||||
const parser = new StreamObjectParser();
|
||||
const streamObjects = parser.mergeTextDelta(result);
|
||||
const content = parser.mergeContent(streamObjects);
|
||||
session.push({
|
||||
role: 'assistant',
|
||||
content: endBeforePromiseResolve
|
||||
? '> Request aborted'
|
||||
: content,
|
||||
streamObjects: endBeforePromiseResolve ? null : streamObjects,
|
||||
createdAt: new Date(),
|
||||
onConnectionClosed(isAborted => {
|
||||
const parser = new StreamObjectParser();
|
||||
const streamObjects = parser.mergeTextDelta(result);
|
||||
const content = parser.mergeContent(streamObjects);
|
||||
session.push({
|
||||
role: 'assistant',
|
||||
content: isAborted ? '> Request aborted' : content,
|
||||
streamObjects: isAborted ? null : streamObjects,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
void session
|
||||
.save()
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Failed to save session in sse stream',
|
||||
err
|
||||
)
|
||||
);
|
||||
});
|
||||
void session
|
||||
.save()
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Failed to save session in sse stream',
|
||||
err
|
||||
)
|
||||
);
|
||||
}),
|
||||
ignoreElements()
|
||||
)
|
||||
@@ -491,13 +477,6 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
this.ongoingStreamCount$.next(this.ongoingStreamCount$.value + 1);
|
||||
|
||||
const { signal, onConnectionClosed } = getSignal(req);
|
||||
let endBeforePromiseResolve = false;
|
||||
onConnectionClosed(isAborted => {
|
||||
if (isAborted) {
|
||||
endBeforePromiseResolve = true;
|
||||
}
|
||||
});
|
||||
|
||||
const source$ = from(
|
||||
this.workflow.runGraph(params, session.model, {
|
||||
...session.config.promptConfig,
|
||||
@@ -547,21 +526,21 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
return acc;
|
||||
}, ''),
|
||||
tap(content => {
|
||||
session.push({
|
||||
role: 'assistant',
|
||||
content: endBeforePromiseResolve
|
||||
? '> Request aborted'
|
||||
: content,
|
||||
createdAt: new Date(),
|
||||
onConnectionClosed(isAborted => {
|
||||
session.push({
|
||||
role: 'assistant',
|
||||
content: isAborted ? '> Request aborted' : content,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
void session
|
||||
.save()
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Failed to save session in sse stream',
|
||||
err
|
||||
)
|
||||
);
|
||||
});
|
||||
void session
|
||||
.save()
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Failed to save session in sse stream',
|
||||
err
|
||||
)
|
||||
);
|
||||
}),
|
||||
ignoreElements()
|
||||
)
|
||||
@@ -625,13 +604,6 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
this.ongoingStreamCount$.next(this.ongoingStreamCount$.value + 1);
|
||||
|
||||
const { signal, onConnectionClosed } = getSignal(req);
|
||||
let endBeforePromiseResolve = false;
|
||||
onConnectionClosed(isAborted => {
|
||||
if (isAborted) {
|
||||
endBeforePromiseResolve = true;
|
||||
}
|
||||
});
|
||||
|
||||
const source$ = from(
|
||||
provider.streamImages(
|
||||
{
|
||||
@@ -667,20 +639,22 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
shared$.pipe(
|
||||
reduce((acc, chunk) => acc.concat([chunk]), [] as string[]),
|
||||
tap(attachments => {
|
||||
session.push({
|
||||
role: 'assistant',
|
||||
content: endBeforePromiseResolve ? '> Request aborted' : '',
|
||||
attachments: endBeforePromiseResolve ? [] : attachments,
|
||||
createdAt: new Date(),
|
||||
onConnectionClosed(isAborted => {
|
||||
session.push({
|
||||
role: 'assistant',
|
||||
content: isAborted ? '> Request aborted' : '',
|
||||
attachments: isAborted ? [] : attachments,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
void session
|
||||
.save()
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Failed to save session in sse stream',
|
||||
err
|
||||
)
|
||||
);
|
||||
});
|
||||
void session
|
||||
.save()
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Failed to save session in sse stream',
|
||||
err
|
||||
)
|
||||
);
|
||||
}),
|
||||
ignoreElements()
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
CopilotPromptNotFound,
|
||||
CopilotProviderNotSupported,
|
||||
} from '../../../base';
|
||||
import { CopilotFailedToGenerateEmbedding } from '../../../base/error/errors.gen';
|
||||
import { ChunkSimilarity, Embedding } from '../../../models';
|
||||
import { PromptService } from '../prompt';
|
||||
import {
|
||||
@@ -75,12 +74,6 @@ class ProductionEmbeddingClient extends EmbeddingClient {
|
||||
input,
|
||||
{ dimensions: EMBEDDING_DIMENSIONS }
|
||||
);
|
||||
if (embeddings.length !== input.length) {
|
||||
throw new CopilotFailedToGenerateEmbedding({
|
||||
provider: provider.type,
|
||||
message: `Expected ${input.length} embeddings, got ${embeddings.length}`,
|
||||
});
|
||||
}
|
||||
|
||||
return Array.from(embeddings.entries()).map(([index, embedding]) => ({
|
||||
index,
|
||||
|
||||
@@ -366,31 +366,6 @@ Convert a multi-speaker audio recording into a structured JSON format by transcr
|
||||
requireAttachment: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Conversation Summary',
|
||||
action: 'Conversation Summary',
|
||||
model: 'gpt-4.1-2025-04-14',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `You are an expert conversation summarizer. Your job is to distill long dialogues into clear, compact summaries that preserve every key decision, fact, and open question. When asked, always:
|
||||
• Honor any explicit “focus” the user gives you.
|
||||
• Match the desired length style:
|
||||
- “brief” → 1-2 sentences
|
||||
- “detailed” → ≈ 5 sentences or short bullet list
|
||||
- “comprehensive” → full paragraph(s) covering all salient points.
|
||||
• Write in neutral, third-person prose and never add new information.
|
||||
Return only the summary text—no headings, labels, or commentary.`,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Summarize the conversation below so it can be carried forward without loss.\n\nFocus: {{focus}}\nDesired length: {{length}}\n\nConversation:\n{{#messages}}\n{{role}}: {{content}}\n{{/messages}}`,
|
||||
},
|
||||
],
|
||||
config: {
|
||||
requireContent: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Summary',
|
||||
action: 'Summary',
|
||||
@@ -1795,74 +1770,11 @@ const chat: Prompt[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const artifactActions: Prompt[] = [
|
||||
{
|
||||
name: 'Code Artifact',
|
||||
model: 'claude-sonnet-4@20250514',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `
|
||||
When sent new notes, respond ONLY with the contents of the html file.
|
||||
DO NOT INCLUDE ANY OTHER TEXT, EXPLANATIONS, APOLOGIES, OR INTRODUCTORY/CLOSING PHRASES.
|
||||
IF USER DOES NOT SPECIFY A STYLE, FOLLOW THE DEFAULT STYLE.
|
||||
<generate_guide>
|
||||
- The results should be a single HTML file.
|
||||
- Use tailwindcss to style the website
|
||||
- Put any additional CSS styles in a style tag and any JavaScript in a script tag.
|
||||
- Use unpkg or skypack to import any required dependencies.
|
||||
- Use Google fonts to pull in any open source fonts you require.
|
||||
- Use lucide icons for any icons.
|
||||
- If you have any images, load them from Unsplash or use solid colored rectangles.
|
||||
</generate_guide>
|
||||
|
||||
<DO_NOT_USE_COLORS>
|
||||
- DO NOT USE ANY COLORS
|
||||
</DO_NOT_USE_COLORS>
|
||||
<DO_NOT_USE_GRADIENTS>
|
||||
- DO NOT USE ANY GRADIENTS
|
||||
</DO_NOT_USE_GRADIENTS>
|
||||
|
||||
<COLOR_THEME>
|
||||
- --affine-blue-300: #93e2fd
|
||||
- --affine-blue-400: #60cffa
|
||||
- --affine-blue-500: #3ab5f7
|
||||
- --affine-blue-600: #1e96eb
|
||||
- --affine-blue-700: #1e67af
|
||||
- --affine-text-primary-color: #121212
|
||||
- --affine-text-secondary-color: #8e8d91
|
||||
- --affine-text-disable-color: #a9a9ad
|
||||
- --affine-background-overlay-panel-color: #fbfbfc
|
||||
- --affine-background-secondary-color: #f4f4f5
|
||||
- --affine-background-primary-color: #fff
|
||||
</COLOR_THEME>
|
||||
<default_style_guide>
|
||||
- MUST USE White and Blue(#1e96eb) as the primary color
|
||||
- KEEP THE DEFAULT STYLE SIMPLE AND CLEAN
|
||||
- DO NOT USE ANY COMPLEX STYLES
|
||||
- DO NOT USE ANY GRADIENTS
|
||||
- USE LESS SHADOWS
|
||||
- USE RADIUS 4px or 8px for rounded corners
|
||||
- USE 12px or 16px for padding
|
||||
- Use the tailwind color gray, zinc, slate, neutral much more.
|
||||
- Use 0.5px border should be better
|
||||
</default_style_guide>
|
||||
`,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: '{{content}}',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const prompts: Prompt[] = [
|
||||
...textActions,
|
||||
...imageActions,
|
||||
...chat,
|
||||
...workflows,
|
||||
...artifactActions,
|
||||
];
|
||||
|
||||
export async function refreshPrompts(db: PrismaClient) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
buildDocKeywordSearchGetter,
|
||||
buildDocSearchGetter,
|
||||
createCodeArtifactTool,
|
||||
createConversationSummaryTool,
|
||||
createDocComposeTool,
|
||||
createDocEditTool,
|
||||
createDocKeywordSearchTool,
|
||||
@@ -140,11 +139,6 @@ export abstract class CopilotProvider<C = any> {
|
||||
if (options?.tools?.length) {
|
||||
this.logger.debug(`getTools: ${JSON.stringify(options.tools)}`);
|
||||
const ac = this.moduleRef.get(AccessController, { strict: false });
|
||||
const docReader = this.moduleRef.get(DocReader, { strict: false });
|
||||
const models = this.moduleRef.get(Models, { strict: false });
|
||||
const prompt = this.moduleRef.get(PromptService, {
|
||||
strict: false,
|
||||
});
|
||||
|
||||
for (const tool of options.tools) {
|
||||
const toolDef = this.getProviderSpecificTools(tool, model);
|
||||
@@ -156,20 +150,9 @@ export abstract class CopilotProvider<C = any> {
|
||||
continue;
|
||||
}
|
||||
switch (tool) {
|
||||
case 'codeArtifact': {
|
||||
tools.code_artifact = createCodeArtifactTool(prompt, this.factory);
|
||||
break;
|
||||
}
|
||||
case 'conversationSummary': {
|
||||
tools.conversation_summary = createConversationSummaryTool(
|
||||
options.session,
|
||||
prompt,
|
||||
this.factory
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'docEdit': {
|
||||
const getDocContent = buildContentGetter(ac, docReader);
|
||||
const doc = this.moduleRef.get(DocReader, { strict: false });
|
||||
const getDocContent = buildContentGetter(ac, doc);
|
||||
tools.doc_edit = createDocEditTool(
|
||||
this.factory,
|
||||
getDocContent.bind(null, options)
|
||||
@@ -180,15 +163,11 @@ export abstract class CopilotProvider<C = any> {
|
||||
const context = this.moduleRef.get(CopilotContextService, {
|
||||
strict: false,
|
||||
});
|
||||
|
||||
const docContext = options.session
|
||||
? await context.getBySessionId(options.session)
|
||||
: null;
|
||||
const searchDocs = buildDocSearchGetter(
|
||||
ac,
|
||||
context,
|
||||
docContext,
|
||||
models
|
||||
);
|
||||
const searchDocs = buildDocSearchGetter(ac, context, docContext);
|
||||
tools.doc_semantic_search = createDocSemanticSearchTool(
|
||||
searchDocs.bind(null, options)
|
||||
);
|
||||
@@ -196,6 +175,9 @@ export abstract class CopilotProvider<C = any> {
|
||||
}
|
||||
case 'docKeywordSearch': {
|
||||
if (this.AFFiNEConfig.indexer.enabled) {
|
||||
const ac = this.moduleRef.get(AccessController, {
|
||||
strict: false,
|
||||
});
|
||||
const indexerService = this.moduleRef.get(IndexerService, {
|
||||
strict: false,
|
||||
});
|
||||
@@ -210,6 +192,9 @@ export abstract class CopilotProvider<C = any> {
|
||||
break;
|
||||
}
|
||||
case 'docRead': {
|
||||
const ac = this.moduleRef.get(AccessController, { strict: false });
|
||||
const models = this.moduleRef.get(Models, { strict: false });
|
||||
const docReader = this.moduleRef.get(DocReader, { strict: false });
|
||||
const getDoc = buildDocContentGetter(ac, docReader, models);
|
||||
tools.doc_read = createDocReadTool(getDoc.bind(null, options));
|
||||
break;
|
||||
@@ -220,7 +205,23 @@ export abstract class CopilotProvider<C = any> {
|
||||
break;
|
||||
}
|
||||
case 'docCompose': {
|
||||
tools.doc_compose = createDocComposeTool(prompt, this.factory);
|
||||
const promptService = this.moduleRef.get(PromptService, {
|
||||
strict: false,
|
||||
});
|
||||
tools.doc_compose = createDocComposeTool(
|
||||
promptService,
|
||||
this.factory
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'codeArtifact': {
|
||||
const promptService = this.moduleRef.get(PromptService, {
|
||||
strict: false,
|
||||
});
|
||||
tools.code_artifact = createCodeArtifactTool(
|
||||
promptService,
|
||||
this.factory
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,6 @@ export const VertexSchema: JSONSchema = {
|
||||
export const PromptConfigStrictSchema = z.object({
|
||||
tools: z
|
||||
.enum([
|
||||
'codeArtifact',
|
||||
'conversationSummary',
|
||||
// work with morph
|
||||
'docEdit',
|
||||
// work with indexer
|
||||
@@ -73,6 +71,7 @@ export const PromptConfigStrictSchema = z.object({
|
||||
'webSearch',
|
||||
// artifact tools
|
||||
'docCompose',
|
||||
'codeArtifact',
|
||||
])
|
||||
.array()
|
||||
.nullable()
|
||||
|
||||
@@ -6,10 +6,20 @@ import {
|
||||
ImagePart,
|
||||
TextPart,
|
||||
TextStreamPart,
|
||||
ToolSet,
|
||||
} from 'ai';
|
||||
import { ZodType } from 'zod';
|
||||
|
||||
import { CustomAITools } from '../tools';
|
||||
import {
|
||||
createCodeArtifactTool,
|
||||
createDocComposeTool,
|
||||
createDocEditTool,
|
||||
createDocKeywordSearchTool,
|
||||
createDocReadTool,
|
||||
createDocSemanticSearchTool,
|
||||
createExaCrawlTool,
|
||||
createExaSearchTool,
|
||||
} from '../tools';
|
||||
import { PromptMessage, StreamObject } from './types';
|
||||
|
||||
type ChatMessage = CoreUserMessage | CoreAssistantMessage;
|
||||
@@ -375,6 +385,17 @@ export class CitationParser {
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomAITools extends ToolSet {
|
||||
doc_edit: ReturnType<typeof createDocEditTool>;
|
||||
doc_semantic_search: ReturnType<typeof createDocSemanticSearchTool>;
|
||||
doc_keyword_search: ReturnType<typeof createDocKeywordSearchTool>;
|
||||
doc_read: ReturnType<typeof createDocReadTool>;
|
||||
doc_compose: ReturnType<typeof createDocComposeTool>;
|
||||
web_search_exa: ReturnType<typeof createExaSearchTool>;
|
||||
web_crawl_exa: ReturnType<typeof createExaCrawlTool>;
|
||||
code_artifact: ReturnType<typeof createCodeArtifactTool>;
|
||||
}
|
||||
|
||||
type ChunkType = TextStreamPart<CustomAITools>['type'];
|
||||
|
||||
export function toError(error: unknown): Error {
|
||||
@@ -430,10 +451,6 @@ export class TextStreamParser {
|
||||
);
|
||||
result = this.addPrefix(result);
|
||||
switch (chunk.toolName) {
|
||||
case 'conversation_summary': {
|
||||
result += `\nSummarizing context\n`;
|
||||
break;
|
||||
}
|
||||
case 'web_search_exa': {
|
||||
result += `\nSearching the web "${chunk.args.query}"\n`;
|
||||
break;
|
||||
|
||||
@@ -569,7 +569,7 @@ export class ChatSessionService {
|
||||
});
|
||||
|
||||
if (!provider) {
|
||||
throw new NoCopilotProviderAvailable({ modelId: prompt.model });
|
||||
throw new NoCopilotProviderAvailable();
|
||||
}
|
||||
|
||||
return provider.text(cond, [...prompt.finish({}), msg], config);
|
||||
|
||||
@@ -5,7 +5,9 @@ import { z } from 'zod';
|
||||
import type { PromptService } from '../prompt';
|
||||
import type { CopilotProviderFactory } from '../providers';
|
||||
import { toolError } from './error';
|
||||
|
||||
const logger = new Logger('CodeArtifactTool');
|
||||
|
||||
/**
|
||||
* A copilot tool that produces a completely self-contained HTML artifact.
|
||||
* The returned HTML must include <style> and <script> tags directly so that
|
||||
@@ -35,20 +37,23 @@ export const createCodeArtifactTool = (
|
||||
}),
|
||||
execute: async ({ title, userPrompt }) => {
|
||||
try {
|
||||
const prompt = await promptService.get('Code Artifact');
|
||||
const prompt = await promptService.get('Make it real with text');
|
||||
if (!prompt) {
|
||||
throw new Error('Prompt not found');
|
||||
}
|
||||
|
||||
const provider = await factory.getProviderByModel(prompt.model);
|
||||
if (!provider) {
|
||||
throw new Error('Provider not found');
|
||||
}
|
||||
|
||||
const content = await provider.text(
|
||||
{
|
||||
modelId: prompt.model,
|
||||
},
|
||||
prompt.finish({ content: userPrompt })
|
||||
[...prompt.finish({}), { role: 'user', content: userPrompt }]
|
||||
);
|
||||
|
||||
// Remove surrounding ``` or ```html fences if present
|
||||
let stripped = content.trim();
|
||||
if (stripped.startsWith('```')) {
|
||||
@@ -60,6 +65,7 @@ export const createCodeArtifactTool = (
|
||||
stripped = stripped.slice(0, -3);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
html: stripped,
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { tool } from 'ai';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { PromptService } from '../prompt';
|
||||
import type { CopilotProviderFactory } from '../providers';
|
||||
import { toolError } from './error';
|
||||
|
||||
const logger = new Logger('ConversationSummaryTool');
|
||||
|
||||
export const createConversationSummaryTool = (
|
||||
sessionId: string | undefined,
|
||||
promptService: PromptService,
|
||||
factory: CopilotProviderFactory
|
||||
) => {
|
||||
return tool({
|
||||
description:
|
||||
'Create a concise, AI-generated summary of the conversation so far—capturing key topics, decisions, and critical details. Use this tool whenever the context becomes lengthy to preserve essential information that might otherwise be lost to truncation in future turns.',
|
||||
parameters: z.object({
|
||||
focus: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Optional focus area for the summary (e.g., "technical decisions", "user requirements", "project status")'
|
||||
),
|
||||
length: z
|
||||
.enum(['brief', 'detailed', 'comprehensive'])
|
||||
.default('detailed')
|
||||
.describe(
|
||||
'The desired length of the summary: brief (1-2 sentences), detailed (paragraph), comprehensive (multiple paragraphs)'
|
||||
),
|
||||
}),
|
||||
execute: async ({ focus, length }, { messages }) => {
|
||||
try {
|
||||
if (!messages || messages.length === 0) {
|
||||
return toolError(
|
||||
'No Conversation Context',
|
||||
'No messages available to summarize'
|
||||
);
|
||||
}
|
||||
|
||||
const prompt = await promptService.get('Conversation Summary');
|
||||
const provider = await factory.getProviderByModel(prompt?.model || '');
|
||||
|
||||
if (!prompt || !provider) {
|
||||
return toolError(
|
||||
'Prompt Not Found',
|
||||
'Failed to summarize conversation.'
|
||||
);
|
||||
}
|
||||
|
||||
const summary = await provider.text(
|
||||
{ modelId: prompt.model },
|
||||
prompt.finish({
|
||||
messages: messages.map(m => ({
|
||||
...m,
|
||||
content: m.content.toString(),
|
||||
})),
|
||||
focus: focus || 'general',
|
||||
length,
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
focusArea: focus || 'general',
|
||||
messageCount: messages.length,
|
||||
summary,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
} catch (err: any) {
|
||||
logger.error(`Failed to summarize conversation (${sessionId})`, err);
|
||||
return toolError('Conversation Summary Failed', err.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { tool } from 'ai';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { AccessController } from '../../../core/permission';
|
||||
import type { ChunkSimilarity, Models } from '../../../models';
|
||||
import type { ChunkSimilarity } from '../../../models';
|
||||
import type { CopilotContextService } from '../context';
|
||||
import type { ContextSession } from '../context/session';
|
||||
import type { CopilotChatOptions } from '../providers';
|
||||
@@ -11,8 +11,7 @@ import { toolError } from './error';
|
||||
export const buildDocSearchGetter = (
|
||||
ac: AccessController,
|
||||
context: CopilotContextService,
|
||||
docContext: ContextSession | null,
|
||||
models: Models
|
||||
docContext: ContextSession | null
|
||||
) => {
|
||||
const searchDocs = async (
|
||||
options: CopilotChatOptions,
|
||||
@@ -46,24 +45,7 @@ export const buildDocSearchGetter = (
|
||||
}
|
||||
if (!docChunks.length && !fileChunks.length)
|
||||
return `No results found for "${query}".`;
|
||||
|
||||
const docMetas = await models.doc
|
||||
.findAuthors(
|
||||
docChunks.map(c => ({
|
||||
// oxlint-disable-next-line no-non-null-assertion
|
||||
workspaceId: options.workspace!,
|
||||
docId: c.docId,
|
||||
}))
|
||||
)
|
||||
.then(docs => new Map(docs.filter(d => !!d).map(doc => [doc.id, doc])));
|
||||
|
||||
return [
|
||||
...fileChunks,
|
||||
...docChunks.map(c => ({
|
||||
...c,
|
||||
...docMetas.get(c.docId),
|
||||
})),
|
||||
] as ChunkSimilarity[];
|
||||
return [...fileChunks, ...docChunks];
|
||||
};
|
||||
return searchDocs;
|
||||
};
|
||||
|
||||
@@ -1,29 +1,4 @@
|
||||
import { ToolSet } from 'ai';
|
||||
|
||||
import { createCodeArtifactTool } from './code-artifact';
|
||||
import { createConversationSummaryTool } from './conversation-summary';
|
||||
import { createDocComposeTool } from './doc-compose';
|
||||
import { createDocEditTool } from './doc-edit';
|
||||
import { createDocKeywordSearchTool } from './doc-keyword-search';
|
||||
import { createDocReadTool } from './doc-read';
|
||||
import { createDocSemanticSearchTool } from './doc-semantic-search';
|
||||
import { createExaCrawlTool } from './exa-crawl';
|
||||
import { createExaSearchTool } from './exa-search';
|
||||
|
||||
export interface CustomAITools extends ToolSet {
|
||||
code_artifact: ReturnType<typeof createCodeArtifactTool>;
|
||||
conversation_summary: ReturnType<typeof createConversationSummaryTool>;
|
||||
doc_edit: ReturnType<typeof createDocEditTool>;
|
||||
doc_semantic_search: ReturnType<typeof createDocSemanticSearchTool>;
|
||||
doc_keyword_search: ReturnType<typeof createDocKeywordSearchTool>;
|
||||
doc_read: ReturnType<typeof createDocReadTool>;
|
||||
doc_compose: ReturnType<typeof createDocComposeTool>;
|
||||
web_search_exa: ReturnType<typeof createExaSearchTool>;
|
||||
web_crawl_exa: ReturnType<typeof createExaCrawlTool>;
|
||||
}
|
||||
|
||||
export * from './code-artifact';
|
||||
export * from './conversation-summary';
|
||||
export * from './doc-compose';
|
||||
export * from './doc-edit';
|
||||
export * from './doc-keyword-search';
|
||||
|
||||
@@ -171,7 +171,7 @@ export class CopilotTranscriptionService {
|
||||
);
|
||||
|
||||
if (!provider) {
|
||||
throw new NoCopilotProviderAvailable({ modelId });
|
||||
throw new NoCopilotProviderAvailable();
|
||||
}
|
||||
|
||||
return provider;
|
||||
|
||||
@@ -140,13 +140,10 @@ export class ElasticsearchProvider extends SearchProvider {
|
||||
const result = await this.request(
|
||||
'POST',
|
||||
url.toString(),
|
||||
JSON.stringify({ query }),
|
||||
'application/json',
|
||||
// ignore 409 error: version_conflict_engine_exception, version conflict, required seqNo [255898790], primary term [3]. current document has seqNo [256133002] and primary term [3]
|
||||
[409]
|
||||
JSON.stringify({ query })
|
||||
);
|
||||
this.logger.debug(
|
||||
`deleted by query ${table} ${JSON.stringify(query)} in ${Date.now() - start}ms, result: ${JSON.stringify(result).substring(0, 500)}`
|
||||
`deleted by query ${table} ${JSON.stringify(query)} in ${Date.now() - start}ms, result: ${JSON.stringify(result)}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -267,8 +264,7 @@ export class ElasticsearchProvider extends SearchProvider {
|
||||
method: 'POST' | 'PUT',
|
||||
url: string,
|
||||
body: string,
|
||||
contentType = 'application/json',
|
||||
ignoreErrorStatus?: number[]
|
||||
contentType = 'application/json'
|
||||
) {
|
||||
const headers = {
|
||||
'Content-Type': contentType,
|
||||
@@ -284,10 +280,6 @@ export class ElasticsearchProvider extends SearchProvider {
|
||||
headers,
|
||||
});
|
||||
const data = await response.json();
|
||||
if (ignoreErrorStatus?.includes(response.status)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// handle error, status >= 400
|
||||
// {
|
||||
// "error": {
|
||||
|
||||
@@ -291,11 +291,6 @@ type CopilotFailedToAddWorkspaceFileEmbeddingDataType {
|
||||
message: String!
|
||||
}
|
||||
|
||||
type CopilotFailedToGenerateEmbeddingDataType {
|
||||
message: String!
|
||||
provider: String!
|
||||
}
|
||||
|
||||
type CopilotFailedToMatchContextDataType {
|
||||
content: String!
|
||||
contextId: String!
|
||||
@@ -600,7 +595,6 @@ type DocType {
|
||||
mode: PublicDocMode!
|
||||
permissions: DocPermissions!
|
||||
public: Boolean!
|
||||
summary: String
|
||||
title: String
|
||||
updatedAt: DateTime
|
||||
workspaceId: String!
|
||||
@@ -621,7 +615,7 @@ type EditorType {
|
||||
name: String!
|
||||
}
|
||||
|
||||
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToAddWorkspaceFileEmbeddingDataType | CopilotFailedToGenerateEmbeddingDataType | CopilotFailedToMatchContextDataType | CopilotFailedToMatchGlobalContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderNotSupportedDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | DocUpdateBlockedDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | HttpRequestErrorDataType | InvalidAppConfigDataType | InvalidAppConfigInputDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidIndexerInputDataType | InvalidLicenseToActivateDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidOauthResponseDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | InvalidSearchProviderRequestDataType | MemberNotFoundInSpaceDataType | MentionUserDocAccessDeniedDataType | MissingOauthQueryParameterDataType | NoCopilotProviderAvailableDataType | NoMoreSeatDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | ValidationErrorDataType | VersionRejectedDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType
|
||||
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToAddWorkspaceFileEmbeddingDataType | CopilotFailedToMatchContextDataType | CopilotFailedToMatchGlobalContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderNotSupportedDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | DocUpdateBlockedDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | HttpRequestErrorDataType | InvalidAppConfigDataType | InvalidAppConfigInputDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidIndexerInputDataType | InvalidLicenseToActivateDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidOauthResponseDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | InvalidSearchProviderRequestDataType | MemberNotFoundInSpaceDataType | MentionUserDocAccessDeniedDataType | MissingOauthQueryParameterDataType | NoMoreSeatDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | ValidationErrorDataType | VersionRejectedDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType
|
||||
|
||||
enum ErrorNames {
|
||||
ACCESS_DENIED
|
||||
@@ -650,7 +644,6 @@ enum ErrorNames {
|
||||
COPILOT_EMBEDDING_UNAVAILABLE
|
||||
COPILOT_FAILED_TO_ADD_WORKSPACE_FILE_EMBEDDING
|
||||
COPILOT_FAILED_TO_CREATE_MESSAGE
|
||||
COPILOT_FAILED_TO_GENERATE_EMBEDDING
|
||||
COPILOT_FAILED_TO_GENERATE_TEXT
|
||||
COPILOT_FAILED_TO_MATCH_CONTEXT
|
||||
COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT
|
||||
@@ -1342,10 +1335,6 @@ type Mutation {
|
||||
verifyEmail(token: String!): Boolean!
|
||||
}
|
||||
|
||||
type NoCopilotProviderAvailableDataType {
|
||||
modelId: String!
|
||||
}
|
||||
|
||||
type NoMoreSeatDataType {
|
||||
spaceId: String!
|
||||
}
|
||||
|
||||
@@ -82,10 +82,6 @@ export type RequestOptions<Q extends GraphQLQuery> = QueryVariablesOption<Q> & {
|
||||
* @default 15000
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* Abort signal
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
export type QueryOptions<Q extends GraphQLQuery> = RequestOptions<Q> & {
|
||||
@@ -211,7 +207,6 @@ export const gqlFetcherFactory = (
|
||||
headers,
|
||||
body: isFormData ? body : JSON.stringify(body),
|
||||
timeout: options.timeout,
|
||||
signal: options.signal,
|
||||
})
|
||||
).then(async res => {
|
||||
if (res.headers.get('content-type')?.startsWith('application/json')) {
|
||||
|
||||
@@ -11,7 +11,7 @@ query getCopilotRecentSessions(
|
||||
options: {
|
||||
fork: false
|
||||
sessionOrder: desc
|
||||
withMessages: false
|
||||
withMessages: true
|
||||
}
|
||||
) {
|
||||
...PaginatedCopilotChats
|
||||
|
||||
@@ -5,8 +5,6 @@ query getWorkspacePageById($workspaceId: String!, $pageId: String!) {
|
||||
mode
|
||||
defaultRole
|
||||
public
|
||||
title
|
||||
summary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,7 +1073,7 @@ export const getCopilotRecentSessionsQuery = {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
chats(
|
||||
pagination: {first: $limit}
|
||||
options: {fork: false, sessionOrder: desc, withMessages: false}
|
||||
options: {fork: false, sessionOrder: desc, withMessages: true}
|
||||
) {
|
||||
...PaginatedCopilotChats
|
||||
}
|
||||
@@ -1584,8 +1584,6 @@ export const getWorkspacePageByIdQuery = {
|
||||
mode
|
||||
defaultRole
|
||||
public
|
||||
title
|
||||
summary
|
||||
}
|
||||
}
|
||||
}`,
|
||||
|
||||
@@ -375,12 +375,6 @@ export interface CopilotFailedToAddWorkspaceFileEmbeddingDataType {
|
||||
message: Scalars['String']['output'];
|
||||
}
|
||||
|
||||
export interface CopilotFailedToGenerateEmbeddingDataType {
|
||||
__typename?: 'CopilotFailedToGenerateEmbeddingDataType';
|
||||
message: Scalars['String']['output'];
|
||||
provider: Scalars['String']['output'];
|
||||
}
|
||||
|
||||
export interface CopilotFailedToMatchContextDataType {
|
||||
__typename?: 'CopilotFailedToMatchContextDataType';
|
||||
content: Scalars['String']['output'];
|
||||
@@ -709,7 +703,6 @@ export interface DocType {
|
||||
mode: PublicDocMode;
|
||||
permissions: DocPermissions;
|
||||
public: Scalars['Boolean']['output'];
|
||||
summary: Maybe<Scalars['String']['output']>;
|
||||
title: Maybe<Scalars['String']['output']>;
|
||||
updatedAt: Maybe<Scalars['DateTime']['output']>;
|
||||
workspaceId: Scalars['String']['output'];
|
||||
@@ -743,7 +736,6 @@ export type ErrorDataUnion =
|
||||
| CopilotContextFileNotSupportedDataType
|
||||
| CopilotDocNotFoundDataType
|
||||
| CopilotFailedToAddWorkspaceFileEmbeddingDataType
|
||||
| CopilotFailedToGenerateEmbeddingDataType
|
||||
| CopilotFailedToMatchContextDataType
|
||||
| CopilotFailedToMatchGlobalContextDataType
|
||||
| CopilotFailedToModifyContextDataType
|
||||
@@ -776,7 +768,6 @@ export type ErrorDataUnion =
|
||||
| MemberNotFoundInSpaceDataType
|
||||
| MentionUserDocAccessDeniedDataType
|
||||
| MissingOauthQueryParameterDataType
|
||||
| NoCopilotProviderAvailableDataType
|
||||
| NoMoreSeatDataType
|
||||
| NotInSpaceDataType
|
||||
| QueryTooLongDataType
|
||||
@@ -824,7 +815,6 @@ export enum ErrorNames {
|
||||
COPILOT_EMBEDDING_UNAVAILABLE = 'COPILOT_EMBEDDING_UNAVAILABLE',
|
||||
COPILOT_FAILED_TO_ADD_WORKSPACE_FILE_EMBEDDING = 'COPILOT_FAILED_TO_ADD_WORKSPACE_FILE_EMBEDDING',
|
||||
COPILOT_FAILED_TO_CREATE_MESSAGE = 'COPILOT_FAILED_TO_CREATE_MESSAGE',
|
||||
COPILOT_FAILED_TO_GENERATE_EMBEDDING = 'COPILOT_FAILED_TO_GENERATE_EMBEDDING',
|
||||
COPILOT_FAILED_TO_GENERATE_TEXT = 'COPILOT_FAILED_TO_GENERATE_TEXT',
|
||||
COPILOT_FAILED_TO_MATCH_CONTEXT = 'COPILOT_FAILED_TO_MATCH_CONTEXT',
|
||||
COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT = 'COPILOT_FAILED_TO_MATCH_GLOBAL_CONTEXT',
|
||||
@@ -1890,11 +1880,6 @@ export interface MutationVerifyEmailArgs {
|
||||
token: Scalars['String']['input'];
|
||||
}
|
||||
|
||||
export interface NoCopilotProviderAvailableDataType {
|
||||
__typename?: 'NoCopilotProviderAvailableDataType';
|
||||
modelId: Scalars['String']['output'];
|
||||
}
|
||||
|
||||
export interface NoMoreSeatDataType {
|
||||
__typename?: 'NoMoreSeatDataType';
|
||||
spaceId: Scalars['String']['output'];
|
||||
@@ -5162,8 +5147,6 @@ export type GetWorkspacePageByIdQuery = {
|
||||
mode: PublicDocMode;
|
||||
defaultRole: DocRole;
|
||||
public: boolean;
|
||||
title: string | null;
|
||||
summary: string | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -541,7 +541,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 0.23.1;
|
||||
MARKETING_VERSION = 0.22.2;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.affine.pro;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -577,7 +577,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 0.23.1;
|
||||
MARKETING_VERSION = 0.22.2;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.affine.pro;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { MindmapElementModel } from '@blocksuite/affine/model';
|
||||
import type { EditorHost } from '@blocksuite/affine/std';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
|
||||
import { createAIScrollableTextRenderer } from '../components/ai-scrollable-text-renderer';
|
||||
import {
|
||||
@@ -53,11 +52,5 @@ export function actionToAnswerRenderer<
|
||||
return createImageRenderer(host, { height: 300 });
|
||||
}
|
||||
|
||||
return createAIScrollableTextRenderer(
|
||||
{
|
||||
theme: host.std.get(ThemeProvider).app$,
|
||||
},
|
||||
320,
|
||||
true
|
||||
);
|
||||
return createAIScrollableTextRenderer(host, {}, 320, true);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from '@blocksuite/affine/shared/utils';
|
||||
import type { EditorHost } from '@blocksuite/affine/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/affine/std/gfx';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
ChatWithAiIcon,
|
||||
DeleteIcon,
|
||||
@@ -307,13 +306,7 @@ export function buildAIPanelConfig(
|
||||
const ctx = new AIContext();
|
||||
const searchService = framework.get(AINetworkSearchService);
|
||||
return {
|
||||
answerRenderer: createAIScrollableTextRenderer(
|
||||
{
|
||||
theme: panel.host.std.get(ThemeProvider).app$,
|
||||
},
|
||||
320,
|
||||
true
|
||||
),
|
||||
answerRenderer: createAIScrollableTextRenderer(panel.host, {}, 320, true),
|
||||
finishStateConfig: buildFinishConfig(panel, 'chat', ctx),
|
||||
generatingStateConfig: buildGeneratingConfig(),
|
||||
errorStateConfig: buildErrorConfig(panel),
|
||||
|
||||
@@ -20,15 +20,16 @@ import { property, state } from 'lit/decorators.js';
|
||||
import { keyed } from 'lit/directives/keyed.js';
|
||||
|
||||
import { AffineIcon } from '../_common/icons';
|
||||
import type { SearchMenuConfig } from '../components/ai-chat-add-context';
|
||||
import type { DocDisplayConfig } from '../components/ai-chat-chips';
|
||||
import type {
|
||||
DocDisplayConfig,
|
||||
SearchMenuConfig,
|
||||
} from '../components/ai-chat-chips';
|
||||
import type { ChatContextValue } from '../components/ai-chat-content';
|
||||
import type {
|
||||
AINetworkSearchConfig,
|
||||
AIPlaygroundConfig,
|
||||
AIReasoningConfig,
|
||||
} from '../components/ai-chat-input';
|
||||
import type { ChatStatus } from '../components/ai-chat-messages';
|
||||
import { createPlaygroundModal } from '../components/playground/modal';
|
||||
import { AIProvider } from '../provider';
|
||||
import type { AppSidebarConfig } from './chat-config';
|
||||
@@ -137,9 +138,6 @@ export class ChatPanel extends SignalWatcher(
|
||||
@state()
|
||||
accessor embeddingProgress: [number, number] = [0, 0];
|
||||
|
||||
@state()
|
||||
accessor status: ChatStatus = 'idle';
|
||||
|
||||
private isSidebarOpen: Signal<boolean | undefined> = signal(false);
|
||||
|
||||
private sidebarWidth: Signal<number | undefined> = signal(undefined);
|
||||
@@ -173,7 +171,6 @@ export class ChatPanel extends SignalWatcher(
|
||||
.session=${this.session}
|
||||
.workspaceId=${this.doc.workspace.id}
|
||||
.docId=${this.doc.id}
|
||||
.status=${this.status}
|
||||
.onNewSession=${this.newSession}
|
||||
.onTogglePin=${this.togglePin}
|
||||
.onOpenSession=${this.openSession}
|
||||
@@ -362,7 +359,6 @@ export class ChatPanel extends SignalWatcher(
|
||||
private readonly onContextChange = async (
|
||||
context: Partial<ChatContextValue>
|
||||
) => {
|
||||
this.status = context.status ?? 'idle';
|
||||
if (context.status === 'success') {
|
||||
await this.rebindSession();
|
||||
}
|
||||
@@ -383,7 +379,6 @@ export class ChatPanel extends SignalWatcher(
|
||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||
.affineThemeService=${this.affineThemeService}
|
||||
.notificationService=${this.notificationService}
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
></playground-content>
|
||||
`;
|
||||
|
||||
|
||||
@@ -3,11 +3,8 @@ 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 BlockStdScope,
|
||||
type EditorHost,
|
||||
ShadowlessElement,
|
||||
} from '@blocksuite/affine/std';
|
||||
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';
|
||||
@@ -40,9 +37,6 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor host: EditorHost | null | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor std: BlockStdScope | null | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor item!: ChatMessage;
|
||||
|
||||
@@ -130,7 +124,6 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
||||
private renderStreamObjects(answer: StreamObject[]) {
|
||||
return html`<chat-content-stream-objects
|
||||
.host=${this.host}
|
||||
.std=${this.std}
|
||||
.answer=${answer}
|
||||
.state=${this.state}
|
||||
.width=${this.width}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { createLitPortal } from '@blocksuite/affine/components/portal';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||
import { PlusIcon } from '@blocksuite/icons/lit';
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
import { css, html } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
|
||||
import type { ChatChip, DocDisplayConfig } from '../ai-chat-chips';
|
||||
import type { SearchMenuConfig } from './type';
|
||||
|
||||
export class AIChatAddContext extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
.ai-chat-add-context {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor addChip!: (chip: ChatChip) => Promise<void>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor addImages!: (images: File[]) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayConfig!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor searchMenuConfig!: SearchMenuConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor portalContainer: HTMLElement | null = null;
|
||||
|
||||
@query('.ai-chat-add-context')
|
||||
accessor addButton!: HTMLDivElement;
|
||||
|
||||
private abortController: AbortController | null = null;
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<div
|
||||
class="ai-chat-add-context"
|
||||
data-testid="chat-panel-with-button"
|
||||
@click=${this.toggleAddDocMenu}
|
||||
>
|
||||
${PlusIcon()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private readonly toggleAddDocMenu = () => {
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
this.abortController = new AbortController();
|
||||
this.abortController.signal.addEventListener('abort', () => {
|
||||
this.abortController = null;
|
||||
});
|
||||
|
||||
createLitPortal({
|
||||
template: html`
|
||||
<chat-panel-add-popover
|
||||
.addChip=${this.addChip}
|
||||
.addImages=${this.addImages}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.abortController=${this.abortController}
|
||||
></chat-panel-add-popover>
|
||||
`,
|
||||
portalStyles: {
|
||||
zIndex: 'var(--affine-z-index-popover)',
|
||||
},
|
||||
container: this.portalContainer ?? document.body,
|
||||
computePosition: {
|
||||
referenceElement: this.addButton,
|
||||
placement: 'top-start',
|
||||
middleware: [offset({ crossAxis: -30, mainAxis: 8 }), flip()],
|
||||
autoUpdate: { animationFrame: true },
|
||||
},
|
||||
abortController: this.abortController,
|
||||
closeOnClickAway: true,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ai-chat-add-context';
|
||||
export * from './type';
|
||||
@@ -1,24 +0,0 @@
|
||||
import type {
|
||||
SearchCollectionMenuAction,
|
||||
SearchDocMenuAction,
|
||||
SearchTagMenuAction,
|
||||
} from '@affine/core/modules/search-menu/services';
|
||||
import type { LinkedMenuGroup } from '@blocksuite/affine/widgets/linked-doc';
|
||||
|
||||
export interface SearchMenuConfig {
|
||||
getDocMenuGroup: (
|
||||
query: string,
|
||||
action: SearchDocMenuAction,
|
||||
abortSignal: AbortSignal
|
||||
) => LinkedMenuGroup;
|
||||
getTagMenuGroup: (
|
||||
query: string,
|
||||
action: SearchTagMenuAction,
|
||||
abortSignal: AbortSignal
|
||||
) => LinkedMenuGroup;
|
||||
getCollectionMenuGroup: (
|
||||
query: string,
|
||||
action: SearchCollectionMenuAction,
|
||||
abortSignal: AbortSignal
|
||||
) => LinkedMenuGroup;
|
||||
}
|
||||
@@ -21,8 +21,8 @@ import { css, html, type TemplateResult } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { SearchMenuConfig } from '../ai-chat-add-context';
|
||||
import type { ChatChip, DocDisplayConfig } from './type';
|
||||
import { MAX_IMAGE_COUNT } from '../ai-chat-input';
|
||||
import type { ChatChip, DocDisplayConfig, SearchMenuConfig } from './type';
|
||||
|
||||
enum AddPopoverMode {
|
||||
Default = 'default',
|
||||
@@ -165,31 +165,35 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
const files = await openFilesWith();
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
this.abortController.abort();
|
||||
const images = files.filter(file => file.type.startsWith('image/'));
|
||||
if (images.length > 0) {
|
||||
this.addImages(images);
|
||||
}
|
||||
|
||||
const others = files.filter(file => !file.type.startsWith('image/'));
|
||||
const addChipPromises = others.map(async file => {
|
||||
for (const file of others) {
|
||||
if (file.size > 50 * 1024 * 1024) {
|
||||
toast(`${file.name} is too large, please upload a file less than 50MB`);
|
||||
return;
|
||||
} else {
|
||||
await this.addChip({
|
||||
file,
|
||||
state: 'processing',
|
||||
});
|
||||
}
|
||||
await this.addChip({
|
||||
file,
|
||||
state: 'processing',
|
||||
});
|
||||
});
|
||||
await Promise.all(addChipPromises);
|
||||
}
|
||||
this._track('file');
|
||||
this.abortController.abort();
|
||||
};
|
||||
|
||||
private readonly _addImageChip = async () => {
|
||||
if (this.isImageUploadDisabled) return;
|
||||
|
||||
const images = await openFilesWith('Images');
|
||||
if (!images) return;
|
||||
this.abortController.abort();
|
||||
if (this.uploadImageCount + images.length > MAX_IMAGE_COUNT) {
|
||||
toast(`You can only upload up to ${MAX_IMAGE_COUNT} images`);
|
||||
return;
|
||||
}
|
||||
this.addImages(images);
|
||||
};
|
||||
|
||||
@@ -285,6 +289,9 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
@property({ attribute: 'data-testid', reflect: true })
|
||||
accessor testId: string = 'ai-search-input';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor isImageUploadDisabled!: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor uploadImageCount!: number;
|
||||
|
||||
@@ -491,31 +498,31 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
}
|
||||
|
||||
private readonly _addDocChip = async (meta: DocMeta) => {
|
||||
this.abortController.abort();
|
||||
await this.addChip({
|
||||
docId: meta.id,
|
||||
state: 'processing',
|
||||
});
|
||||
const mode = this.docDisplayConfig.getDocPrimaryMode(meta.id);
|
||||
this._track('doc', mode);
|
||||
this.abortController.abort();
|
||||
};
|
||||
|
||||
private readonly _addTagChip = async (tag: TagMeta) => {
|
||||
this.abortController.abort();
|
||||
await this.addChip({
|
||||
tagId: tag.id,
|
||||
state: 'processing',
|
||||
});
|
||||
this._track('tags');
|
||||
this.abortController.abort();
|
||||
};
|
||||
|
||||
private readonly _addCollectionChip = async (collection: CollectionMeta) => {
|
||||
this.abortController.abort();
|
||||
await this.addChip({
|
||||
collectionId: collection.id,
|
||||
state: 'processing',
|
||||
});
|
||||
this._track('collections');
|
||||
this.abortController.abort();
|
||||
};
|
||||
|
||||
private readonly _handleKeyDown = (event: KeyboardEvent) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createLitPortal } from '@blocksuite/affine/components/portal';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
|
||||
import { MoreVerticalIcon, PlusIcon } from '@blocksuite/icons/lit';
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
import { computed, type Signal, signal } from '@preact/signals-core';
|
||||
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||
@@ -11,7 +11,16 @@ import { property, query, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import type { ChatChip, DocChip, DocDisplayConfig, FileChip } from './type';
|
||||
import { AIProvider } from '../../provider';
|
||||
import type {
|
||||
ChatChip,
|
||||
CollectionChip,
|
||||
DocChip,
|
||||
DocDisplayConfig,
|
||||
FileChip,
|
||||
SearchMenuConfig,
|
||||
TagChip,
|
||||
} from './type';
|
||||
import {
|
||||
estimateTokenCount,
|
||||
getChipKey,
|
||||
@@ -30,46 +39,44 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
.ai-chat-panel-chips {
|
||||
.chips-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 4px 12px;
|
||||
|
||||
.collapse-button,
|
||||
.more-candidate-button {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
}
|
||||
|
||||
.collapse-button:hover,
|
||||
.more-candidate-button:hover {
|
||||
background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||
}
|
||||
|
||||
.more-candidate-button {
|
||||
border-width: 1px;
|
||||
border-style: dashed;
|
||||
border-color: ${unsafeCSSVarV2('icon/tertiary')};
|
||||
background: ${unsafeCSSVarV2('layer/background/secondary')};
|
||||
color: ${unsafeCSSVarV2('icon/secondary')};
|
||||
}
|
||||
|
||||
.more-candidate-button svg {
|
||||
color: ${unsafeCSSVarV2('icon/secondary')};
|
||||
}
|
||||
}
|
||||
.add-button,
|
||||
.collapse-button,
|
||||
.more-candidate-button {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
}
|
||||
.add-button:hover,
|
||||
.collapse-button:hover,
|
||||
.more-candidate-button:hover {
|
||||
background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||
}
|
||||
.more-candidate-button {
|
||||
border-width: 1px;
|
||||
border-style: dashed;
|
||||
border-color: ${unsafeCSSVarV2('icon/tertiary')};
|
||||
background: ${unsafeCSSVarV2('layer/background/secondary')};
|
||||
color: ${unsafeCSSVarV2('icon/secondary')};
|
||||
}
|
||||
.more-candidate-button svg {
|
||||
color: ${unsafeCSSVarV2('icon/secondary')};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -79,35 +86,38 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
accessor chips!: ChatChip[];
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor isCollapsed!: boolean;
|
||||
accessor createContextId!: () => Promise<string | undefined>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor addChip!: (chip: ChatChip) => Promise<void>;
|
||||
accessor updateChips!: (chips: ChatChip[]) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor updateChip!: (
|
||||
chip: ChatChip,
|
||||
options: Partial<DocChip | FileChip>
|
||||
) => void;
|
||||
accessor addImages!: (images: File[]) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor removeChip!: (chip: ChatChip) => Promise<void>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor toggleCollapse!: () => void;
|
||||
accessor pollContextDocsAndFiles!: () => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayConfig!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor searchMenuConfig!: SearchMenuConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor portalContainer: HTMLElement | null = null;
|
||||
|
||||
@property({ attribute: 'data-testid', reflect: true })
|
||||
accessor testId = 'chat-panel-chips';
|
||||
|
||||
@query('.add-button')
|
||||
accessor addButton!: HTMLDivElement;
|
||||
|
||||
@query('.more-candidate-button')
|
||||
accessor moreCandidateButton!: HTMLDivElement;
|
||||
|
||||
@state()
|
||||
accessor isCollapsed = false;
|
||||
|
||||
@state()
|
||||
accessor referenceDocs: Signal<
|
||||
Array<{
|
||||
@@ -134,7 +144,14 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
const isCollapsed = this.isCollapsed && allChips.length > 1;
|
||||
const chips = isCollapsed ? allChips.slice(0, 1) : allChips;
|
||||
|
||||
return html`<div class="ai-chat-panel-chips">
|
||||
return html`<div class="chips-wrapper">
|
||||
<div
|
||||
class="add-button"
|
||||
data-testid="chat-panel-with-button"
|
||||
@click=${this._toggleAddDocMenu}
|
||||
>
|
||||
${PlusIcon()}
|
||||
</div>
|
||||
${repeat(
|
||||
chips,
|
||||
chip => getChipKey(chip),
|
||||
@@ -142,9 +159,9 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
if (isDocChip(chip)) {
|
||||
return html`<chat-panel-doc-chip
|
||||
.chip=${chip}
|
||||
.addChip=${this.addChip}
|
||||
.updateChip=${this.updateChip}
|
||||
.removeChip=${this.removeChip}
|
||||
.addChip=${this._addChip}
|
||||
.updateChip=${this._updateChip}
|
||||
.removeChip=${this._removeChip}
|
||||
.checkTokenLimit=${this._checkTokenLimit}
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
></chat-panel-doc-chip>`;
|
||||
@@ -152,7 +169,7 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
if (isFileChip(chip)) {
|
||||
return html`<chat-panel-file-chip
|
||||
.chip=${chip}
|
||||
.removeChip=${this.removeChip}
|
||||
.removeChip=${this._removeChip}
|
||||
></chat-panel-file-chip>`;
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
@@ -163,7 +180,7 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
return html`<chat-panel-tag-chip
|
||||
.chip=${chip}
|
||||
.tag=${tag}
|
||||
.removeChip=${this.removeChip}
|
||||
.removeChip=${this._removeChip}
|
||||
></chat-panel-tag-chip>`;
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
@@ -176,7 +193,7 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
return html`<chat-panel-collection-chip
|
||||
.chip=${chip}
|
||||
.collection=${collection}
|
||||
.removeChip=${this.removeChip}
|
||||
.removeChip=${this._removeChip}
|
||||
></chat-panel-collection-chip>`;
|
||||
}
|
||||
return null;
|
||||
@@ -191,7 +208,7 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
</div>`
|
||||
: nothing}
|
||||
${isCollapsed
|
||||
? html`<div class="collapse-button" @click=${this.toggleCollapse}>
|
||||
? html`<div class="collapse-button" @click=${this._toggleCollapse}>
|
||||
+${allChips.length - 1}
|
||||
</div>`
|
||||
: nothing}
|
||||
@@ -210,6 +227,14 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
}
|
||||
|
||||
protected override updated(_changedProperties: PropertyValues): void {
|
||||
if (
|
||||
_changedProperties.has('chatContextValue') &&
|
||||
_changedProperties.get('chatContextValue')?.status === 'loading' &&
|
||||
this.isCollapsed === false
|
||||
) {
|
||||
this.isCollapsed = true;
|
||||
}
|
||||
|
||||
if (_changedProperties.has('chips')) {
|
||||
this._updateReferenceDocs();
|
||||
}
|
||||
@@ -220,6 +245,46 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
this._cleanup?.();
|
||||
}
|
||||
|
||||
private readonly _toggleCollapse = () => {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
};
|
||||
|
||||
private readonly _toggleAddDocMenu = () => {
|
||||
if (this._abortController) {
|
||||
this._abortController.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
this._abortController = new AbortController();
|
||||
this._abortController.signal.addEventListener('abort', () => {
|
||||
this._abortController = null;
|
||||
});
|
||||
|
||||
createLitPortal({
|
||||
template: html`
|
||||
<chat-panel-add-popover
|
||||
.addChip=${this._addChip}
|
||||
.addImages=${this.addImages}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.abortController=${this._abortController}
|
||||
></chat-panel-add-popover>
|
||||
`,
|
||||
portalStyles: {
|
||||
zIndex: 'var(--affine-z-index-popover)',
|
||||
},
|
||||
container: this.portalContainer ?? document.body,
|
||||
computePosition: {
|
||||
referenceElement: this.addButton,
|
||||
placement: 'top-start',
|
||||
middleware: [offset({ crossAxis: -30, mainAxis: 8 }), flip()],
|
||||
autoUpdate: { animationFrame: true },
|
||||
},
|
||||
abortController: this._abortController,
|
||||
closeOnClickAway: true,
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _toggleMoreCandidatesMenu = () => {
|
||||
if (this._abortController) {
|
||||
this._abortController.abort();
|
||||
@@ -238,7 +303,7 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
createLitPortal({
|
||||
template: html`
|
||||
<chat-panel-candidates-popover
|
||||
.addChip=${this.addChip}
|
||||
.addChip=${this._addChip}
|
||||
.referenceDocs=${referenceDocs}
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.abortController=${this._abortController}
|
||||
@@ -259,6 +324,190 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _addChip = async (chip: ChatChip) => {
|
||||
this.isCollapsed = false;
|
||||
// remove the chip if it already exists
|
||||
const chips = this._omitChip(this.chips, chip);
|
||||
this.updateChips([...chips, chip]);
|
||||
if (chips.length < this.chips.length) {
|
||||
await this._removeFromContext(chip);
|
||||
}
|
||||
await this._addToContext(chip);
|
||||
this.pollContextDocsAndFiles();
|
||||
};
|
||||
|
||||
private readonly _updateChip = (
|
||||
chip: ChatChip,
|
||||
options: Partial<DocChip | FileChip>
|
||||
) => {
|
||||
const index = this._findChipIndex(this.chips, chip);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
const nextChip: ChatChip = {
|
||||
...chip,
|
||||
...options,
|
||||
};
|
||||
this.updateChips([
|
||||
...this.chips.slice(0, index),
|
||||
nextChip,
|
||||
...this.chips.slice(index + 1),
|
||||
]);
|
||||
};
|
||||
|
||||
private readonly _removeChip = async (chip: ChatChip) => {
|
||||
const chips = this._omitChip(this.chips, chip);
|
||||
this.updateChips(chips);
|
||||
if (chips.length < this.chips.length) {
|
||||
await this._removeFromContext(chip);
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _addToContext = async (chip: ChatChip) => {
|
||||
if (isDocChip(chip)) {
|
||||
return await this._addDocToContext(chip);
|
||||
}
|
||||
if (isFileChip(chip)) {
|
||||
return await this._addFileToContext(chip);
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
return await this._addTagToContext(chip);
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
return await this._addCollectionToContext(chip);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
private readonly _addDocToContext = async (chip: DocChip) => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
await AIProvider.context.addContextDoc({
|
||||
contextId,
|
||||
docId: chip.docId,
|
||||
});
|
||||
} catch (e) {
|
||||
this._updateChip(chip, {
|
||||
state: 'failed',
|
||||
tooltip: e instanceof Error ? e.message : 'Add context doc error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _addFileToContext = async (chip: FileChip) => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
const contextFile = await AIProvider.context.addContextFile(chip.file, {
|
||||
contextId,
|
||||
});
|
||||
this._updateChip(chip, {
|
||||
state: contextFile.status,
|
||||
blobId: contextFile.blobId,
|
||||
fileId: contextFile.id,
|
||||
});
|
||||
} catch (e) {
|
||||
this._updateChip(chip, {
|
||||
state: 'failed',
|
||||
tooltip: e instanceof Error ? e.message : 'Add context file error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _addTagToContext = async (chip: TagChip) => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
// TODO: server side docIds calculation
|
||||
const docIds = this.docDisplayConfig.getTagPageIds(chip.tagId);
|
||||
await AIProvider.context.addContextTag({
|
||||
contextId,
|
||||
tagId: chip.tagId,
|
||||
docIds,
|
||||
});
|
||||
this._updateChip(chip, {
|
||||
state: 'finished',
|
||||
});
|
||||
} catch (e) {
|
||||
this._updateChip(chip, {
|
||||
state: 'failed',
|
||||
tooltip: e instanceof Error ? e.message : 'Add context tag error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _addCollectionToContext = async (chip: CollectionChip) => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
// TODO: server side docIds calculation
|
||||
const docIds = this.docDisplayConfig.getCollectionPageIds(
|
||||
chip.collectionId
|
||||
);
|
||||
await AIProvider.context.addContextCollection({
|
||||
contextId,
|
||||
collectionId: chip.collectionId,
|
||||
docIds,
|
||||
});
|
||||
this._updateChip(chip, {
|
||||
state: 'finished',
|
||||
});
|
||||
} catch (e) {
|
||||
this._updateChip(chip, {
|
||||
state: 'failed',
|
||||
tooltip:
|
||||
e instanceof Error ? e.message : 'Add context collection error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _removeFromContext = async (
|
||||
chip: ChatChip
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
return true;
|
||||
}
|
||||
if (isDocChip(chip)) {
|
||||
return await AIProvider.context.removeContextDoc({
|
||||
contextId,
|
||||
docId: chip.docId,
|
||||
});
|
||||
}
|
||||
if (isFileChip(chip) && chip.fileId) {
|
||||
return await AIProvider.context.removeContextFile({
|
||||
contextId,
|
||||
fileId: chip.fileId,
|
||||
});
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
return await AIProvider.context.removeContextTag({
|
||||
contextId,
|
||||
tagId: chip.tagId,
|
||||
});
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
return await AIProvider.context.removeContextCollection({
|
||||
contextId,
|
||||
collectionId: chip.collectionId,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _checkTokenLimit = (
|
||||
newChip: DocChip,
|
||||
newTokenCount: number
|
||||
@@ -295,4 +544,44 @@ export class ChatPanelChips extends SignalWatcher(
|
||||
this.referenceDocs = signal;
|
||||
this._cleanup = cleanup;
|
||||
};
|
||||
|
||||
private readonly _omitChip = (chips: ChatChip[], chip: ChatChip) => {
|
||||
return chips.filter(item => {
|
||||
if (isDocChip(chip)) {
|
||||
return !isDocChip(item) || item.docId !== chip.docId;
|
||||
}
|
||||
if (isFileChip(chip)) {
|
||||
return !isFileChip(item) || item.file !== chip.file;
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
return !isTagChip(item) || item.tagId !== chip.tagId;
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
return (
|
||||
!isCollectionChip(item) || item.collectionId !== chip.collectionId
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _findChipIndex = (chips: ChatChip[], chip: ChatChip) => {
|
||||
return chips.findIndex(item => {
|
||||
if (isDocChip(chip)) {
|
||||
return isDocChip(item) && item.docId === chip.docId;
|
||||
}
|
||||
if (isFileChip(chip)) {
|
||||
return isFileChip(item) && item.file === chip.file;
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
return isTagChip(item) && item.tagId === chip.tagId;
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
return (
|
||||
isCollectionChip(item) && item.collectionId === chip.collectionId
|
||||
);
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import type { TagMeta } from '@affine/core/components/page-list';
|
||||
import type {
|
||||
SearchCollectionMenuAction,
|
||||
SearchDocMenuAction,
|
||||
SearchTagMenuAction,
|
||||
} from '@affine/core/modules/search-menu/services';
|
||||
import type { DocMeta, Store } from '@blocksuite/affine/store';
|
||||
import type { LinkedMenuGroup } from '@blocksuite/affine/widgets/linked-doc';
|
||||
import type { Signal } from '@preact/signals-core';
|
||||
|
||||
export type ChipState = 'candidate' | 'processing' | 'finished' | 'failed';
|
||||
@@ -69,3 +75,21 @@ export interface DocDisplayConfig {
|
||||
};
|
||||
getCollectionPageIds: (collectionId: string) => string[];
|
||||
}
|
||||
|
||||
export interface SearchMenuConfig {
|
||||
getDocMenuGroup: (
|
||||
query: string,
|
||||
action: SearchDocMenuAction,
|
||||
abortSignal: AbortSignal
|
||||
) => LinkedMenuGroup;
|
||||
getTagMenuGroup: (
|
||||
query: string,
|
||||
action: SearchTagMenuAction,
|
||||
abortSignal: AbortSignal
|
||||
) => LinkedMenuGroup;
|
||||
getCollectionMenuGroup: (
|
||||
query: string,
|
||||
action: SearchCollectionMenuAction,
|
||||
abortSignal: AbortSignal
|
||||
) => LinkedMenuGroup;
|
||||
}
|
||||
|
||||
@@ -78,42 +78,6 @@ export function getChipKey(chip: ChatChip) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function omitChip(chips: ChatChip[], chip: ChatChip) {
|
||||
return chips.filter(item => {
|
||||
if (isDocChip(chip)) {
|
||||
return !isDocChip(item) || item.docId !== chip.docId;
|
||||
}
|
||||
if (isFileChip(chip)) {
|
||||
return !isFileChip(item) || item.file !== chip.file;
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
return !isTagChip(item) || item.tagId !== chip.tagId;
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
return !isCollectionChip(item) || item.collectionId !== chip.collectionId;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function findChipIndex(chips: ChatChip[], chip: ChatChip) {
|
||||
return chips.findIndex(item => {
|
||||
if (isDocChip(chip)) {
|
||||
return isDocChip(item) && item.docId === chip.docId;
|
||||
}
|
||||
if (isFileChip(chip)) {
|
||||
return isFileChip(item) && item.file === chip.file;
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
return isTagChip(item) && item.tagId === chip.tagId;
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
return isCollectionChip(item) && item.collectionId === chip.collectionId;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
export function estimateTokenCount(text: string): number {
|
||||
const chinese = text.match(/[\u4e00-\u9fa5]/g)?.length || 0;
|
||||
const english = text.replace(/[\u4e00-\u9fa5]/g, '');
|
||||
|
||||
@@ -12,28 +12,20 @@ import type {
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import type { EditorHost } from '@blocksuite/affine/std';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||
import { css, html, type PropertyValues } from 'lit';
|
||||
import { css, html } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
|
||||
import { AIProvider } from '../../provider';
|
||||
import type { SearchMenuConfig } from '../ai-chat-add-context';
|
||||
import type {
|
||||
ChatChip,
|
||||
CollectionChip,
|
||||
DocChip,
|
||||
DocDisplayConfig,
|
||||
FileChip,
|
||||
SearchMenuConfig,
|
||||
TagChip,
|
||||
} from '../ai-chat-chips';
|
||||
import {
|
||||
findChipIndex,
|
||||
isCollectionChip,
|
||||
isDocChip,
|
||||
isFileChip,
|
||||
isTagChip,
|
||||
omitChip,
|
||||
} from '../ai-chat-chips';
|
||||
import { isCollectionChip, isDocChip, isTagChip } from '../ai-chat-chips';
|
||||
import type {
|
||||
AIChatInputContext,
|
||||
AINetworkSearchConfig,
|
||||
@@ -113,15 +105,9 @@ export class AIChatComposer extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor notificationService!: NotificationService;
|
||||
|
||||
@state()
|
||||
accessor chips: ChatChip[] = [];
|
||||
|
||||
@state()
|
||||
accessor isChipsCollapsed = false;
|
||||
|
||||
@state()
|
||||
accessor embeddingCompleted = false;
|
||||
|
||||
@@ -135,12 +121,11 @@ export class AIChatComposer extends SignalWatcher(
|
||||
return html`
|
||||
<chat-panel-chips
|
||||
.chips=${this.chips}
|
||||
.isCollapsed=${this.isChipsCollapsed}
|
||||
.addChip=${this.addChip}
|
||||
.updateChip=${this.updateChip}
|
||||
.removeChip=${this.removeChip}
|
||||
.toggleCollapse=${this.toggleChipsCollapse}
|
||||
.createContextId=${this._createContextId}
|
||||
.updateChips=${this.updateChips}
|
||||
.pollContextDocsAndFiles=${this._pollContextDocsAndFiles}
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.portalContainer=${this.portalContainer}
|
||||
.addImages=${this.addImages}
|
||||
></chat-panel-chips>
|
||||
@@ -151,18 +136,15 @@ export class AIChatComposer extends SignalWatcher(
|
||||
.docId=${this.docId}
|
||||
.session=${this.session}
|
||||
.chips=${this.chips}
|
||||
.addChip=${this.addChip}
|
||||
.addImages=${this.addImages}
|
||||
.createSession=${this.createSession}
|
||||
.chatContextValue=${this.chatContextValue}
|
||||
.updateContext=${this.updateContext}
|
||||
.networkSearchConfig=${this.networkSearchConfig}
|
||||
.reasoningConfig=${this.reasoningConfig}
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.portalContainer=${this.portalContainer}
|
||||
.onChatSuccess=${this.onChatSuccess}
|
||||
.trackOptions=${this.trackOptions}
|
||||
.addImages=${this.addImages}
|
||||
></ai-chat-input>
|
||||
<div class="chat-panel-footer">
|
||||
<ai-chat-composer-tip
|
||||
@@ -183,7 +165,7 @@ export class AIChatComposer extends SignalWatcher(
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.initComposer().catch(console.error);
|
||||
this._initComposer().catch(console.error);
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
@@ -192,17 +174,6 @@ export class AIChatComposer extends SignalWatcher(
|
||||
this._abortPollEmbeddingStatus();
|
||||
}
|
||||
|
||||
protected override willUpdate(changedProperties: PropertyValues): void {
|
||||
if (
|
||||
changedProperties.has('chatContextValue') &&
|
||||
changedProperties.get('chatContextValue')?.status !== 'loading' &&
|
||||
this.chatContextValue.status === 'loading' &&
|
||||
this.isChipsCollapsed === false
|
||||
) {
|
||||
this.isChipsCollapsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _getContextId = async () => {
|
||||
if (this._contextId) {
|
||||
return this._contextId;
|
||||
@@ -219,7 +190,7 @@ export class AIChatComposer extends SignalWatcher(
|
||||
return this._contextId;
|
||||
};
|
||||
|
||||
private readonly createContextId = async () => {
|
||||
private readonly _createContextId = async () => {
|
||||
if (this._contextId) {
|
||||
return this._contextId;
|
||||
}
|
||||
@@ -234,7 +205,7 @@ export class AIChatComposer extends SignalWatcher(
|
||||
return this._contextId;
|
||||
};
|
||||
|
||||
private readonly initChips = async () => {
|
||||
private readonly _initChips = async () => {
|
||||
// context not initialized
|
||||
const sessionId = this.session?.sessionId;
|
||||
const contextId = await this._getContextId();
|
||||
@@ -304,206 +275,14 @@ export class AIChatComposer extends SignalWatcher(
|
||||
this.chips = chips;
|
||||
};
|
||||
|
||||
private readonly updateChip = (
|
||||
chip: ChatChip,
|
||||
options: Partial<DocChip | FileChip>
|
||||
) => {
|
||||
const index = findChipIndex(this.chips, chip);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
const nextChip: ChatChip = {
|
||||
...chip,
|
||||
...options,
|
||||
};
|
||||
this.updateChips([
|
||||
...this.chips.slice(0, index),
|
||||
nextChip,
|
||||
...this.chips.slice(index + 1),
|
||||
]);
|
||||
};
|
||||
|
||||
private readonly addChip = async (chip: ChatChip) => {
|
||||
this.isChipsCollapsed = false;
|
||||
// if already exists
|
||||
const index = findChipIndex(this.chips, chip);
|
||||
if (index !== -1) {
|
||||
this.notificationService.toast('chip already exists');
|
||||
return;
|
||||
}
|
||||
this.updateChips([...this.chips, chip]);
|
||||
await this.addToContext(chip);
|
||||
await this.pollContextDocsAndFiles();
|
||||
};
|
||||
|
||||
private readonly removeChip = async (chip: ChatChip) => {
|
||||
const chips = omitChip(this.chips, chip);
|
||||
this.updateChips(chips);
|
||||
await this.removeFromContext(chip);
|
||||
};
|
||||
|
||||
private readonly addToContext = async (chip: ChatChip) => {
|
||||
if (isDocChip(chip)) {
|
||||
return await this.addDocToContext(chip);
|
||||
}
|
||||
if (isFileChip(chip)) {
|
||||
return await this.addFileToContext(chip);
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
return await this.addTagToContext(chip);
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
return await this.addCollectionToContext(chip);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
private readonly addDocToContext = async (chip: DocChip) => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
await AIProvider.context.addContextDoc({
|
||||
contextId,
|
||||
docId: chip.docId,
|
||||
});
|
||||
} catch (e) {
|
||||
this.updateChip(chip, {
|
||||
state: 'failed',
|
||||
tooltip: e instanceof Error ? e.message : 'Add context doc error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly addFileToContext = async (chip: FileChip) => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
const contextFile = await AIProvider.context.addContextFile(chip.file, {
|
||||
contextId,
|
||||
});
|
||||
this.updateChip(chip, {
|
||||
state: contextFile.status,
|
||||
blobId: contextFile.blobId,
|
||||
fileId: contextFile.id,
|
||||
});
|
||||
} catch (e) {
|
||||
this.updateChip(chip, {
|
||||
state: 'failed',
|
||||
tooltip: e instanceof Error ? e.message : 'Add context file error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly addTagToContext = async (chip: TagChip) => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
// TODO: server side docIds calculation
|
||||
const docIds = this.docDisplayConfig.getTagPageIds(chip.tagId);
|
||||
await AIProvider.context.addContextTag({
|
||||
contextId,
|
||||
tagId: chip.tagId,
|
||||
docIds,
|
||||
});
|
||||
this.updateChip(chip, {
|
||||
state: 'finished',
|
||||
});
|
||||
} catch (e) {
|
||||
this.updateChip(chip, {
|
||||
state: 'failed',
|
||||
tooltip: e instanceof Error ? e.message : 'Add context tag error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly addCollectionToContext = async (chip: CollectionChip) => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
throw new Error('Context not found');
|
||||
}
|
||||
// TODO: server side docIds calculation
|
||||
const docIds = this.docDisplayConfig.getCollectionPageIds(
|
||||
chip.collectionId
|
||||
);
|
||||
await AIProvider.context.addContextCollection({
|
||||
contextId,
|
||||
collectionId: chip.collectionId,
|
||||
docIds,
|
||||
});
|
||||
this.updateChip(chip, {
|
||||
state: 'finished',
|
||||
});
|
||||
} catch (e) {
|
||||
this.updateChip(chip, {
|
||||
state: 'failed',
|
||||
tooltip:
|
||||
e instanceof Error ? e.message : 'Add context collection error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly removeFromContext = async (
|
||||
chip: ChatChip
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const contextId = await this.createContextId();
|
||||
if (!contextId || !AIProvider.context) {
|
||||
return true;
|
||||
}
|
||||
if (isDocChip(chip)) {
|
||||
return await AIProvider.context.removeContextDoc({
|
||||
contextId,
|
||||
docId: chip.docId,
|
||||
});
|
||||
}
|
||||
if (isFileChip(chip) && chip.fileId) {
|
||||
return await AIProvider.context.removeContextFile({
|
||||
contextId,
|
||||
fileId: chip.fileId,
|
||||
});
|
||||
}
|
||||
if (isTagChip(chip)) {
|
||||
return await AIProvider.context.removeContextTag({
|
||||
contextId,
|
||||
tagId: chip.tagId,
|
||||
});
|
||||
}
|
||||
if (isCollectionChip(chip)) {
|
||||
return await AIProvider.context.removeContextCollection({
|
||||
contextId,
|
||||
collectionId: chip.collectionId,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private readonly toggleChipsCollapse = () => {
|
||||
this.isChipsCollapsed = !this.isChipsCollapsed;
|
||||
};
|
||||
|
||||
private readonly addImages = (images: File[]) => {
|
||||
const oldImages = this.chatContextValue.images;
|
||||
if (oldImages.length + images.length > MAX_IMAGE_COUNT) {
|
||||
this.notificationService.toast(
|
||||
`You can only upload up to ${MAX_IMAGE_COUNT} images`
|
||||
);
|
||||
}
|
||||
this.updateContext({
|
||||
images: [...oldImages, ...images].slice(0, MAX_IMAGE_COUNT),
|
||||
});
|
||||
};
|
||||
|
||||
private readonly pollContextDocsAndFiles = async () => {
|
||||
private readonly _pollContextDocsAndFiles = async () => {
|
||||
const sessionId = this.session?.sessionId;
|
||||
const contextId = await this._getContextId();
|
||||
if (!sessionId || !contextId || !AIProvider.context) {
|
||||
@@ -523,7 +302,7 @@ export class AIChatComposer extends SignalWatcher(
|
||||
);
|
||||
};
|
||||
|
||||
private readonly pollEmbeddingStatus = async () => {
|
||||
private readonly _pollEmbeddingStatus = async () => {
|
||||
if (this._pollEmbeddingStatusAbortController) {
|
||||
this._pollEmbeddingStatusAbortController.abort();
|
||||
}
|
||||
@@ -538,11 +317,12 @@ export class AIChatComposer extends SignalWatcher(
|
||||
this.embeddingCompleted = false;
|
||||
return;
|
||||
}
|
||||
const prevCompleted = this.embeddingCompleted;
|
||||
const completed = status.embedded === status.total;
|
||||
this.embeddingCompleted = completed;
|
||||
if (prevCompleted !== completed) {
|
||||
this.requestUpdate();
|
||||
if (completed) {
|
||||
this.embeddingCompleted = true;
|
||||
} else {
|
||||
this.embeddingCompleted = false;
|
||||
}
|
||||
},
|
||||
signal
|
||||
@@ -619,18 +399,18 @@ export class AIChatComposer extends SignalWatcher(
|
||||
this._pollEmbeddingStatusAbortController = null;
|
||||
};
|
||||
|
||||
private readonly initComposer = async () => {
|
||||
private readonly _initComposer = async () => {
|
||||
const userId = (await AIProvider.userInfo)?.id;
|
||||
if (!userId || !this.session) return;
|
||||
|
||||
await this.initChips();
|
||||
await this._initChips();
|
||||
const needPoll = this.chips.some(
|
||||
chip =>
|
||||
chip.state === 'processing' || isTagChip(chip) || isCollectionChip(chip)
|
||||
);
|
||||
if (needPoll) {
|
||||
await this.pollContextDocsAndFiles();
|
||||
await this._pollContextDocsAndFiles();
|
||||
}
|
||||
await this.pollEmbeddingStatus();
|
||||
await this._pollEmbeddingStatus();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import type {
|
||||
CopilotChatHistoryFragment,
|
||||
} from '@affine/graphql';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
||||
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';
|
||||
@@ -25,8 +26,7 @@ import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { HISTORY_IMAGE_ACTIONS } from '../../chat-panel/const';
|
||||
import { type AIChatParams, AIProvider } from '../../provider/ai-provider';
|
||||
import { extractSelectedContent } from '../../utils/extract';
|
||||
import type { SearchMenuConfig } from '../ai-chat-add-context';
|
||||
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
|
||||
import type {
|
||||
AINetworkSearchConfig,
|
||||
AIReasoningConfig,
|
||||
@@ -63,7 +63,7 @@ export class AIChatContent extends SignalWatcher(
|
||||
.ai-chat-title {
|
||||
background: var(--affine-background-primary-color);
|
||||
position: relative;
|
||||
padding: 8px var(--h-padding);
|
||||
padding: 8px 0px;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
@@ -80,8 +80,7 @@ export class AIChatContent extends SignalWatcher(
|
||||
|
||||
ai-chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 var(--h-padding);
|
||||
overflow-y: hidden;
|
||||
transition:
|
||||
flex-grow 0.32s cubic-bezier(0.07, 0.83, 0.46, 1),
|
||||
padding-top 0.32s ease,
|
||||
@@ -100,25 +99,18 @@ export class AIChatContent extends SignalWatcher(
|
||||
container-name: chat-panel-split-view;
|
||||
}
|
||||
.chat-panel-main {
|
||||
--h-padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 8px calc(24px - var(--h-padding)) 0 calc(24px - var(--h-padding));
|
||||
padding: 8px 24px 0 24px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
ai-chat-composer {
|
||||
padding: 0 var(--h-padding);
|
||||
}
|
||||
|
||||
@container chat-panel-split-view (width < 540px) {
|
||||
.chat-panel-main {
|
||||
padding: 8px calc(12px - var(--h-padding)) 0
|
||||
calc(12px - var(--h-padding));
|
||||
padding: 8px 12px 0 12px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -444,7 +436,6 @@ export class AIChatContent extends SignalWatcher(
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
.notificationService=${this.notificationService}
|
||||
.trackOptions=${{
|
||||
where: 'chat-panel',
|
||||
control: 'chat-send',
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { toast } from '@affine/component';
|
||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
import { openFilesWith } from '@blocksuite/affine/shared/utils';
|
||||
import type { EditorHost } from '@blocksuite/affine/std';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||
import { ArrowUpBigIcon, CloseIcon } from '@blocksuite/icons/lit';
|
||||
import { ArrowUpBigIcon, CloseIcon, ImageIcon } from '@blocksuite/icons/lit';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
@@ -14,7 +16,6 @@ import { type AIError, AIProvider, type AISendParams } from '../../provider';
|
||||
import { reportResponse } from '../../utils/action-reporter';
|
||||
import { readBlobAsURL } from '../../utils/image';
|
||||
import { mergeStreamObjects } from '../../utils/stream-objects';
|
||||
import type { SearchMenuConfig } from '../ai-chat-add-context';
|
||||
import type { ChatChip, DocDisplayConfig } from '../ai-chat-chips/type';
|
||||
import { isDocChip } from '../ai-chat-chips/utils';
|
||||
import {
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
isChatMessage,
|
||||
StreamObjectSchema,
|
||||
} from '../ai-chat-messages';
|
||||
import { MAX_IMAGE_COUNT } from './const';
|
||||
import type {
|
||||
AIChatInputContext,
|
||||
AINetworkSearchConfig,
|
||||
@@ -333,12 +335,6 @@ export class AIChatInput extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor updateContext!: (context: Partial<AIChatInputContext>) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor addImages!: (images: File[]) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor addChip!: (chip: ChatChip) => Promise<void>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor networkSearchConfig!: AINetworkSearchConfig;
|
||||
|
||||
@@ -348,9 +344,6 @@ export class AIChatInput extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayConfig!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor searchMenuConfig!: SearchMenuConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor isRootSession: boolean = true;
|
||||
|
||||
@@ -364,7 +357,7 @@ export class AIChatInput extends SignalWatcher(
|
||||
accessor testId = 'chat-panel-input-container';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor portalContainer: HTMLElement | null = null;
|
||||
accessor addImages!: (images: File[]) => void;
|
||||
|
||||
private get _isNetworkActive() {
|
||||
return (
|
||||
@@ -377,6 +370,10 @@ export class AIChatInput extends SignalWatcher(
|
||||
return !!this.reasoningConfig.enabled.value;
|
||||
}
|
||||
|
||||
private get _isImageUploadDisabled() {
|
||||
return this.chatContextValue.images.length >= MAX_IMAGE_COUNT;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._disposables.add(
|
||||
@@ -456,14 +453,14 @@ export class AIChatInput extends SignalWatcher(
|
||||
data-testid="chat-panel-input"
|
||||
></textarea>
|
||||
<div class="chat-panel-input-actions">
|
||||
<div class="chat-input-icon">
|
||||
<ai-chat-add-context
|
||||
.addChip=${this.addChip}
|
||||
.addImages=${this.addImages}
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.portalContainer=${this.portalContainer}
|
||||
></ai-chat-add-context>
|
||||
<div
|
||||
class="chat-input-icon"
|
||||
data-testid="chat-panel-input-image-upload"
|
||||
aria-disabled=${this._isImageUploadDisabled}
|
||||
@click=${this._uploadImageFiles}
|
||||
>
|
||||
${ImageIcon()}
|
||||
<affine-tooltip>Upload</affine-tooltip>
|
||||
</div>
|
||||
<div class="chat-input-footer-spacer"></div>
|
||||
<chat-input-preference
|
||||
@@ -558,6 +555,18 @@ export class AIChatInput extends SignalWatcher(
|
||||
this.updateContext({ images: newImages });
|
||||
};
|
||||
|
||||
private readonly _uploadImageFiles = async (_e: MouseEvent) => {
|
||||
if (this._isImageUploadDisabled) return;
|
||||
|
||||
const images = await openFilesWith('Images');
|
||||
if (!images) return;
|
||||
if (this.chatContextValue.images.length + images.length > MAX_IMAGE_COUNT) {
|
||||
toast(`You can only upload up to ${MAX_IMAGE_COUNT} images`);
|
||||
return;
|
||||
}
|
||||
this.addImages(images);
|
||||
};
|
||||
|
||||
private readonly _onTextareaSend = async (e: MouseEvent | KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
type FeatureFlagService,
|
||||
type NotificationService,
|
||||
} from '@blocksuite/affine/shared/services';
|
||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
||||
import type { EditorHost } from '@blocksuite/affine/std';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||
import type { BaseSelection, ExtensionType } from '@blocksuite/affine/store';
|
||||
import { ArrowDownBigIcon as ArrowDownIcon } from '@blocksuite/icons/lit';
|
||||
import type { Signal } from '@preact/signals-core';
|
||||
@@ -42,8 +43,9 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
chat-panel-assistant-message,
|
||||
@@ -435,7 +437,6 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
||||
const last = messages[messages.length - 1];
|
||||
if ('content' in last) {
|
||||
last.content = '';
|
||||
last.streamObjects = [];
|
||||
last.createdAt = new Date().toISOString();
|
||||
}
|
||||
this.updateContext({
|
||||
|
||||
@@ -15,7 +15,6 @@ import { css, html } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
|
||||
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||
import type { ChatStatus } from '../ai-chat-messages';
|
||||
|
||||
export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
@@ -27,9 +26,6 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor docId: string | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor status!: ChatStatus;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onNewSession!: () => void;
|
||||
|
||||
@@ -53,10 +49,6 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
private abortController: AbortController | null = null;
|
||||
|
||||
get isGenerating() {
|
||||
return this.status === 'transmitting' || this.status === 'loading';
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
.ai-chat-toolbar {
|
||||
display: flex;
|
||||
@@ -80,10 +72,6 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
||||
height: 16px;
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -96,11 +84,7 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
||||
${PlusIcon()}
|
||||
<affine-tooltip>New Chat</affine-tooltip>
|
||||
</div>
|
||||
<div
|
||||
class="chat-toolbar-icon"
|
||||
@click=${this.onPinClick}
|
||||
data-disabled=${this.isGenerating}
|
||||
>
|
||||
<div class="chat-toolbar-icon" @click=${this.onTogglePin}>
|
||||
${pinned ? PinedIcon() : PinIcon()}
|
||||
<affine-tooltip>
|
||||
${pinned ? 'Unpin this Chat' : 'Pin this Chat'}
|
||||
@@ -117,16 +101,6 @@ export class AIChatToolbar extends WithDisposable(ShadowlessElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private readonly onPinClick = async () => {
|
||||
if (this.isGenerating) {
|
||||
this.notificationService.toast(
|
||||
'Cannot pin a chat while generating an answer'
|
||||
);
|
||||
return;
|
||||
}
|
||||
await this.onTogglePin();
|
||||
};
|
||||
|
||||
private readonly unpinConfirm = async () => {
|
||||
if (this.session && this.session.pinned) {
|
||||
try {
|
||||
|
||||
@@ -225,7 +225,7 @@ export class AISessionHistory extends WithDisposable(ShadowlessElement) {
|
||||
}}
|
||||
>
|
||||
<div class="ai-session-title">
|
||||
${session.title || 'New chat'}
|
||||
${session.sessionId}
|
||||
<affine-tooltip .offsetX=${60}>
|
||||
Click to open this chat
|
||||
</affine-tooltip>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||
import {
|
||||
type BlockStdScope,
|
||||
type EditorHost,
|
||||
ShadowlessElement,
|
||||
} from '@blocksuite/affine/std';
|
||||
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';
|
||||
@@ -33,9 +29,6 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
@property({ attribute: false })
|
||||
accessor host: EditorHost | null | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor std: BlockStdScope | null | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor state: AffineAIPanelState = 'finished';
|
||||
|
||||
@@ -77,7 +70,7 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
case 'doc_compose':
|
||||
return html`
|
||||
<doc-compose-tool
|
||||
.std=${this.std || this.host?.std}
|
||||
.std=${this.host?.std}
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.theme=${this.theme}
|
||||
@@ -87,10 +80,9 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
case 'code_artifact':
|
||||
return html`
|
||||
<code-artifact-tool
|
||||
.std=${this.std || this.host?.std}
|
||||
.std=${this.host?.std}
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.theme=${this.theme}
|
||||
></code-artifact-tool>
|
||||
`;
|
||||
case 'doc_edit':
|
||||
@@ -133,7 +125,7 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
case 'doc_compose':
|
||||
return html`
|
||||
<doc-compose-tool
|
||||
.std=${this.std || this.host?.std}
|
||||
.std=${this.host?.std}
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.theme=${this.theme}
|
||||
@@ -143,7 +135,7 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
case 'code_artifact':
|
||||
return html`
|
||||
<code-artifact-tool
|
||||
.std=${this.std || this.host?.std}
|
||||
.std=${this.host?.std}
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.theme=${this.theme}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { css, html } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
@@ -81,6 +81,9 @@ export class AIScrollableTextRenderer extends WithDisposable(
|
||||
@property({ attribute: false })
|
||||
accessor answer!: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor state: AffineAIPanelState | undefined;
|
||||
|
||||
@@ -98,16 +101,19 @@ export class AIScrollableTextRenderer extends WithDisposable(
|
||||
}
|
||||
|
||||
export const createAIScrollableTextRenderer: (
|
||||
host: EditorHost,
|
||||
textRendererOptions: TextRendererOptions,
|
||||
maxHeight: number,
|
||||
autoScroll: boolean
|
||||
) => AffineAIPanelWidgetConfig['answerRenderer'] = (
|
||||
host,
|
||||
textRendererOptions,
|
||||
maxHeight,
|
||||
autoScroll
|
||||
) => {
|
||||
return (answer: string, state: AffineAIPanelState | undefined) => {
|
||||
return html`<ai-scrollable-text-renderer
|
||||
.host=${host}
|
||||
.answer=${answer}
|
||||
.state=${state}
|
||||
.textRendererOptions=${textRendererOptions}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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 { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import type { Signal } from '@preact/signals-core';
|
||||
import {
|
||||
css,
|
||||
@@ -30,17 +29,12 @@ export abstract class ArtifactTool<
|
||||
> extends SignalWatcher(WithDisposable(ShadowlessElement)) {
|
||||
static override styles = css`
|
||||
.artifact-tool-card {
|
||||
cursor: pointer;
|
||||
margin: 8px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.affine-embed-linked-doc-block {
|
||||
box-shadow: ${unsafeCSSVar('overlayPanelShadow')};
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.affine-embed-linked-doc-block:hover {
|
||||
background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||
}
|
||||
.artifact-tool-card:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -125,24 +119,23 @@ export abstract class ArtifactTool<
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="artifact-tool-card ${className ?? ''}"
|
||||
class="affine-embed-linked-doc-block artifact-tool-card ${className ??
|
||||
''} horizontal"
|
||||
@click=${this.onCardClick}
|
||||
>
|
||||
<div class="affine-embed-linked-doc-block horizontal">
|
||||
<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 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>
|
||||
${banner
|
||||
? html`<div class="affine-embed-linked-doc-banner">${banner}</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
${banner
|
||||
? html`<div class="affine-embed-linked-doc-banner">${banner}</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getEmbedLinkedDocIcons } from '@blocksuite/affine/blocks/embed-doc';
|
||||
import { LoadingIcon } from '@blocksuite/affine/components/icons';
|
||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||
import { NotificationProvider } from '@blocksuite/affine/shared/services';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
||||
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
|
||||
@@ -110,6 +111,7 @@ export class DocComposeTool extends ArtifactTool<
|
||||
|
||||
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;
|
||||
@@ -120,7 +122,8 @@ export class DocComposeTool extends ArtifactTool<
|
||||
${successResult
|
||||
? html`<text-renderer
|
||||
.answer=${successResult.markdown}
|
||||
.schema=${this.std?.store.schema}
|
||||
.host=${std.host}
|
||||
.schema=${std.store.schema}
|
||||
.options=${{
|
||||
customHeading: true,
|
||||
extensions: getCustomPageEditorBlockSpecs(),
|
||||
@@ -158,6 +161,7 @@ export class DocComposeTool extends ArtifactTool<
|
||||
return;
|
||||
}
|
||||
const workspace = std.store.workspace;
|
||||
const notificationService = std.get(NotificationProvider);
|
||||
const refNodeSlots = std.getOptional(RefNodeSlotsProvider);
|
||||
const docId = await MarkdownTransformer.importMarkdownToDoc({
|
||||
collection: workspace,
|
||||
@@ -167,7 +171,7 @@ export class DocComposeTool extends ArtifactTool<
|
||||
extensions: getStoreManager().config.init().value.get('store'),
|
||||
});
|
||||
if (docId) {
|
||||
const open = await this.notificationService.confirm({
|
||||
const open = await notificationService.confirm({
|
||||
title: 'Open the doc you just created',
|
||||
message: 'Doc saved successfully! Would you like to open it now?',
|
||||
cancelText: 'Cancel',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import track from '@affine/track';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
|
||||
@@ -204,33 +203,24 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
|
||||
private async _handleApply(markdown: string) {
|
||||
if (!this.host || this.data.type !== 'tool-result') {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
track.applyModel.chat.$.apply({
|
||||
instruction: this.data.args.instructions,
|
||||
});
|
||||
await this.blockDiffService?.apply(this.host.store, markdown);
|
||||
}
|
||||
|
||||
private async _handleReject(changedMarkdown: string) {
|
||||
if (!this.host || this.data.type !== 'tool-result') {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
track.applyModel.chat.$.reject({
|
||||
instruction: this.data.args.instructions,
|
||||
});
|
||||
this.blockDiffService?.setChangedMarkdown(changedMarkdown);
|
||||
this.blockDiffService?.rejectAll();
|
||||
}
|
||||
|
||||
private async _handleAccept(changedMarkdown: string) {
|
||||
if (!this.host || this.data.type !== 'tool-result') {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
track.applyModel.chat.$.accept({
|
||||
instruction: this.data.args.instructions,
|
||||
});
|
||||
await this.blockDiffService?.apply(this.host.store, changedMarkdown);
|
||||
await this.blockDiffService?.acceptAll(this.host.store);
|
||||
}
|
||||
@@ -243,7 +233,6 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
|
||||
if (!this.host) {
|
||||
return;
|
||||
}
|
||||
track.applyModel.chat.$.copy();
|
||||
const success = await copyText(removeMarkdownComments(changedMarkdown));
|
||||
if (success) {
|
||||
this.notificationService.notify({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||
import { Tooltip } from '@blocksuite/affine/components/tooltip';
|
||||
import { Tooltip } from '@blocksuite/affine/components/toolbar';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { noop } from '@blocksuite/affine/global/utils';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
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 {
|
||||
@@ -20,8 +19,7 @@ import { throttle } from 'lodash-es';
|
||||
import type { AppSidebarConfig } from '../../chat-panel/chat-config';
|
||||
import { HISTORY_IMAGE_ACTIONS } from '../../chat-panel/const';
|
||||
import { AIProvider } from '../../provider';
|
||||
import type { SearchMenuConfig } from '../ai-chat-add-context';
|
||||
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
|
||||
import type { ChatContextValue } from '../ai-chat-content';
|
||||
import type {
|
||||
AINetworkSearchConfig,
|
||||
@@ -167,9 +165,6 @@ export class PlaygroundChat extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor affineThemeService!: AppThemeService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor notificationService!: NotificationService;
|
||||
|
||||
@@ -356,8 +351,6 @@ export class PlaygroundChat extends SignalWatcher(
|
||||
.playgroundConfig=${this.playgroundConfig}
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.notificationService=${this.notificationService}
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
></ai-chat-composer>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@ import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { AppSidebarConfig } from '../../chat-panel/chat-config';
|
||||
import { AIProvider } from '../../provider';
|
||||
import type { SearchMenuConfig } from '../ai-chat-add-context';
|
||||
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
|
||||
import type {
|
||||
AINetworkSearchConfig,
|
||||
AIPlaygroundConfig,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { effects as tooltipEffects } from '@blocksuite/affine-components/tooltip';
|
||||
|
||||
import { AIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-block';
|
||||
import { EdgelessAIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-edgeless-block';
|
||||
import { LitTranscriptionBlock } from './blocks/ai-chat-block/ai-transcription-block';
|
||||
@@ -26,7 +24,6 @@ import { ChatMessageAction } from './chat-panel/message/action';
|
||||
import { ChatMessageAssistant } from './chat-panel/message/assistant';
|
||||
import { ChatMessageUser } from './chat-panel/message/user';
|
||||
import { ChatPanelSplitView } from './chat-panel/split-view';
|
||||
import { AIChatAddContext } from './components/ai-chat-add-context';
|
||||
import { ChatPanelAddPopover } from './components/ai-chat-chips/add-popover';
|
||||
import { ChatPanelCandidatesPopover } from './components/ai-chat-chips/candidates-popover';
|
||||
import { ChatPanelChips } from './components/ai-chat-chips/chat-panel-chips';
|
||||
@@ -116,7 +113,6 @@ export function registerAIEffects() {
|
||||
registerMiniMindmapBlocks();
|
||||
componentAiItemEffects();
|
||||
componentPlaygroundEffects();
|
||||
tooltipEffects();
|
||||
|
||||
customElements.define('ask-ai-icon', AskAIIcon);
|
||||
customElements.define('ask-ai-button', AskAIButton);
|
||||
@@ -139,7 +135,6 @@ export function registerAIEffects() {
|
||||
customElements.define('ai-chat-messages', AIChatMessages);
|
||||
customElements.define('chat-panel', ChatPanel);
|
||||
customElements.define('ai-chat-input', AIChatInput);
|
||||
customElements.define('ai-chat-add-context', AIChatAddContext);
|
||||
customElements.define(
|
||||
'ai-chat-embedding-status-tooltip',
|
||||
AIChatEmbeddingStatusTooltip
|
||||
|
||||
@@ -29,8 +29,10 @@ import {
|
||||
queryHistoryMessages,
|
||||
} from '../_common/chat-actions-handle';
|
||||
import { type AIChatBlockModel } from '../blocks';
|
||||
import type { SearchMenuConfig } from '../components/ai-chat-add-context';
|
||||
import type { DocDisplayConfig } from '../components/ai-chat-chips';
|
||||
import type {
|
||||
DocDisplayConfig,
|
||||
SearchMenuConfig,
|
||||
} from '../components/ai-chat-chips';
|
||||
import type {
|
||||
AINetworkSearchConfig,
|
||||
AIReasoningConfig,
|
||||
@@ -370,7 +372,7 @@ export class AIChatBlockPeekView extends LitElement {
|
||||
const last = messages[messages.length - 1];
|
||||
if ('content' in last) {
|
||||
last.content = '';
|
||||
last.streamObjects = [];
|
||||
last.id = '';
|
||||
last.createdAt = new Date().toISOString();
|
||||
}
|
||||
this.updateContext({
|
||||
@@ -607,7 +609,6 @@ export class AIChatBlockPeekView extends LitElement {
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
.notificationService=${notificationService}
|
||||
.onChatSuccess=${this._onChatSuccess}
|
||||
.trackOptions=${{
|
||||
where: 'ai-chat-block',
|
||||
|
||||
@@ -165,8 +165,7 @@ export class CopilotClient {
|
||||
docId?: string,
|
||||
options?: RequestOptions<
|
||||
typeof getCopilotSessionsQuery
|
||||
>['variables']['options'],
|
||||
signal?: AbortSignal
|
||||
>['variables']['options']
|
||||
) {
|
||||
try {
|
||||
const res = await this.gql({
|
||||
@@ -177,7 +176,6 @@ export class CopilotClient {
|
||||
docId,
|
||||
options,
|
||||
},
|
||||
signal,
|
||||
});
|
||||
return res.currentUser?.copilot?.chats.edges.map(e => e.node);
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import track from '@affine/track';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
import { CloseIcon, DoneIcon } from '@blocksuite/icons/lit';
|
||||
@@ -51,12 +50,12 @@ export class BlockDiffOptions extends WithDisposable(LitElement) {
|
||||
accessor onReject!: (op: PatchOp) => void;
|
||||
|
||||
private readonly _handleAcceptClick = () => {
|
||||
track.applyModel.widget.block.accept();
|
||||
console.log('accept', this.op);
|
||||
this.onAccept(this.op);
|
||||
};
|
||||
|
||||
private readonly _handleRejectClick = () => {
|
||||
track.applyModel.widget.block.reject();
|
||||
console.log('reject', this.op);
|
||||
this.onReject(this.op);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { track } from '@affine/track';
|
||||
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/affine/std';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import {
|
||||
@@ -83,16 +82,6 @@ export class AffineBlockDiffWidgetForPage extends WidgetComponent {
|
||||
diffs[this.currentIndex].scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
async _handleAcceptAll() {
|
||||
track.applyModel.widget.page.acceptAll();
|
||||
await this.diffService.acceptAll(this.std.store);
|
||||
}
|
||||
|
||||
_handleRejectAll() {
|
||||
track.applyModel.widget.page.rejectAll();
|
||||
this.diffService.rejectAll();
|
||||
}
|
||||
|
||||
get diffService() {
|
||||
return this.std.get(BlockDiffProvider);
|
||||
}
|
||||
@@ -123,7 +112,7 @@ export class AffineBlockDiffWidgetForPage extends WidgetComponent {
|
||||
</div>
|
||||
<div
|
||||
class="ai-block-diff-all-option"
|
||||
@click=${() => this._handleRejectAll()}
|
||||
@click=${() => this.diffService.rejectAll()}
|
||||
>
|
||||
${CloseIcon({
|
||||
style: `color: ${unsafeCSSVarV2('icon/secondary')}`,
|
||||
@@ -132,7 +121,7 @@ export class AffineBlockDiffWidgetForPage extends WidgetComponent {
|
||||
</div>
|
||||
<div
|
||||
class="ai-block-diff-all-option"
|
||||
@click=${() => this._handleAcceptAll()}
|
||||
@click=${() => this.diffService.acceptAll(this.std.store)}
|
||||
>
|
||||
${DoneIcon({
|
||||
style: `color: ${unsafeCSSVarV2('icon/activated')}`,
|
||||
|
||||
@@ -87,7 +87,7 @@ const usePatchSpecs = (mode: DocMode, shared?: boolean) => {
|
||||
|
||||
// comment may not be supported by the server
|
||||
const enableComment =
|
||||
isCloud && serverConfig.features.includes(ServerFeature.Comment) && !shared;
|
||||
serverConfig.features.includes(ServerFeature.Comment) && !shared;
|
||||
|
||||
const patchedSpecs = useMemo(() => {
|
||||
const manager = getViewManager()
|
||||
|
||||
@@ -44,8 +44,6 @@ const optionsSchema = z.object({
|
||||
.args(z.custom<ConfirmModalProps>().optional(), z.any().optional()),
|
||||
closeConfirmModal: z.function(),
|
||||
}),
|
||||
|
||||
scope: z.enum(['doc', 'workspace']).optional(),
|
||||
});
|
||||
|
||||
export type AffineEditorViewOptions = z.infer<typeof optionsSchema>;
|
||||
@@ -95,7 +93,16 @@ export class AffineEditorViewExtension extends ViewExtensionProvider<AffineEdito
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
const { framework, reactToLit, confirmModal, scope = 'doc' } = options;
|
||||
const {
|
||||
framework,
|
||||
|
||||
reactToLit,
|
||||
confirmModal,
|
||||
} = options;
|
||||
|
||||
const docService = framework.get(DocService);
|
||||
const docsService = framework.get(DocsService);
|
||||
const editorService = framework.get(EditorService);
|
||||
|
||||
const referenceRenderer = this._getCustomReferenceRenderer(framework);
|
||||
|
||||
@@ -105,20 +112,12 @@ export class AffineEditorViewExtension extends ViewExtensionProvider<AffineEdito
|
||||
patchNotificationService(confirmModal),
|
||||
patchOpenDocExtension(),
|
||||
patchSideBarService(framework),
|
||||
patchDocModeService(docService, docsService, editorService),
|
||||
patchFileSizeLimitExtension(framework),
|
||||
buildDocDisplayMetaExtension(framework),
|
||||
patchForAudioEmbedView(reactToLit),
|
||||
])
|
||||
.register(patchDocUrlExtensions(framework))
|
||||
.register(patchQuickSearchService(framework));
|
||||
|
||||
if (scope === 'doc') {
|
||||
const docService = framework.get(DocService);
|
||||
const docsService = framework.get(DocsService);
|
||||
const editorService = framework.get(EditorService);
|
||||
context.register([
|
||||
patchDocModeService(docService, docsService, editorService),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { IconButton, notify, toast } from '@affine/component';
|
||||
import { IconButton, notify } from '@affine/component';
|
||||
import { LitDocEditor, type PageEditor } from '@affine/core/blocksuite/editors';
|
||||
import { SnapshotHelper } from '@affine/core/modules/comment/services/snapshot-helper';
|
||||
import type { CommentAttachment } from '@affine/core/modules/comment/types';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { downloadResourceWithUrl } from '@affine/core/utils/resource';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { getAttachmentFileIconRC } from '@blocksuite/affine/components/icons';
|
||||
import { type RichText, selectTextModel } from '@blocksuite/affine/rich-text';
|
||||
@@ -81,6 +80,16 @@ export interface CommentEditorRef {
|
||||
focus: () => void;
|
||||
}
|
||||
|
||||
const download = (url: string, name: string) => {
|
||||
const element = document.createElement('a');
|
||||
element.setAttribute('download', name);
|
||||
element.setAttribute('href', url);
|
||||
element.style.display = 'none';
|
||||
document.body.append(element);
|
||||
element.click();
|
||||
element.remove();
|
||||
};
|
||||
|
||||
// todo: get rid of circular data changes
|
||||
const useSnapshotDoc = (
|
||||
defaultSnapshotOrDoc: DocSnapshot | Store,
|
||||
@@ -304,28 +313,6 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
|
||||
[addAttachments]
|
||||
);
|
||||
|
||||
const handleDragOver = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
if (readonly) return;
|
||||
// Prevent default to allow drop
|
||||
e.preventDefault();
|
||||
},
|
||||
[readonly]
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
if (readonly) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const files = Array.from(e.dataTransfer?.files ?? []);
|
||||
if (files.length) {
|
||||
addAttachments(files);
|
||||
}
|
||||
},
|
||||
[addAttachments, readonly]
|
||||
);
|
||||
|
||||
const openFilePicker = useAsyncCallback(async () => {
|
||||
if (isUploadDisabled) return;
|
||||
const files = await openFilesWith('Any');
|
||||
@@ -395,6 +382,8 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
|
||||
if (!attachments) return;
|
||||
const att = attachments[index];
|
||||
if (!att) return;
|
||||
const url = att.url || att.localUrl;
|
||||
if (!url) return;
|
||||
if (isImageAttachment(att)) {
|
||||
// translate attachment index to image index
|
||||
const imageAttachments = attachments.filter(isImageAttachment);
|
||||
@@ -402,19 +391,13 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
|
||||
if (imageIndex >= 0) {
|
||||
handleImagePreview(imageIndex);
|
||||
}
|
||||
} else if (att.url) {
|
||||
} else if (att.url || att.localUrl) {
|
||||
// todo: open attachment preview. for now, just download it
|
||||
downloadResourceWithUrl(
|
||||
att.url,
|
||||
att.filename ?? att.file?.name ?? 'attachment'
|
||||
).catch(e => {
|
||||
console.error('Failed to download attachment', e);
|
||||
notify.error({
|
||||
title: 'Failed to download attachment',
|
||||
message: e.message,
|
||||
});
|
||||
download(url, att.filename ?? att.file?.name ?? 'attachment');
|
||||
notify({
|
||||
title: 'Downloading attachment',
|
||||
message: 'The attachment is being downloaded to your computer.',
|
||||
});
|
||||
toast('The attachment is being downloaded to your computer.');
|
||||
}
|
||||
},
|
||||
[attachments, handleImagePreview]
|
||||
@@ -555,8 +538,6 @@ export const CommentEditor = forwardRef<CommentEditorRef, CommentEditorProps>(
|
||||
onClick={readonly ? undefined : handleClickEditor}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
data-readonly={!!readonly}
|
||||
className={clsx(styles.container, 'comment-editor-viewport')}
|
||||
>
|
||||
|
||||
@@ -596,7 +596,7 @@ const CommentList = ({ entity }: { entity: DocCommentEntity }) => {
|
||||
const [filterState, setFilterState] = useState<CommentFilterState>({
|
||||
showResolvedComments: false,
|
||||
onlyMyReplies: false,
|
||||
onlyCurrentMode: false,
|
||||
onlyCurrentMode: true,
|
||||
});
|
||||
|
||||
const onFilterChange = useCallback(
|
||||
|
||||
@@ -39,7 +39,6 @@ export const commentList = style({
|
||||
export const empty = style({
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
padding: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { useConfirmModal, useLitPortalFactory } from '@affine/component';
|
||||
import { getViewManager } from '@affine/core/blocksuite/manager/view';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useFramework, useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useEnableAI } from './use-enable-ai';
|
||||
|
||||
export const useAISpecs = () => {
|
||||
const framework = useFramework();
|
||||
const enableAI = useEnableAI();
|
||||
const confirmModal = useConfirmModal();
|
||||
const [reactToLit, _portals] = useLitPortalFactory();
|
||||
|
||||
const { workspaceService, featureFlagService } = useServices({
|
||||
WorkspaceService,
|
||||
FeatureFlagService,
|
||||
});
|
||||
|
||||
const enablePDFEmbedPreview = useLiveData(
|
||||
featureFlagService.flags.enable_pdf_embed_preview.$
|
||||
);
|
||||
|
||||
const isCloud = workspaceService.workspace.flavour !== 'local';
|
||||
|
||||
const specs = useMemo(() => {
|
||||
const manager = getViewManager()
|
||||
.config.init()
|
||||
.foundation(framework)
|
||||
.ai(enableAI, framework)
|
||||
.editorConfig(framework)
|
||||
.editorView({
|
||||
framework,
|
||||
reactToLit,
|
||||
confirmModal,
|
||||
scope: 'workspace',
|
||||
})
|
||||
.cloud(framework, isCloud)
|
||||
.pdf(enablePDFEmbedPreview, reactToLit)
|
||||
.database(framework)
|
||||
.linkedDoc(framework)
|
||||
.paragraph(enableAI)
|
||||
.mobile(framework)
|
||||
.electron(framework)
|
||||
.linkPreview(framework)
|
||||
.codeBlockHtmlPreview(framework).value;
|
||||
|
||||
return manager.get('page');
|
||||
}, [
|
||||
framework,
|
||||
reactToLit,
|
||||
enableAI,
|
||||
enablePDFEmbedPreview,
|
||||
isCloud,
|
||||
confirmModal,
|
||||
]);
|
||||
|
||||
return specs;
|
||||
};
|
||||
@@ -2,32 +2,9 @@ import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { keyframes, style } from '@vanilla-extract/css';
|
||||
|
||||
export const container = style({
|
||||
export const containerScrollViewport = style({
|
||||
maxHeight: '448px',
|
||||
width: '360px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const header = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: cssVar('fontSm'),
|
||||
lineHeight: '22px',
|
||||
padding: '4px 8px 8px',
|
||||
borderBottom: `1px solid ${cssVarV2('layer/insideBorder/border')}`,
|
||||
});
|
||||
|
||||
export const scrollRoot = style({
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const scrollViewport = style({
|
||||
flex: 1,
|
||||
padding: '8px 0px ',
|
||||
});
|
||||
|
||||
export const itemList = style({
|
||||
|
||||
@@ -2,8 +2,6 @@ import {
|
||||
Avatar,
|
||||
Button,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
notify,
|
||||
observeIntersection,
|
||||
Scrollable,
|
||||
@@ -33,7 +31,6 @@ import {
|
||||
CollaborationIcon,
|
||||
DeleteIcon,
|
||||
EdgelessIcon,
|
||||
MoreHorizontalIcon,
|
||||
NotificationIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
@@ -83,59 +80,37 @@ export const NotificationList = () => {
|
||||
return;
|
||||
}, [hasMore, notificationListService]);
|
||||
|
||||
const handleDeleteAll = useCallback(() => {
|
||||
notificationListService.readAllNotifications().catch(err => {
|
||||
notify.error(UserFriendlyError.fromAny(err));
|
||||
});
|
||||
}, [notificationListService]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<span>{t['com.affine.rootAppSidebar.notifications']()}</span>
|
||||
{notifications.length > 0 && (
|
||||
<Menu
|
||||
items={
|
||||
<MenuItem prefixIcon={<DeleteIcon />} onClick={handleDeleteAll}>
|
||||
<span>{t['com.affine.notification.delete-all']()}</span>
|
||||
</MenuItem>
|
||||
}
|
||||
>
|
||||
<IconButton icon={<MoreHorizontalIcon />} />
|
||||
</Menu>
|
||||
<Scrollable.Root>
|
||||
<Scrollable.Viewport className={styles.containerScrollViewport}>
|
||||
{notifications.length > 0 ? (
|
||||
<ul className={styles.itemList}>
|
||||
{notifications.map(notification => (
|
||||
<li key={notification.id}>
|
||||
<NotificationItem notification={notification} />
|
||||
</li>
|
||||
))}
|
||||
{userFriendlyError && (
|
||||
<div className={styles.error}>{userFriendlyError.message}</div>
|
||||
)}
|
||||
</ul>
|
||||
) : isLoading ? (
|
||||
<NotificationItemSkeleton />
|
||||
) : userFriendlyError ? (
|
||||
<div className={styles.error}>{userFriendlyError.message}</div>
|
||||
) : (
|
||||
<NotificationListEmpty />
|
||||
)}
|
||||
</div>
|
||||
<Scrollable.Root className={styles.scrollRoot}>
|
||||
<Scrollable.Viewport className={styles.scrollViewport}>
|
||||
{notifications.length > 0 ? (
|
||||
<ul className={styles.itemList}>
|
||||
{notifications.map(notification => (
|
||||
<li key={notification.id}>
|
||||
<NotificationItem notification={notification} />
|
||||
</li>
|
||||
))}
|
||||
{userFriendlyError && (
|
||||
<div className={styles.error}>{userFriendlyError.message}</div>
|
||||
)}
|
||||
</ul>
|
||||
) : isLoading ? (
|
||||
<NotificationItemSkeleton />
|
||||
) : userFriendlyError ? (
|
||||
<div className={styles.error}>{userFriendlyError.message}</div>
|
||||
) : (
|
||||
<NotificationListEmpty />
|
||||
)}
|
||||
|
||||
<div
|
||||
ref={loadMoreIndicatorRef}
|
||||
className={hasMore ? styles.loadMoreIndicator : ''}
|
||||
>
|
||||
{hasMore ? t['com.affine.notification.loading-more']() : null}
|
||||
</div>
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
</div>
|
||||
<div
|
||||
ref={loadMoreIndicatorRef}
|
||||
className={hasMore ? styles.loadMoreIndicator : ''}
|
||||
>
|
||||
{hasMore ? t['com.affine.notification.loading-more']() : null}
|
||||
</div>
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ const AIChatButton = () => {
|
||||
|
||||
return (
|
||||
<MenuLinkItem icon={<AiOutlineIcon />} active={aiChatActive} to={'/chat'}>
|
||||
<span data-testid="ai-chat">Intelligence</span>
|
||||
<span data-testid="ai-chat">AFFiNE Intelligence</span>
|
||||
</MenuLinkItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
} from '@blocksuite/affine/std/gfx';
|
||||
import type { Block, Store } from '@blocksuite/affine/store';
|
||||
import { useFramework } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { map, pairwise } from 'rxjs';
|
||||
@@ -66,7 +65,7 @@ export const EdgelessSnapshot = (props: Props) => {
|
||||
.codeBlockHtmlPreview(framework).value;
|
||||
return manager
|
||||
.get('preview-edgeless')
|
||||
.concat([ViewportElementExtension('.setting-editor-snapshot')]);
|
||||
.concat([ViewportElementExtension('.ref-viewport')]);
|
||||
}, [framework]);
|
||||
|
||||
const updateElements = useCallback(() => {
|
||||
@@ -157,7 +156,7 @@ export const EdgelessSnapshot = (props: Props) => {
|
||||
}, [editorSetting.provider, keyName, updateElements]);
|
||||
|
||||
return (
|
||||
<div className={clsx(snapshotContainer, 'setting-editor-snapshot')}>
|
||||
<div className={snapshotContainer}>
|
||||
<div className={snapshotTitle}>{title}</div>
|
||||
<div className={snapshotLabel}>{title}</div>
|
||||
<div ref={wrapperRef} className={editorWrapper} style={{ height }}>
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
import { useSignOut } from '@affine/core/components/hooks/affine/use-sign-out';
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||
import {
|
||||
FrameworkScope,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
RouteLogic,
|
||||
useNavigateHelper,
|
||||
} from '../../../components/hooks/use-navigate-helper';
|
||||
import { ServersService } from '../../../modules/cloud';
|
||||
import { AuthService } from '../../../modules/cloud';
|
||||
import { SignIn } from '../auth/sign-in';
|
||||
|
||||
/**
|
||||
@@ -28,15 +27,9 @@ export const PageNotFound = ({
|
||||
}: {
|
||||
noPermission?: boolean;
|
||||
}): ReactElement => {
|
||||
const serversService = useService(ServersService);
|
||||
const serversWithAccount = useLiveData(serversService.serversWithAccount$);
|
||||
|
||||
const authService = useService(AuthService);
|
||||
const desktopApi = useServiceOptional(DesktopApiService);
|
||||
|
||||
// Check all servers for any logged in accounts to avoid showing sign-in page if user has an active session on any server
|
||||
const firstLogged = serversWithAccount.find(
|
||||
({ account }) => account !== null
|
||||
);
|
||||
const account = useLiveData(authService.session.account$);
|
||||
const { jumpToIndex } = useNavigateHelper();
|
||||
const openSignOutModal = useSignOut();
|
||||
|
||||
@@ -53,23 +46,19 @@ export const PageNotFound = ({
|
||||
// strip the origin
|
||||
const currentUrl = window.location.href.replace(window.location.origin, '');
|
||||
|
||||
return (
|
||||
<FrameworkScope scope={firstLogged?.server.scope}>
|
||||
{noPermission ? (
|
||||
<NoPermissionOrNotFound
|
||||
user={firstLogged?.account}
|
||||
onBack={handleBackButtonClick}
|
||||
onSignOut={openSignOutModal}
|
||||
signInComponent={<SignIn redirectUrl={currentUrl} />}
|
||||
/>
|
||||
) : (
|
||||
<NotFoundPage
|
||||
user={firstLogged?.account}
|
||||
onBack={handleBackButtonClick}
|
||||
onSignOut={openSignOutModal}
|
||||
/>
|
||||
)}
|
||||
</FrameworkScope>
|
||||
return noPermission ? (
|
||||
<NoPermissionOrNotFound
|
||||
user={account}
|
||||
onBack={handleBackButtonClick}
|
||||
onSignOut={openSignOutModal}
|
||||
signInComponent={<SignIn redirectUrl={currentUrl} />}
|
||||
/>
|
||||
) : (
|
||||
<NotFoundPage
|
||||
user={account}
|
||||
onBack={handleBackButtonClick}
|
||||
onSignOut={openSignOutModal}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { observeResize, useConfirmModal } from '@affine/component';
|
||||
import { CopilotClient } from '@affine/core/blocksuite/ai';
|
||||
import {
|
||||
AIChatContent,
|
||||
type ChatContextValue,
|
||||
} from '@affine/core/blocksuite/ai/components/ai-chat-content';
|
||||
import type { ChatStatus } from '@affine/core/blocksuite/ai/components/ai-chat-messages';
|
||||
import { AIChatContent } from '@affine/core/blocksuite/ai/components/ai-chat-content';
|
||||
import { AIChatToolbar } from '@affine/core/blocksuite/ai/components/ai-chat-toolbar';
|
||||
import type { PromptKey } from '@affine/core/blocksuite/ai/provider/prompt';
|
||||
import { getViewManager } from '@affine/core/blocksuite/manager/view';
|
||||
import { NotificationServiceImpl } from '@affine/core/blocksuite/view-extensions/editor-view/notification-service';
|
||||
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
|
||||
import { useAISpecs } from '@affine/core/components/hooks/affine/use-ai-specs';
|
||||
import {
|
||||
EventSourceService,
|
||||
FetchService,
|
||||
@@ -28,12 +22,9 @@ import {
|
||||
WorkbenchService,
|
||||
} from '@affine/core/modules/workbench';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||
import { BlockStdScope } from '@blocksuite/affine/std';
|
||||
import type { Workspace } from '@blocksuite/affine/store';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { type Signal, signal } from '@preact/signals-core';
|
||||
import { useFramework, useService } from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import * as styles from './index.css';
|
||||
@@ -56,29 +47,8 @@ function useCopilotClient() {
|
||||
);
|
||||
}
|
||||
|
||||
function createMockStd(workspace: Workspace) {
|
||||
workspace.meta.initialize();
|
||||
// just pick a random doc for now
|
||||
const store = workspace.docs.values().next().value?.getStore();
|
||||
if (!store) return null;
|
||||
const std = new BlockStdScope({
|
||||
store,
|
||||
extensions: [...getViewManager().config.init().value.get('page')],
|
||||
});
|
||||
std.render();
|
||||
return std;
|
||||
}
|
||||
|
||||
function useMockStd() {
|
||||
const workspace = useService(WorkspaceService).workspace;
|
||||
const std = useMemo(() => {
|
||||
if (!workspace) return null;
|
||||
return createMockStd(workspace.docCollection);
|
||||
}, [workspace]);
|
||||
return std;
|
||||
}
|
||||
|
||||
export const Component = () => {
|
||||
const t = useI18n();
|
||||
const framework = useFramework();
|
||||
const [isBodyProvided, setIsBodyProvided] = useState(false);
|
||||
const [isHeaderProvided, setIsHeaderProvided] = useState(false);
|
||||
@@ -87,7 +57,6 @@ export const Component = () => {
|
||||
const [currentSession, setCurrentSession] = useState<CopilotSession | null>(
|
||||
null
|
||||
);
|
||||
const [status, setStatus] = useState<ChatStatus>('idle');
|
||||
const [isTogglingPin, setIsTogglingPin] = useState(false);
|
||||
const [isOpeningSession, setIsOpeningSession] = useState(false);
|
||||
const chatContainerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -109,7 +78,6 @@ export const Component = () => {
|
||||
const sessionId = await client.createSession({
|
||||
workspaceId,
|
||||
promptName: 'Chat With AFFiNE AI' satisfies PromptKey,
|
||||
reuseLatestChat: false,
|
||||
...options,
|
||||
});
|
||||
|
||||
@@ -152,10 +120,7 @@ export const Component = () => {
|
||||
.getSession(workspaceId, sessionId)
|
||||
.then(session => {
|
||||
setCurrentSession(session);
|
||||
if (chatContent) {
|
||||
chatContent.session = session;
|
||||
chatContent.reloadSession();
|
||||
}
|
||||
chatContent?.reloadSession();
|
||||
chatTool?.closeHistoryMenu();
|
||||
})
|
||||
.catch(console.error)
|
||||
@@ -166,13 +131,7 @@ export const Component = () => {
|
||||
[chatContent, chatTool, client, isOpeningSession, workspaceId]
|
||||
);
|
||||
|
||||
const onContextChange = useCallback((context: Partial<ChatContextValue>) => {
|
||||
setStatus(context.status ?? 'idle');
|
||||
}, []);
|
||||
|
||||
const confirmModal = useConfirmModal();
|
||||
const specs = useAISpecs();
|
||||
const mockStd = useMockStd();
|
||||
|
||||
// init or update ai-chat-content
|
||||
useEffect(() => {
|
||||
@@ -188,13 +147,10 @@ export const Component = () => {
|
||||
|
||||
content.session = currentSession;
|
||||
content.workspaceId = workspaceId;
|
||||
content.extensions = specs;
|
||||
content.host = mockStd?.host;
|
||||
content.docDisplayConfig = docDisplayConfig;
|
||||
content.searchMenuConfig = searchMenuConfig;
|
||||
content.networkSearchConfig = networkSearchConfig;
|
||||
content.reasoningConfig = reasoningConfig;
|
||||
content.onContextChange = onContextChange;
|
||||
content.affineFeatureFlagService = framework.get(FeatureFlagService);
|
||||
content.affineWorkspaceDialogService = framework.get(
|
||||
WorkspaceDialogService
|
||||
@@ -221,14 +177,11 @@ export const Component = () => {
|
||||
docDisplayConfig,
|
||||
framework,
|
||||
isBodyProvided,
|
||||
mockStd,
|
||||
networkSearchConfig,
|
||||
reasoningConfig,
|
||||
searchMenuConfig,
|
||||
workspaceId,
|
||||
confirmModal,
|
||||
onContextChange,
|
||||
specs,
|
||||
]);
|
||||
|
||||
// init or update header ai-chat-toolbar
|
||||
@@ -244,7 +197,6 @@ export const Component = () => {
|
||||
|
||||
tool.session = currentSession;
|
||||
tool.workspaceId = workspaceId;
|
||||
tool.status = status;
|
||||
tool.docDisplayConfig = docDisplayConfig;
|
||||
tool.onOpenSession = onOpenSession;
|
||||
tool.notificationService = new NotificationServiceImpl(
|
||||
@@ -287,25 +239,8 @@ export const Component = () => {
|
||||
workspaceId,
|
||||
confirmModal,
|
||||
framework,
|
||||
status,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const refNodeSlots = mockStd?.getOptional(RefNodeSlotsProvider);
|
||||
if (!refNodeSlots) return;
|
||||
const sub = refNodeSlots.docLinkClicked.subscribe(event => {
|
||||
const { workbench } = framework.get(WorkbenchService);
|
||||
workbench.openDoc({
|
||||
docId: event.pageId,
|
||||
mode: event.params?.mode,
|
||||
blockIds: event.params?.blockIds,
|
||||
elementIds: event.params?.elementIds,
|
||||
refreshKey: nanoid(),
|
||||
});
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
}, [framework, mockStd]);
|
||||
|
||||
const onChatContainerRef = useCallback((node: HTMLDivElement) => {
|
||||
if (node) {
|
||||
setIsBodyProvided(true);
|
||||
@@ -331,7 +266,7 @@ export const Component = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewTitle title="Intelligence" />
|
||||
<ViewTitle title={t['AFFiNE AI']()} />
|
||||
<ViewIcon icon="ai" />
|
||||
<ViewHeader>
|
||||
<div className={styles.chatHeader}>
|
||||
|
||||
@@ -119,9 +119,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
const serverConfig = useLiveData(serverService.server.config$);
|
||||
|
||||
// comment may not be supported by the server
|
||||
const enableComment =
|
||||
workspace.flavour !== 'local' &&
|
||||
serverConfig.features.includes(ServerFeature.Comment);
|
||||
const enableComment = serverConfig.features.includes(ServerFeature.Comment);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActiveView) {
|
||||
|
||||
@@ -3,11 +3,11 @@ import { AIProvider, ChatPanel } from '@affine/core/blocksuite/ai';
|
||||
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
|
||||
import { NotificationServiceImpl } from '@affine/core/blocksuite/view-extensions/editor-view/notification-service';
|
||||
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
|
||||
import { useAISpecs } from '@affine/core/components/hooks/affine/use-ai-specs';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { AppThemeService } from '@affine/core/modules/theme';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
|
||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||
import { DocModeProvider } from '@blocksuite/affine/shared/services';
|
||||
import { createSignalFromObservable } from '@blocksuite/affine/shared/utils';
|
||||
@@ -55,7 +55,6 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
||||
playgroundConfig,
|
||||
} = useAIChatConfig();
|
||||
const confirmModal = useConfirmModal();
|
||||
const specs = useAISpecs();
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor || !editor.host) return;
|
||||
@@ -82,7 +81,9 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
||||
chatPanelRef.current.networkSearchConfig = networkSearchConfig;
|
||||
chatPanelRef.current.reasoningConfig = reasoningConfig;
|
||||
chatPanelRef.current.playgroundConfig = playgroundConfig;
|
||||
chatPanelRef.current.extensions = specs;
|
||||
chatPanelRef.current.extensions = editor.host.std
|
||||
.get(ViewExtensionManagerIdentifier)
|
||||
.get('preview-page');
|
||||
chatPanelRef.current.affineFeatureFlagService =
|
||||
framework.get(FeatureFlagService);
|
||||
chatPanelRef.current.affineWorkspaceDialogService = framework.get(
|
||||
@@ -126,7 +127,6 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
||||
reasoningConfig,
|
||||
playgroundConfig,
|
||||
confirmModal,
|
||||
specs,
|
||||
]);
|
||||
|
||||
const [autoResized, setAutoResized] = useState(false);
|
||||
|
||||
@@ -47,17 +47,6 @@ export class ServersService extends Service {
|
||||
[] as any
|
||||
);
|
||||
|
||||
serversWithAccount$ = this.servers$
|
||||
.map(servers =>
|
||||
servers.map(server =>
|
||||
server.account$.map(account => ({
|
||||
server,
|
||||
account,
|
||||
}))
|
||||
)
|
||||
)
|
||||
.flat();
|
||||
|
||||
server$(id: string) {
|
||||
return this.servers$.map(servers =>
|
||||
servers.find(server => server.id === id)
|
||||
|
||||
@@ -337,7 +337,6 @@ export class DocCommentStore extends Entity<{
|
||||
}
|
||||
|
||||
const res = await graphql.gql({
|
||||
timeout: 180_000,
|
||||
query: uploadCommentAttachmentMutation,
|
||||
variables: {
|
||||
workspaceId: this.currentWorkspaceId,
|
||||
|
||||
@@ -88,23 +88,4 @@ export class NotificationListService extends Service {
|
||||
Math.max(this.notificationCount.count$.value - 1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
async readAllNotifications() {
|
||||
// optimistic clear all notifications
|
||||
this.reset();
|
||||
this.notificationCount.setCount(0);
|
||||
// avoid loading more notifications after clear all notifications
|
||||
this.hasMore$.setValue(false);
|
||||
|
||||
try {
|
||||
await this.store.readAllNotifications();
|
||||
} catch (err) {
|
||||
// rollback the optimistic clear all notifications
|
||||
this.reset();
|
||||
this.loadMore();
|
||||
|
||||
// rethrow the error to the caller, to notify the user
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
mentionUserMutation,
|
||||
notificationCountQuery,
|
||||
type PaginationInput,
|
||||
readAllNotificationsMutation,
|
||||
readNotificationMutation,
|
||||
type UnionNotificationBodyType,
|
||||
} from '@affine/graphql';
|
||||
@@ -86,12 +85,6 @@ export class NotificationStore extends Store {
|
||||
});
|
||||
}
|
||||
|
||||
readAllNotifications() {
|
||||
return this.gqlService.gql({
|
||||
query: readAllNotificationsMutation,
|
||||
});
|
||||
}
|
||||
|
||||
async mentionUser(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
|
||||
@@ -33,14 +33,6 @@ export async function downloadBlob(blob: Blob, filename: string) {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
|
||||
export function downloadFile(url: string, filename: string) {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.target = '_blank';
|
||||
a.click();
|
||||
}
|
||||
|
||||
export async function downloadResourceWithUrl(url: string, filename: string) {
|
||||
// given input url may not have correct mime type
|
||||
const blob = await resourceUrlToBlob(url);
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"ar": 100,
|
||||
"ar": 99,
|
||||
"ca": 4,
|
||||
"da": 4,
|
||||
"de": 100,
|
||||
"el-GR": 100,
|
||||
"de": 99,
|
||||
"el-GR": 99,
|
||||
"en": 100,
|
||||
"es-AR": 100,
|
||||
"es-AR": 99,
|
||||
"es-CL": 100,
|
||||
"es": 100,
|
||||
"fa": 100,
|
||||
"fr": 100,
|
||||
"es": 99,
|
||||
"fa": 99,
|
||||
"fr": 99,
|
||||
"hi": 2,
|
||||
"it-IT": 100,
|
||||
"it-IT": 99,
|
||||
"it": 1,
|
||||
"ja": 100,
|
||||
"ja": 99,
|
||||
"ko": 53,
|
||||
"pl": 100,
|
||||
"pt-BR": 100,
|
||||
"ru": 100,
|
||||
"sv-SE": 100,
|
||||
"uk": 100,
|
||||
"pl": 99,
|
||||
"pt-BR": 99,
|
||||
"ru": 99,
|
||||
"sv-SE": 99,
|
||||
"uk": 99,
|
||||
"ur": 2,
|
||||
"zh-Hans": 100,
|
||||
"zh-Hant": 100
|
||||
"zh-Hans": 99,
|
||||
"zh-Hant": 99
|
||||
}
|
||||
|
||||
@@ -7778,10 +7778,6 @@ export function useAFFiNEI18N(): {
|
||||
* `Accept & Join`
|
||||
*/
|
||||
["com.affine.notification.invitation.accept"](): string;
|
||||
/**
|
||||
* `Delete all notifications`
|
||||
*/
|
||||
["com.affine.notification.delete-all"](): string;
|
||||
/**
|
||||
* `Tips`
|
||||
*/
|
||||
@@ -8243,7 +8239,7 @@ export function useAFFiNEI18N(): {
|
||||
*/
|
||||
["com.affine.comment.comments"](): string;
|
||||
/**
|
||||
* `No comments yet, select content to add comment to`
|
||||
* `No comments yet`
|
||||
*/
|
||||
["com.affine.comment.no-comments"](): string;
|
||||
/**
|
||||
@@ -8706,22 +8702,13 @@ export function useAFFiNEI18N(): {
|
||||
*/
|
||||
["error.COPILOT_SESSION_DELETED"](): string;
|
||||
/**
|
||||
* `No copilot provider available: {{modelId}}`
|
||||
* `No copilot provider available.`
|
||||
*/
|
||||
["error.NO_COPILOT_PROVIDER_AVAILABLE"](options: {
|
||||
readonly modelId: string;
|
||||
}): string;
|
||||
["error.NO_COPILOT_PROVIDER_AVAILABLE"](): string;
|
||||
/**
|
||||
* `Failed to generate text.`
|
||||
*/
|
||||
["error.COPILOT_FAILED_TO_GENERATE_TEXT"](): string;
|
||||
/**
|
||||
* `Failed to generate embedding with {{provider}}: {{message}}`
|
||||
*/
|
||||
["error.COPILOT_FAILED_TO_GENERATE_EMBEDDING"](options: Readonly<{
|
||||
provider: string;
|
||||
message: string;
|
||||
}>): string;
|
||||
/**
|
||||
* `Failed to create chat message.`
|
||||
*/
|
||||
|
||||
@@ -240,8 +240,6 @@
|
||||
"com.affine.appearanceSettings.title": "إعدادات المظهر",
|
||||
"com.affine.appearanceSettings.translucentUI.description": "استخدام تأثير الشفافية على الشريط الجانبي.",
|
||||
"com.affine.appearanceSettings.translucentUI.title": "واجهة شفافة على الشريط الجانبي",
|
||||
"com.affine.appearanceSettings.showLinkedDocInSidebar.title": "عرض الوثيقة المرتبطة في الشريط الجانبي",
|
||||
"com.affine.appearanceSettings.showLinkedDocInSidebar.description": "التحكم في عرض هيكل الوثائق المرتبطة في الشريط الجانبي.",
|
||||
"com.affine.auth.change.email.message": "بريدك الإلكتروني الحالي هو {{email}}. سنرسل رابط تحقق مؤقت إلى هذا البريد الإلكتروني.",
|
||||
"com.affine.auth.change.email.page.subtitle": "يرجى إدخال عنوان بريدك الإلكتروني الجديد أدناه. سنرسل رابط تحقق إلى هذا العنوان لإكمال العملية.",
|
||||
"com.affine.auth.change.email.page.success.subtitle": "تهانينا! لقد قمت بتحديث عنوان البريد الإلكتروني المرتبط بحساب AFFiNE Cloud الخاص بك بنجاح.",
|
||||
@@ -602,8 +600,6 @@
|
||||
"com.affine.import.notion.tooltip": "استيراد بيانات Notion الخاصة بك. تنسيقات الاستيراد المدعومة: HTML مع الصفحات الفرعية.",
|
||||
"com.affine.import.snapshot": "لقطة",
|
||||
"com.affine.import.snapshot.tooltip": "استيراد مساحة العمل AFFiNE وملف لقطة الصفحة.",
|
||||
"com.affine.import.dotaffinefile": "ملف .affine",
|
||||
"com.affine.import.dotaffinefile.tooltip": "استيراد ملف قاعدة بيانات AFFiNE الخاص بك (.affine)",
|
||||
"com.affine.import.status.failed.message": "فشل الاستيراد، يرجى المحاولة مرة أخرى.",
|
||||
"com.affine.import.status.failed.message.no-file-selected": "لم يتم تحديد ملف",
|
||||
"com.affine.import.status.failed.title": "فشل الاستيراد",
|
||||
@@ -689,7 +685,6 @@
|
||||
"com.affine.mobile.setting.others.terms": "شروط الاستخدام",
|
||||
"com.affine.mobile.setting.others.title": "الخصوصية والأخرى",
|
||||
"com.affine.mobile.setting.others.website": "الموقع الرسمي",
|
||||
"com.affine.mobile.setting.others.delete-account": "حذف الحساب الخاص بي",
|
||||
"com.affine.mobile.sign-in.skip.hint": "هل تريد الحفاظ على البيانات المحلية؟",
|
||||
"com.affine.mobile.sign-in.skip.link": "ابدأ AFFiNE بدون حساب",
|
||||
"com.affine.moreThan30Days": "أقدم من شهر",
|
||||
@@ -1121,9 +1116,7 @@
|
||||
"com.affine.split-view-folder-warning.description": "عرض المقسم لا يدعم المجلدات.",
|
||||
"do-not-show-this-again": "عدم إظهار هذا مرة أخرى",
|
||||
"com.affine.quicksearch.group.creation": "جديد",
|
||||
"com.affine.quicksearch.search-locally": "بحث محلي",
|
||||
"com.affine.quicksearch.group.searchfor": "البحث عن \"{{query}}\"",
|
||||
"com.affine.quicksearch.group.searchfor-locally": "البحث عن \"{{query}}\" (محليًا)",
|
||||
"com.affine.resetSyncStatus.button": "إعادة تعيين المزامنة",
|
||||
"com.affine.resetSyncStatus.description": "قد تحل هذه العملية بعض مشكلات المزامنة.",
|
||||
"com.affine.rootAppSidebar.collections": "مجموعات",
|
||||
@@ -1199,8 +1192,6 @@
|
||||
"com.affine.setting.notifications.email.mention.subtitle": "ستتلقى إشعارًا عبر البريد الإلكتروني عند ذكر الأعضاء الآخرين في مساحة العمل لك.",
|
||||
"com.affine.setting.notifications.email.invites.title": "الدعوات",
|
||||
"com.affine.setting.notifications.email.invites.subtitle": "ستُرسل الرسائل المتعلقة بالدعوة عبر البريد الإلكتروني.",
|
||||
"com.affine.setting.notifications.email.comments.title": "تعليقات",
|
||||
"com.affine.setting.notifications.email.comments.subtitle": "ستتلقى إشعارًا عبر البريد الإلكتروني عندما يعلق أعضاء آخرون في مساحة العمل على مستنداتك.",
|
||||
"com.affine.setting.account": "إعدادات الحساب",
|
||||
"com.affine.setting.account.delete": "حذف حسابك",
|
||||
"com.affine.setting.account.delete.message": "بمجرد الحذف، لن يكون حسابك متاحًا للوصول، وسيتم حذف جميع البيانات في مساحة التخزين السحابية الشخصية الخاصة بك بشكل دائم.",
|
||||
@@ -1401,9 +1392,6 @@
|
||||
"com.affine.settings.meetings.privacy.microphone": "ميكروفون",
|
||||
"com.affine.settings.meetings.privacy.microphone.description": "تتطلب ميزة الاجتماع إذنًا للاستخدام.",
|
||||
"com.affine.settings.meetings.privacy.microphone.permission-setting": "انقر للسماح",
|
||||
"com.affine.settings.meetings.privacy.issues": "مشكلات الأذونات",
|
||||
"com.affine.settings.meetings.privacy.issues.description": "تم منح الأذونات، ولكن لم يتم تحديث الحالة؟ أعد تشغيل التطبيق لتحديث الأذونات.",
|
||||
"com.affine.settings.meetings.privacy.issues.restart": "إعادة تشغيل التطبيق",
|
||||
"com.affine.settings.meetings.record.recording-mode.none": "لا تفعل شيئًا",
|
||||
"com.affine.settings.meetings.record.recording-mode.auto-start": "بدء التسجيل تلقائيًا",
|
||||
"com.affine.settings.meetings.record.recording-mode.prompt": "إظهار طلب تسجيل",
|
||||
@@ -1427,8 +1415,6 @@
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-network-search.description": "تمكين أو تعطيل ميزة البحث في الشبكة عبر الذكاء الاصطناعي.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name": "تمكين التبديل لنموذج الذكاء الاصطناعي",
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description": "تمكين أو تعطيل ميزة تبديل نموذج الذكاء الاصطناعي.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-playground.name": "تمكين ملعب AI",
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-playground.description": "تمكين أو تعطيل ميزة ملعب AI.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-database-full-width.name": "قاعدة بيانات بعرض كامل",
|
||||
"com.affine.settings.workspace.experimental-features.enable-database-full-width.description": "سيتم عرض قاعدة البيانات في وضع العرض الكامل.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-database-attachment-note.name": "ملاحظة ارتباط قاعدة البيانات",
|
||||
@@ -1586,7 +1572,6 @@
|
||||
"com.affine.settings.workspace.indexer-embedding.description": "إدارة فهرسة AFFiNE وتضمين AI AFFiNE لمعالجة المحتوى المحلي",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.title": "التضمين",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.description": "التضمين يسمح للذكاء الاصطناعي باسترجاع محتواك. إذا كان الفهرس يستخدم إعدادات محلية، فقد يؤثر ذلك على بعض نتائج التضمين.",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.disabled-tooltip": "فقط مالك مساحة العمل يمكنه تمكين تضمين مساحة العمل.",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.select-doc": "حدد مستند",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.upload-file": "رفع ملف",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.switch.title": "تضمين مساحة العمل",
|
||||
@@ -1934,9 +1919,6 @@
|
||||
"com.affine.page-starter-bar.edgeless": "بلا حدود",
|
||||
"com.affine.notification.unsupported": "رسالة غير مدعومة",
|
||||
"com.affine.notification.mention": "<1>{{username}}</1> ذكرَك في <2>{{docTitle}}</2>",
|
||||
"com.affine.notification.comment": "<1>{{username}}</1> علق في <2>{{docTitle}}</2>",
|
||||
"com.affine.notification.comment-mention": "<1>{{username}}</1> ذكرك في تعليق في <2>{{docTitle}}</2>",
|
||||
"com.affine.notification.comment-prompt": "ما هي أفكارك؟",
|
||||
"com.affine.notification.empty": "لا توجد إشعارات جديدة",
|
||||
"com.affine.notification.loading-more": "تحميل المزيد...",
|
||||
"com.affine.notification.empty.description": "سيتم إشعارك هنا بشأن الإشارات @ ودعوات مساحة العمل.",
|
||||
@@ -1948,7 +1930,6 @@
|
||||
"com.affine.notification.invitation-blocked": "\u001c\tهناك مشكلة متعلقة بدعوتك إلى <1>{{workspaceName}}</1> \u001c",
|
||||
"com.affine.notification.invitation": "<1>{{username}}</1> دعاك للانضمام إلى <2>{{workspaceName}}</2>",
|
||||
"com.affine.notification.invitation.accept": "قبول والانضمام",
|
||||
"com.affine.notification.delete-all": "حذف جميع الإشعارات",
|
||||
"tips": "نصائح",
|
||||
"Template": "نموذج",
|
||||
"com.affine.template-list.delete": "إزالة النموذج",
|
||||
@@ -2063,22 +2044,8 @@
|
||||
"com.affine.recording.start": "ابدأ",
|
||||
"com.affine.recording.dismiss": "تجاهل",
|
||||
"com.affine.recording.stop": "توقف",
|
||||
"com.affine.migration-all-docs-notification.header": "ترحيل البيانات لتحسين تجربة المستخدم",
|
||||
"com.affine.migration-all-docs-notification.desc": "نقوم بتحديث البيانات المحلية لتسهيل تسجيل وتصنيف المعلومات الخاصة بـ \"تم الإنشاء بواسطة\" و\"آخر تعديل بواسطة\". يرجى النقر على زر “ترحيل البيانات” والتأكد من استقرار الاتصال بالشبكة أثناء العملية.",
|
||||
"com.affine.migration-all-docs-notification.error": "فشل الترحيل: {{errorMessage}}",
|
||||
"com.affine.migration-all-docs-notification.button": "ترحيل البيانات",
|
||||
"com.affine.comment.comments": "تعليقات",
|
||||
"com.affine.comment.no-comments": "لا توجد تعليقات حتى الآن، حدد المحتوى لإضافة تعليق إلى",
|
||||
"com.affine.comment.delete.confirm.title": "حذف السلسلة؟",
|
||||
"com.affine.comment.delete.confirm.description": "سيتم أيضًا حذف جميع التعليقات، ولا يمكن التراجع عن هذا الإجراء.",
|
||||
"com.affine.comment.reply.delete.confirm.title": "حذف هذا الرد؟",
|
||||
"com.affine.comment.reply.delete.confirm.description": "حذف هذا الرد؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
"com.affine.comment.reply.show-more": "إظهار {{count}} ردود أخرى",
|
||||
"com.affine.comment.filter.show-resolved": "إظهار التعليقات المحلولة",
|
||||
"com.affine.comment.filter.only-my-replies": "الردود والذكر الخاصة بي فقط",
|
||||
"com.affine.comment.filter.only-current-mode": "الوضع الحالي فقط",
|
||||
"com.affine.comment.reply": "رد",
|
||||
"com.affine.comment.copy-link": "نسخ الرابط",
|
||||
"error.INTERNAL_SERVER_ERROR": "حدث خطأ داخلي.",
|
||||
"error.NETWORK_ERROR": "خطأ في الشبكة.",
|
||||
"error.TOO_MANY_REQUEST": "طلبات كثيرة جداً.",
|
||||
@@ -2162,11 +2129,9 @@
|
||||
"error.WORKSPACE_ID_REQUIRED_FOR_TEAM_SUBSCRIPTION": "مساحة العمل مطلوبة لإتمام الاشتراك في الفريق.",
|
||||
"error.WORKSPACE_ID_REQUIRED_TO_UPDATE_TEAM_SUBSCRIPTION": "معرف مساحة العمل مطلوب لتحديث اشتراك الفريق.",
|
||||
"error.COPILOT_SESSION_NOT_FOUND": "لم يتم العثور على جلسة Copilot.",
|
||||
"error.COPILOT_SESSION_INVALID_INPUT": "المدخلات غير صالحة لجلسة Copilot.",
|
||||
"error.COPILOT_SESSION_DELETED": "تم حذف جلسة Copilot.",
|
||||
"error.NO_COPILOT_PROVIDER_AVAILABLE": "لا يوجد مقدم لخدمة Copilot متاح: {{modelId}}",
|
||||
"error.NO_COPILOT_PROVIDER_AVAILABLE": "لا يوجد مقدم لخدمة Copilot متاح.",
|
||||
"error.COPILOT_FAILED_TO_GENERATE_TEXT": "فشل في توليد النص.",
|
||||
"error.COPILOT_FAILED_TO_GENERATE_EMBEDDING": "فشل في إنشاء تضمين بـ {{provider}}: {{message}}",
|
||||
"error.COPILOT_FAILED_TO_CREATE_MESSAGE": "فشل في إنشاء رسالة الدردشة.",
|
||||
"error.UNSPLASH_IS_NOT_CONFIGURED": "لم يتم تكوين Unsplash.",
|
||||
"error.COPILOT_ACTION_TAKEN": "تم اتخاذ إجراء، ولا يُسمح بمزيد من الرسائل.",
|
||||
@@ -2214,9 +2179,5 @@
|
||||
"error.INVALID_APP_CONFIG_INPUT": "إدخال تكوين التطبيق غير صالح: {{message}}",
|
||||
"error.SEARCH_PROVIDER_NOT_FOUND": "لم يتم العثور على موفر البحث.",
|
||||
"error.INVALID_SEARCH_PROVIDER_REQUEST": "قيمة غير صالحة لمزود البحث: {{reason}}",
|
||||
"error.INVALID_INDEXER_INPUT": "إدخال فهرسة غير صالح: {{reason}}",
|
||||
"error.COMMENT_NOT_FOUND": "لم يتم العثور على التعليق.",
|
||||
"error.REPLY_NOT_FOUND": "لم يتم العثور على الرد.",
|
||||
"error.COMMENT_ATTACHMENT_NOT_FOUND": "لم يتم العثور على مرفق التعليق.",
|
||||
"error.COMMENT_ATTACHMENT_QUOTA_EXCEEDED": "لقد تجاوزت حجم حصة مرفقات التعليقات."
|
||||
"error.INVALID_INDEXER_INPUT": "إدخال فهرسة غير صالح: {{reason}}"
|
||||
}
|
||||
|
||||
@@ -240,8 +240,6 @@
|
||||
"com.affine.appearanceSettings.title": "Erscheinungseinstellungen",
|
||||
"com.affine.appearanceSettings.translucentUI.description": "Transparenzeffekt in der Seitenleiste verwenden.",
|
||||
"com.affine.appearanceSettings.translucentUI.title": "Durchscheinende Benutzeroberfläche in der Seitenleiste",
|
||||
"com.affine.appearanceSettings.showLinkedDocInSidebar.title": "Verlinkte Seite in der Seitenleiste anzeigen",
|
||||
"com.affine.appearanceSettings.showLinkedDocInSidebar.description": "Steuern, ob die Struktur der verlinkten Seiten in der Seitenleiste angezeigt werden soll.",
|
||||
"com.affine.auth.change.email.message": "Deine aktuelle E-Mail ist {{email}}. Wir werden einen temporären Bestätigungslink an diese E-Mail senden.",
|
||||
"com.affine.auth.change.email.page.subtitle": "Bitte gib unten deine neue E-Mail-Adresse ein. Wir senden einen Bestätigungslink an diese E-Mail-Adresse, um den Vorgang abzuschließen.",
|
||||
"com.affine.auth.change.email.page.success.subtitle": "Herzlichen Glückwunsch! Du hast die mit deinem AFFiNE Cloud-Konto verknüpfte E-Mail-Adresse erfolgreich aktualisiert.",
|
||||
@@ -602,8 +600,6 @@
|
||||
"com.affine.import.notion.tooltip": "Importiere deine Notion-Daten. Unterstützte Importformate: HTML mit Unterseiten.",
|
||||
"com.affine.import.snapshot": "Snapshot",
|
||||
"com.affine.import.snapshot.tooltip": "Importiere deine AFFiNE-Workspace- und Seiten-Snapshot-Datei.",
|
||||
"com.affine.import.dotaffinefile": ".affine-Datei",
|
||||
"com.affine.import.dotaffinefile.tooltip": "Importiere deine AFFiNE-Datenbankdatei (.affine)",
|
||||
"com.affine.import.status.failed.message": "Import fehlgeschlagen, bitte erneut versuchen.",
|
||||
"com.affine.import.status.failed.message.no-file-selected": "Keine Datei ausgewählt",
|
||||
"com.affine.import.status.failed.title": "Importfehler",
|
||||
@@ -689,7 +685,6 @@
|
||||
"com.affine.mobile.setting.others.terms": "Nutzungsbedingungen",
|
||||
"com.affine.mobile.setting.others.title": "Datenschutz & andere",
|
||||
"com.affine.mobile.setting.others.website": "Offizielle Website",
|
||||
"com.affine.mobile.setting.others.delete-account": "Mein Konto löschen",
|
||||
"com.affine.mobile.sign-in.skip.hint": "Möchtest du die Daten lokal halten?",
|
||||
"com.affine.mobile.sign-in.skip.link": "Starte AFFiNE ohne ein Konto",
|
||||
"com.affine.moreThan30Days": "Älter als ein Monat",
|
||||
@@ -1121,9 +1116,7 @@
|
||||
"com.affine.split-view-folder-warning.description": "Geteilte Ansicht unterstützt keine Ordner.",
|
||||
"do-not-show-this-again": "Dies nicht erneut anzeigen",
|
||||
"com.affine.quicksearch.group.creation": "Neu",
|
||||
"com.affine.quicksearch.search-locally": "Lokal suchen",
|
||||
"com.affine.quicksearch.group.searchfor": "Suche nach \"{{query}}\"",
|
||||
"com.affine.quicksearch.group.searchfor-locally": "Suche nach \"{{query}}\" (lokal)",
|
||||
"com.affine.resetSyncStatus.button": "Synchronisierung zurücksetzen",
|
||||
"com.affine.resetSyncStatus.description": "Dieser Vorgang kann einige Synchronisierungsprobleme beheben.",
|
||||
"com.affine.rootAppSidebar.collections": "Sammlungen",
|
||||
@@ -1199,8 +1192,6 @@
|
||||
"com.affine.setting.notifications.email.mention.subtitle": "Du wirst per E-Mail benachrichtigt, wenn andere Mitglieder des Workspaces dich erwähnen (@).",
|
||||
"com.affine.setting.notifications.email.invites.title": "Einladungen",
|
||||
"com.affine.setting.notifications.email.invites.subtitle": "Einladungsbezogene Nachrichten werden per E-Mail gesendet.",
|
||||
"com.affine.setting.notifications.email.comments.title": "Kommentare",
|
||||
"com.affine.setting.notifications.email.comments.subtitle": "Du wirst per E-Mail benachrichtigt, wenn andere Mitglieder des Workspace Kommentare zu deinen Seiten abgeben.",
|
||||
"com.affine.setting.account": "Kontoeinstellungen",
|
||||
"com.affine.setting.account.delete": "Dein Konto löschen",
|
||||
"com.affine.setting.account.delete.message": "Sobald gelöscht, ist dein Konto nicht mehr zugänglich, und alle Daten in deinem persönlichen Cloud-Speicher werden dauerhaft gelöscht.",
|
||||
@@ -1427,8 +1418,6 @@
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-network-search.description": "KI-Netzwerksuchfunktion aktivieren oder deaktivieren.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name": "KI-Modellwechsel aktivieren",
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description": "KI-Modellwechselfunktion aktivieren oder deaktivieren.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-playground.name": "AI Playground aktivieren",
|
||||
"com.affine.settings.workspace.experimental-features.enable-ai-playground.description": "Funktion AI Playground aktivieren oder deaktivieren.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-database-full-width.name": "Datenbank volle Breite",
|
||||
"com.affine.settings.workspace.experimental-features.enable-database-full-width.description": "Die Datenbank wird in voller Breite angezeigt.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-database-attachment-note.name": "Datenbankanhangsnotiz",
|
||||
@@ -1586,7 +1575,6 @@
|
||||
"com.affine.settings.workspace.indexer-embedding.description": "Verwalte die AFFiNE-Indizierung und AFFiNE AI-Einbettung für die lokale Inhaltsverarbeitung",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.title": "Einbettung",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.description": "Das Einbetten ermöglicht es der KI, deine Inhalte abzurufen. Falls der Indexer lokale Einstellungen verwendet, kann dies einige der Ergebnisse des Einbettens beeinflussen.",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.disabled-tooltip": "Nur der Workspace-Besitzer kann die Workspace-Einbettung aktivieren.",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.select-doc": "Seite auswählen",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.upload-file": "Datei hochladen",
|
||||
"com.affine.settings.workspace.indexer-embedding.embedding.switch.title": "Einbetten von Workspaces",
|
||||
@@ -1934,9 +1922,6 @@
|
||||
"com.affine.page-starter-bar.edgeless": "Edgeless",
|
||||
"com.affine.notification.unsupported": "Nicht unterstützte Nachricht",
|
||||
"com.affine.notification.mention": "<1>{{username}}</1> hat dich in <2>{{docTitle}}</2> erwähnt",
|
||||
"com.affine.notification.comment": "<1>{{username}}</1> hat in <2>{{docTitle}}</2> kommentiert",
|
||||
"com.affine.notification.comment-mention": "<1>{{username}}</1> hat dich in einem Kommentar in <2>{{docTitle}}</2> erwähnt",
|
||||
"com.affine.notification.comment-prompt": "Was sind deine Gedanken?",
|
||||
"com.affine.notification.empty": "Keine neuen Benachrichtigungen",
|
||||
"com.affine.notification.loading-more": "Mehr laden...",
|
||||
"com.affine.notification.empty.description": "Du wirst hier über @Erwähnungen und Einladungen zum Workspace benachrichtigt.",
|
||||
@@ -1948,7 +1933,6 @@
|
||||
"com.affine.notification.invitation-blocked": "Es gibt ein Problem mit deiner Einladung zu <1>{{workspaceName}}</1> ",
|
||||
"com.affine.notification.invitation": "<1>{{username}}</1> hat dich eingeladen, <2>{{workspaceName}}</2> beizutreten",
|
||||
"com.affine.notification.invitation.accept": "Annehmen & Beitreten",
|
||||
"com.affine.notification.delete-all": "Alle Benachrichtigungen löschen",
|
||||
"tips": "Tipps",
|
||||
"Template": "Vorlage",
|
||||
"com.affine.template-list.delete": "Vorlage löschen",
|
||||
@@ -2063,22 +2047,8 @@
|
||||
"com.affine.recording.start": "Start",
|
||||
"com.affine.recording.dismiss": "Verwerfen",
|
||||
"com.affine.recording.stop": "Stopp",
|
||||
"com.affine.migration-all-docs-notification.header": "Migriere die Daten, um die Benutzererfahrung zu verbessern",
|
||||
"com.affine.migration-all-docs-notification.desc": "Wir aktualisieren die lokalen Daten, um die Erfassung und Filterung der Informationen „Erstellt von“ und „Zuletzt bearbeitet von“ zu erleichtern. Klicke bitte auf „Daten migrieren“ und stelle während des Vorgangs eine stabile Netzwerkverbindung sicher.",
|
||||
"com.affine.migration-all-docs-notification.error": "Migration fehlgeschlagen: {{errorMessage}}",
|
||||
"com.affine.migration-all-docs-notification.button": "Daten migrieren",
|
||||
"com.affine.comment.comments": "Kommentare",
|
||||
"com.affine.comment.no-comments": "Noch keine Kommentare. Wähle den Inhalt aus, dem du einen Kommentar hinzufügen möchtest.",
|
||||
"com.affine.comment.delete.confirm.title": "Thread löschen?",
|
||||
"com.affine.comment.delete.confirm.description": "Alle Kommentare werden ebenfalls gelöscht und diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"com.affine.comment.reply.delete.confirm.title": "Diese Antwort löschen?",
|
||||
"com.affine.comment.reply.delete.confirm.description": "Diese Antwort löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"com.affine.comment.reply.show-more": "{{count}} weitere Antworten anzeigen",
|
||||
"com.affine.comment.filter.show-resolved": "Gelöste Kommentare anzeigen",
|
||||
"com.affine.comment.filter.only-my-replies": "Nur meine Antworten und Erwähnungen",
|
||||
"com.affine.comment.filter.only-current-mode": "Nur aktueller Modus",
|
||||
"com.affine.comment.reply": "Antworten",
|
||||
"com.affine.comment.copy-link": "Link kopieren",
|
||||
"error.INTERNAL_SERVER_ERROR": "Es ist ein interner Fehler aufgetreten.",
|
||||
"error.NETWORK_ERROR": "Netzwerkfehler.",
|
||||
"error.TOO_MANY_REQUEST": "Zu viele Anfragen.",
|
||||
@@ -2162,11 +2132,9 @@
|
||||
"error.WORKSPACE_ID_REQUIRED_FOR_TEAM_SUBSCRIPTION": "Ein Workspace ist erforderlich, um für Team-Abonnement auszuchecken.",
|
||||
"error.WORKSPACE_ID_REQUIRED_TO_UPDATE_TEAM_SUBSCRIPTION": "Workspace-ID ist erforderlich, um Teamabonnement zu aktualisieren.",
|
||||
"error.COPILOT_SESSION_NOT_FOUND": "Copilot-Sitzung nicht gefunden.",
|
||||
"error.COPILOT_SESSION_INVALID_INPUT": "Die Eingabe für die Copilot-Sitzung ist ungültig.",
|
||||
"error.COPILOT_SESSION_DELETED": "Copilot-Sitzung wurde gelöscht.",
|
||||
"error.NO_COPILOT_PROVIDER_AVAILABLE": "Kein Copilot-Anbieter verfügbar: {{modelId}}",
|
||||
"error.NO_COPILOT_PROVIDER_AVAILABLE": "Kein Copilot-Anbieter verfügbar.",
|
||||
"error.COPILOT_FAILED_TO_GENERATE_TEXT": "Fehler beim Generieren von Text.",
|
||||
"error.COPILOT_FAILED_TO_GENERATE_EMBEDDING": "Einbettung mit {{provider}} konnte nicht generiert werden: {{message}}",
|
||||
"error.COPILOT_FAILED_TO_CREATE_MESSAGE": "Fehler beim Erstellen der Chat-Nachricht.",
|
||||
"error.UNSPLASH_IS_NOT_CONFIGURED": "Unsplash ist nicht konfiguriert.",
|
||||
"error.COPILOT_ACTION_TAKEN": "Maßnahmen wurden ergriffen, keine weiteren Nachrichten erlaubt.",
|
||||
@@ -2214,9 +2182,5 @@
|
||||
"error.INVALID_APP_CONFIG_INPUT": "Ungültige App-Konfigurationseingabe: {{message}}",
|
||||
"error.SEARCH_PROVIDER_NOT_FOUND": "Suchanbieter nicht gefunden.",
|
||||
"error.INVALID_SEARCH_PROVIDER_REQUEST": "Ungültiges Anforderungsargument an den Suchanbieter: {{reason}}",
|
||||
"error.INVALID_INDEXER_INPUT": "Ungültige Indexer-Eingabe: {{reason}}",
|
||||
"error.COMMENT_NOT_FOUND": "Kommentar nicht gefunden.",
|
||||
"error.REPLY_NOT_FOUND": "Antwort nicht gefunden.",
|
||||
"error.COMMENT_ATTACHMENT_NOT_FOUND": "Kommentar-Anhang nicht gefunden.",
|
||||
"error.COMMENT_ATTACHMENT_QUOTA_EXCEEDED": "Du hast das Größenkontingent für den Kommentar-Anhang überschritten."
|
||||
"error.INVALID_INDEXER_INPUT": "Ungültige Indexer-Eingabe: {{reason}}"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user