diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c219026afe81a183bb7c04fbfc72dd8ba612ed..1e1374b8fb5a02b82b824bc031cb58af14d8d091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Added - 文件上传、图片上传、图片裁剪上传、html、轮播图、签名、消息占位、文件表格列组件支持启用无权限模式,上传文件夹需拼接'$'字符,也不需要计算下载凭证 +- 新增行内AI工具调用信息 ## [0.7.41-alpha.43] - 2025-12-02 diff --git a/src/locale/en/index.ts b/src/locale/en/index.ts index 62b8d7b03d2210c37f9e032d35b6b7d8aebb7a1c..8ebaf7caaf46c133711fff5a3c1c5022127ef6dd 100644 --- a/src/locale/en/index.ts +++ b/src/locale/en/index.ts @@ -904,6 +904,10 @@ export default { warningDesc: 'Are you sure to terminate the creation?', thinking: 'In depth thinking', thinked: 'Deeply pondered', + collapseToolCall: 'Retract tool calls', + expandToolCall: 'Expand all {number} tool calls', + error: 'An error occurred', + copy: 'Copied', }, aiChartUtil: { feedback: 'Feedback', diff --git a/src/locale/zh-CN/index.ts b/src/locale/zh-CN/index.ts index 79b7b27601e08928b659eec6af92644b4ba93e3c..b17ebbbced5eaac790978db5bb5a3b3e16ed188b 100644 --- a/src/locale/zh-CN/index.ts +++ b/src/locale/zh-CN/index.ts @@ -848,6 +848,10 @@ export default { warningDesc: '确认中止创作吗?', thinking: '深度思考中', thinked: '已深度思考', + collapseToolCall: '收起工具调用', + expandToolCall: '展开全部 {number} 个工具调用', + error: '发生错误', + copy: '已复制', }, aiChartUtil: { feedback: '反馈', diff --git a/src/util/inline-ai-util/inline-ai-textarea/common/ai-think/ai-think.scss b/src/util/inline-ai-util/inline-ai-textarea/common/ai-think/ai-think.scss new file mode 100644 index 0000000000000000000000000000000000000000..7adca947eb407e40bb418484278981adf6841b87 --- /dev/null +++ b/src/util/inline-ai-util/inline-ai-textarea/common/ai-think/ai-think.scss @@ -0,0 +1,48 @@ +@include b(ai-think) { + width: 100%; + height: auto; + font-size: getCssVar(font-size, small); + color: getCssVar(inline-ai-textarea-container, color-text-1); + @include e(header) { + display: flex; + gap: getCssVar(spacing, extra-tight); + align-items: center; + margin-bottom: getCssVar(spacing, tight); + cursor: pointer; + + &:hover { + color: getCssVar(inline-ai-textarea-container, color-text-hove-1); + } + @include m(state-icon) { + display: flex; + align-items: center; + color: getCssVar(color, success); + @include when(loading) { + color: getCssVar(inline-ai-textarea-container, color-loading); + } + } + @include m(collapse-icon) { + display: flex; + align-items: center; + } + } + @include e(content) { + position: relative; + padding-left: getCssVar(spacing, tight); + word-wrap: break-word; + white-space: pre-wrap; + transition: + height 0.3s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &::before { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 2px; + content: ''; + background-color: getCssVar(inline-ai-textarea-container, color-border); + } + } +} diff --git a/src/util/inline-ai-util/inline-ai-textarea/common/ai-think/ai-think.tsx b/src/util/inline-ai-util/inline-ai-textarea/common/ai-think/ai-think.tsx new file mode 100644 index 0000000000000000000000000000000000000000..16f2d3313814230d7d7c32f70e85bb242ba31cc3 --- /dev/null +++ b/src/util/inline-ai-util/inline-ai-textarea/common/ai-think/ai-think.tsx @@ -0,0 +1,66 @@ +import { defineComponent } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { DownIcon, LoadingIcon, ThinkSuccessIcon, UpIcon } from '../../icon'; +import './ai-think.scss'; + +export const AIThink = defineComponent({ + props: { + think: { + type: String, + }, + isLoading: { + type: Boolean, + required: true, + }, + isCollapse: { + type: Boolean, + required: true, + }, + }, + emits: { + collapseChange: (_value: boolean) => true, + }, + setup(props, { emit }) { + const ns = useNamespace('ai-think'); + + /** + * @description 折叠变更 + */ + const onCollapseChange = (): void => { + emit('collapseChange', !props.isCollapse); + }; + + return { + ns, + onCollapseChange, + }; + }, + render() { + if (!this.think) return; + return ( +
+
+
+ {this.isLoading ? LoadingIcon : ThinkSuccessIcon} +
+
+ {this.isLoading + ? ibiz.i18n.t('util.inlineAiUtil.thinking') + : ibiz.i18n.t('util.inlineAiUtil.thinked')} +
+
+ {this.isCollapse ? DownIcon : UpIcon} +
+
+ {!this.isCollapse && ( +
{this.think}
+ )} +
+ ); + }, +}); diff --git a/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call-item/ai-tool-call-item.scss b/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call-item/ai-tool-call-item.scss new file mode 100644 index 0000000000000000000000000000000000000000..3ea14f9f9288be98cf092715e781c79733fe0b09 --- /dev/null +++ b/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call-item/ai-tool-call-item.scss @@ -0,0 +1,110 @@ +$ai-tool-call-item: ( + bg-primary: #1e1e1e, + bg-secondary: #2d2d2d, + bg-tertiary: #3c3c3c, + text-primary: #d4d4d4, + text-secondary: #9cdcfe, + text-error: #f44747, + text-string: #ce9178, + text-boolean: #569cd6, + text-property: #9cdcfe, + text-number: #b5cea8, + accent-primary: #0e639c, + accent-error: rgb(244 71 71 / 20%), + color-bg: getCssVar(inline-ai-textarea-container, color-bg-1), + color-border: getCssVar(inline-ai-textarea-container, color-border), +); + +@include b(ai-tool-call-item) { + @include set-component-css-var('ai-tool-call-item', $ai-tool-call-item); + + margin-bottom: getCssVar(spacing, tight); + background-color: getCssVar(ai-tool-call-item, color-bg); + border: 1px solid getCssVar(ai-tool-call-item, color-border); + border-radius: getCssVar(border-radius, small); + + @include e(header) { + display: flex; + gap: getCssVar(spacing, tight); + align-items: center; + justify-content: space-between; + width: 100%; + padding: getCssVar(spacing, tight); + } + + @include e(header-left) { + display: flex; + gap: getCssVar(spacing, tight); + align-items: center; + @include m(caption) { + flex-shrink: 0; + } + @include m(desc) { + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + @include e(header-right) { + display: flex; + flex-shrink: 0; + gap: getCssVar(spacing, tight); + align-items: center; + @include m(icon) { + display: flex; + align-items: center; + cursor: pointer; + } + @include m(error-text) { + color: getCssVar(color, danger); + } + } + + @include e(content) { + padding: getCssVar(spacing, tight) 0; + } + + @include e(code-line) { + display: flex; + min-height: 18px; + margin-bottom: getCssVar(spacing, base, tight); + font-family: Consolas, monospace; + font-size: 0.95rem; + @include m(line-number) { + min-width: 20px; + margin-right: 15px; + color: getCssVar(color, success); + text-align: right; + user-select: none; + } + } + + @include e(property) { + color: getCssVar(ai-tool-call-item, text-property); + } + + @include e(string) { + overflow: hidden; + color: getCssVar(ai-tool-call-item, text-string); + text-overflow: ellipsis; + white-space: nowrap; + } + + @include e(boolean) { + color: getCssVar(ai-tool-call-item, text-boolean); + } + + @include e(number) { + color: getCssVar(ai-tool-call-item, text-number); + } + + @include e('null') { + color: getCssVar(ai-tool-call-item, text-boolean); + } + + @include e(error) { + color: getCssVar(ai-tool-call-item, text-error); + } +} diff --git a/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call-item/ai-tool-call-item.tsx b/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call-item/ai-tool-call-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fd4917fda50fff632a7bb9296bef704704be75bc --- /dev/null +++ b/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call-item/ai-tool-call-item.tsx @@ -0,0 +1,198 @@ +import { PropType, defineComponent, ref, onMounted } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IChatToolCall } from '../../interface'; +import { CopyIcon, DownIcon, ErrorIcon, RightIcon } from '../../icon'; +import './ai-tool-call-item.scss'; + +export const AIToolCallItem = defineComponent({ + props: { + toolCall: { + type: Object as PropType, + required: true, + }, + }, + setup(props) { + const ns = useNamespace('ai-tool-call-item'); + + /** + * 是否展开 + */ + const isExpanded = ref(false); + + /** + * @description 处理展开变更 + */ + const handleExpandChange = (): void => { + isExpanded.value = !isExpanded.value; + }; + + /** + * @description 拷贝 + */ + const onCopy = (event: MouseEvent): void => { + event.stopPropagation(); + ibiz.util.text.copy(JSON.stringify(props.toolCall, undefined, 2)); + ibiz.message.success(ibiz.i18n.t('util.inlineAiUtil.copy')); + }; + + /** + * @description 格式化值 + * @param {unknown} value + * @returns {*} {string} + */ + const formatValue = (value: unknown): string => { + if (typeof value === 'string') { + // 检查是否是错误消息 + if ( + value.includes('Failed') || + value.includes('Error') || + value.includes('ERR_') + ) { + return `"${value}"`; + } + return `"${value}"`; + } + if (typeof value === 'number') { + return `${value}`; + } + if (typeof value === 'boolean') { + return `${value}`; + } + if (value === null) { + return `null`; + } + return ''; + }; + + /** + * @description 递归格式化JSON字符串 + * @param {unknown} data + * @param {number} [indentLevel=0] + * @returns {*} + */ + const formatJSON = (data: unknown, indentLevel: number = 0) => { + const indent = ' '.repeat(indentLevel); + const lines = []; + + if (Array.isArray(data)) { + if (data.length === 0) { + return [`${indent}[]`]; + } + + lines.push(`${indent}[`); + + data.forEach((item, index) => { + const itemLines = formatJSON(item, indentLevel + 1); + const comma = index < data.length - 1 ? ',' : ''; + + if (typeof item === 'object' && item !== null) { + lines.push(...itemLines.slice(0, -1)); + lines.push(`${itemLines[itemLines.length - 1]}${comma}`); + } else { + lines.push(`${itemLines[0]}${comma}`); + } + }); + + lines.push(`${indent}]`); + } else if (typeof data === 'object' && data !== null) { + const keys = Object.keys(data); + if (keys.length === 0) { + return [`${indent}{}`]; + } + + lines.push(`${indent}{`); + + keys.forEach((key, index) => { + const value = (data as Record)[key]; + const comma = index < keys.length - 1 ? ',' : ''; + const keyElement = `"${key}":`; + + if (typeof value === 'object' && value !== null) { + const valueLines = formatJSON(value, indentLevel + 1); + lines.push(`${indent} ${keyElement} ${valueLines[0].trim()}`); + + if (valueLines.length > 1) { + lines.push(...valueLines.slice(1, -1)); + lines.push(`${valueLines[valueLines.length - 1]}${comma}`); + } + } else { + const valueElement = formatValue(value); + lines.push(`${indent} ${keyElement} ${valueElement}${comma}`); + } + }); + + lines.push(`${indent}}`); + } else { + lines.push(`${indent}${formatValue(data)}`); + } + + return lines; + }; + + const lines = ref([]); + + const onInit = (): void => { + const items = formatJSON(props.toolCall, 0); + lines.value = items.map( + (item, index) => + `${index + 1}${item}`, + ); + }; + + onMounted(() => { + onInit(); + }); + + return { + ns, + lines, + isExpanded, + onCopy, + handleExpandChange, + }; + }, + render() { + return ( +
+
+
+
+ {this.toolCall.name} +
+
+ {this.toolCall.parameters?.desc} +
+
+
+ {this.toolCall.error && ( +
+ + {ibiz.i18n.t('util.inlineAiUtil.error')} + +
+ {ErrorIcon} +
+
+ )} +
+ {CopyIcon} +
+
+ {this.isExpanded ? DownIcon : RightIcon} +
+
+
+ {this.isExpanded && ( +
+ {this.lines.map(line => { + return
; + })} +
+ )} +
+ ); + }, +}); diff --git a/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call/ai-tool-call.scss b/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call/ai-tool-call.scss new file mode 100644 index 0000000000000000000000000000000000000000..326932f8f3ab16726f285a598cd3c0c57ffd9438 --- /dev/null +++ b/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call/ai-tool-call.scss @@ -0,0 +1,17 @@ +@include b(ai-tool-call) { + color: getCssVar(inline-ai-textarea-container, color-text-0); + + @include e(toggle) { + display: block; + padding: getCssVar(spacing, tight) 0; + margin-top: getCssVar(spacing, tight); + text-align: center; + cursor: pointer; + background-color: getCssVar(inline-ai-textarea-container, color-bg-1); + border-radius: getCssVar(border-radius, small); + + &:hover { + background-color: getCssVar(inline-ai-textarea-container, color-bg-hover-1); + } + } +} diff --git a/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call/ai-tool-call.tsx b/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call/ai-tool-call.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f148947f6b94e945ca2f3fb45f38ae5c0ab510ad --- /dev/null +++ b/src/util/inline-ai-util/inline-ai-textarea/common/ai-tool-call/ai-tool-call.tsx @@ -0,0 +1,72 @@ +import { PropType, computed, defineComponent, ref } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IChatToolCall } from '../../interface'; +import { AIToolCallItem } from '../ai-tool-call-item/ai-tool-call-item'; +import './ai-tool-call.scss'; + +export const AIToolCall = defineComponent({ + props: { + toolCalls: { + type: Object as PropType, + required: true, + }, + }, + setup(props) { + const ns = useNamespace('ai-tool-call'); + + /** + * 是否展开 + */ + const isExpanded = ref(false); + + /** + * 显示切换 + */ + const showToggle = computed(() => { + return props.toolCalls.length > 4; + }); + + /** + * 调用工具 + */ + const items = computed(() => { + return showToggle.value && !isExpanded.value + ? props.toolCalls.slice(0, 4) + : props.toolCalls; + }); + + /** + * @description 处理切换 + */ + const handleToggle = (): void => { + isExpanded.value = !isExpanded.value; + }; + + return { + ns, + items, + showToggle, + isExpanded, + handleToggle, + }; + }, + render() { + if (!this.toolCalls.length) return; + return ( +
+ {this.items.map(item => { + return ; + })} + {this.showToggle && ( +
+ {this.isExpanded + ? ibiz.i18n.t('util.inlineAiUtil.collapseToolCall') + : ibiz.i18n.t('util.inlineAiUtil.expandToolCall', { + number: this.toolCalls.length, + })} +
+ )} +
+ ); + }, +}); diff --git a/src/util/inline-ai-util/inline-ai-textarea/common/index.ts b/src/util/inline-ai-util/inline-ai-textarea/common/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..717231ee887985b8b622c78f3ea00e02b3dc684b --- /dev/null +++ b/src/util/inline-ai-util/inline-ai-textarea/common/index.ts @@ -0,0 +1,2 @@ +export { AIThink } from './ai-think/ai-think'; +export { AIToolCall } from './ai-tool-call/ai-tool-call'; diff --git a/src/util/inline-ai-util/inline-ai-textarea/icon.tsx b/src/util/inline-ai-util/inline-ai-textarea/icon.tsx index d53a33627102963ff8437eff04d3ad89a6716885..cdf47f5aaef50e0d16454044d03dcb7d6fba8667 100644 --- a/src/util/inline-ai-util/inline-ai-textarea/icon.tsx +++ b/src/util/inline-ai-util/inline-ai-textarea/icon.tsx @@ -294,3 +294,64 @@ export const DownIcon = ( ); +/** + * 向右图标 + */ +export const RightIcon = ( + + + +); +/** + * 错误图标 + */ +export const ErrorIcon = ( + +); + +/** + * 拷贝图标 + */ +export const CopyIcon = ( + +); diff --git a/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.hook.ts b/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.hook.ts index 69cd0104ca06de7e26048bac7defd55a1aa2bedd..d001431cf471930544ae4444bd087380544d8c8e 100644 --- a/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.hook.ts +++ b/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.hook.ts @@ -1,5 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable prefer-regex-literals */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-useless-escape */ import qs from 'qs'; import { notNilEmpty } from 'qx-util'; import { useUIStore } from '@ibiz-template/vue3-util'; @@ -22,49 +25,7 @@ import { RegenerateIcon, ReplaceTextIcon, } from './icon'; - -export interface IAnswer { - /** - * @description 回答状态 - * @type {(10 | 20 | 30 | 40)} (未开始 | 回答中 | 回答完成 | 回答错误) - * @memberof IAnswer - */ - state: 10 | 20 | 30 | 40; - - /** - * @description 回答内容 - * @type {string} - * @memberof IAnswer - */ - content: string; -} - -export interface IMessage { - /** - * @description 内容信息 - * @type {string} - * @memberof IMessage - */ - content?: string; - /** - * @description 错误信息 - * @type {string} - * @memberof IMessage - */ - error?: string; - /** - * @description 思考信息 - * @type {string} - * @memberof IMessage - */ - think?: string; - /** - * @description 消息角色 - * @type {('USER' | 'ASSISTANT')} - * @memberof IMessage - */ - role: 'USER' | 'ASSISTANT'; -} +import { IAnswer, IChatToolCall, IMessage } from './interface'; /** * @description 计算参数 @@ -186,6 +147,7 @@ export const useAI = ( parseContent: (text?: string) => { think: string | undefined; content: string | undefined; + toolcalls: IChatToolCall[]; }; loadAiHistory: () => Promise; } => { @@ -310,7 +272,8 @@ export const useAI = ( const parseContent = (text?: string) => { let think: string | undefined; let content: string | undefined; - if (!text) return { think, content }; + const toolcalls: IChatToolCall[] = []; + if (!text) return { think, content, toolcalls }; const openThinkIndex = text.indexOf(''); const closeThinkIndex = text.indexOf(''); // 如果存在思考过程则解析思考内容 @@ -322,9 +285,32 @@ export const useAI = ( content = closeThinkIndex === -1 ? undefined : text.slice(closeThinkIndex + 8); } else { - content = text; + const toolCallRegex = new RegExp( + '\\s*({[\\s\\S]*?})\\s*', + 'g', + ); + const matches = text.matchAll(toolCallRegex); + for (const match of matches) { + try { + const toolCallData = JSON.parse(match[1]); + const tempToolCall = { + name: toolCallData.name, + parameters: toolCallData.parameters, + error: toolCallData.error || false, + }; + if (toolCallData.result) + Object.assign(tempToolCall, { + result: toolCallData.result, + }); + toolcalls.push(tempToolCall); + } catch (e) { + console.error('解析工具调用失败:', e); + } + } + // 清除文本调用信息 + content = text.replace(/\[^]*?\<\/tool_call\>/gs, '').trim(); } - return { think, content }; + return { think, content, toolcalls }; }; /** @@ -543,6 +529,9 @@ export const useBase = ( textareaRef.value.style.height = `${textareaRef.value.scrollHeight}px`; }); }, + { + immediate: true, + }, ); /** diff --git a/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.scss b/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.scss index c4dd81f462285a555896d0364443c95f9f091e32..f84289e32def6fd2180e8cda093b181bdde83c50 100644 --- a/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.scss +++ b/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.scss @@ -77,12 +77,14 @@ $inline-ai-textarea-container: ( .#{bem(inline-ai-textarea-container, content)} { border: none; - border-radius: getCssVar(border-radius, small) getCssVar(border-radius, small) 0 0; + border-radius: getCssVar(border-radius, small) + getCssVar(border-radius, small) 0 0; box-shadow: none; } .#{bem(inline-ai-textarea-container, footer)} { - border-radius: 0 0 getCssVar(border-radius, small) getCssVar(border-radius, small); + border-radius: 0 0 getCssVar(border-radius, small) + getCssVar(border-radius, small); } } @@ -103,7 +105,6 @@ $inline-ai-textarea-container: ( display: flex; flex-direction: column; flex-grow: 1; - gap: getCssVar(spacing, tight); overflow-y: auto; textarea { @@ -115,7 +116,9 @@ $inline-ai-textarea-container: ( background-color: getCssVar(inline-ai-textarea-container, color-bg-0); border: none; outline: none; - transition: height .3s cubic-bezier(.4,0,.2,1),opacity .3s cubic-bezier(.4,0,.2,1); + transition: + height 0.3s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); &:disabled { background-color: getCssVar(inline-ai-textarea-container, color-bg-0); @@ -144,8 +147,10 @@ $inline-ai-textarea-container: ( border-radius: getCssVar(spacing, extra, tight); &:hover { - background-color: getCssVar(inline-ai-textarea-container, - color-bg-hover-1); + background-color: getCssVar( + inline-ai-textarea-container, + color-bg-hover-1 + ); } } @@ -189,7 +194,10 @@ $inline-ai-textarea-container: ( cursor: pointer; &:hover { - background-color: getCssVar(inline-ai-textarea-container, color-bg-hover-2); + background-color: getCssVar( + inline-ai-textarea-container, + color-bg-hover-2 + ); } @include when(danger) { @@ -201,7 +209,8 @@ $inline-ai-textarea-container: ( @include m(divider) { margin: getCssVar(spacing, extra, tight) getCssVar(spacing, base, loose); - border-top: 1px solid getCssVar(inline-ai-textarea-container, color-border); + border-top: 1px solid + getCssVar(inline-ai-textarea-container, color-border); } } @@ -212,50 +221,12 @@ $inline-ai-textarea-container: ( @include e(think) { flex-shrink: 0; - width: 100%; - height: auto; - font-size: getCssVar(font-size, small); - color: getCssVar(inline-ai-textarea-container, color-text-1); - @include m(header) { - display: flex; - gap: getCssVar(spacing, extra-tight); - align-items: center; - margin-bottom: getCssVar(spacing, tight); - cursor: pointer; + margin-bottom: getCssVar(spacing, tight); + } - &:hover { - color: getCssVar(inline-ai-textarea-container, color-text-hove-1); - } - } - @include m(state-icon) { - display: flex; - align-items: center; - color: getCssVar(color, success); - @include when(loading) { - color: getCssVar(inline-ai-textarea-container, color-loading); - } - } - @include m(collapse-icon) { - display: flex; - align-items: center; - } - @include m(content) { - position: relative; - padding-left: getCssVar(spacing, tight); - word-wrap: break-word; - white-space: pre-wrap; - transition: height .3s cubic-bezier(.4,0,.2,1),opacity .3s cubic-bezier(.4,0,.2,1); - - &::before { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 2px; - content: ""; - background-color: getCssVar(inline-ai-textarea-container, color-border);; - } - } + @include e(tool-call) { + flex-shrink: 0; + margin-bottom: getCssVar(spacing, tight); } @include e(loading) { @@ -297,4 +268,4 @@ $inline-ai-textarea-container: ( } } } -} \ No newline at end of file +} diff --git a/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.tsx b/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.tsx index 3dd91ef06e0c7d20e28713ed1a66c88a5247fe63..9c3e9b0fa181d62d6d3dd70a7313593f3b022d11 100644 --- a/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.tsx +++ b/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.tsx @@ -6,21 +6,13 @@ import { useNamespace } from '@ibiz-template/vue3-util'; import { IInLineAiChatOptions } from '@ibiz-template/runtime'; import { useAI, - IAnswer, useBase, - IMessage, computedInLineAIParams, useInLineAIContainerClick, } from './inline-ai-textarea.hook'; -import { - AIIcon, - UpIcon, - SendIcon, - StopIcon, - DownIcon, - LoadingIcon, - ThinkSuccessIcon, -} from './icon'; +import { AIIcon, SendIcon, StopIcon } from './icon'; +import { IAnswer, IMessage } from './interface'; +import { AIToolCall, AIThink } from './common'; import './inline-ai-textarea.scss'; export const InlineAITextArea = defineComponent({ @@ -92,6 +84,7 @@ export const InlineAITextArea = defineComponent({ */ const message = ref({ role: 'USER', + toolcalls: [], error: undefined, think: undefined, content: props.content, @@ -179,14 +172,17 @@ export const InlineAITextArea = defineComponent({ break; case 30: answerContent = answer.content; - Object.assign(message.value, parseContent(answerContent)); + const { think, content } = parseContent(answerContent); + // 完成时只更新思考和内容 + Object.assign(message.value, { think, content }); break; case 40: isCollapse.value = true; Object.assign(message.value, { - error: answer.content, + toolcalls: [], think: undefined, content: undefined, + error: answer.content, }); break; default: @@ -213,6 +209,7 @@ export const InlineAITextArea = defineComponent({ // 重置数据 answerContent = undefined; Object.assign(message.value, { + toolcalls: [], error: undefined, think: undefined, content: undefined, @@ -244,13 +241,6 @@ export const InlineAITextArea = defineComponent({ } }; - /** - * @description 折叠变更 - */ - const onCollapseChange = (): void => { - isCollapse.value = !isCollapse.value; - }; - /** * @description 处理行为 * @param {MouseEvent} e @@ -312,35 +302,9 @@ export const InlineAITextArea = defineComponent({ * @description 绘制内容 * @returns {*} */ - const renderContent = () => { + const renderError = () => { if (message.value.error) return
{message.value.error}
; - if (message.value.think) - return ( -
-
-
- {isLoading.value ? LoadingIcon : ThinkSuccessIcon} -
-
- {isLoading.value - ? ibiz.i18n.t('util.inlineAiUtil.thinking') - : ibiz.i18n.t('util.inlineAiUtil.thinked')} -
-
- {isCollapse.value ? DownIcon : UpIcon} -
-
- {!isCollapse.value && ( -
{message.value.think}
- )} -
- ); }; return { @@ -351,6 +315,7 @@ export const InlineAITextArea = defineComponent({ message, disabled, isLoading, + isCollapse, actionsRef, textareaRef, actionStyle, @@ -358,10 +323,10 @@ export const InlineAITextArea = defineComponent({ contentStyle, containerStyle, onKeydown, + renderError, sendQuestion, handleAction, renderLoading, - renderContent, stopQuestionAndClose, }; }, @@ -384,7 +349,20 @@ export const InlineAITextArea = defineComponent({ )}
{this.renderLoading()} - {this.renderContent()} + + { + this.isCollapse = val; + }} + /> + {this.renderError()}