kagg886 преди 3 месеца
родител
ревизия
5f7dcf32d9
променени са 1 файла, в които са добавени 196 реда и са изтрити 143 реда
  1. 196 143
      src/views/assistant/index.vue

+ 196 - 143
src/views/assistant/index.vue

@@ -1,14 +1,12 @@
 <script setup lang="ts">
-import { ref, nextTick, onMounted } from 'vue'
+import { ref, nextTick, onMounted, reactive } from 'vue'
 import { Local } from '/@/utils/storage'
 import { User, ChatDotRound, Delete, Edit, Check, Close } from '@element-plus/icons-vue'
 import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
 import EChartsPlugin from '/@/components/markdown/plugins/markdown-it-echarts'
 import Markdown from '/@/components/markdown/Markdown.vue'
 
-const plugins: Array<MarkdownPlugin<any>> = [
-	EChartsPlugin()
-]
+const plugins: Array<MarkdownPlugin<any>> = [EChartsPlugin()]
 
 const getUserInfos = ref<{
 	avatar: string
@@ -24,12 +22,10 @@ interface Message {
 }
 
 // 会话列表
-const conversations = ref<{id: number,title: string,active: boolean}[]>([])
+const conversations = ref<{ id: number; title: string; active: boolean }[]>([])
 
 for (let i = 0; i < 100; i++) {
-	conversations.value.push(
-		{ id: i, title: `Summary ${i}`, active: false }
-	)
+	conversations.value.push({ id: i, title: `Summary ${i}`, active: false })
 }
 
 // 编辑状态管理
@@ -51,17 +47,17 @@ const toolOptions = ref([
 		children: [
 			{
 				value: 'codebase-retrieval',
-				label: '代码库检索'
+				label: '代码库检索',
 			},
 			{
 				value: 'str-replace-editor',
-				label: '代码编辑器'
+				label: '代码编辑器',
 			},
 			{
 				value: 'save-file',
-				label: '文件保存'
-			}
-		]
+				label: '文件保存',
+			},
+		],
 	},
 	{
 		value: 'web',
@@ -69,13 +65,13 @@ const toolOptions = ref([
 		children: [
 			{
 				value: 'web-search',
-				label: '网络搜索'
+				label: '网络搜索',
 			},
 			{
 				value: 'web-fetch',
-				label: '网页获取'
-			}
-		]
+				label: '网页获取',
+			},
+		],
 	},
 	{
 		value: 'system',
@@ -83,14 +79,14 @@ const toolOptions = ref([
 		children: [
 			{
 				value: 'execute-command',
-				label: '命令执行'
+				label: '命令执行',
 			},
 			{
 				value: 'launch-process',
-				label: '进程启动'
-			}
-		]
-	}
+				label: '进程启动',
+			},
+		],
+	},
 ])
 
 // 模型选择数据
@@ -98,7 +94,7 @@ const modelOptions = ref([
 	{ label: 'Claude Sonnet 4', value: 'claude-sonnet-4' },
 	{ label: 'GPT-4', value: 'gpt-4' },
 	{ label: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' },
-	{ label: 'Gemini Pro', value: 'gemini-pro' }
+	{ label: 'Gemini Pro', value: 'gemini-pro' },
 ])
 
 // 选中的工具和模型
@@ -120,21 +116,30 @@ const sendMessage = () => {
 		id: Date.now(),
 		type: 'user',
 		content: inputMessage.value,
-		timestamp: new Date()
+		timestamp: new Date(),
 	})
 
+	//清空用户聊天框
 	const userMessage = inputMessage.value
 	inputMessage.value = ''
 
 	// 滚动到底部
 	scrollToBottom()
 
-	// 模拟AI回复
-	setTimeout(() => {
-		messages.value.push({
-			id: Date.now() + 1,
-			type: 'ai',
-			content: `我收到了您的消息:"${userMessage}"。这是一个示例回复,包含markdown格式的内容。
+	const ai = reactive<Message>({
+		id: Date.now()+1,
+		type: 'ai',
+		content: '',
+		timestamp: new Date()
+	})
+
+	// 添加一个回复
+	messages.value.push(ai)
+
+	const delay = async (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))
+
+	const action = async () => {
+		const message = `我收到了您的消息:"${userMessage}"。这是一个示例回复,包含markdown格式的内容。
 
 ## 回复内容
 
@@ -144,12 +149,27 @@ const sendMessage = () => {
 
 \`\`\`javascript
 console.log('这是代码示例');
-\`\`\``,
-			timestamp: new Date()
-		})
+\`\`\``
+		for (let i = 0; i < message.length; i++) {
+			ai.content += message[i]
+			await delay(50)
+		}
+	}
+	action().finally(()=> {
 		isConversationActive.value = false
 		scrollToBottom()
-	}, 1000)
+	})
+
+	// setTimeout(() => {
+	// 	messages.value.push({
+	// 		id: Date.now() + 1,
+	// 		type: 'ai',
+	// 		content: ,
+	// 		timestamp: new Date(),
+	// 	})
+	// 	isConversationActive.value = false
+	// 	scrollToBottom()
+	// }, 1000)
 }
 
 // 终止对话
