mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(core): cite source documents in the AI answer (#9863)
Support issue [BS-2424](https://linear.app/affine-design/issue/BS-2424). ### What changed? - Add relevant document prompt templates. - Add citation rules in system prompts. - Change message `params` type to `Record<string, any>` - Add unit test. <div class='graphite__hidden'> <div>🎥 Video uploaded on Graphite:</div> <a href="https://app.graphite.dev/media/video/sJGviKxfE3Ap685cl5bj/ec24e664-039e-4fab-bd26-b3312f011daf.mov"> <img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/sJGviKxfE3Ap685cl5bj/ec24e664-039e-4fab-bd26-b3312f011daf.mov"> </a> </div> <video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/ec24e664-039e-4fab-bd26-b3312f011daf.mov">录屏2025-01-23 10.40.38.mov</video>
This commit is contained in:
@@ -2,6 +2,8 @@ import type { getCopilotHistoriesQuery, RequestOptions } from '@affine/graphql';
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import type { BlockModel } from '@blocksuite/affine/store';
|
||||
|
||||
import type { DocContext } from '../chat-panel/chat-context';
|
||||
|
||||
export const translateLangs = [
|
||||
'English',
|
||||
'Spanish',
|
||||
@@ -37,7 +39,7 @@ export const imageProcessingTypes = [
|
||||
] as const;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
// oxlint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace BlockSuitePresets {
|
||||
type TrackerControl =
|
||||
| 'format-bar'
|
||||
@@ -57,6 +59,7 @@ declare global {
|
||||
}
|
||||
|
||||
interface AITextActionOptions {
|
||||
// user input text
|
||||
input?: string;
|
||||
stream?: boolean;
|
||||
attachments?: (string | File | Blob)[]; // blob could only be strings for the moments (url or data urls)
|
||||
@@ -101,6 +104,8 @@ declare global {
|
||||
T['stream'] extends true ? TextStream : Promise<string>;
|
||||
|
||||
interface ChatOptions extends AITextActionOptions {
|
||||
// related documents
|
||||
docs?: DocContext[];
|
||||
sessionId?: string;
|
||||
isRootSession?: boolean;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export interface BaseChip {
|
||||
|
||||
export interface DocChip extends BaseChip {
|
||||
docId: string;
|
||||
content?: Signal<string>;
|
||||
markdown?: Signal<string>;
|
||||
}
|
||||
|
||||
export interface FileChip extends BaseChip {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { AIProvider } from '../provider';
|
||||
import { reportResponse } from '../utils/action-reporter';
|
||||
import { readBlobAsURL } from '../utils/image';
|
||||
import type { AINetworkSearchConfig } from './chat-config';
|
||||
import type { ChatContextValue, ChatMessage } from './chat-context';
|
||||
import type { ChatContextValue, ChatMessage, DocContext } from './chat-context';
|
||||
import { isDocChip } from './components/utils';
|
||||
|
||||
const MaximumImageCount = 32;
|
||||
@@ -512,16 +512,11 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
if (status === 'loading' || status === 'transmitting') return;
|
||||
|
||||
const { images } = this.chatContextValue;
|
||||
if (!text && images.length === 0) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const { doc } = this.host;
|
||||
|
||||
const docsContent = chips
|
||||
.filter(isDocChip)
|
||||
.map(chip => chip.content?.value || '')
|
||||
.join('\n');
|
||||
|
||||
this.updateContext({
|
||||
images: [],
|
||||
status: 'loading',
|
||||
@@ -534,16 +529,14 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
images?.map(image => readBlobAsURL(image))
|
||||
);
|
||||
|
||||
const content =
|
||||
(markdown ? `${markdown}\n` : '') + `${docsContent}\n` + text;
|
||||
|
||||
const userInput = (markdown ? `${markdown}\n` : '') + text;
|
||||
this.updateContext({
|
||||
items: [
|
||||
...this.chatContextValue.items,
|
||||
{
|
||||
id: '',
|
||||
role: 'user',
|
||||
content: content,
|
||||
content: userInput,
|
||||
createdAt: new Date().toISOString(),
|
||||
attachments,
|
||||
},
|
||||
@@ -558,8 +551,16 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
|
||||
try {
|
||||
const abortController = new AbortController();
|
||||
const docs: DocContext[] = chips
|
||||
.filter(isDocChip)
|
||||
.filter(chip => !!chip.markdown?.value && chip.state === 'success')
|
||||
.map(chip => ({
|
||||
docId: chip.docId,
|
||||
markdown: chip.markdown?.value || '',
|
||||
}));
|
||||
const stream = AIProvider.actions.chat?.({
|
||||
input: content,
|
||||
input: userInput,
|
||||
docs: docs,
|
||||
docId: doc.id,
|
||||
attachments: images,
|
||||
workspaceId: doc.workspace.id,
|
||||
|
||||
@@ -100,10 +100,10 @@ export class ChatPanelDocChip extends SignalWatcher(
|
||||
doc.load();
|
||||
}
|
||||
const result = await extractMarkdownFromDoc(doc, this.host.std.provider);
|
||||
if (this.chip.content) {
|
||||
this.chip.content.value = result.markdown;
|
||||
if (this.chip.markdown) {
|
||||
this.chip.markdown.value = result.markdown;
|
||||
} else {
|
||||
this.chip.content = new Signal<string>(result.markdown);
|
||||
this.chip.markdown = new Signal<string>(result.markdown);
|
||||
}
|
||||
this.updateChip(this.chip, {
|
||||
state: 'success',
|
||||
|
||||
@@ -17,7 +17,7 @@ export type TextToTextOptions = {
|
||||
sessionId?: string | Promise<string>;
|
||||
content?: string;
|
||||
attachments?: (string | Blob | File)[];
|
||||
params?: Record<string, string>;
|
||||
params?: Record<string, any>;
|
||||
timeout?: number;
|
||||
stream?: boolean;
|
||||
signal?: AbortSignal;
|
||||
|
||||
@@ -117,11 +117,22 @@ export function setupAIProvider(
|
||||
const sessionId =
|
||||
options.sessionId ??
|
||||
getChatSessionId(options.workspaceId, options.docId, options.attachments);
|
||||
const { input, docs, ...rest } = options;
|
||||
const params = docs?.length
|
||||
? {
|
||||
docs: docs.map((doc, i) => ({
|
||||
docId: doc.docId,
|
||||
markdown: doc.markdown,
|
||||
index: i + 1,
|
||||
})),
|
||||
}
|
||||
: undefined;
|
||||
return textToText({
|
||||
...options,
|
||||
...rest,
|
||||
client,
|
||||
content: options.input,
|
||||
content: input,
|
||||
sessionId,
|
||||
params,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user