From e4269277e493dad1c410ad40f8fd4e2c934a9b75 Mon Sep 17 00:00:00 2001
From: 小小儁爺 <1694218219@qq.com>
Date: 星期二, 24 三月 2026 13:36:02 +0800
Subject: [PATCH] 1.新增ai测试页
---
src/views/systemSetting/ai.vue | 796 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 796 insertions(+), 0 deletions(-)
diff --git a/src/views/systemSetting/ai.vue b/src/views/systemSetting/ai.vue
new file mode 100644
index 0000000..cc8a0a8
--- /dev/null
+++ b/src/views/systemSetting/ai.vue
@@ -0,0 +1,796 @@
+<template>
+ <div class="chat-container">
+ <!-- 宸︿晶鍘嗗彶瀵硅瘽鍒楄〃 -->
+ <div class="sidebar">
+ <div class="sidebar-header">
+ <el-button type="primary" icon="el-icon-plus" @click="createNewChat">
+ 鏂板缓瀵硅瘽
+ </el-button>
+ </div>
+
+ <div class="chat-list">
+ <div
+ v-for="(chat, index) in chatList"
+ :key="index"
+ :class="['chat-item', currentChatIndex === index ? 'active' : '']"
+ @click="switchChat(index)"
+ >
+ <div class="chat-item-icon">
+ <i class="el-icon-chat-dot-round" />
+ </div>
+ <div class="chat-item-content">
+ <div class="chat-item-title">{{ chat.title }}</div>
+ <div class="chat-item-time">{{ chat.time }}</div>
+ </div>
+ <div class="chat-item-delete" @click.stop="deleteChat(index)">
+ <i class="el-icon-close" />
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鑱婂ぉ涓诲尯鍩� -->
+ <div class="chat-main">
+ <!-- 娑堟伅鍒楄〃 -->
+ <div ref="messageList" class="message-list">
+ <div
+ v-for="(message, index) in messages"
+ :key="index"
+ :class="['message-item', message.role === 'user' ? 'message-user' : 'message-ai']"
+ >
+ <!-- AI 澶村儚 -->
+ <div v-if="message.role === 'assistant'" class="avatar avatar-ai">
+ <span>AI</span>
+ </div>
+
+ <!-- 娑堟伅鍐呭 -->
+ <div class="message-content">
+ <div class="message-info">
+ <span class="sender-name">{{ message.role === 'user' ? '鎴�' : 'AI 鍔╂墜' }}</span>
+ <span class="message-time">{{ message.time }}</span>
+ </div>
+ <div class="message-text" v-html="formattedContent(message.content)" />
+
+ <!-- 娴佸紡鍔犺浇涓殑鍏夋爣 -->
+ <span v-if="message.streaming" class="cursor-blink">|</span>
+ </div>
+
+ <!-- 鐢ㄦ埛澶村儚 -->
+ <div v-if="message.role === 'user'" class="avatar avatar-user">
+ <span>鎴�</span>
+ </div>
+ </div>
+
+ <!-- 鍔犺浇涓彁绀� -->
+ <div v-if="isLoading" class="loading-indicator">
+ <span class="loading-dot" />
+ <span class="loading-dot" />
+ <span class="loading-dot" />
+ </div>
+ </div>
+
+ <!-- 杈撳叆鍖哄煙 -->
+ <div class="input-area">
+ <div class="input-wrapper">
+ <el-input
+ v-model="userInput"
+ type="textarea"
+ :rows="3"
+ autofocus
+ placeholder="璇疯緭鍏ユ偍鐨勯棶棰�... (鍥炶溅鍙戦��)"
+ resize="none"
+ class="chat-input"
+ @keyup.enter.native="sendMessage"
+ @keydown.enter.native="e => e.preventDefault()"
+ />
+ <!-- @keydown.enter.exact.prevent="sendMessage"-->
+ <el-button
+ type="primary"
+ :disabled="!userInput.trim() || isLoading"
+ class="send-button"
+ @click="sendMessage"
+ >
+ {{ isLoading ? '鎬濊�冧腑...' : '鍙戦��' }}
+ </el-button>
+ </div>
+ <div class="input-tips">
+ <span>AI 鐢熸垚鍐呭浠呬緵鍙傝�冿紝璇疯皑鎱庣攧鍒�</span>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</template>
+
+<script>
+import { marked } from 'marked'
+
+// 閰嶇疆 marked 閫夐」
+marked.setOptions({
+ breaks: true, // 鏀寔 GFM 鎹㈣
+ gfm: true, // GitHub Flavored Markdown
+ sanitize: false // 涓嶈繃婊� HTML锛堝鏋滈渶瑕� XSS 淇濇姢锛屽彲浠ヤ娇鐢� DOMPurify锛�
+})
+
+export default {
+ name: 'ProcessSetting',
+ data() {
+ return {
+ userInput: '',
+ messages: [],
+ isLoading: false,
+ currentStreamingIndex: null,
+ // 闃块噷浜戝崈闂厤缃�
+ API_KEY: 'sk-825b87021a0a4dfb9d3fdf20acb4fcc2', // 璇锋浛鎹负浣犵殑 API Key
+ API_URL: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
+ // 鍘嗗彶瀵硅瘽鐩稿叧
+ chatList: [],
+ currentChatIndex: 0
+ }
+ },
+ watch: {
+ // 鐩戝惉娑堟伅鍙樺寲锛岃嚜鍔ㄦ洿鏂版爣棰�
+ messages: {
+ handler() {
+ this.updateChatTitle()
+ this.saveChatHistory()
+ },
+ deep: true
+ }
+ },
+ mounted() {
+ // 鍒濆鍖栨杩庢秷鎭�
+ this.createNewChat()
+ // 浠庢湰鍦板瓨鍌ㄥ姞杞藉巻鍙插璇�
+ this.loadChatHistory()
+ },
+ methods: {
+ // 浣跨敤 marked 搴撴覆鏌� Markdown
+ formattedContent(content) {
+ if (!content) return ''
+
+ try {
+ return marked.parse(content)
+ } catch (e) {
+ console.error('Markdown 瑙f瀽澶辫触:', e)
+ return content.replace(/\n/g, '<br>')
+ }
+ },
+
+ // 澶勭悊 Enter 閿� - 鐩存帴鍙戦��
+ handleEnterKey(event) {
+ event.preventDefault()
+ this.sendMessage()
+ },
+
+ // 鍙戦�佹秷鎭�
+ async sendMessage() {
+ const text = this.userInput.trim()
+ if (!text || this.isLoading) return
+
+ // 娣诲姞鐢ㄦ埛娑堟伅
+ this.addMessage('user', text)
+ this.userInput = ''
+
+ // 鍒涘缓 AI 鍥炲娑堟伅锛堢┖鍐呭锛�
+ const aiMessageIndex = this.addMessage('assistant', '', true)
+
+ // 璋冪敤闃块噷浜戝崈闂祦寮忔帴鍙�
+ await this.fetchQwenStreamingResponse(aiMessageIndex)
+ },
+
+ // 娣诲姞娑堟伅鍒板垪琛�
+ addMessage(role, content, streaming = false) {
+ const now = new Date()
+ const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
+
+ const message = {
+ role,
+ content,
+ time,
+ streaming
+ }
+
+ this.messages.push(message)
+ this.$nextTick(() => {
+ this.scrollToBottom()
+ })
+
+ return this.messages.length - 1
+ },
+
+ // 闃块噷浜戝崈闂祦寮忓搷搴�
+ async fetchQwenStreamingResponse(messageIndex) {
+ this.isLoading = true
+ this.currentStreamingIndex = messageIndex
+
+ try {
+ // 鏋勫缓鍘嗗彶瀵硅瘽锛堢敤浜庝笂涓嬫枃锛�
+ const conversationHistory = this.messages
+ .filter((msg, idx) => idx < this.messages.length - 1 || msg.role === 'user')
+ .map(msg => ({
+ role: msg.role === 'assistant' ? 'assistant' : 'user',
+ content: msg.content
+ }))
+
+ // 鑾峰彇鏈�鍚庝竴鏉$敤鎴锋秷鎭綔涓哄綋鍓嶉棶棰�
+ const lastUserMessage = this.messages[this.messages.length - 2]
+ if (lastUserMessage) {
+ conversationHistory.push({
+ role: 'user',
+ content: lastUserMessage.content
+ })
+ }
+
+ const response = await fetch(this.API_URL, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.API_KEY}`
+ },
+ body: JSON.stringify({
+ model: 'qwen-plus', // 鎴� qwen-turbo, qwen-max 绛�
+ messages: conversationHistory,
+ stream: true, // 鍚敤娴佸紡杈撳嚭
+ temperature: 0.7
+ })
+ })
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`)
+ }
+
+ // 澶勭悊娴佸紡鍝嶅簲
+ const reader = response.body.getReader()
+ const decoder = new TextDecoder('utf-8')
+ let accumulatedContent = ''
+
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ const { done, value } = await reader.read()
+
+ if (done) {
+ break
+ }
+
+ // 瑙g爜鏁版嵁
+ const chunk = decoder.decode(value, { stream: true })
+ const lines = chunk.split('\n')
+
+ for (const line of lines) {
+ if (line.startsWith('data: ')) {
+ const data = line.slice(6)
+
+ // [DONE] 鏍囪琛ㄧず娴佸紡缁撴潫
+ if (data.trim() === '[DONE]') {
+ continue
+ }
+
+ try {
+ const parsed = JSON.parse(data)
+ const delta = parsed.choices?.[0]?.delta?.content
+
+ if (delta) {
+ accumulatedContent += delta
+
+ // 浣跨敤 $set 纭繚 Vue2 鐨勫搷搴斿紡鏇存柊
+ this.$set(this.messages[messageIndex], 'content', accumulatedContent)
+
+ // 婊氬姩鍒板簳閮�
+ this.$nextTick(() => {
+ this.scrollToBottom()
+ })
+ }
+ } catch (e) {
+ console.error('瑙f瀽 SSE 鏁版嵁澶辫触:', e)
+ }
+ }
+ }
+ }
+
+ // 缁撴潫娴佸紡鐘舵��
+ this.$set(this.messages[messageIndex], 'streaming', false)
+ } catch (error) {
+ console.error('璋冪敤鍗冮棶鎺ュ彛澶辫触:', error)
+ this.$message.error('AI 鍝嶅簲澶辫触锛�' + error.message)
+
+ // 璁剧疆閿欒淇℃伅鍒版秷鎭腑
+ this.$set(this.messages[messageIndex], 'content', '鎶辨瓑锛屽搷搴斿け璐ワ細' + error.message)
+ this.$set(this.messages[messageIndex], 'streaming', false)
+ } finally {
+ this.isLoading = false
+ this.currentStreamingIndex = null
+ }
+ },
+
+ // 婊氬姩鍒板簳閮�
+ scrollToBottom() {
+ const container = this.$refs.messageList
+ if (container) {
+ this.$nextTick(() => {
+ container.scrollTop = container.scrollHeight
+ })
+ }
+ },
+
+ // 鍒涘缓鏂板璇�
+ createNewChat() {
+ const now = new Date()
+ const time = `${now.getMonth() + 1}/${now.getDate()} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
+
+ const newChat = {
+ title: '鏂板璇�',
+ time: time,
+ messages: [{
+ role: 'assistant',
+ content: '鎮ㄥソ锛佹垜鏄� AI 鍔╂墜锛岃闂湁浠�涔堝彲浠ュ府鎮紵',
+ time: time,
+ streaming: false
+ }]
+ }
+
+ this.chatList.unshift(newChat)
+ this.currentChatIndex = 0
+ this.messages = newChat.messages
+ this.saveChatHistory()
+ },
+
+ // 鍒囨崲瀵硅瘽
+ switchChat(index) {
+ this.currentChatIndex = index
+ this.messages = this.chatList[index].messages
+ this.$nextTick(() => {
+ this.scrollToBottom()
+ })
+ },
+
+ // 鍒犻櫎瀵硅瘽
+ deleteChat(index) {
+ this.$confirm('纭畾瑕佸垹闄よ繖涓璇濆悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ this.chatList.splice(index, 1)
+
+ if (this.chatList.length === 0) {
+ this.createNewChat()
+ } else if (index === this.currentChatIndex) {
+ this.currentChatIndex = Math.max(0, index - 1)
+ this.switchChat(this.currentChatIndex)
+ } else if (index < this.currentChatIndex) {
+ this.currentChatIndex--
+ }
+
+ this.saveChatHistory()
+ this.$message.success('鍒犻櫎鎴愬姛')
+ }).catch(() => {})
+ },
+
+ // 鏇存柊褰撳墠瀵硅瘽鏍囬
+ updateChatTitle() {
+ if (this.messages.length > 0 && this.chatList[this.currentChatIndex]) {
+ const firstUserMessage = this.messages.find(msg => msg.role === 'user')
+ if (firstUserMessage) {
+ const title = firstUserMessage.content.substring(0, 20) + (firstUserMessage.content.length > 20 ? '...' : '')
+ this.$set(this.chatList[this.currentChatIndex], 'title', title)
+ this.saveChatHistory()
+ }
+ }
+ },
+
+ // 淇濆瓨瀵硅瘽鍘嗗彶鍒版湰鍦板瓨鍌�
+ saveChatHistory() {
+ try {
+ localStorage.setItem('chatHistory', JSON.stringify({
+ chatList: this.chatList,
+ currentChatIndex: this.currentChatIndex
+ }))
+ } catch (e) {
+ console.error('淇濆瓨鑱婂ぉ璁板綍澶辫触:', e)
+ }
+ },
+
+ // 浠庢湰鍦板瓨鍌ㄥ姞杞藉璇濆巻鍙�
+ loadChatHistory() {
+ try {
+ const saved = localStorage.getItem('chatHistory')
+ if (saved) {
+ const data = JSON.parse(saved)
+ this.chatList = data.chatList || []
+ this.currentChatIndex = data.currentChatIndex || 0
+
+ if (this.chatList.length > 0) {
+ this.messages = this.chatList[this.currentChatIndex].messages
+ } else {
+ this.createNewChat()
+ }
+ }
+ } catch (e) {
+ console.error('鍔犺浇鑱婂ぉ璁板綍澶辫触:', e)
+ this.createNewChat()
+ }
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+.chat-container {
+ display: flex;
+ height: 100vh;
+ background: #f5f7fa;
+}
+
+.sidebar {
+ width: 260px;
+ background: #1a1a2e;
+ display: flex;
+ flex-direction: column;
+ //border-radius: 8px 0 0 8px;
+ overflow: hidden;
+
+ .sidebar-header {
+ padding: 16px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+
+ button {
+ width: 100%;
+ background: rgba(255, 255, 255, 0.1);
+ border-color: transparent;
+ color: white;
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.2);
+ }
+ }
+ }
+
+ .chat-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px;
+
+ &::-webkit-scrollbar {
+ width: 4px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 2px;
+ }
+ }
+
+ .chat-item {
+ display: flex;
+ align-items: center;
+ padding: 12px;
+ margin-bottom: 4px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.3s;
+ position: relative;
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.1);
+
+ .chat-item-delete {
+ opacity: 1;
+ }
+ }
+
+ &.active {
+ background: rgba(64, 158, 255, 0.2);
+ border-left: 3px solid #409eff;
+ }
+
+ .chat-item-icon {
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-size: 16px;
+ margin-right: 12px;
+ flex-shrink: 0;
+ }
+
+ .chat-item-content {
+ flex: 1;
+ overflow: hidden;
+
+ .chat-item-title {
+ color: white;
+ font-size: 14px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 4px;
+ }
+
+ .chat-item-time {
+ color: rgba(255, 255, 255, 0.5);
+ font-size: 12px;
+ }
+ }
+
+ .chat-item-delete {
+ opacity: 0;
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 14px;
+ padding: 4px;
+ transition: opacity 0.3s;
+
+ &:hover {
+ color: #ff4d4f;
+ }
+ }
+ }
+}
+
+.chat-main {
+ flex: 1;
+ overflow: hidden;
+ padding: 20px;
+ background: white;
+ border-radius: 0 8px 8px 0;
+}
+
+.message-list {
+ height: 85%;
+ overflow-y: auto;
+ padding: 0 10px;
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #c0c4cc;
+ border-radius: 3px;
+ }
+}
+
+.message-item {
+ display: flex;
+ margin-bottom: 24px;
+ align-items: flex-start;
+
+ &.message-user {
+ flex-direction: row-reverse;
+
+ .message-content {
+ margin-right: 12px;
+
+ .message-info {
+ justify-content: flex-end;
+ }
+
+ .message-text {
+ background: #409eff;
+ color: white;
+ border-radius: 12px 12px 0 12px;
+ }
+ }
+ }
+
+ &.message-ai {
+ .message-content {
+ margin-left: 12px;
+ }
+ }
+}
+
+.avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+ font-weight: bold;
+ flex-shrink: 0;
+
+ &.avatar-ai {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ }
+
+ &.avatar-user {
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+ color: white;
+ }
+}
+
+.message-content {
+ max-width: 70%;
+
+ .message-info {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 6px;
+ font-size: 12px;
+ color: #909399;
+ }
+
+ .message-text {
+ padding: 12px 16px;
+ line-height: 1.6;
+ word-break: break-word;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+
+ ::v-deep {
+ pre {
+ background: #282c34;
+ color: #abb2bf;
+ padding: 12px;
+ border-radius: 6px;
+ overflow-x: auto;
+ margin: 8px 0;
+ }
+
+ code {
+ background: rgba(27,31,35,0.05);
+ padding: 2px 4px;
+ border-radius: 3px;
+ font-family: Consolas, monospace;
+ }
+
+ pre code {
+ background: transparent;
+ padding: 0;
+ }
+
+ strong {
+ color: #303133;
+ }
+
+ table {
+ border-collapse: collapse;
+ width: 100%;
+ margin: 12px 0;
+ font-size: 14px;
+
+ th, td {
+ border: 1px solid #dcdfe6;
+ padding: 8px 12px;
+ text-align: left;
+ }
+
+ th {
+ background: #f5f7fa;
+ font-weight: 600;
+ color: #606266;
+ }
+
+ tr:nth-child(even) {
+ background: #fafafa;
+ }
+
+ tr:hover {
+ background: #f5f7fa;
+ }
+ }
+
+ blockquote {
+ border-left: 4px solid #409eff;
+ margin: 12px 0;
+ padding: 8px 16px;
+ background: #ecf5ff;
+ color: #606266;
+ }
+
+ ul, ol {
+ padding-left: 24px;
+ margin: 8px 0;
+
+ li {
+ margin: 4px 0;
+ }
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ margin: 16px 0 8px;
+ color: #303133;
+ font-weight: 600;
+ }
+
+ h1 { font-size: 24px; }
+ h2 { font-size: 20px; }
+ h3 { font-size: 18px; }
+ h4 { font-size: 16px; }
+ }
+ }
+}
+
+.cursor-blink {
+ display: inline-block;
+ animation: blink 1s infinite;
+ color: #409eff;
+ font-weight: bold;
+}
+
+@keyframes blink {
+ 0%, 50% { opacity: 1; }
+ 51%, 100% { opacity: 0; }
+}
+
+.loading-indicator {
+ display: flex;
+ justify-content: center;
+ gap: 4px;
+ padding: 16px;
+
+ .loading-dot {
+ width: 8px;
+ height: 8px;
+ background: #409eff;
+ border-radius: 50%;
+ animation: bounce 1.4s infinite ease-in-out both;
+
+ &:nth-child(1) {
+ animation-delay: -0.32s;
+ }
+
+ &:nth-child(2) {
+ animation-delay: -0.16s;
+ }
+ }
+}
+
+@keyframes bounce {
+ 0%, 80%, 100% {
+ transform: scale(0);
+ }
+ 40% {
+ transform: scale(1);
+ }
+}
+
+.input-area {
+ background: white;
+ padding: 16px 20px ;
+ border-top: 1px solid #e4e7ed;
+
+ .input-wrapper {
+ display: flex;
+ gap: 12px;
+ align-items: flex-end;
+ max-width: 1200px;
+ margin: 0 auto;
+ }
+
+ .chat-input {
+ flex: 1;
+
+ ::v-deep textarea {
+ resize: none;
+ padding: 12px 16px;
+ font-size: 14px;
+ line-height: 1.6;
+
+ &:focus {
+ border-color: #409eff;
+ }
+ }
+ }
+
+ .send-button {
+ min-width: 80px;
+ height: auto;
+ padding: 12px 24px;
+ }
+
+ .input-tips {
+ text-align: center;
+ margin-top: 8px;
+ font-size: 12px;
+ color: #909399;
+ }
+}
+</style>
--
Gitblit v1.9.3