@@ -168,7 +188,7 @@ const scrollToBottom = () => {
 }
 // 选择会话
 const selectConversation = (id: number) => {
-	conversations.value.forEach(conv => {
+	conversations.value.forEach((conv) => {
 		conv.active = conv.id === id
 	})
 }
@@ -181,7 +201,7 @@ const deleteConversation = (id: number) => {
 
 // 编辑摘要
 const editSummary = (id: number) => {
-	const conversation = conversations.value.find(conv => conv.id === id)
+	const conversation = conversations.value.find((conv) => conv.id === id)
 	if (conversation) {
 		// 设置当前编辑的会话ID
 		editingConversationId.value = id
@@ -199,7 +219,7 @@ const editSummary = (id: number) => {
 
 // 确认编辑
 const confirmEdit = (id: number) => {
-	const conversation = conversations.value.find(conv => conv.id === id)
+	const conversation = conversations.value.find((conv) => conv.id === id)
 	if (conversation && editingTitle.value.trim()) {
 		conversation.title = editingTitle.value.trim()
 		// 清除编辑状态
@@ -218,7 +238,6 @@ const cancelEdit = (id: number) => {
 onMounted(() => {
 	scrollToBottom()
 })
-
 </script>
 
 <template>
@@ -232,7 +251,7 @@ onMounted(() => {
 				<div
 					v-for="conv in conversations"
 					:key="conv.id"
-					@click="selectConversation(conv.id)"
+					@click="editingConversationId !== conv.id ? selectConversation(conv.id) : () => {}"
 					:class="['conversation-item', { active: conv.active, editing: editingConversationId === conv.id }]"
 				>
 					<!-- 非编辑状态 -->
@@ -279,27 +298,15 @@ onMounted(() => {
 
 						<!-- 编辑状态的按钮 -->
 						<template v-else>
-							<el-button
-								type="success"
-								size="small"
-								@click.stop="confirmEdit(conv.id)"
-								class="action-btn confirm-btn"
-								title="确认修改"
-								plain
-								circle
-							>
-								<el-icon><Check /></el-icon>
+							<el-button type="success" size="small" @click.stop="confirmEdit(conv.id)" class="action-btn confirm-btn" title="确认修改" plain circle>
+								<el-icon>
+									<Check />
+								</el-icon>
 							</el-button>
-							<el-button
-								type="info"
-								size="small"
-								@click.stop="cancelEdit(conv.id)"
-								class="action-btn cancel-btn"
-								title="取消编辑"
-								plain
-								circle
-							>
-								<el-icon><Close /></el-icon>
+							<el-button type="info" size="small" @click.stop="cancelEdit(conv.id)" class="action-btn cancel-btn" title="取消编辑" plain circle>
+								<el-icon>
+									<Close />
+								</el-icon>
 							</el-button>
 						</template>
 					</div>
@@ -313,20 +320,12 @@ onMounted(() => {
 			<!-- 消息展示区域 -->
 			<div class="messages-container" ref="messagesContainer">
 				<div v-if="messages.length !== 0">
-					<div
-						v-for="message in messages"
-						:key="message.id"
-						:class="['message-wrapper', message.type]"
-					>
+					<div v-for="message in messages" :key="message.id" :class="['message-wrapper', message.type]">
 						<!-- AI消息 -->
 						<div v-if="message.type === 'ai'" class="ai-message-container">
 							<el-avatar class="message-avatar" :icon="ChatDotRound" />
 							<div class="message-bubble ai-bubble">
-								<Markdown
-									:content="message.content"
-									:plugins="plugins"
-									class="markdown-content"
-								/>
+								<Markdown :content="message.content" :plugins="plugins" class="markdown-content" />
 							</div>
 						</div>
 
@@ -335,11 +334,7 @@ onMounted(() => {
 							<div class="message-bubble user-bubble">
 								{{ message.content }}
 							</div>
-							<el-avatar
-								class="message-avatar"
-								:src="getUserInfos.avatar"
-								:icon="User"
-							/>
+							<el-avatar class="message-avatar" :src="getUserInfos.avatar" :icon="User" />
 						</div>
 					</div>
 				</div>
@@ -357,13 +352,9 @@ onMounted(() => {
 					<!-- 标题和描述 -->
 					<div class="empty-text">
 						<h2 class="empty-title">开始新的对话</h2>
-						<p class="empty-description">
-							选择工具和模型,然后在下方输入您的问题开始对话
-						</p>
+						<p class="empty-description">选择工具和模型,然后在下方输入您的问题开始对话</p>
 					</div>
 
-
-
 					<!-- 快速开始提示 -->
 					<div class="quick-start">
 						<div class="quick-start-item">
@@ -384,27 +375,13 @@ onMounted(() => {
 					<div class="example-questions">
 						<h4>试试这些问题:</h4>
 						<div class="question-tags">
-							<el-tag
-								class="question-tag"
-								@click="inputMessage = '帮我查看设备运行状态和告警信息'"
-								type="info"
-							>
+							<el-tag class="question-tag" @click="inputMessage = '帮我查看设备运行状态和告警信息'" type="info">
 								帮我查看设备运行状态和告警信息
 							</el-tag>
-							<el-tag
-								class="question-tag"
-								@click="inputMessage = '分析用户权限配置和角色分配情况'"
-								type="success"
-							>
+							<el-tag class="question-tag" @click="inputMessage = '分析用户权限配置和角色分配情况'" type="success">
 								分析用户权限配置和角色分配情况
 							</el-tag>
-							<el-tag
-								class="question-tag"
-								@click="inputMessage = '检查系统性能和在线用户统计'"
-								type="warning"
-							>
-								检查系统性能和在线用户统计
-							</el-tag>
+							<el-tag class="question-tag" @click="inputMessage = '检查系统性能和在线用户统计'" type="warning"> 检查系统性能和在线用户统计 </el-tag>
 						</div>
 					</div>
 				</div>
@@ -432,18 +409,8 @@ onMounted(() => {
 
 					<!-- 模型选择 -->
 					<div class="model-selector">
-						<el-select
-							v-model="selectedModel"
-							placeholder="选择模型"
-							size="small"
-							style="width: 200px"
-						>
-							<el-option
-								v-for="item in modelOptions"
-								:key="item.value"
-								:label="item.label"
-								:value="item.value"
-							/>
+						<el-select v-model="selectedModel" placeholder="选择模型" size="small" style="width: 200px">
+							<el-option v-for="item in modelOptions" :key="item.value" :label="item.label" :value="item.value" />
 						</el-select>
 					</div>
 				</div>
@@ -475,14 +442,7 @@ onMounted(() => {
 						>
 							发送
 						</el-button>
-						<el-button
-							v-else
-							type="danger"
-							size="small"
-							@click="stopConversation"
-						>
-							终止
-						</el-button>
+						<el-button v-else type="danger" size="small" @click="stopConversation"> 终止 </el-button>
 					</div>
 				</div>
 			</div>
@@ -492,16 +452,18 @@ onMounted(() => {
 
 <style scoped lang="scss">
 :deep(.el-icon) {
-	margin-right: 0!important;
+	margin-right: 0 !important;
 }
 
 .chat-container {
 	height: 100%;
 	background: var(--el-bg-color-page);
 }
+
 .create-conversation-btn {
 	margin: 16px;
 }
+
 /* 左侧边栏样式 */
 .chat-sidebar {
 	background: var(--el-bg-color);
@@ -650,10 +612,11 @@ onMounted(() => {
 }
 
 .ai-bubble {
-	background: #f3f4f6;
+	background: var(--el-fill-color-light);
 	color: var(--el-text-color-primary);
 	position: relative;
 	max-width: 70%;
+	border: 1px solid var(--el-border-color-lighter);
 
 	&::before {
 		content: '';
@@ -662,7 +625,7 @@ onMounted(() => {
 		top: 12px;
 		width: 0;
 		height: 0;
-		border-right: 8px solid #f3f4f6;
+		border-right: 8px solid var(--el-fill-color-light);
 		border-top: 8px solid transparent;
 		border-bottom: 8px solid transparent;
 	}
@@ -677,7 +640,7 @@ onMounted(() => {
 }
 
 .user-bubble {
-	background: #4ade80;
+	background: var(--el-color-primary);
 	color: white;
 	position: relative;
 	max-width: 70%;
@@ -689,7 +652,7 @@ onMounted(() => {
 		top: 12px;
 		width: 0;
 		height: 0;
-		border-left: 8px solid #4ade80;
+		border-left: 8px solid var(--el-color-primary);
 		border-top: 8px solid transparent;
 		border-bottom: 8px solid transparent;
 	}
@@ -729,40 +692,134 @@ onMounted(() => {
 	gap: 12px;
 }
 
-/* Markdown 内容样式优化 */
+/* Markdown 内容样式优化 - 接入Element Plus颜色系统 */
 :deep(.markdown-content) {
-	h2 {
-		color: var(--el-text-color-primary);
-		margin: 16px 0 12px 0;
-		font-size: 18px;
+	/* 标题样式 */
+	h1, h2, h3, h4, h5, h6 {
+		margin-top: 24px;
+		margin-bottom: 16px;
 		font-weight: 600;
+		line-height: 1.25;
+		color: var(--el-text-color-primary);
 	}
 
-	ul, ol {
-		margin: 12px 0;
-		padding-left: 20px;
+	h1 {
+		font-size: 2em;
+		border-bottom: 1px solid var(--el-border-color-light);
+		padding-bottom: 8px;
 	}
 
-	li {
-		margin: 4px 0;
+	h2 {
+		font-size: 1.5em;
+		border-bottom: 1px solid var(--el-border-color-lighter);
+		padding-bottom: 6px;
+	}
+
+	h3 {
+		font-size: 1.25em;
+	}
+
+	/* 段落样式 */
+	p {
+		margin-bottom: 16px;
+		line-height: 1.6;
 		color: var(--el-text-color-regular);
 	}
 
+	/* 代码块样式 */
 	pre {
-		background: rgba(0, 0, 0, 0.05);
+		background-color: var(--el-fill-color-light);
+		border: 1px solid var(--el-border-color-light);
 		border-radius: 6px;
-		margin: 12px 0;
+		padding: 16px;
+		overflow: auto;
+		margin: 16px 0;
+		color: var(--el-text-color-primary);
 	}
 
+	/* 行内代码样式 */
 	code {
-		background: rgba(0, 0, 0, 0.05);
-		padding: 2px 6px;
+		background-color: var(--el-fill-color-light);
+		color: var(--el-color-danger);
+		padding: 2px 4px;
+		border-radius: 3px;
+		font-size: 85%;
+		border: 1px solid var(--el-border-color-lighter);
+	}
+
+	/* 引用块样式 */
+	blockquote {
+		border-left: 4px solid var(--el-color-primary);
+		padding-left: 16px;
+		margin: 16px 0;
+		color: var(--el-text-color-secondary);
+		background-color: var(--el-fill-color-extra-light);
+		padding: 12px 16px;
 		border-radius: 4px;
-		font-size: 14px;
 	}
 
-	p {
-		margin: 8px 0;
+	/* 列表样式 */
+	ul, ol {
+		margin: 16px 0;
+		padding-left: 32px;
+		color: var(--el-text-color-regular);
+	}
+
+	li {
+		margin: 4px 0;
+		line-height: 1.6;
+	}
+
+	/* 表格样式 */
+	table {
+		border-collapse: collapse;
+		margin: 16px 0;
+		width: 100%;
+		border: 1px solid var(--el-border-color-light);
+	}
+
+	th, td {
+		border: 1px solid var(--el-border-color-light);
+		padding: 8px 12px;
+		text-align: left;
+	}
+
+	th {
+		background-color: var(--el-fill-color-light);
+		font-weight: 600;
+		color: var(--el-text-color-primary);
+	}
+
+	td {
+		color: var(--el-text-color-regular);
+	}
+
+	/* 分割线样式 */
+	hr {
+		border: none;
+		border-top: 1px solid var(--el-border-color-light);
+		margin: 24px 0;
+	}
+
+	/* 链接样式 */
+	a {
+		color: var(--el-color-primary);
+		text-decoration: none;
+
+		&:hover {
+			color: var(--el-color-primary-light-3);
+			text-decoration: underline;
+		}
+	}
+
+	/* 强调文本样式 */
+	strong {
+		font-weight: 600;
+		color: var(--el-text-color-primary);
+	}
+
+	em {
+		font-style: italic;
 		color: var(--el-text-color-regular);
 	}
 }
@@ -834,7 +891,7 @@ onMounted(() => {
 	position: absolute;
 	left: 50%;
 	top: 50%;
-	transform: translate(-50%,-50%);
+	transform: translate(-50%, -50%);
 }
 
 .empty-icon {
@@ -860,8 +917,6 @@ onMounted(() => {
 	line-height: 1.5;
 }
 
-
-
 .quick-start {
 	display: flex;
 	justify-content: center;
@@ -924,6 +979,4 @@ onMounted(() => {
 		box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
 	}
 }
-
-
 </style>