diff --git a/src/components/chart-material/chart-material-item/chart-material-item.scss b/src/components/chart-material/chart-material-item/chart-material-item.scss new file mode 100644 index 0000000000000000000000000000000000000000..d236da24500c4c7a319accfcb45c41631eac9d68 --- /dev/null +++ b/src/components/chart-material/chart-material-item/chart-material-item.scss @@ -0,0 +1,63 @@ +@include b('chart-material-item') { + position: relative; + display: flex; + gap: 0.75rem; + width: 10rem; + height: 3.25rem; + padding: 0.2rem; + color: getCssVar('ai-chat', 'color'); + background-color: getCssVar('ai-chat', 'bg-color'); + border: 1px solid getCssVar('ai-chat', 'border-color'); + border-radius: 0.25rem; + + @include e('icon') { + position: absolute; + top: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + color: getCssVar('ai-chat', 'color'); + + svg { + width: 1.25rem; + height: 1.25rem; + } + } + + @include e('left') { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + + svg { + width: 1.75rem; + height: 1.75rem; + } + } + + @include e('right') { + flex-grow: 1; + min-width: 0; + } + + @include e('caption') { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + @include e('metadata') { + display: flex; + gap: 0.75rem; + align-items: center; + font-size: 0.85rem; + + @include m('state') { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } +} diff --git a/src/components/chart-material/chart-material-item/chart-material-item.tsx b/src/components/chart-material/chart-material-item/chart-material-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6afbad552ed2b75da8c2b144e156b00dc0856cae --- /dev/null +++ b/src/components/chart-material/chart-material-item/chart-material-item.tsx @@ -0,0 +1,81 @@ +import { PropType, computed, defineComponent } from 'vue'; +import { Namespace } from '../../../utils'; +import { IMaterial } from '../../../interface'; +import { FileIcon, RemoveIcon } from '../../../icon'; +import './chart-material-item.scss'; + +export const ChartMaterialItem = defineComponent({ + props: { + material: { + type: Object as PropType, + required: true, + }, + readonly: { + type: Boolean, + default: false, + }, + }, + emits: { + remove: () => true, + }, + setup(props, { emit }) { + const ns = new Namespace('chart-material-item'); + + const onRemove = (): void => { + emit('remove'); + }; + + const state = computed(() => { + const _state = (props.material.metadata as any).state; + const result = { color: '#ff4d4f', label: '未知状态' }; + switch (_state) { + case 'successed': + Object.assign(result, { color: '#1890ff', label: '上传成功' }); + break; + case 'uploading': + Object.assign(result, { color: '#52c41a', label: '上传中' }); + break; + case 'failed': + Object.assign(result, { color: '#ff4d4f', label: '上传失败' }); + break; + default: + break; + } + return result; + }); + + return { + ns, + state, + onRemove, + }; + }, + render() { + return ( +
+ {!this.readonly && ( +
+ {RemoveIcon} +
+ )} +
{FileIcon}
+
+
+ {(this.material.data as any).name} +
+
+
+ {(this.material.metadata as any).size}B +
+
+ {this.state.label} +
+
+
+
+ ); + }, +}); diff --git a/src/components/chart-material/chart-material.scss b/src/components/chart-material/chart-material.scss new file mode 100644 index 0000000000000000000000000000000000000000..e59dd17800e41d9636bbdeba4b08f52d84aacd0e --- /dev/null +++ b/src/components/chart-material/chart-material.scss @@ -0,0 +1,6 @@ +@include b('chart-material') { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-bottom: 0.75rem; +} diff --git a/src/components/chart-material/chart-material.tsx b/src/components/chart-material/chart-material.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e1f451e8fe9141b06f064b74e3f3cd71041062af --- /dev/null +++ b/src/components/chart-material/chart-material.tsx @@ -0,0 +1,49 @@ +import { PropType, defineComponent } from 'vue'; +import { Namespace } from '../../utils'; +import { IMaterial } from '../../interface'; +import { ChartMaterialItem } from './chart-material-item/chart-material-item'; +import './chart-material.scss'; + +export const ChartMaterial = defineComponent({ + props: { + materials: { + type: Array as PropType, + required: true, + }, + readonly: { + type: Boolean, + default: false, + }, + }, + emits: { + remove: (_material: IMaterial) => true, + }, + setup(props, { emit }) { + const ns = new Namespace('chart-material'); + + const onRemove = (material: IMaterial): void => { + emit('remove', material); + }; + + return { + ns, + onRemove, + }; + }, + render() { + if (!this.materials.length) return; + return ( +
+ {this.materials.map(item => { + return ( + this.onRemove(item)} + /> + ); + })} +
+ ); + }, +}); diff --git a/src/components/chat-container/chat-container.scss b/src/components/chat-container/chat-container.scss index f4209e51f1e16778751c3de1799a72a8e66c38c9..d85ba5ed4ce4a6f1a9fdbbf5900e391f1c708be5 100644 --- a/src/components/chat-container/chat-container.scss +++ b/src/components/chat-container/chat-container.scss @@ -10,6 +10,7 @@ $ai-chat: ( 'bg-color-4': #f6f3f4, 'bg-color-5': rgb(53 54 60 / 100%), 'primary-color': #2b7fff, + 'light-primary-color': #c7e4ff, 'loading-color': #65b3fc, 'danger-color': #fb2c36, 'success-color': #3bb346, @@ -25,13 +26,13 @@ $ai-chat: ( 'disabled-bg-color': #ebe6e7, // 阴影色 'box-shadow': ( - rgb(0 0 0 / 0%) 0 0 0 0, - rgb(0 0 0 / 0%) 0 0 0 0, - rgb(0 0 0 / 0%) 0 0 0 0, - rgb(0 0 0 / 0%) 0 0 0 0, - rgb(0 0 0 / 10%) 0 1px 3px 0, - rgb(0 0 0 / 10%) 0 1px 2px -1px - ), + rgb(0 0 0 / 0%) 0 0 0 0, + rgb(0 0 0 / 0%) 0 0 0 0, + rgb(0 0 0 / 0%) 0 0 0 0, + rgb(0 0 0 / 0%) 0 0 0 0, + rgb(0 0 0 / 10%) 0 1px 3px 0, + rgb(0 0 0 / 10%) 0 1px 2px -1px + ) ); @include b('chat-container') { @@ -46,6 +47,72 @@ $ai-chat: ( height: 100%; background: getCssVar('ai-chat', 'bg-color'); + // markdown 样式 + .cherry { + min-height: 0; + border-radius: 0.25rem; + box-shadow: none; + + *::-webkit-scrollbar { + width: 0.5rem; + height: 0.5rem; + } + + *::-webkit-scrollbar-thumb { + background-color: getCssVar('ai-chat', 'thumb-bg-color'); + border-radius: 0.25rem; + } + + *::-webkit-scrollbar-thumb:hover { + background-color: getCssVar('ai-chat', 'thumb-bg-color'); + } + + div[data-type='codeBlock'] + pre[class*='language-']::-webkit-scrollbar-thumb { + background-color: getCssVar('ai-chat', 'bg-color-2'); + } + + .cherry-markdown { + pre { + background-color: getCssVar('ai-chat', 'bg-color'); + } + + p { + margin: 0; + } + } + + div.cherry-previewer { + padding: 0; + border: 0; + + figure { + max-width: 62.5%; + + > svg { + width: 100%; + min-height: 6rem; + } + } + } + + .anchor { + display: none !important; + } + + .cherry-markdown.theme__dark ul.cherry-list__default, + .cherry.theme__dark.cherry--no-toolbar, + div.cherry-previewer { + color: getCssVar('ai-chat', 'color'); + background-color: getCssVar('ai-chat', 'bg-color'); + } + } + + .pre-wrap-container { + word-wrap: break-word; + white-space: pre-wrap; + } + @include e('header') { display: flex; flex-shrink: 0; diff --git a/src/components/chat-input/chat-input.scss b/src/components/chat-input/chat-input.scss index 96f9e218957268b09d5d4b082b5d7438338a8bf8..3179070ec4e56191730293505c5e8db8548e176c 100644 --- a/src/components/chat-input/chat-input.scss +++ b/src/components/chat-input/chat-input.scss @@ -1,9 +1,12 @@ @include b('chat-input') { - display: flex; - gap: 0.75rem; - align-items: center; padding: 1rem; + @include e('content') { + display: flex; + gap: 0.75rem; + align-items: center; + } + @include e('icon') { display: flex; flex-shrink: 0; diff --git a/src/components/chat-input/chat-input.tsx b/src/components/chat-input/chat-input.tsx index 43276e14f3a3b0e318af0f2eba4e20f9ffbf5f03..b8c2dd01fe58f3802ea027fe3923af13a130b6df 100644 --- a/src/components/chat-input/chat-input.tsx +++ b/src/components/chat-input/chat-input.tsx @@ -1,8 +1,15 @@ import { PropType, computed, defineComponent, onMounted, ref } from 'vue'; import { Namespace } from '../../utils'; -import { IChatToolbarItem } from '../../interface'; -import { AIChatController } from '../../controller'; -import { SendIcon, OpenVoiceIcon, CloseVoiceIcon, StopIcon } from '../../icon'; +import { IChatToolbarItem, IMaterial } from '../../interface'; +import { AIChatController, AIMaterialFactory } from '../../controller'; +import { + SendIcon, + StopIcon, + UploadIcon, + OpenVoiceIcon, + CloseVoiceIcon, +} from '../../icon'; +import { ChartMaterial } from '../chart-material/chart-material'; import './chat-input.scss'; export const ChatInput = defineComponent({ @@ -50,7 +57,7 @@ export const ChatInput = defineComponent({ recognition.value.onresult = e => { const transcript = e.results?.[0]?.[0]?.transcript; - console.log('语音内容', transcript); + if (transcript) message.value += `${transcript}`; }; } }; @@ -59,6 +66,19 @@ export const ChatInput = defineComponent({ onInitRecognition(); }); + /** + * @description 文件上传 + * @param {MouseEvent} event + * @returns {*} {Promise} + */ + const uploadFile = async (event: MouseEvent): Promise => { + const materialHelper = AIMaterialFactory.getMaterialHelper( + 'ossfile', + props.controller, + ); + await materialHelper.excuteAction(event); + }; + /** * @description 发送消息 * @returns {*} {Promise} @@ -82,43 +102,66 @@ export const ChatInput = defineComponent({ recording.value ? recognition.value.end() : recognition.value.start(); }; + /** + * @description 删除素材 + * @param {IMaterial} material + */ + const onRemoveMaterial = (material: IMaterial): void => { + props.controller.deleteMaterial(material); + }; + return { ns, message, isLoading, recording, + uploadFile, onSendMessage, + onRemoveMaterial, onSpeechRecognition, }; }, render() { return (
-
- {this.recording ? CloseVoiceIcon : OpenVoiceIcon} -
-