소스 검색

添加结构数据插件

kagg886 1 개월 전
부모
커밋
3fbdfb7d94
4개의 변경된 파일89개의 추가작업 그리고 18개의 파일을 삭제
  1. 6 2
      src/api/assist/type.ts
  2. 11 0
      src/components/markdown/plugins/impl/VueStructData.vue
  3. 57 0
      src/components/markdown/plugins/struct-data.ts
  4. 15 16
      src/views/assistant/index.vue

+ 6 - 2
src/api/assist/type.ts

@@ -49,7 +49,7 @@ export type ChatRequest = {
 	message: Message[]
 }
 
-export type ChatResponseType = 'message' | 'toolcall' | 'toolres' | 'error' | 'datamsg'
+export type ChatResponseType = 'message' | 'toolcall' | 'toolres' | 'error' | 'datamsg' | 'structdata'
 
 export type ChatResponseBase<T = ChatResponseType> = {
 	type: T
@@ -87,7 +87,11 @@ export type DataResponse = ChatResponseBase<'datamsg'> & {
 	data: string
 }
 
-export type ChatResponse = Text | ToolCallRequest | ToolCallResponse | ErrorResponse | DataResponse
+export type StructDataResponse = ChatResponseBase<'structdata'> & {
+	uuid: string
+}
+
+export type ChatResponse = Text | ToolCallRequest | ToolCallResponse | ErrorResponse | DataResponse | StructDataResponse
 
 // 大语言模型配置相关类型定义
 

+ 11 - 0
src/components/markdown/plugins/impl/VueStructData.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+const props = defineProps<{
+	uuid: string
+}>()
+</script>
+
+<template>
+	<div>{{props.uuid}}</div>
+</template>
+
+<style scoped lang="scss"></style>

+ 57 - 0
src/components/markdown/plugins/struct-data.ts

@@ -0,0 +1,57 @@
+import MarkdownIt from 'markdown-it'
+import type { RenderRule } from 'markdown-it/lib/renderer.mjs'
+import type Token from 'markdown-it/lib/token.mjs'
+import { defineMarkdownPlugin } from '../type/markdown.ts'
+import { h } from 'vue'
+import VueStructData from '/@/components/markdown/plugins/impl/VueStructData.vue'
+
+// 渲染echarts代码块
+const renderStructData: RenderRule = (tokens: Token[], idx: number) => {
+	const token = tokens[idx]
+	const content = token.content.trim()
+
+	if (!content) {
+		return '<div style="padding: 16px;background-color: #fff5f5;border: 1px solid #fed7d7;border-radius: 6px;color: #c53030;margin: 16px 0;">ECharts配置不能为空</div>'
+	}
+	// 生成完整HTML
+	return `<struct-data style="width: 100%;height: 350px;margin: 16px 0; border-radius: 6px" data="${encodeURIComponent(
+		content
+	)}"></struct-data>`
+}
+
+const StructDataPlugin = defineMarkdownPlugin({
+	tagName: 'struct-data',
+	mdItPlugin: function (md: MarkdownIt) {
+		// 保存原始的fence渲染器
+		const defaultRender =
+			md.renderer.rules.fence ??
+			function (tokens, idx, options, _env, renderer) {
+				return renderer.renderToken(tokens, idx, options)
+			}
+
+		// if (customElements.get('struct-data') === undefined) {
+		//   customElements.define('struct-data', EChartsElement, { extends: 'div' })
+		// }
+
+		// 重写fence渲染器
+		md.renderer.rules.fence = function (tokens, idx, options, env, renderer) {
+			const token = tokens[idx]
+			const info = token.info ? token.info.trim() : ''
+
+			// 检查是否是echarts代码块
+			if (info === 'structdata') {
+				return renderStructData(tokens, idx, options, env, renderer)
+			}
+
+			// 其他代码块使用默认渲染器
+			return defaultRender(tokens, idx, options, env, renderer)
+		}
+	},
+	renderer: (node: { attribs: Record<string, string> }) => {
+		return h(VueStructData, {
+			data: node.attribs.data,
+		})
+	},
+})
+
+export default StructDataPlugin

+ 15 - 16
src/views/assistant/index.vue

@@ -31,8 +31,9 @@ import { useLoading } from '/@/utils/loading-util'
 import { Setting as EleSetting } from '@element-plus/icons-vue'
 import { useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
+import StructDataPlugin from '/@/components/markdown/plugins/struct-data'
 
-const plugins: Array<MarkdownPlugin<any>> = [EChartsPlugin(), ToolsLoadingPlugin(), TablePlugin()]
+const plugins: Array<MarkdownPlugin<any>> = [EChartsPlugin(), ToolsLoadingPlugin(), TablePlugin(),StructDataPlugin()]
 
 //聊天管理接口
 // 消息列表
@@ -103,11 +104,11 @@ const openPromptDialog = ref(false)
 
 // 模型选择
 const modelOptions = ref<LmConfigInfo[]>([])
-const modelLabel = computed(()=> {
-	const select = modelOptions.value.filter(i=>i.id === selectedModel.value)
+const modelLabel = computed(() => {
+	const select = modelOptions.value.filter((i) => i.id === selectedModel.value)
 
 	if (select.length === 0) {
-		return "选择模型"
+		return '选择模型'
 	}
 
 	return select[0].modelName
@@ -271,6 +272,14 @@ const chatInternal = (rtn: Message, context: Message[] = messages.value) => {
 		},
 		onReceive: (resp: ChatResponse) => {
 			switch (resp.type) {
+				case 'structdata':
+					rtn.render_content += `
+\`\`\`structdata
+${resp.uuid}
+\`\`\`
+
+`
+					break
 				case 'message':
 					rtn.render_content += resp.message
 					rtn.content += resp.message
@@ -718,8 +727,7 @@ const { loading: exportConversationLoading, doLoading: exportConversation } = us
 	exportId.value = -1
 })
 
-
-const isBlank = (str:string)=> {
+const isBlank = (str: string) => {
 	return str == null || str.trim().length === 0
 }
 </script>
@@ -782,16 +790,7 @@ const isBlank = (str:string)=> {
 						<!-- 非编辑状态的三点菜单 -->
 						<template v-if="editingConversationId !== conv.session_id">
 							<el-dropdown trigger="click" placement="bottom-end" @click.stop>
-								<el-button
-									type="primary"
-									size="small"
-									:icon="MoreFilled"
-									class="action-btn more-btn"
-									title="更多操作"
-									plain
-									circle
-									@click.stop
-								/>
+								<el-button type="primary" size="small" :icon="MoreFilled" class="action-btn more-btn" title="更多操作" plain circle @click.stop />
 								<template #dropdown>
 									<el-dropdown-menu>
 										<el-dropdown-item @click="exportConversation(conv.session_id)">