mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-06 11:59:51 +08:00
fix(core): slash ask ai returns make it real action result (#10349)
Fix issue [AF-2252](https://linear.app/affine-design/issue/AF-2252). ## What Changed? - Remove useless `generateAnswer` configuration for inline ask ai - Refactor the common `updateAIPanelConfig` function - Use empty string instead of meaningless `placeholder` string - Remove unnecessary high-order function wrappers
This commit is contained in:
@@ -278,7 +278,7 @@ export class AffineAIPanelWidget extends WidgetComponent {
|
|||||||
input?: string,
|
input?: string,
|
||||||
shouldTriggerCallback?: boolean
|
shouldTriggerCallback?: boolean
|
||||||
) => {
|
) => {
|
||||||
if (input) {
|
if (typeof input === 'string') {
|
||||||
this._inputText = input;
|
this._inputText = input;
|
||||||
this.generate();
|
this.generate();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { type EditorHost, TextSelection } from '@blocksuite/affine/block-std';
|
import { type EditorHost, TextSelection } from '@blocksuite/affine/block-std';
|
||||||
import {
|
import {
|
||||||
type AffineAIPanelWidget,
|
type AffineAIPanelWidget,
|
||||||
type AffineAIPanelWidgetConfig,
|
|
||||||
type AIError,
|
type AIError,
|
||||||
type AIItemGroupConfig,
|
type AIItemGroupConfig,
|
||||||
|
AIStarIconWithAnimation,
|
||||||
createLitPortal,
|
createLitPortal,
|
||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
import { assertExists } from '@blocksuite/affine/global/utils';
|
||||||
@@ -64,66 +64,63 @@ export function bindTextStream(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function actionToStream<T extends keyof BlockSuitePresets.AIActions>(
|
function actionToStream<T extends keyof BlockSuitePresets.AIActions>(
|
||||||
|
host: EditorHost,
|
||||||
id: T,
|
id: T,
|
||||||
|
input: string,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
variants?: Omit<
|
variants?: Omit<
|
||||||
Parameters<BlockSuitePresets.AIActions[T]>[0],
|
Parameters<BlockSuitePresets.AIActions[T]>[0],
|
||||||
keyof BlockSuitePresets.AITextActionOptions
|
keyof BlockSuitePresets.AITextActionOptions
|
||||||
>,
|
>,
|
||||||
trackerOptions?: BlockSuitePresets.TrackerOptions
|
trackerOptions?: BlockSuitePresets.TrackerOptions
|
||||||
) {
|
): BlockSuitePresets.TextStream | undefined {
|
||||||
const action = AIProvider.actions[id];
|
const action = AIProvider.actions[id];
|
||||||
if (!action || typeof action !== 'function') return;
|
if (!action || typeof action !== 'function') return;
|
||||||
return (host: EditorHost): BlockSuitePresets.TextStream => {
|
|
||||||
let stream: BlockSuitePresets.TextStream | undefined;
|
|
||||||
return {
|
|
||||||
async *[Symbol.asyncIterator]() {
|
|
||||||
const { currentTextSelection, selectedBlocks } = getSelections(host);
|
|
||||||
|
|
||||||
let markdown: string;
|
let stream: BlockSuitePresets.TextStream | undefined;
|
||||||
let attachments: File[] = [];
|
return {
|
||||||
|
async *[Symbol.asyncIterator]() {
|
||||||
|
const { currentTextSelection, selectedBlocks } = getSelections(host);
|
||||||
|
|
||||||
if (currentTextSelection?.isCollapsed()) {
|
let markdown: string;
|
||||||
markdown = await selectAboveBlocks(host);
|
let attachments: File[] = [];
|
||||||
} else {
|
|
||||||
[markdown, attachments] = await Promise.all([
|
|
||||||
getSelectedTextContent(host),
|
|
||||||
getSelectedImagesAsBlobs(host),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for now if there are more than one selected blocks, we will not omit the attachments
|
if (currentTextSelection?.isCollapsed()) {
|
||||||
const sendAttachments =
|
markdown = await selectAboveBlocks(host);
|
||||||
selectedBlocks?.length === 1 && attachments.length > 0;
|
} else {
|
||||||
const models = selectedBlocks?.map(block => block.model);
|
[markdown, attachments] = await Promise.all([
|
||||||
const control = trackerOptions?.control ?? 'format-bar';
|
getSelectedTextContent(host),
|
||||||
const where = trackerOptions?.where ?? 'ai-panel';
|
getSelectedImagesAsBlobs(host),
|
||||||
const options = {
|
]);
|
||||||
...variants,
|
}
|
||||||
attachments: sendAttachments ? attachments : undefined,
|
|
||||||
input: sendAttachments ? '' : markdown,
|
const models = selectedBlocks?.map(block => block.model);
|
||||||
stream: true,
|
const control = trackerOptions?.control ?? 'format-bar';
|
||||||
host,
|
const where = trackerOptions?.where ?? 'ai-panel';
|
||||||
models,
|
const options = {
|
||||||
signal,
|
...variants,
|
||||||
control,
|
attachments,
|
||||||
where,
|
input: input ? `${markdown}\n${input}` : markdown,
|
||||||
docId: host.doc.id,
|
stream: true,
|
||||||
workspaceId: host.doc.workspace.id,
|
host,
|
||||||
} as Parameters<typeof action>[0];
|
models,
|
||||||
// @ts-expect-error TODO(@Peng): maybe fix this
|
signal,
|
||||||
stream = action(options);
|
control,
|
||||||
if (!stream) return;
|
where,
|
||||||
yield* stream;
|
docId: host.doc.id,
|
||||||
},
|
workspaceId: host.doc.workspace.id,
|
||||||
};
|
} as Parameters<typeof action>[0];
|
||||||
|
// @ts-expect-error TODO(@Peng): maybe fix this
|
||||||
|
stream = action(options);
|
||||||
|
if (!stream) return;
|
||||||
|
yield* stream;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function actionToGenerateAnswer<
|
function actionToGenerateAnswer<T extends keyof BlockSuitePresets.AIActions>(
|
||||||
T extends keyof BlockSuitePresets.AIActions,
|
host: EditorHost,
|
||||||
>(
|
|
||||||
id: T,
|
id: T,
|
||||||
variants?: Omit<
|
variants?: Omit<
|
||||||
Parameters<BlockSuitePresets.AIActions[T]>[0],
|
Parameters<BlockSuitePresets.AIActions[T]>[0],
|
||||||
@@ -131,28 +128,29 @@ export function actionToGenerateAnswer<
|
|||||||
>,
|
>,
|
||||||
trackerOptions?: BlockSuitePresets.TrackerOptions
|
trackerOptions?: BlockSuitePresets.TrackerOptions
|
||||||
) {
|
) {
|
||||||
return (host: EditorHost) => {
|
return ({
|
||||||
return ({
|
input,
|
||||||
|
signal,
|
||||||
|
update,
|
||||||
|
finish,
|
||||||
|
}: {
|
||||||
|
input: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
update: (text: string) => void;
|
||||||
|
finish: (state: 'success' | 'error' | 'aborted', err?: AIError) => void;
|
||||||
|
}) => {
|
||||||
|
const { selectedBlocks: blocks } = getSelections(host);
|
||||||
|
if (!blocks || blocks.length === 0) return;
|
||||||
|
const stream = actionToStream(
|
||||||
|
host,
|
||||||
|
id,
|
||||||
|
input,
|
||||||
signal,
|
signal,
|
||||||
update,
|
variants,
|
||||||
finish,
|
trackerOptions
|
||||||
}: {
|
);
|
||||||
input: string;
|
if (!stream) return;
|
||||||
signal?: AbortSignal;
|
bindTextStream(stream, { update, finish, signal });
|
||||||
update: (text: string) => void;
|
|
||||||
finish: (state: 'success' | 'error' | 'aborted', err?: AIError) => void;
|
|
||||||
}) => {
|
|
||||||
const { selectedBlocks: blocks } = getSelections(host);
|
|
||||||
if (!blocks || blocks.length === 0) return;
|
|
||||||
const stream = actionToStream(
|
|
||||||
id,
|
|
||||||
signal,
|
|
||||||
variants,
|
|
||||||
trackerOptions
|
|
||||||
)?.(host);
|
|
||||||
if (!stream) return;
|
|
||||||
bindTextStream(stream, { update, finish, signal });
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,10 +172,11 @@ function updateAIPanelConfig<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
const { config, host } = aiPanel;
|
const { config, host } = aiPanel;
|
||||||
assertExists(config);
|
assertExists(config);
|
||||||
config.generateAnswer = actionToGenerateAnswer(
|
config.generateAnswer = actionToGenerateAnswer(
|
||||||
|
host,
|
||||||
id,
|
id,
|
||||||
variants,
|
variants,
|
||||||
trackerOptions
|
trackerOptions
|
||||||
)(host);
|
);
|
||||||
|
|
||||||
const ctx = new AIContext();
|
const ctx = new AIContext();
|
||||||
config.answerRenderer = actionToAnswerRenderer(id, host, ctx);
|
config.answerRenderer = actionToAnswerRenderer(id, host, ctx);
|
||||||
@@ -206,7 +205,7 @@ export function actionToHandler<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
if (!blocks || blocks.length === 0) return;
|
if (!blocks || blocks.length === 0) return;
|
||||||
const block = blocks.at(-1);
|
const block = blocks.at(-1);
|
||||||
assertExists(block);
|
assertExists(block);
|
||||||
aiPanel.toggle(block, 'placeholder');
|
aiPanel.toggle(block, '');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,43 +221,12 @@ export function handleInlineAskAIAction(
|
|||||||
if (!lastBlockPath) return;
|
if (!lastBlockPath) return;
|
||||||
const block = host.view.getBlock(lastBlockPath);
|
const block = host.view.getBlock(lastBlockPath);
|
||||||
if (!block) return;
|
if (!block) return;
|
||||||
|
|
||||||
const generateAnswer: AffineAIPanelWidgetConfig['generateAnswer'] = ({
|
|
||||||
finish,
|
|
||||||
input,
|
|
||||||
signal,
|
|
||||||
update,
|
|
||||||
}) => {
|
|
||||||
if (!AIProvider.actions.chat) return;
|
|
||||||
|
|
||||||
// recover selection to get content from above blocks
|
|
||||||
assertExists(selection);
|
|
||||||
host.selection.set([selection]);
|
|
||||||
|
|
||||||
selectAboveBlocks(host)
|
|
||||||
.then(async context => {
|
|
||||||
if (!AIProvider.session || !AIProvider.actions.chat) return;
|
|
||||||
const sessionId = await AIProvider.session.createSession(
|
|
||||||
host.doc.workspace.id,
|
|
||||||
host.doc.id
|
|
||||||
);
|
|
||||||
const stream = AIProvider.actions.chat({
|
|
||||||
sessionId,
|
|
||||||
input: `${context}\n${input}`,
|
|
||||||
stream: true,
|
|
||||||
host,
|
|
||||||
where: 'inline-chat-panel',
|
|
||||||
control: 'chat-send',
|
|
||||||
docId: host.doc.id,
|
|
||||||
workspaceId: host.doc.workspace.id,
|
|
||||||
});
|
|
||||||
bindTextStream(stream, { update, finish, signal });
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
};
|
|
||||||
if (!panel.config) return;
|
if (!panel.config) return;
|
||||||
|
|
||||||
panel.config.generateAnswer = generateAnswer;
|
updateAIPanelConfig(panel, 'chat', AIStarIconWithAnimation, undefined, {
|
||||||
|
control: 'chat-send',
|
||||||
|
where: 'inline-chat-panel',
|
||||||
|
});
|
||||||
|
|
||||||
if (!actionGroups) {
|
if (!actionGroups) {
|
||||||
panel.toggle(block);
|
panel.toggle(block);
|
||||||
|
|||||||
@@ -451,11 +451,7 @@ export function actionToHandler<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
|
|
||||||
togglePanel()
|
togglePanel()
|
||||||
.then(isEmpty => {
|
.then(isEmpty => {
|
||||||
aiPanel.toggle(
|
aiPanel.toggle(referenceElement, isEmpty ? undefined : '', false);
|
||||||
referenceElement,
|
|
||||||
isEmpty ? undefined : 'placeholder',
|
|
||||||
false
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -346,10 +346,9 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) {
|
|||||||
if (!this.doc) throw new Error('doc is required');
|
if (!this.doc) throw new Error('doc is required');
|
||||||
|
|
||||||
this._disposables.add(
|
this._disposables.add(
|
||||||
AIProvider.slots.actions.on(({ action, event }) => {
|
AIProvider.slots.actions.on(({ event }) => {
|
||||||
const { status } = this.chatContextValue;
|
const { status } = this.chatContextValue;
|
||||||
if (
|
if (
|
||||||
action !== 'chat' &&
|
|
||||||
event === 'finished' &&
|
event === 'finished' &&
|
||||||
(status === 'idle' || status === 'success')
|
(status === 'idle' || status === 'success')
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user