mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 18:09:58 +08:00
refactor(core): remove useless chat-input components (#11426)
Close [BS-2583](https://linear.app/affine-design/issue/BS-2583).
This commit is contained in:
@@ -1,108 +0,0 @@
|
|||||||
import { AIChatInput } from '../components/ai-chat-input';
|
|
||||||
import type { ChatMessage } from '../components/ai-chat-messages';
|
|
||||||
import { type AIError, AIProvider } from '../provider';
|
|
||||||
import { readBlobAsURL } from '../utils/image';
|
|
||||||
|
|
||||||
export class ChatPanelInput extends AIChatInput {
|
|
||||||
send = async (text: string) => {
|
|
||||||
const { status, markdown, images } = this.chatContextValue;
|
|
||||||
if (status === 'loading' || status === 'transmitting') return;
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { doc } = this.host;
|
|
||||||
const promptName = this.getPromptName();
|
|
||||||
|
|
||||||
this.updateContext({
|
|
||||||
images: [],
|
|
||||||
status: 'loading',
|
|
||||||
error: null,
|
|
||||||
quote: '',
|
|
||||||
markdown: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const attachments = await Promise.all(
|
|
||||||
images?.map(image => readBlobAsURL(image))
|
|
||||||
);
|
|
||||||
|
|
||||||
const userInput = (markdown ? `${markdown}\n` : '') + text;
|
|
||||||
this.updateContext({
|
|
||||||
messages: [
|
|
||||||
...this.chatContextValue.messages,
|
|
||||||
{
|
|
||||||
id: '',
|
|
||||||
role: 'user',
|
|
||||||
content: userInput,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
attachments,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '',
|
|
||||||
role: 'assistant',
|
|
||||||
content: '',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// must update prompt name after local chat message is updated
|
|
||||||
// otherwise, the unauthorized error can not be rendered properly
|
|
||||||
await this.updatePromptName(promptName);
|
|
||||||
|
|
||||||
const abortController = new AbortController();
|
|
||||||
const sessionId = await this.getSessionId();
|
|
||||||
if (!sessionId) return;
|
|
||||||
|
|
||||||
const contexts = await this.getMatchedContexts(userInput);
|
|
||||||
const stream = AIProvider.actions.chat?.({
|
|
||||||
sessionId,
|
|
||||||
input: userInput,
|
|
||||||
contexts,
|
|
||||||
docId: doc.id,
|
|
||||||
attachments: images,
|
|
||||||
workspaceId: doc.workspace.id,
|
|
||||||
host: this.host,
|
|
||||||
stream: true,
|
|
||||||
signal: abortController.signal,
|
|
||||||
where: 'chat-panel',
|
|
||||||
control: 'chat-send',
|
|
||||||
isRootSession: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (stream) {
|
|
||||||
this.updateContext({ abortController });
|
|
||||||
|
|
||||||
for await (const text of stream) {
|
|
||||||
const messages = [...this.chatContextValue.messages];
|
|
||||||
const last = messages[messages.length - 1] as ChatMessage;
|
|
||||||
last.content += text;
|
|
||||||
this.updateContext({ messages, status: 'transmitting' });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateContext({ status: 'success' });
|
|
||||||
|
|
||||||
const { messages } = this.chatContextValue;
|
|
||||||
const last = messages[messages.length - 1] as ChatMessage;
|
|
||||||
if (!last.id) {
|
|
||||||
const historyIds = await AIProvider.histories?.ids(
|
|
||||||
doc.workspace.id,
|
|
||||||
doc.id,
|
|
||||||
{ sessionId }
|
|
||||||
);
|
|
||||||
if (!historyIds || !historyIds[0]) return;
|
|
||||||
last.id = historyIds[0].messages.at(-1)?.id ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.updateContext({ status: 'error', error: error as AIError });
|
|
||||||
} finally {
|
|
||||||
this.updateContext({ abortController: null });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
'chat-panel-input': ChatPanelInput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import './chat-panel-input';
|
|
||||||
import './chat-panel-messages';
|
import './chat-panel-messages';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -640,7 +639,7 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
.searchMenuConfig=${this.searchMenuConfig}
|
.searchMenuConfig=${this.searchMenuConfig}
|
||||||
></chat-panel-chips>
|
></chat-panel-chips>
|
||||||
<chat-panel-input
|
<ai-chat-input
|
||||||
.chips=${this.chips}
|
.chips=${this.chips}
|
||||||
.chatContextValue=${this.chatContextValue}
|
.chatContextValue=${this.chatContextValue}
|
||||||
.getSessionId=${this._getSessionId}
|
.getSessionId=${this._getSessionId}
|
||||||
@@ -650,7 +649,7 @@ export class ChatPanel extends SignalWatcher(
|
|||||||
.updateContext=${this.updateContext}
|
.updateContext=${this.updateContext}
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
.cleanupHistories=${this._cleanupHistories}
|
.cleanupHistories=${this._cleanupHistories}
|
||||||
></chat-panel-input>
|
></ai-chat-input>
|
||||||
<div class="chat-panel-footer">
|
<div class="chat-panel-footer">
|
||||||
${InformationIcon()}
|
${InformationIcon()}
|
||||||
<div>AI outputs can be misleading or wrong</div>
|
<div>AI outputs can be misleading or wrong</div>
|
||||||
|
|||||||
+136
-10
@@ -15,14 +15,16 @@ import { repeat } from 'lit/directives/repeat.js';
|
|||||||
import { styleMap } from 'lit/directives/style-map.js';
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
|
|
||||||
import { ChatAbortIcon, ChatSendIcon } from '../../_common/icons';
|
import { ChatAbortIcon, ChatSendIcon } from '../../_common/icons';
|
||||||
import { AIProvider } from '../../provider';
|
import { type AIError, AIProvider } from '../../provider';
|
||||||
import { reportResponse } from '../../utils/action-reporter';
|
import { reportResponse } from '../../utils/action-reporter';
|
||||||
|
import { readBlobAsURL } from '../../utils/image';
|
||||||
import type {
|
import type {
|
||||||
ChatChip,
|
ChatChip,
|
||||||
DocDisplayConfig,
|
DocDisplayConfig,
|
||||||
FileChip,
|
FileChip,
|
||||||
} from '../ai-chat-chips/type';
|
} from '../ai-chat-chips/type';
|
||||||
import { isDocChip, isFileChip } from '../ai-chat-chips/utils';
|
import { isDocChip, isFileChip } from '../ai-chat-chips/utils';
|
||||||
|
import type { ChatMessage } from '../ai-chat-messages';
|
||||||
import { PROMPT_NAME_AFFINE_AI, PROMPT_NAME_NETWORK_SEARCH } from './const';
|
import { PROMPT_NAME_AFFINE_AI, PROMPT_NAME_NETWORK_SEARCH } from './const';
|
||||||
import type { AIChatInputContext, AINetworkSearchConfig } from './type';
|
import type { AIChatInputContext, AINetworkSearchConfig } from './type';
|
||||||
|
|
||||||
@@ -33,9 +35,7 @@ function getFirstTwoLines(text: string) {
|
|||||||
return lines.slice(0, 2);
|
return lines.slice(0, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AIChatInput extends SignalWatcher(
|
export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
|
||||||
WithDisposable(LitElement)
|
|
||||||
) {
|
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
:host {
|
:host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -220,10 +220,10 @@ export abstract class AIChatInput extends SignalWatcher(
|
|||||||
accessor chips: ChatChip[] = [];
|
accessor chips: ChatChip[] = [];
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor getSessionId!: () => Promise<string | null | undefined>;
|
accessor getSessionId!: () => Promise<string | undefined>;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor getContextId!: () => Promise<string | null | undefined>;
|
accessor getContextId!: () => Promise<string | undefined>;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor updateContext!: (context: Partial<AIChatInputContext>) => void;
|
accessor updateContext!: (context: Partial<AIChatInputContext>) => void;
|
||||||
@@ -237,6 +237,18 @@ export abstract class AIChatInput extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor docDisplayConfig!: DocDisplayConfig;
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor isRootSession: boolean = true;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor onChatSuccess = () => null;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor trackOptions: BlockSuitePresets.TrackerOptions = {
|
||||||
|
control: 'chat-send',
|
||||||
|
where: 'chat-panel',
|
||||||
|
};
|
||||||
|
|
||||||
@property({ attribute: 'data-testid', reflect: true })
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
accessor testId = 'chat-panel-input-container';
|
accessor testId = 'chat-panel-input-container';
|
||||||
|
|
||||||
@@ -262,7 +274,7 @@ export abstract class AIChatInput extends SignalWatcher(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getPromptName() {
|
private _getPromptName() {
|
||||||
if (this._isNetworkDisabled) {
|
if (this._isNetworkDisabled) {
|
||||||
return PROMPT_NAME_AFFINE_AI;
|
return PROMPT_NAME_AFFINE_AI;
|
||||||
}
|
}
|
||||||
@@ -271,7 +283,7 @@ export abstract class AIChatInput extends SignalWatcher(
|
|||||||
: PROMPT_NAME_AFFINE_AI;
|
: PROMPT_NAME_AFFINE_AI;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async updatePromptName(promptName: string) {
|
private async _updatePromptName(promptName: string) {
|
||||||
const sessionId = await this.getSessionId();
|
const sessionId = await this.getSessionId();
|
||||||
if (sessionId && AIProvider.session) {
|
if (sessionId && AIProvider.session) {
|
||||||
await AIProvider.session.updateSession(sessionId, promptName);
|
await AIProvider.session.updateSession(sessionId, promptName);
|
||||||
@@ -501,9 +513,117 @@ export abstract class AIChatInput extends SignalWatcher(
|
|||||||
await this.send(value);
|
await this.send(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
protected abstract send(text: string): Promise<void>;
|
send = async (text: string) => {
|
||||||
|
const { status, markdown, images } = this.chatContextValue;
|
||||||
|
if (status === 'loading' || status === 'transmitting') return;
|
||||||
|
if (!text) return;
|
||||||
|
if (!AIProvider.actions.chat) return;
|
||||||
|
|
||||||
protected async getMatchedContexts(userInput: string) {
|
try {
|
||||||
|
const promptName = this._getPromptName();
|
||||||
|
const abortController = new AbortController();
|
||||||
|
this.updateContext({
|
||||||
|
images: [],
|
||||||
|
status: 'loading',
|
||||||
|
error: null,
|
||||||
|
quote: '',
|
||||||
|
markdown: '',
|
||||||
|
abortController,
|
||||||
|
});
|
||||||
|
|
||||||
|
const attachments = await Promise.all(
|
||||||
|
images?.map(image => readBlobAsURL(image))
|
||||||
|
);
|
||||||
|
const userInput = (markdown ? `${markdown}\n` : '') + text;
|
||||||
|
|
||||||
|
// optimistic update messages
|
||||||
|
await this._preUpdateMessages(userInput, attachments);
|
||||||
|
// must update prompt name after local chat message is updated
|
||||||
|
// otherwise, the unauthorized error can not be rendered properly
|
||||||
|
await this._updatePromptName(promptName);
|
||||||
|
|
||||||
|
const sessionId = await this.getSessionId();
|
||||||
|
const contexts = await this._getMatchedContexts(userInput);
|
||||||
|
if (abortController.signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stream = AIProvider.actions.chat({
|
||||||
|
sessionId,
|
||||||
|
input: userInput,
|
||||||
|
contexts,
|
||||||
|
docId: this.host.doc.id,
|
||||||
|
attachments: images,
|
||||||
|
workspaceId: this.host.doc.workspace.id,
|
||||||
|
host: this.host,
|
||||||
|
stream: true,
|
||||||
|
signal: abortController.signal,
|
||||||
|
isRootSession: this.isRootSession,
|
||||||
|
where: this.trackOptions.where,
|
||||||
|
control: this.trackOptions.control,
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const text of stream) {
|
||||||
|
const messages = [...this.chatContextValue.messages];
|
||||||
|
const last = messages[messages.length - 1] as ChatMessage;
|
||||||
|
last.content += text;
|
||||||
|
this.updateContext({ messages, status: 'transmitting' });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateContext({ status: 'success' });
|
||||||
|
this.onChatSuccess();
|
||||||
|
// update message id from server
|
||||||
|
await this._postUpdateMessages();
|
||||||
|
} catch (error) {
|
||||||
|
this.updateContext({ status: 'error', error: error as AIError });
|
||||||
|
} finally {
|
||||||
|
this.updateContext({ abortController: null });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly _preUpdateMessages = async (
|
||||||
|
userInput: string,
|
||||||
|
attachments: string[]
|
||||||
|
) => {
|
||||||
|
const userInfo = await AIProvider.userInfo;
|
||||||
|
this.updateContext({
|
||||||
|
messages: [
|
||||||
|
...this.chatContextValue.messages,
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
role: 'user',
|
||||||
|
content: userInput,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
attachments,
|
||||||
|
userId: userInfo?.id,
|
||||||
|
userName: userInfo?.name,
|
||||||
|
avatarUrl: userInfo?.avatarUrl ?? undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
role: 'assistant',
|
||||||
|
content: '',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly _postUpdateMessages = async () => {
|
||||||
|
const { messages } = this.chatContextValue;
|
||||||
|
const last = messages[messages.length - 1] as ChatMessage;
|
||||||
|
if (!last.id) {
|
||||||
|
const sessionId = await this.getSessionId();
|
||||||
|
const historyIds = await AIProvider.histories?.ids(
|
||||||
|
this.host.doc.workspace.id,
|
||||||
|
this.host.doc.id,
|
||||||
|
{ sessionId }
|
||||||
|
);
|
||||||
|
if (!historyIds || !historyIds[0]) return;
|
||||||
|
last.id = historyIds[0].messages.at(-1)?.id ?? '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private async _getMatchedContexts(userInput: string) {
|
||||||
const contextId = await this.getContextId();
|
const contextId = await this.getContextId();
|
||||||
if (!contextId) {
|
if (!contextId) {
|
||||||
return { files: [], docs: [] };
|
return { files: [], docs: [] };
|
||||||
@@ -586,3 +706,9 @@ export abstract class AIChatInput extends SignalWatcher(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'ai-chat-input': AIChatInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import { ActionMindmap } from './chat-panel/actions/mindmap';
|
|||||||
import { ActionSlides } from './chat-panel/actions/slides';
|
import { ActionSlides } from './chat-panel/actions/slides';
|
||||||
import { ActionText } from './chat-panel/actions/text';
|
import { ActionText } from './chat-panel/actions/text';
|
||||||
import { AILoading } from './chat-panel/ai-loading';
|
import { AILoading } from './chat-panel/ai-loading';
|
||||||
import { ChatPanelInput } from './chat-panel/chat-panel-input';
|
|
||||||
import { ChatPanelMessages } from './chat-panel/chat-panel-messages';
|
import { ChatPanelMessages } from './chat-panel/chat-panel-messages';
|
||||||
import { AssistantAvatar } from './chat-panel/content/assistant-avatar';
|
import { AssistantAvatar } from './chat-panel/content/assistant-avatar';
|
||||||
import { ChatContentImages } from './chat-panel/content/images';
|
import { ChatContentImages } from './chat-panel/content/images';
|
||||||
@@ -40,6 +39,7 @@ import { ChatPanelCollectionChip } from './components/ai-chat-chips/collection-c
|
|||||||
import { ChatPanelDocChip } from './components/ai-chat-chips/doc-chip';
|
import { ChatPanelDocChip } from './components/ai-chat-chips/doc-chip';
|
||||||
import { ChatPanelFileChip } from './components/ai-chat-chips/file-chip';
|
import { ChatPanelFileChip } from './components/ai-chat-chips/file-chip';
|
||||||
import { ChatPanelTagChip } from './components/ai-chat-chips/tag-chip';
|
import { ChatPanelTagChip } from './components/ai-chat-chips/tag-chip';
|
||||||
|
import { AIChatInput } from './components/ai-chat-input/ai-chat-input';
|
||||||
import { effects as componentAiItemEffects } from './components/ai-item';
|
import { effects as componentAiItemEffects } from './components/ai-item';
|
||||||
import { AIScrollableTextRenderer } from './components/ai-scrollable-text-renderer';
|
import { AIScrollableTextRenderer } from './components/ai-scrollable-text-renderer';
|
||||||
import { AskAIButton } from './components/ask-ai-button';
|
import { AskAIButton } from './components/ask-ai-button';
|
||||||
@@ -54,7 +54,6 @@ import { AIErrorWrapper } from './messages/error';
|
|||||||
import { AISlidesRenderer } from './messages/slides-renderer';
|
import { AISlidesRenderer } from './messages/slides-renderer';
|
||||||
import { AIAnswerWrapper } from './messages/wrapper';
|
import { AIAnswerWrapper } from './messages/wrapper';
|
||||||
import { registerMiniMindmapBlocks } from './mini-mindmap';
|
import { registerMiniMindmapBlocks } from './mini-mindmap';
|
||||||
import { ChatBlockInput } from './peek-view/chat-block-input';
|
|
||||||
import { AIChatBlockPeekView } from './peek-view/chat-block-peek-view';
|
import { AIChatBlockPeekView } from './peek-view/chat-block-peek-view';
|
||||||
import { DateTime } from './peek-view/date-time';
|
import { DateTime } from './peek-view/date-time';
|
||||||
import {
|
import {
|
||||||
@@ -96,9 +95,9 @@ export function registerAIEffects() {
|
|||||||
customElements.define('action-slides', ActionSlides);
|
customElements.define('action-slides', ActionSlides);
|
||||||
customElements.define('action-text', ActionText);
|
customElements.define('action-text', ActionText);
|
||||||
customElements.define('ai-loading', AILoading);
|
customElements.define('ai-loading', AILoading);
|
||||||
customElements.define('chat-panel-input', ChatPanelInput);
|
|
||||||
customElements.define('chat-panel-messages', ChatPanelMessages);
|
customElements.define('chat-panel-messages', ChatPanelMessages);
|
||||||
customElements.define('chat-panel', ChatPanel);
|
customElements.define('chat-panel', ChatPanel);
|
||||||
|
customElements.define('ai-chat-input', AIChatInput);
|
||||||
customElements.define('chat-panel-chips', ChatPanelChips);
|
customElements.define('chat-panel-chips', ChatPanelChips);
|
||||||
customElements.define('chat-panel-add-popover', ChatPanelAddPopover);
|
customElements.define('chat-panel-add-popover', ChatPanelAddPopover);
|
||||||
customElements.define(
|
customElements.define(
|
||||||
@@ -113,7 +112,6 @@ export function registerAIEffects() {
|
|||||||
customElements.define('ai-error-wrapper', AIErrorWrapper);
|
customElements.define('ai-error-wrapper', AIErrorWrapper);
|
||||||
customElements.define('ai-slides-renderer', AISlidesRenderer);
|
customElements.define('ai-slides-renderer', AISlidesRenderer);
|
||||||
customElements.define('ai-answer-wrapper', AIAnswerWrapper);
|
customElements.define('ai-answer-wrapper', AIAnswerWrapper);
|
||||||
customElements.define('chat-block-input', ChatBlockInput);
|
|
||||||
customElements.define('ai-chat-block-peek-view', AIChatBlockPeekView);
|
customElements.define('ai-chat-block-peek-view', AIChatBlockPeekView);
|
||||||
customElements.define('date-time', DateTime);
|
customElements.define('date-time', DateTime);
|
||||||
customElements.define(
|
customElements.define(
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
import { property } from 'lit/decorators.js';
|
|
||||||
|
|
||||||
import { AIChatInput } from '../components/ai-chat-input';
|
|
||||||
import type { ChatMessage } from '../components/ai-chat-messages';
|
|
||||||
import { type AIError, AIProvider } from '../provider';
|
|
||||||
import { readBlobAsURL } from '../utils/image';
|
|
||||||
|
|
||||||
export class ChatBlockInput extends AIChatInput {
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor getBlockId!: () => string | null | undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor updateChatBlock!: () => Promise<void>;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor createChatBlock!: () => Promise<void>;
|
|
||||||
|
|
||||||
send = async (text: string) => {
|
|
||||||
const { images, status } = this.chatContextValue;
|
|
||||||
const sessionId = await this.getSessionId();
|
|
||||||
if (!sessionId) return;
|
|
||||||
let content = '';
|
|
||||||
|
|
||||||
if (status === 'loading' || status === 'transmitting') return;
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { doc } = this.host;
|
|
||||||
const promptName = this.getPromptName();
|
|
||||||
|
|
||||||
this.updateContext({
|
|
||||||
images: [],
|
|
||||||
status: 'loading',
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const attachments = await Promise.all(
|
|
||||||
images?.map(image => readBlobAsURL(image))
|
|
||||||
);
|
|
||||||
|
|
||||||
const userInfo = await AIProvider.userInfo;
|
|
||||||
this.updateContext({
|
|
||||||
messages: [
|
|
||||||
...this.chatContextValue.messages,
|
|
||||||
{
|
|
||||||
id: '',
|
|
||||||
content: text,
|
|
||||||
role: 'user',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
attachments,
|
|
||||||
userId: userInfo?.id,
|
|
||||||
userName: userInfo?.name,
|
|
||||||
avatarUrl: userInfo?.avatarUrl ?? undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '',
|
|
||||||
content: '',
|
|
||||||
role: 'assistant',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// must update prompt name after local chat message is updated
|
|
||||||
// otherwise, the unauthorized error can not be rendered properly
|
|
||||||
await this.updatePromptName(promptName);
|
|
||||||
|
|
||||||
const abortController = new AbortController();
|
|
||||||
const stream = AIProvider.actions.chat?.({
|
|
||||||
input: text,
|
|
||||||
sessionId,
|
|
||||||
docId: doc.id,
|
|
||||||
attachments: images,
|
|
||||||
workspaceId: doc.workspace.id,
|
|
||||||
host: this.host,
|
|
||||||
stream: true,
|
|
||||||
signal: abortController.signal,
|
|
||||||
where: 'ai-chat-block',
|
|
||||||
control: 'chat-send',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (stream) {
|
|
||||||
this.updateContext({
|
|
||||||
abortController,
|
|
||||||
});
|
|
||||||
|
|
||||||
for await (const text of stream) {
|
|
||||||
const messages = [...this.chatContextValue.messages];
|
|
||||||
const last = messages[messages.length - 1] as ChatMessage;
|
|
||||||
last.content += text;
|
|
||||||
this.updateContext({ messages, status: 'transmitting' });
|
|
||||||
content += text;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateContext({ status: 'success' });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
this.updateContext({ status: 'error', error: error as AIError });
|
|
||||||
} finally {
|
|
||||||
if (content) {
|
|
||||||
const chatBlockExists = !!this.getBlockId();
|
|
||||||
if (!chatBlockExists) {
|
|
||||||
await this.createChatBlock();
|
|
||||||
}
|
|
||||||
// Update new chat block messages if there are contents returned from AI
|
|
||||||
await this.updateChatBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateContext({ abortController: null });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
'chat-block-input': ChatBlockInput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -173,8 +173,12 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
return this._chatContextId;
|
return this._chatContextId;
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _getBlockId = () => {
|
private readonly _onChatSuccess = async () => {
|
||||||
return this._chatBlockId;
|
if (!this._chatBlockId) {
|
||||||
|
await this.createAIChatBlock();
|
||||||
|
}
|
||||||
|
// Update new chat block messages if there are contents returned from AI
|
||||||
|
await this.updateChatBlockMessages();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -518,8 +522,6 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
const latestHistoryMessage = _historyMessages[_historyMessages.length - 1];
|
const latestHistoryMessage = _historyMessages[_historyMessages.length - 1];
|
||||||
const latestMessageCreatedAt = latestHistoryMessage.createdAt;
|
const latestMessageCreatedAt = latestHistoryMessage.createdAt;
|
||||||
const {
|
const {
|
||||||
updateChatBlockMessages,
|
|
||||||
createAIChatBlock,
|
|
||||||
cleanCurrentChatHistories,
|
cleanCurrentChatHistories,
|
||||||
chatContext,
|
chatContext,
|
||||||
updateContext,
|
updateContext,
|
||||||
@@ -541,20 +543,22 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
${this.CurrentMessages(currentChatMessages)}
|
${this.CurrentMessages(currentChatMessages)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<chat-block-input
|
<ai-chat-input
|
||||||
.host=${host}
|
.host=${host}
|
||||||
.chips=${this.chips}
|
.chips=${this.chips}
|
||||||
.getSessionId=${this._getSessionId}
|
.getSessionId=${this._getSessionId}
|
||||||
.getContextId=${this._getContextId}
|
.getContextId=${this._getContextId}
|
||||||
.getBlockId=${this._getBlockId}
|
|
||||||
.updateChatBlock=${updateChatBlockMessages}
|
|
||||||
.createChatBlock=${createAIChatBlock}
|
|
||||||
.cleanupHistories=${cleanCurrentChatHistories}
|
.cleanupHistories=${cleanCurrentChatHistories}
|
||||||
.chatContextValue=${chatContext}
|
.chatContextValue=${chatContext}
|
||||||
.updateContext=${updateContext}
|
.updateContext=${updateContext}
|
||||||
.networkSearchConfig=${networkSearchConfig}
|
.networkSearchConfig=${networkSearchConfig}
|
||||||
.docDisplayConfig=${this.docDisplayConfig}
|
.docDisplayConfig=${this.docDisplayConfig}
|
||||||
></chat-block-input>
|
.onChatSuccess=${this._onChatSuccess}
|
||||||
|
.trackOptions=${{
|
||||||
|
where: 'ai-chat-block',
|
||||||
|
control: 'chat-send',
|
||||||
|
}}
|
||||||
|
></ai-chat-input>
|
||||||
<div class="peek-view-footer">
|
<div class="peek-view-footer">
|
||||||
${InformationIcon()}
|
${InformationIcon()}
|
||||||
<div>AI outputs can be misleading or wrong</div>
|
<div>AI outputs can be misleading or wrong</div>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ test.describe('AIChatWith/Network', () => {
|
|||||||
buffer: buffer,
|
buffer: buffer,
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.waitForSelector('chat-panel-input img');
|
await page.waitForSelector('ai-chat-input img');
|
||||||
const isNetworkSearchEnabled =
|
const isNetworkSearchEnabled =
|
||||||
await utils.chatPanel.isNetworkSearchEnabled(page);
|
await utils.chatPanel.isNetworkSearchEnabled(page);
|
||||||
await expect(isNetworkSearchEnabled).toBe(false);
|
await expect(isNetworkSearchEnabled).toBe(false);
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ export class ChatPanelUtils {
|
|||||||
const fileChooser = await fileChooserPromise;
|
const fileChooser = await fileChooserPromise;
|
||||||
await fileChooser.setFiles(images);
|
await fileChooser.setFiles(images);
|
||||||
|
|
||||||
await page.waitForSelector('chat-panel-input img');
|
await page.waitForSelector('ai-chat-input img');
|
||||||
await this.makeChat(page, text);
|
await this.makeChat(page, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user