mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 13:25:12 +00: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:
@@ -1,9 +1,9 @@
|
||||
import { type EditorHost, TextSelection } from '@blocksuite/affine/block-std';
|
||||
import {
|
||||
type AffineAIPanelWidget,
|
||||
type AffineAIPanelWidgetConfig,
|
||||
type AIError,
|
||||
type AIItemGroupConfig,
|
||||
AIStarIconWithAnimation,
|
||||
createLitPortal,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
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,
|
||||
input: string,
|
||||
signal?: AbortSignal,
|
||||
variants?: Omit<
|
||||
Parameters<BlockSuitePresets.AIActions[T]>[0],
|
||||
keyof BlockSuitePresets.AITextActionOptions
|
||||
>,
|
||||
trackerOptions?: BlockSuitePresets.TrackerOptions
|
||||
) {
|
||||
): BlockSuitePresets.TextStream | undefined {
|
||||
const action = AIProvider.actions[id];
|
||||
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 attachments: File[] = [];
|
||||
let stream: BlockSuitePresets.TextStream | undefined;
|
||||
return {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
const { currentTextSelection, selectedBlocks } = getSelections(host);
|
||||
|
||||
if (currentTextSelection?.isCollapsed()) {
|
||||
markdown = await selectAboveBlocks(host);
|
||||
} else {
|
||||
[markdown, attachments] = await Promise.all([
|
||||
getSelectedTextContent(host),
|
||||
getSelectedImagesAsBlobs(host),
|
||||
]);
|
||||
}
|
||||
let markdown: string;
|
||||
let attachments: File[] = [];
|
||||
|
||||
// for now if there are more than one selected blocks, we will not omit the attachments
|
||||
const sendAttachments =
|
||||
selectedBlocks?.length === 1 && attachments.length > 0;
|
||||
const models = selectedBlocks?.map(block => block.model);
|
||||
const control = trackerOptions?.control ?? 'format-bar';
|
||||
const where = trackerOptions?.where ?? 'ai-panel';
|
||||
const options = {
|
||||
...variants,
|
||||
attachments: sendAttachments ? attachments : undefined,
|
||||
input: sendAttachments ? '' : markdown,
|
||||
stream: true,
|
||||
host,
|
||||
models,
|
||||
signal,
|
||||
control,
|
||||
where,
|
||||
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;
|
||||
},
|
||||
};
|
||||
if (currentTextSelection?.isCollapsed()) {
|
||||
markdown = await selectAboveBlocks(host);
|
||||
} else {
|
||||
[markdown, attachments] = await Promise.all([
|
||||
getSelectedTextContent(host),
|
||||
getSelectedImagesAsBlobs(host),
|
||||
]);
|
||||
}
|
||||
|
||||
const models = selectedBlocks?.map(block => block.model);
|
||||
const control = trackerOptions?.control ?? 'format-bar';
|
||||
const where = trackerOptions?.where ?? 'ai-panel';
|
||||
const options = {
|
||||
...variants,
|
||||
attachments,
|
||||
input: input ? `${markdown}\n${input}` : markdown,
|
||||
stream: true,
|
||||
host,
|
||||
models,
|
||||
signal,
|
||||
control,
|
||||
where,
|
||||
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<
|
||||
T extends keyof BlockSuitePresets.AIActions,
|
||||
>(
|
||||
function actionToGenerateAnswer<T extends keyof BlockSuitePresets.AIActions>(
|
||||
host: EditorHost,
|
||||
id: T,
|
||||
variants?: Omit<
|
||||
Parameters<BlockSuitePresets.AIActions[T]>[0],
|
||||
@@ -131,28 +128,29 @@ export function actionToGenerateAnswer<
|
||||
>,
|
||||
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,
|
||||
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(
|
||||
id,
|
||||
signal,
|
||||
variants,
|
||||
trackerOptions
|
||||
)?.(host);
|
||||
if (!stream) return;
|
||||
bindTextStream(stream, { update, finish, signal });
|
||||
};
|
||||
variants,
|
||||
trackerOptions
|
||||
);
|
||||
if (!stream) return;
|
||||
bindTextStream(stream, { update, finish, signal });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,10 +172,11 @@ function updateAIPanelConfig<T extends keyof BlockSuitePresets.AIActions>(
|
||||
const { config, host } = aiPanel;
|
||||
assertExists(config);
|
||||
config.generateAnswer = actionToGenerateAnswer(
|
||||
host,
|
||||
id,
|
||||
variants,
|
||||
trackerOptions
|
||||
)(host);
|
||||
);
|
||||
|
||||
const ctx = new AIContext();
|
||||
config.answerRenderer = actionToAnswerRenderer(id, host, ctx);
|
||||
@@ -206,7 +205,7 @@ export function actionToHandler<T extends keyof BlockSuitePresets.AIActions>(
|
||||
if (!blocks || blocks.length === 0) return;
|
||||
const block = blocks.at(-1);
|
||||
assertExists(block);
|
||||
aiPanel.toggle(block, 'placeholder');
|
||||
aiPanel.toggle(block, '');
|
||||
};
|
||||
}
|
||||
|
||||
@@ -222,43 +221,12 @@ export function handleInlineAskAIAction(
|
||||
if (!lastBlockPath) return;
|
||||
const block = host.view.getBlock(lastBlockPath);
|
||||
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;
|
||||
|
||||
panel.config.generateAnswer = generateAnswer;
|
||||
updateAIPanelConfig(panel, 'chat', AIStarIconWithAnimation, undefined, {
|
||||
control: 'chat-send',
|
||||
where: 'inline-chat-panel',
|
||||
});
|
||||
|
||||
if (!actionGroups) {
|
||||
panel.toggle(block);
|
||||
|
||||
@@ -451,11 +451,7 @@ export function actionToHandler<T extends keyof BlockSuitePresets.AIActions>(
|
||||
|
||||
togglePanel()
|
||||
.then(isEmpty => {
|
||||
aiPanel.toggle(
|
||||
referenceElement,
|
||||
isEmpty ? undefined : 'placeholder',
|
||||
false
|
||||
);
|
||||
aiPanel.toggle(referenceElement, isEmpty ? undefined : '', false);
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
|
||||
@@ -346,10 +346,9 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) {
|
||||
if (!this.doc) throw new Error('doc is required');
|
||||
|
||||
this._disposables.add(
|
||||
AIProvider.slots.actions.on(({ action, event }) => {
|
||||
AIProvider.slots.actions.on(({ event }) => {
|
||||
const { status } = this.chatContextValue;
|
||||
if (
|
||||
action !== 'chat' &&
|
||||
event === 'finished' &&
|
||||
(status === 'idle' || status === 'success')
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user