mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-01 17:50:50 +08:00
feat(core): add ai workspace all docs switch (#13345)
Close [AI-397](https://linear.app/affine-design/issue/AI-397) <img width="272" height="186" alt="截屏2025-07-29 11 54 20" src="https://github.com/user-attachments/assets/e171fb57-66cf-4244-894d-c27b18cbe83a" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced an AI tools configuration service, allowing users to customize AI tool usage (e.g., workspace search, reading docs) in chat and AI features. * Added a toggle in chat preferences for enabling or disabling workspace-wide document search. * AI chat components now respect user-configured tool settings across chat, retry, and playground scenarios. * **Improvements** * Enhanced chat and AI interfaces to propagate and honor user tool configuration throughout the frontend and backend. * Made draft and tool configuration services optional and safely handled their absence in chat components. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -56,7 +56,7 @@ import { StreamObjectParser } from './providers/utils';
|
||||
import { ChatSession, ChatSessionService } from './session';
|
||||
import { CopilotStorage } from './storage';
|
||||
import { ChatMessage, ChatQuerySchema } from './types';
|
||||
import { getSignal } from './utils';
|
||||
import { getSignal, getTools } from './utils';
|
||||
import { CopilotWorkflowService, GraphExecutorState } from './workflow';
|
||||
|
||||
export interface ChatEvent {
|
||||
@@ -244,7 +244,8 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
info.finalMessage = finalMessage.filter(m => m.role !== 'system');
|
||||
metrics.ai.counter('chat_calls').add(1, { model });
|
||||
|
||||
const { reasoning, webSearch } = ChatQuerySchema.parse(query);
|
||||
const { reasoning, webSearch, toolsConfig } =
|
||||
ChatQuerySchema.parse(query);
|
||||
const content = await provider.text({ modelId: model }, finalMessage, {
|
||||
...session.config.promptConfig,
|
||||
signal: getSignal(req).signal,
|
||||
@@ -253,6 +254,7 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
workspace: session.config.workspaceId,
|
||||
reasoning,
|
||||
webSearch,
|
||||
tools: getTools(session.config.promptConfig?.tools, toolsConfig),
|
||||
});
|
||||
|
||||
session.push({
|
||||
@@ -306,7 +308,8 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
}
|
||||
});
|
||||
|
||||
const { messageId, reasoning, webSearch } = ChatQuerySchema.parse(query);
|
||||
const { messageId, reasoning, webSearch, toolsConfig } =
|
||||
ChatQuerySchema.parse(query);
|
||||
|
||||
const source$ = from(
|
||||
provider.streamText({ modelId: model }, finalMessage, {
|
||||
@@ -317,6 +320,7 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
workspace: session.config.workspaceId,
|
||||
reasoning,
|
||||
webSearch,
|
||||
tools: getTools(session.config.promptConfig?.tools, toolsConfig),
|
||||
})
|
||||
).pipe(
|
||||
connect(shared$ =>
|
||||
@@ -398,7 +402,8 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
}
|
||||
});
|
||||
|
||||
const { messageId, reasoning, webSearch } = ChatQuerySchema.parse(query);
|
||||
const { messageId, reasoning, webSearch, toolsConfig } =
|
||||
ChatQuerySchema.parse(query);
|
||||
|
||||
const source$ = from(
|
||||
provider.streamObject({ modelId: model }, finalMessage, {
|
||||
@@ -409,6 +414,7 @@ export class CopilotController implements BeforeApplicationShutdown {
|
||||
workspace: session.config.workspaceId,
|
||||
reasoning,
|
||||
webSearch,
|
||||
tools: getTools(session.config.promptConfig?.tools, toolsConfig),
|
||||
})
|
||||
).pipe(
|
||||
connect(shared$ =>
|
||||
|
||||
@@ -57,28 +57,28 @@ export const VertexSchema: JSONSchema = {
|
||||
|
||||
// ========== prompt ==========
|
||||
|
||||
export const PromptToolsSchema = z
|
||||
.enum([
|
||||
'codeArtifact',
|
||||
'conversationSummary',
|
||||
// work with morph
|
||||
'docEdit',
|
||||
// work with indexer
|
||||
'docRead',
|
||||
'docKeywordSearch',
|
||||
// work with embeddings
|
||||
'docSemanticSearch',
|
||||
// work with exa/model internal tools
|
||||
'webSearch',
|
||||
// artifact tools
|
||||
'docCompose',
|
||||
// section editing
|
||||
'sectionEdit',
|
||||
])
|
||||
.array();
|
||||
|
||||
export const PromptConfigStrictSchema = z.object({
|
||||
tools: z
|
||||
.enum([
|
||||
'codeArtifact',
|
||||
'conversationSummary',
|
||||
// work with morph
|
||||
'docEdit',
|
||||
// work with indexer
|
||||
'docRead',
|
||||
'docKeywordSearch',
|
||||
// work with embeddings
|
||||
'docSemanticSearch',
|
||||
// work with exa/model internal tools
|
||||
'webSearch',
|
||||
// artifact tools
|
||||
'docCompose',
|
||||
// section editing
|
||||
'sectionEdit',
|
||||
])
|
||||
.array()
|
||||
.nullable()
|
||||
.optional(),
|
||||
tools: PromptToolsSchema.nullable().optional(),
|
||||
// params requirements
|
||||
requireContent: z.boolean().nullable().optional(),
|
||||
requireAttachment: z.boolean().nullable().optional(),
|
||||
@@ -107,6 +107,8 @@ export const PromptConfigSchema =
|
||||
|
||||
export type PromptConfig = z.infer<typeof PromptConfigSchema>;
|
||||
|
||||
export type PromptTools = z.infer<typeof PromptToolsSchema>;
|
||||
|
||||
// ========== message ==========
|
||||
|
||||
export const EmbeddingMessage = z.array(z.string().trim().min(1)).min(1);
|
||||
|
||||
@@ -16,6 +16,23 @@ const zMaybeString = z.preprocess(val => {
|
||||
return s === '' || s == null ? undefined : s;
|
||||
}, z.string().min(1).optional());
|
||||
|
||||
const ToolsConfigSchema = z.preprocess(
|
||||
val => {
|
||||
// if val is a string, try to parse it as JSON
|
||||
if (typeof val === 'string') {
|
||||
try {
|
||||
return JSON.parse(val);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return val || {};
|
||||
},
|
||||
z.record(z.enum(['searchWorkspace', 'readingDocs']), z.boolean()).default({})
|
||||
);
|
||||
|
||||
export type ToolsConfig = z.infer<typeof ToolsConfigSchema>;
|
||||
|
||||
export const ChatQuerySchema = z
|
||||
.object({
|
||||
messageId: zMaybeString,
|
||||
@@ -23,15 +40,25 @@ export const ChatQuerySchema = z
|
||||
retry: zBool,
|
||||
reasoning: zBool,
|
||||
webSearch: zBool,
|
||||
toolsConfig: ToolsConfigSchema,
|
||||
})
|
||||
.catchall(z.string())
|
||||
.transform(
|
||||
({ messageId, modelId, retry, reasoning, webSearch, ...params }) => ({
|
||||
({
|
||||
messageId,
|
||||
modelId,
|
||||
retry,
|
||||
reasoning,
|
||||
webSearch,
|
||||
toolsConfig,
|
||||
...params
|
||||
}) => ({
|
||||
messageId,
|
||||
modelId,
|
||||
retry,
|
||||
reasoning,
|
||||
webSearch,
|
||||
toolsConfig,
|
||||
params,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Readable } from 'node:stream';
|
||||
import type { Request } from 'express';
|
||||
|
||||
import { readBufferWithLimit } from '../../base';
|
||||
import { MAX_EMBEDDABLE_SIZE } from './types';
|
||||
import { PromptTools } from './providers';
|
||||
import { MAX_EMBEDDABLE_SIZE, ToolsConfig } from './types';
|
||||
|
||||
export function readStream(
|
||||
readable: Readable,
|
||||
@@ -49,3 +50,33 @@ export function getSignal(req: Request): SignalReturnType {
|
||||
onConnectionClosed: cb => (callback = cb),
|
||||
};
|
||||
}
|
||||
|
||||
export function getTools(
|
||||
tools?: PromptTools | null,
|
||||
toolsConfig?: ToolsConfig
|
||||
) {
|
||||
if (!tools || !toolsConfig) {
|
||||
return tools;
|
||||
}
|
||||
let result: PromptTools = tools;
|
||||
(Object.keys(toolsConfig) as Array<keyof ToolsConfig>).forEach(key => {
|
||||
const value = toolsConfig[key];
|
||||
switch (key) {
|
||||
case 'searchWorkspace':
|
||||
if (value === false) {
|
||||
result = result.filter(tool => {
|
||||
return tool !== 'docKeywordSearch' && tool !== 'docSemanticSearch';
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'readingDocs':
|
||||
if (value === false) {
|
||||
result = result.filter(tool => {
|
||||
return tool !== 'docRead';
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AIToolsConfig } from '@affine/core/modules/ai-button';
|
||||
import type {
|
||||
AddContextFileInput,
|
||||
ContextMatchedDocChunk,
|
||||
@@ -142,6 +143,7 @@ declare global {
|
||||
webSearch?: boolean;
|
||||
reasoning?: boolean;
|
||||
modelId?: string;
|
||||
toolsConfig?: AIToolsConfig | undefined;
|
||||
contexts?: {
|
||||
docs: AIDocContextOption[];
|
||||
files: AIFileContextOption[];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
|
||||
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';
|
||||
@@ -105,6 +106,9 @@ export class AIChatPanelTitle extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor notificationService!: NotificationService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor session!: CopilotChatHistoryFragment | null | undefined;
|
||||
|
||||
@@ -142,6 +146,7 @@ export class AIChatPanelTitle extends SignalWatcher(
|
||||
.affineThemeService=${this.affineThemeService}
|
||||
.notificationService=${this.notificationService}
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
></playground-content>
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { AIDraftService } from '@affine/core/modules/ai-button';
|
||||
import type {
|
||||
AIDraftService,
|
||||
AIToolsConfigService,
|
||||
} from '@affine/core/modules/ai-button';
|
||||
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';
|
||||
@@ -119,6 +122,9 @@ export class ChatPanel extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor aiDraftService!: AIDraftService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@state()
|
||||
accessor session: CopilotChatHistoryFragment | null | undefined;
|
||||
|
||||
@@ -387,6 +393,7 @@ export class ChatPanel extends SignalWatcher(
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
.affineThemeService=${this.affineThemeService}
|
||||
.notificationService=${this.notificationService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.session=${this.session}
|
||||
.status=${this.status}
|
||||
.embeddingProgress=${this.embeddingProgress}
|
||||
@@ -413,6 +420,7 @@ export class ChatPanel extends SignalWatcher(
|
||||
.affineThemeService=${this.affineThemeService}
|
||||
.notificationService=${this.notificationService}
|
||||
.aiDraftService=${this.aiDraftService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
||||
.onContextChange=${this.onContextChange}
|
||||
.width=${this.sidebarWidth}
|
||||
|
||||
+9
-2
@@ -1,6 +1,9 @@
|
||||
import './ai-chat-composer-tip';
|
||||
|
||||
import type { AIDraftService } from '@affine/core/modules/ai-button';
|
||||
import type {
|
||||
AIDraftService,
|
||||
AIToolsConfigService,
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import type {
|
||||
ContextEmbedStatus,
|
||||
@@ -118,7 +121,10 @@ export class AIChatComposer extends SignalWatcher(
|
||||
accessor notificationService!: NotificationService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiDraftService!: AIDraftService;
|
||||
accessor aiDraftService: AIDraftService | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@state()
|
||||
accessor chips: ChatChip[] = [];
|
||||
@@ -166,6 +172,7 @@ export class AIChatComposer extends SignalWatcher(
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.aiDraftService=${this.aiDraftService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.portalContainer=${this.portalContainer}
|
||||
.onChatSuccess=${this.onChatSuccess}
|
||||
.trackOptions=${this.trackOptions}
|
||||
|
||||
+24
-11
@@ -1,4 +1,7 @@
|
||||
import type { AIDraftService } from '@affine/core/modules/ai-button';
|
||||
import type {
|
||||
AIDraftService,
|
||||
AIToolsConfigService,
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import type { AIDraftState } from '@affine/core/modules/ai-button/services/ai-draft';
|
||||
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
@@ -153,7 +156,10 @@ export class AIChatContent extends SignalWatcher(
|
||||
accessor notificationService!: NotificationService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiDraftService!: AIDraftService;
|
||||
accessor aiDraftService: AIDraftService | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onEmbeddingProgressChange:
|
||||
@@ -273,6 +279,9 @@ export class AIChatContent extends SignalWatcher(
|
||||
};
|
||||
|
||||
private readonly updateDraft = async (context: Partial<ChatContextValue>) => {
|
||||
if (!this.aiDraftService) {
|
||||
return;
|
||||
}
|
||||
const draft: Partial<AIDraftState> = pick(context, [
|
||||
'quote',
|
||||
'images',
|
||||
@@ -344,15 +353,17 @@ export class AIChatContent extends SignalWatcher(
|
||||
|
||||
this.initChatContent().catch(console.error);
|
||||
|
||||
this.aiDraftService
|
||||
.getDraft()
|
||||
.then(draft => {
|
||||
this.chatContextValue = {
|
||||
...this.chatContextValue,
|
||||
...draft,
|
||||
};
|
||||
})
|
||||
.catch(console.error);
|
||||
if (this.aiDraftService) {
|
||||
this.aiDraftService
|
||||
.getDraft()
|
||||
.then(draft => {
|
||||
this.chatContextValue = {
|
||||
...this.chatContextValue,
|
||||
...draft,
|
||||
};
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
this._disposables.add(
|
||||
AIProvider.slots.actions.subscribe(({ event }) => {
|
||||
@@ -405,6 +416,7 @@ export class AIChatContent extends SignalWatcher(
|
||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||
.affineThemeService=${this.affineThemeService}
|
||||
.notificationService=${this.notificationService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.networkSearchConfig=${this.networkSearchConfig}
|
||||
.reasoningConfig=${this.reasoningConfig}
|
||||
.width=${this.width}
|
||||
@@ -434,6 +446,7 @@ export class AIChatContent extends SignalWatcher(
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
.notificationService=${this.notificationService}
|
||||
.aiDraftService=${this.aiDraftService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.trackOptions=${{
|
||||
where: 'chat-panel',
|
||||
control: 'chat-send',
|
||||
|
||||
+29
-15
@@ -1,4 +1,7 @@
|
||||
import type { AIDraftService } from '@affine/core/modules/ai-button';
|
||||
import type {
|
||||
AIDraftService,
|
||||
AIToolsConfigService,
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
@@ -353,7 +356,10 @@ export class AIChatInput extends SignalWatcher(
|
||||
accessor searchMenuConfig!: SearchMenuConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiDraftService!: AIDraftService;
|
||||
accessor aiDraftService: AIDraftService | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor isRootSession: boolean = true;
|
||||
@@ -406,13 +412,15 @@ export class AIChatInput extends SignalWatcher(
|
||||
|
||||
protected override firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.aiDraftService
|
||||
.getDraft()
|
||||
.then(draft => {
|
||||
this.textarea.value = draft.input;
|
||||
this.isInputEmpty = !this.textarea.value.trim();
|
||||
})
|
||||
.catch(console.error);
|
||||
if (this.aiDraftService) {
|
||||
this.aiDraftService
|
||||
.getDraft()
|
||||
.then(draft => {
|
||||
this.textarea.value = draft.input;
|
||||
this.isInputEmpty = !this.textarea.value.trim();
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
@@ -493,6 +501,7 @@ export class AIChatInput extends SignalWatcher(
|
||||
.networkSearchVisible=${!!this.networkSearchConfig.visible.value}
|
||||
.isNetworkActive=${this._isNetworkActive}
|
||||
.onNetworkActiveChange=${this._toggleNetworkSearch}
|
||||
.toolsConfigService=${this.aiToolsConfigService}
|
||||
></chat-input-preference>
|
||||
${status === 'transmitting' || status === 'loading'
|
||||
? html`<button
|
||||
@@ -536,9 +545,11 @@ export class AIChatInput extends SignalWatcher(
|
||||
textarea.style.overflowY = 'scroll';
|
||||
}
|
||||
|
||||
await this.aiDraftService.setDraft({
|
||||
input: value,
|
||||
});
|
||||
if (this.aiDraftService) {
|
||||
await this.aiDraftService.setDraft({
|
||||
input: value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _handleKeyDown = async (evt: KeyboardEvent) => {
|
||||
@@ -593,9 +604,11 @@ export class AIChatInput extends SignalWatcher(
|
||||
this.isInputEmpty = true;
|
||||
this.textarea.style.height = 'unset';
|
||||
|
||||
await this.aiDraftService.setDraft({
|
||||
input: '',
|
||||
});
|
||||
if (this.aiDraftService) {
|
||||
await this.aiDraftService.setDraft({
|
||||
input: '',
|
||||
});
|
||||
}
|
||||
await this.send(value);
|
||||
};
|
||||
|
||||
@@ -647,6 +660,7 @@ export class AIChatInput extends SignalWatcher(
|
||||
control: this.trackOptions?.control,
|
||||
webSearch: this._isNetworkActive,
|
||||
reasoning: this._isReasoningActive,
|
||||
toolsConfig: this.aiToolsConfigService.config.value,
|
||||
modelId: this.modelId,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
|
||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||
import {
|
||||
menu,
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import {
|
||||
ArrowDownSmallIcon,
|
||||
CloudWorkspaceIcon,
|
||||
ThinkingIcon,
|
||||
WebIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
@@ -81,6 +83,9 @@ export class ChatInputPreference extends SignalWatcher(
|
||||
| undefined;
|
||||
// --------- search props end ---------
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor toolsConfigService!: AIToolsConfigService;
|
||||
|
||||
// private readonly _onModelChange = (modelId: string) => {
|
||||
// this.onModelChange?.(modelId);
|
||||
// };
|
||||
@@ -126,6 +131,19 @@ export class ChatInputPreference extends SignalWatcher(
|
||||
onChange: (value: boolean) => this.onNetworkActiveChange?.(value),
|
||||
class: { 'preference-action': true },
|
||||
testId: 'chat-network-search',
|
||||
}),
|
||||
menu.toggleSwitch({
|
||||
name: 'Workspace All Docs',
|
||||
prefix: CloudWorkspaceIcon(),
|
||||
on:
|
||||
!!this.toolsConfigService.config.value.searchWorkspace &&
|
||||
!!this.toolsConfigService.config.value.readingDocs,
|
||||
onChange: (value: boolean) =>
|
||||
this.toolsConfigService.setConfig({
|
||||
searchWorkspace: value,
|
||||
readingDocs: value,
|
||||
}),
|
||||
class: { 'preference-action': true },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
+5
@@ -1,3 +1,4 @@
|
||||
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
|
||||
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
@@ -206,6 +207,9 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayService!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
@@ -467,6 +471,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
||||
isRootSession: true,
|
||||
reasoning: this._isReasoningActive,
|
||||
webSearch: this._isNetworkActive,
|
||||
toolsConfig: this.aiToolsConfigService.config.value,
|
||||
});
|
||||
|
||||
for await (const text of stream) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
|
||||
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';
|
||||
@@ -173,6 +174,9 @@ export class PlaygroundChat extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor notificationService!: NotificationService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor addChat!: () => Promise<void>;
|
||||
|
||||
@@ -338,6 +342,7 @@ export class PlaygroundChat extends SignalWatcher(
|
||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||
.affineThemeService=${this.affineThemeService}
|
||||
.notificationService=${this.notificationService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.networkSearchConfig=${this.networkSearchConfig}
|
||||
.reasoningConfig=${this.reasoningConfig}
|
||||
.messages=${this.messages}
|
||||
@@ -357,6 +362,7 @@ export class PlaygroundChat extends SignalWatcher(
|
||||
.docDisplayConfig=${this.docDisplayConfig}
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.notificationService=${this.notificationService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
></ai-chat-composer>
|
||||
</div>`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
|
||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||
@@ -92,6 +93,9 @@ export class PlaygroundContent extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor notificationService!: NotificationService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@state()
|
||||
accessor sessions: CopilotChatHistoryFragment[] = [];
|
||||
|
||||
@@ -347,6 +351,7 @@ export class PlaygroundContent extends SignalWatcher(
|
||||
.affineFeatureFlagService=${this.affineFeatureFlagService}
|
||||
.affineThemeService=${this.affineThemeService}
|
||||
.notificationService=${this.notificationService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.addChat=${this.addChat}
|
||||
></playground-chat>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import type {
|
||||
AIDraftService,
|
||||
AIToolsConfigService,
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import type {
|
||||
@@ -393,6 +397,7 @@ export class AIChatBlockPeekView extends LitElement {
|
||||
control: 'chat-send',
|
||||
reasoning: this._isReasoningActive,
|
||||
webSearch: this._isNetworkActive,
|
||||
toolsConfig: this.aiToolsConfigService.config.value,
|
||||
});
|
||||
|
||||
for await (const text of stream) {
|
||||
@@ -608,6 +613,7 @@ export class AIChatBlockPeekView extends LitElement {
|
||||
.searchMenuConfig=${this.searchMenuConfig}
|
||||
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
|
||||
.notificationService=${notificationService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.onChatSuccess=${this._onChatSuccess}
|
||||
.trackOptions=${{
|
||||
where: 'ai-chat-block',
|
||||
@@ -646,6 +652,12 @@ export class AIChatBlockPeekView extends LitElement {
|
||||
@property({ attribute: false })
|
||||
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiDraftService!: AIDraftService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@state()
|
||||
accessor _historyMessages: ChatMessage[] = [];
|
||||
|
||||
@@ -682,7 +694,9 @@ export const AIChatBlockPeekViewTemplate = (
|
||||
networkSearchConfig: AINetworkSearchConfig,
|
||||
reasoningConfig: AIReasoningConfig,
|
||||
affineFeatureFlagService: FeatureFlagService,
|
||||
affineWorkspaceDialogService: WorkspaceDialogService
|
||||
affineWorkspaceDialogService: WorkspaceDialogService,
|
||||
aiDraftService: AIDraftService,
|
||||
aiToolsConfigService: AIToolsConfigService
|
||||
) => {
|
||||
return html`<ai-chat-block-peek-view
|
||||
.blockModel=${blockModel}
|
||||
@@ -693,5 +707,7 @@ export const AIChatBlockPeekViewTemplate = (
|
||||
.reasoningConfig=${reasoningConfig}
|
||||
.affineFeatureFlagService=${affineFeatureFlagService}
|
||||
.affineWorkspaceDialogService=${affineWorkspaceDialogService}
|
||||
.aiDraftService=${aiDraftService}
|
||||
.aiToolsConfigService=${aiToolsConfigService}
|
||||
></ai-chat-block-peek-view>`;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { showAILoginRequiredAtom } from '@affine/core/components/affine/auth/ai-login-required';
|
||||
import type { AIToolsConfig } from '@affine/core/modules/ai-button';
|
||||
import type { UserFriendlyError } from '@affine/error';
|
||||
import {
|
||||
addContextCategoryMutation,
|
||||
@@ -415,6 +416,7 @@ export class CopilotClient {
|
||||
reasoning,
|
||||
webSearch,
|
||||
modelId,
|
||||
toolsConfig,
|
||||
signal,
|
||||
}: {
|
||||
sessionId: string;
|
||||
@@ -422,6 +424,7 @@ export class CopilotClient {
|
||||
reasoning?: boolean;
|
||||
webSearch?: boolean;
|
||||
modelId?: string;
|
||||
toolsConfig?: AIToolsConfig;
|
||||
signal?: AbortSignal;
|
||||
}) {
|
||||
let url = `/api/copilot/chat/${sessionId}`;
|
||||
@@ -430,6 +433,7 @@ export class CopilotClient {
|
||||
reasoning,
|
||||
webSearch,
|
||||
modelId,
|
||||
toolsConfig,
|
||||
});
|
||||
if (queryString) {
|
||||
url += `?${queryString}`;
|
||||
@@ -446,12 +450,14 @@ export class CopilotClient {
|
||||
reasoning,
|
||||
webSearch,
|
||||
modelId,
|
||||
toolsConfig,
|
||||
}: {
|
||||
sessionId: string;
|
||||
messageId?: string;
|
||||
reasoning?: boolean;
|
||||
webSearch?: boolean;
|
||||
modelId?: string;
|
||||
toolsConfig?: AIToolsConfig;
|
||||
},
|
||||
endpoint = Endpoint.Stream
|
||||
) {
|
||||
@@ -461,6 +467,7 @@ export class CopilotClient {
|
||||
reasoning,
|
||||
webSearch,
|
||||
modelId,
|
||||
toolsConfig,
|
||||
});
|
||||
if (queryString) {
|
||||
url += `?${queryString}`;
|
||||
@@ -486,7 +493,9 @@ export class CopilotClient {
|
||||
return this.eventSource(url);
|
||||
}
|
||||
|
||||
paramsToQueryString(params: Record<string, string | boolean | undefined>) {
|
||||
paramsToQueryString(
|
||||
params: Record<string, string | boolean | undefined | Record<string, any>>
|
||||
) {
|
||||
const queryString = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (typeof value === 'boolean') {
|
||||
@@ -495,6 +504,8 @@ export class CopilotClient {
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
queryString.append(key, value);
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
queryString.append(key, JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
return queryString.toString();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AIToolsConfig } from '@affine/core/modules/ai-button';
|
||||
import { partition } from 'lodash-es';
|
||||
|
||||
import { AIProvider } from './ai-provider';
|
||||
@@ -22,6 +23,7 @@ export type TextToTextOptions = {
|
||||
reasoning?: boolean;
|
||||
webSearch?: boolean;
|
||||
modelId?: string;
|
||||
toolsConfig?: AIToolsConfig;
|
||||
};
|
||||
|
||||
export type ToImageOptions = TextToTextOptions & {
|
||||
@@ -119,6 +121,7 @@ export function textToText({
|
||||
reasoning,
|
||||
webSearch,
|
||||
modelId,
|
||||
toolsConfig,
|
||||
}: TextToTextOptions) {
|
||||
let messageId: string | undefined;
|
||||
|
||||
@@ -141,6 +144,7 @@ export function textToText({
|
||||
reasoning,
|
||||
webSearch,
|
||||
modelId,
|
||||
toolsConfig,
|
||||
},
|
||||
endpoint
|
||||
);
|
||||
|
||||
@@ -11,7 +11,10 @@ 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 { AIDraftService } from '@affine/core/modules/ai-button';
|
||||
import {
|
||||
AIDraftService,
|
||||
AIToolsConfigService,
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import {
|
||||
EventSourceService,
|
||||
FetchService,
|
||||
@@ -223,6 +226,7 @@ export const Component = () => {
|
||||
confirmModal.openConfirmModal
|
||||
);
|
||||
content.aiDraftService = framework.get(AIDraftService);
|
||||
content.aiToolsConfigService = framework.get(AIToolsConfigService);
|
||||
content.createSession = createSession;
|
||||
content.onOpenDoc = onOpenDoc;
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-
|
||||
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 { AIDraftService } from '@affine/core/modules/ai-button';
|
||||
import {
|
||||
AIDraftService,
|
||||
AIToolsConfigService,
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { AppThemeService } from '@affine/core/modules/theme';
|
||||
@@ -97,6 +100,8 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
||||
confirmModal.openConfirmModal
|
||||
);
|
||||
chatPanelRef.current.aiDraftService = framework.get(AIDraftService);
|
||||
chatPanelRef.current.aiToolsConfigService =
|
||||
framework.get(AIToolsConfigService);
|
||||
|
||||
containerRef.current?.append(chatPanelRef.current);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export { AIButtonProvider } from './provider/ai-button';
|
||||
export { AIButtonService } from './services/ai-button';
|
||||
export { AIDraftService } from './services/ai-draft';
|
||||
export {
|
||||
type AIToolsConfig,
|
||||
AIToolsConfigService,
|
||||
} from './services/tools-config';
|
||||
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
@@ -13,6 +17,7 @@ import { AIDraftService } from './services/ai-draft';
|
||||
import { AINetworkSearchService } from './services/network-search';
|
||||
import { AIPlaygroundService } from './services/playground';
|
||||
import { AIReasoningService } from './services/reasoning';
|
||||
import { AIToolsConfigService } from './services/tools-config';
|
||||
|
||||
export const configureAIButtonModule = (framework: Framework) => {
|
||||
framework.service(AIButtonService, container => {
|
||||
@@ -40,3 +45,7 @@ export function configureAIDraftModule(framework: Framework) {
|
||||
.scope(WorkspaceScope)
|
||||
.service(AIDraftService, [GlobalStateService, CacheStorage]);
|
||||
}
|
||||
|
||||
export function configureAIToolsConfigModule(framework: Framework) {
|
||||
framework.service(AIToolsConfigService, [GlobalStateService]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
createSignalFromObservable,
|
||||
type Signal,
|
||||
} from '@blocksuite/affine/shared/utils';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import type { GlobalStateService } from '../../storage';
|
||||
|
||||
const AI_TOOLS_CONFIG_KEY = 'AIToolsConfig';
|
||||
|
||||
export interface AIToolsConfig {
|
||||
searchWorkspace?: boolean;
|
||||
readingDocs?: boolean;
|
||||
}
|
||||
|
||||
export class AIToolsConfigService extends Service {
|
||||
constructor(private readonly globalStateService: GlobalStateService) {
|
||||
super();
|
||||
|
||||
const { signal, cleanup: enabledCleanup } =
|
||||
createSignalFromObservable<AIToolsConfig>(this.config$, {
|
||||
searchWorkspace: true,
|
||||
readingDocs: true,
|
||||
});
|
||||
this.config = signal;
|
||||
this.disposables.push(enabledCleanup);
|
||||
}
|
||||
|
||||
config: Signal<AIToolsConfig>;
|
||||
|
||||
private readonly config$ = LiveData.from(
|
||||
this.globalStateService.globalState.watch<AIToolsConfig>(
|
||||
AI_TOOLS_CONFIG_KEY
|
||||
),
|
||||
undefined
|
||||
).pipe(
|
||||
map(config => ({
|
||||
searchWorkspace: config?.searchWorkspace ?? true,
|
||||
readingDocs: config?.readingDocs ?? true,
|
||||
}))
|
||||
);
|
||||
|
||||
setConfig = (data: Partial<AIToolsConfig>) => {
|
||||
this.globalStateService.globalState.set(AI_TOOLS_CONFIG_KEY, {
|
||||
...this.config.value,
|
||||
...data,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
configureAINetworkSearchModule,
|
||||
configureAIPlaygroundModule,
|
||||
configureAIReasoningModule,
|
||||
configureAIToolsConfigModule,
|
||||
} from './ai-button';
|
||||
import { configureAppSidebarModule } from './app-sidebar';
|
||||
import { configAtMenuConfigModule } from './at-menu-config';
|
||||
@@ -112,6 +113,7 @@ export function configureCommonModules(framework: Framework) {
|
||||
configureAIPlaygroundModule(framework);
|
||||
configureAIButtonModule(framework);
|
||||
configureAIDraftModule(framework);
|
||||
configureAIToolsConfigModule(framework);
|
||||
configureTemplateDocModule(framework);
|
||||
configureBlobManagementModule(framework);
|
||||
configureMediaModule(framework);
|
||||
|
||||
+11
-1
@@ -2,6 +2,10 @@ import { toReactNode } from '@affine/component';
|
||||
import { AIChatBlockPeekViewTemplate } from '@affine/core/blocksuite/ai';
|
||||
import type { AIChatBlockModel } from '@affine/core/blocksuite/ai/blocks/ai-chat-block/model/ai-chat-model';
|
||||
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
|
||||
import {
|
||||
AIDraftService,
|
||||
AIToolsConfigService,
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import type { EditorHost } from '@blocksuite/affine/std';
|
||||
@@ -27,6 +31,8 @@ export const AIChatBlockPeekView = ({
|
||||
const framework = useFramework();
|
||||
const affineFeatureFlagService = framework.get(FeatureFlagService);
|
||||
const affineWorkspaceDialogService = framework.get(WorkspaceDialogService);
|
||||
const aiDraftService = framework.get(AIDraftService);
|
||||
const aiToolsConfigService = framework.get(AIToolsConfigService);
|
||||
|
||||
return useMemo(() => {
|
||||
const template = AIChatBlockPeekViewTemplate(
|
||||
@@ -37,7 +43,9 @@ export const AIChatBlockPeekView = ({
|
||||
networkSearchConfig,
|
||||
reasoningConfig,
|
||||
affineFeatureFlagService,
|
||||
affineWorkspaceDialogService
|
||||
affineWorkspaceDialogService,
|
||||
aiDraftService,
|
||||
aiToolsConfigService
|
||||
);
|
||||
return toReactNode(template);
|
||||
}, [
|
||||
@@ -49,5 +57,7 @@ export const AIChatBlockPeekView = ({
|
||||
reasoningConfig,
|
||||
affineFeatureFlagService,
|
||||
affineWorkspaceDialogService,
|
||||
aiDraftService,
|
||||
aiToolsConfigService,
|
||||
]);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user