13 changed files with 3438 additions and 0 deletions
			
			
		@ -0,0 +1,24 @@ | 
				
			|||||
 | 
					# Logs | 
				
			||||
 | 
					logs | 
				
			||||
 | 
					*.log | 
				
			||||
 | 
					npm-debug.log* | 
				
			||||
 | 
					yarn-debug.log* | 
				
			||||
 | 
					yarn-error.log* | 
				
			||||
 | 
					pnpm-debug.log* | 
				
			||||
 | 
					lerna-debug.log* | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					node_modules | 
				
			||||
 | 
					dist | 
				
			||||
 | 
					dist-ssr | 
				
			||||
 | 
					*.local | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					# Editor directories and files | 
				
			||||
 | 
					.vscode/* | 
				
			||||
 | 
					!.vscode/extensions.json | 
				
			||||
 | 
					.idea | 
				
			||||
 | 
					.DS_Store | 
				
			||||
 | 
					*.suo | 
				
			||||
 | 
					*.ntvs* | 
				
			||||
 | 
					*.njsproj | 
				
			||||
 | 
					*.sln | 
				
			||||
 | 
					*.sw? | 
				
			||||
@ -0,0 +1,16 @@ | 
				
			|||||
 | 
					<!DOCTYPE html> | 
				
			||||
 | 
					<html lang="en"> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					<head> | 
				
			||||
 | 
					    <meta charset="UTF-8"> | 
				
			||||
 | 
					    <link rel="icon" type="image/svg+xml" href="/vite.svg"/> | 
				
			||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 
				
			||||
 | 
					    <title>硅谷小智(医疗版)</title> | 
				
			||||
 | 
					    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"> | 
				
			||||
 | 
					</head> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					<body> | 
				
			||||
 | 
					<div id="app"></div> | 
				
			||||
 | 
					<script type="module" src="/src/main.js"></script> | 
				
			||||
 | 
					</body> | 
				
			||||
 | 
					</html> | 
				
			||||
								
									
										File diff suppressed because it is too large
									
								
							
						
					@ -0,0 +1,24 @@ | 
				
			|||||
 | 
					{ | 
				
			||||
 | 
					  "name": "xiaozhi-ui", | 
				
			||||
 | 
					  "private": true, | 
				
			||||
 | 
					  "version": "0.0.0", | 
				
			||||
 | 
					  "type": "module", | 
				
			||||
 | 
					  "scripts": { | 
				
			||||
 | 
					    "dev": "vite", | 
				
			||||
 | 
					    "build": "vite build", | 
				
			||||
 | 
					    "preview": "vite preview" | 
				
			||||
 | 
					  }, | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  "dependencies": { | 
				
			||||
 | 
					    "@element-plus/icons-vue": "^2.3.1", | 
				
			||||
 | 
					    "axios": "^1.7.7", | 
				
			||||
 | 
					    "element-plus": "^2.8.4", | 
				
			||||
 | 
					    "uuid": "^10.0.0", | 
				
			||||
 | 
					    "vue": "^3.5.13" | 
				
			||||
 | 
					  }, | 
				
			||||
 | 
					  "devDependencies": { | 
				
			||||
 | 
					    "@vitejs/plugin-vue": "^5.0.5", | 
				
			||||
 | 
					    "vite": "^5.4.8" | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
								
									
										File diff suppressed because it is too large
									
								
							
						
					| 
		 After Width: | Height: | Size: 1.5 KiB  | 
@ -0,0 +1,14 @@ | 
				
			|||||
 | 
					<template> | 
				
			||||
 | 
					  <ChatWindow/> | 
				
			||||
 | 
					</template> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					<script setup> | 
				
			||||
 | 
					import ChatWindow from '@/components/ChatWindow.vue' | 
				
			||||
 | 
					</script> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					<style> | 
				
			||||
 | 
					html, | 
				
			||||
 | 
					body { | 
				
			||||
 | 
					  overflow: hidden; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					</style> | 
				
			||||
| 
		 After Width: | Height: | Size: 24 KiB  | 
| 
		 After Width: | Height: | Size: 496 B  | 
@ -0,0 +1,381 @@ | 
				
			|||||
 | 
					<template> | 
				
			||||
 | 
					  <div class="app-layout"> | 
				
			||||
 | 
					    <div class="sidebar"> | 
				
			||||
 | 
					      <div class="logo-section"> | 
				
			||||
 | 
					        <img src="@/assets/logo.png" alt="硅谷小智" width="160" height="160" /> | 
				
			||||
 | 
					        <span class="logo-text">硅谷小智(医疗版)</span> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					      <el-button class="new-chat-button" @click="newChat"> | 
				
			||||
 | 
					        <i class="fa-solid fa-plus"></i> | 
				
			||||
 | 
					         新会话 | 
				
			||||
 | 
					      </el-button> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					    <div class="main-content"> | 
				
			||||
 | 
					      <div class="chat-container"> | 
				
			||||
 | 
					        <div class="message-list" ref="messaggListRef"> | 
				
			||||
 | 
					          <div | 
				
			||||
 | 
					            v-for="(message, index) in messages" | 
				
			||||
 | 
					            :key="index" | 
				
			||||
 | 
					            :class=" | 
				
			||||
 | 
					              message.isUser ? 'message user-message' : 'message bot-message' | 
				
			||||
 | 
					            " | 
				
			||||
 | 
					          > | 
				
			||||
 | 
					            <!-- 会话图标 --> | 
				
			||||
 | 
					            <i | 
				
			||||
 | 
					              :class=" | 
				
			||||
 | 
					                message.isUser | 
				
			||||
 | 
					                  ? 'fa-solid fa-user message-icon' | 
				
			||||
 | 
					                  : 'fa-solid fa-robot message-icon' | 
				
			||||
 | 
					              " | 
				
			||||
 | 
					            ></i> | 
				
			||||
 | 
					            <!-- 会话内容 --> | 
				
			||||
 | 
					            <span> | 
				
			||||
 | 
					              <span v-html="message.content"></span> | 
				
			||||
 | 
					              <!-- loading --> | 
				
			||||
 | 
					              <span | 
				
			||||
 | 
					                class="loading-dots" | 
				
			||||
 | 
					                v-if="message.isThinking || message.isTyping" | 
				
			||||
 | 
					              > | 
				
			||||
 | 
					                <span class="dot"></span> | 
				
			||||
 | 
					                <span class="dot"></span> | 
				
			||||
 | 
					              </span> | 
				
			||||
 | 
					            </span> | 
				
			||||
 | 
					          </div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					        <div class="input-container"> | 
				
			||||
 | 
					          <el-input | 
				
			||||
 | 
					            v-model="inputMessage" | 
				
			||||
 | 
					            placeholder="请输入消息" | 
				
			||||
 | 
					            @keyup.enter="sendMessage" | 
				
			||||
 | 
					          ></el-input> | 
				
			||||
 | 
					          <el-button @click="sendMessage" :disabled="isSending" type="primary" | 
				
			||||
 | 
					            >发送</el-button | 
				
			||||
 | 
					          > | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					  </div> | 
				
			||||
 | 
					</template> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					<script setup> | 
				
			||||
 | 
					import { onMounted, ref, watch } from 'vue' | 
				
			||||
 | 
					import axios from 'axios' | 
				
			||||
 | 
					import { v4 as uuidv4 } from 'uuid' | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const messaggListRef = ref() | 
				
			||||
 | 
					const isSending = ref(false) | 
				
			||||
 | 
					const uuid = ref() | 
				
			||||
 | 
					const inputMessage = ref('') | 
				
			||||
 | 
					const messages = ref([]) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					onMounted(() => { | 
				
			||||
 | 
					  initUUID() | 
				
			||||
 | 
					  // 移除 setInterval,改用手动滚动 | 
				
			||||
 | 
					  watch(messages, () => scrollToBottom(), { deep: true }) | 
				
			||||
 | 
					  hello() | 
				
			||||
 | 
					}) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const scrollToBottom = () => { | 
				
			||||
 | 
					  if (messaggListRef.value) { | 
				
			||||
 | 
					    messaggListRef.value.scrollTop = messaggListRef.value.scrollHeight | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const hello = () => { | 
				
			||||
 | 
					  sendRequest('你好') | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const sendMessage = () => { | 
				
			||||
 | 
					  if (inputMessage.value.trim()) { | 
				
			||||
 | 
					    sendRequest(inputMessage.value.trim()) | 
				
			||||
 | 
					    inputMessage.value = '' | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const sendRequest = (message) => { | 
				
			||||
 | 
					  isSending.value = true | 
				
			||||
 | 
					  const userMsg = { | 
				
			||||
 | 
					    isUser: true, | 
				
			||||
 | 
					    content: message, | 
				
			||||
 | 
					    isTyping: false, | 
				
			||||
 | 
					    isThinking: false, | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  //第一条默认发送的用户消息”你好“不放入会话列表 | 
				
			||||
 | 
					  if(messages.value.length > 0){ | 
				
			||||
 | 
					    messages.value.push(userMsg) | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // 添加机器人加载消息 | 
				
			||||
 | 
					  const botMsg = { | 
				
			||||
 | 
					    isUser: false, | 
				
			||||
 | 
					    content: '', // 增量填充 | 
				
			||||
 | 
					    isTyping: true, // 显示加载动画 | 
				
			||||
 | 
					    isThinking: false, | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  messages.value.push(botMsg) | 
				
			||||
 | 
					  const lastMsg = messages.value[messages.value.length - 1] | 
				
			||||
 | 
					  scrollToBottom() | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  axios | 
				
			||||
 | 
					    .post( | 
				
			||||
 | 
					      '/api/xiaozhi/chat', | 
				
			||||
 | 
					      { memoryId: uuid.value, message }, | 
				
			||||
 | 
					      { | 
				
			||||
 | 
					        responseType: 'stream', // 必须为合法值 "text" | 
				
			||||
 | 
					        onDownloadProgress: (e) => { | 
				
			||||
 | 
					          const fullText = e.event.target.responseText // 累积的完整文本 | 
				
			||||
 | 
					          let newText = fullText.substring(lastMsg.content.length) | 
				
			||||
 | 
					          lastMsg.content += newText //增量更新 | 
				
			||||
 | 
					          console.log(lastMsg) | 
				
			||||
 | 
					          scrollToBottom() // 实时滚动 | 
				
			||||
 | 
					        }, | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					    ) | 
				
			||||
 | 
					    .then(() => { | 
				
			||||
 | 
					      // 流结束后隐藏加载动画 | 
				
			||||
 | 
					      messages.value.at(-1).isTyping = false | 
				
			||||
 | 
					      isSending.value = false | 
				
			||||
 | 
					    }) | 
				
			||||
 | 
					    .catch((error) => { | 
				
			||||
 | 
					      console.error('流式错误:', error) | 
				
			||||
 | 
					      messages.value.at(-1).content = '请求失败,请重试' | 
				
			||||
 | 
					      messages.value.at(-1).isTyping = false | 
				
			||||
 | 
					      isSending.value = false | 
				
			||||
 | 
					    }) | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// 初始化 UUID | 
				
			||||
 | 
					const initUUID = () => { | 
				
			||||
 | 
					  let storedUUID = localStorage.getItem('user_uuid') | 
				
			||||
 | 
					  if (!storedUUID) { | 
				
			||||
 | 
					    storedUUID = uuidToNumber(uuidv4()) | 
				
			||||
 | 
					    localStorage.setItem('user_uuid', storedUUID) | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  uuid.value = storedUUID | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const uuidToNumber = (uuid) => { | 
				
			||||
 | 
					  let number = 0 | 
				
			||||
 | 
					  for (let i = 0; i < uuid.length && i < 6; i++) { | 
				
			||||
 | 
					    const hexValue = uuid[i] | 
				
			||||
 | 
					    number = number * 16 + (parseInt(hexValue, 16) || 0) | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  return number % 1000000 | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// 转换特殊字符 | 
				
			||||
 | 
					const convertStreamOutput = (output) => { | 
				
			||||
 | 
					  return output | 
				
			||||
 | 
					    .replace(/\n/g, '<br>') | 
				
			||||
 | 
					    .replace(/\t/g, '    ') | 
				
			||||
 | 
					    .replace(/&/g, '&') // 新增转义,避免 HTML 注入 | 
				
			||||
 | 
					    .replace(/</g, '<') | 
				
			||||
 | 
					    .replace(/>/g, '>') | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const newChat = () => { | 
				
			||||
 | 
					  // 这里添加新会话的逻辑 | 
				
			||||
 | 
					  console.log('开始新会话') | 
				
			||||
 | 
					  localStorage.removeItem('user_uuid') | 
				
			||||
 | 
					  window.location.reload() | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					</script> | 
				
			||||
 | 
					<style scoped> | 
				
			||||
 | 
					.app-layout { | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					  height: 100vh; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.sidebar { | 
				
			||||
 | 
					  width: 200px; | 
				
			||||
 | 
					  background-color: #f4f4f9; | 
				
			||||
 | 
					  padding: 20px; | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					  flex-direction: column; | 
				
			||||
 | 
					  align-items: center; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.logo-section { | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					  flex-direction: column; | 
				
			||||
 | 
					  align-items: center; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.logo-text { | 
				
			||||
 | 
					  font-size: 18px; | 
				
			||||
 | 
					  font-weight: bold; | 
				
			||||
 | 
					  margin-top: 10px; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.new-chat-button { | 
				
			||||
 | 
					  width: 100%; | 
				
			||||
 | 
					  margin-top: 20px; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.main-content { | 
				
			||||
 | 
					  flex: 1; | 
				
			||||
 | 
					  padding: 20px; | 
				
			||||
 | 
					  overflow-y: auto; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					.chat-container { | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					  flex-direction: column; | 
				
			||||
 | 
					  height: 100%; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.message-list { | 
				
			||||
 | 
					  flex: 1; | 
				
			||||
 | 
					  overflow-y: auto; | 
				
			||||
 | 
					  padding: 10px; | 
				
			||||
 | 
					  border: 1px solid #e0e0e0; | 
				
			||||
 | 
					  border-radius: 4px; | 
				
			||||
 | 
					  background-color: #fff; | 
				
			||||
 | 
					  margin-bottom: 10px; | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					  flex-direction: column; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.message { | 
				
			||||
 | 
					  margin-bottom: 10px; | 
				
			||||
 | 
					  padding: 10px; | 
				
			||||
 | 
					  border-radius: 4px; | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					  /* align-items: center; */ | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.user-message { | 
				
			||||
 | 
					  max-width: 70%; | 
				
			||||
 | 
					  background-color: #e1f5fe; | 
				
			||||
 | 
					  align-self: flex-end; | 
				
			||||
 | 
					  flex-direction: row-reverse; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.bot-message { | 
				
			||||
 | 
					  max-width: 100%; | 
				
			||||
 | 
					  background-color: #f1f8e9; | 
				
			||||
 | 
					  align-self: flex-start; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.message-icon { | 
				
			||||
 | 
					  margin: 0 10px; | 
				
			||||
 | 
					  font-size: 1.2em; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.loading-dots { | 
				
			||||
 | 
					  padding-left: 5px; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.dot { | 
				
			||||
 | 
					  display: inline-block; | 
				
			||||
 | 
					  margin-left: 5px; | 
				
			||||
 | 
					  width: 8px; | 
				
			||||
 | 
					  height: 8px; | 
				
			||||
 | 
					  background-color: #000000; | 
				
			||||
 | 
					  border-radius: 50%; | 
				
			||||
 | 
					  animation: pulse 1.2s infinite ease-in-out both; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.dot:nth-child(2) { | 
				
			||||
 | 
					  animation-delay: -0.6s; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					@keyframes pulse { | 
				
			||||
 | 
					  0%, | 
				
			||||
 | 
					  100% { | 
				
			||||
 | 
					    transform: scale(0.6); | 
				
			||||
 | 
					    opacity: 0.4; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  50% { | 
				
			||||
 | 
					    transform: scale(1); | 
				
			||||
 | 
					    opacity: 1; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					.input-container { | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.input-container .el-input { | 
				
			||||
 | 
					  flex: 1; | 
				
			||||
 | 
					  margin-right: 10px; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/* 媒体查询,当设备宽度小于等于 768px 时应用以下样式 */ | 
				
			||||
 | 
					@media (max-width: 768px) { | 
				
			||||
 | 
					  .main-content { | 
				
			||||
 | 
					    padding: 10px 0 10px 0; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  .app-layout { | 
				
			||||
 | 
					    flex-direction: column; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .sidebar { | 
				
			||||
 | 
					    /* display: none; */ | 
				
			||||
 | 
					    width: 100%; | 
				
			||||
 | 
					    flex-direction: row; | 
				
			||||
 | 
					    justify-content: space-between; | 
				
			||||
 | 
					    align-items: center; | 
				
			||||
 | 
					    padding: 10px; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .logo-section { | 
				
			||||
 | 
					    flex-direction: row; | 
				
			||||
 | 
					    align-items: center; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .logo-text { | 
				
			||||
 | 
					    font-size: 20px; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .logo-section img { | 
				
			||||
 | 
					    width: 40px; | 
				
			||||
 | 
					    height: 40px; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .new-chat-button { | 
				
			||||
 | 
					    margin-right: 30px; | 
				
			||||
 | 
					    width: auto; | 
				
			||||
 | 
					    margin-top: 5px; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/* 媒体查询,当设备宽度大于 768px 时应用原来的样式 */ | 
				
			||||
 | 
					@media (min-width: 769px) { | 
				
			||||
 | 
					  .main-content { | 
				
			||||
 | 
					    padding: 0 0 10px 10px; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .app-layout { | 
				
			||||
 | 
					    display: flex; | 
				
			||||
 | 
					    height: 100vh; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .sidebar { | 
				
			||||
 | 
					    width: 200px; | 
				
			||||
 | 
					    background-color: #f4f4f9; | 
				
			||||
 | 
					    padding: 20px; | 
				
			||||
 | 
					    display: flex; | 
				
			||||
 | 
					    flex-direction: column; | 
				
			||||
 | 
					    align-items: center; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .logo-section { | 
				
			||||
 | 
					    display: flex; | 
				
			||||
 | 
					    flex-direction: column; | 
				
			||||
 | 
					    align-items: center; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .logo-text { | 
				
			||||
 | 
					    font-size: 18px; | 
				
			||||
 | 
					    font-weight: bold; | 
				
			||||
 | 
					    margin-top: 10px; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .new-chat-button { | 
				
			||||
 | 
					    width: 100%; | 
				
			||||
 | 
					    margin-top: 20px; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					</style> | 
				
			||||
@ -0,0 +1,10 @@ | 
				
			|||||
 | 
					import { createApp } from 'vue'; | 
				
			||||
 | 
					import App from './App.vue'; | 
				
			||||
 | 
					import ElementPlus from 'element-plus'; | 
				
			||||
 | 
					import 'element-plus/dist/index.css'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const app = createApp(App); | 
				
			||||
 | 
					app.use(ElementPlus); | 
				
			||||
 | 
					app.mount('#app'); | 
				
			||||
 | 
					
 | 
				
			||||
@ -0,0 +1,79 @@ | 
				
			|||||
 | 
					:root { | 
				
			||||
 | 
					  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; | 
				
			||||
 | 
					  line-height: 1.5; | 
				
			||||
 | 
					  font-weight: 400; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  color-scheme: light dark; | 
				
			||||
 | 
					  color: rgba(255, 255, 255, 0.87); | 
				
			||||
 | 
					  background-color: #242424; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  font-synthesis: none; | 
				
			||||
 | 
					  text-rendering: optimizeLegibility; | 
				
			||||
 | 
					  -webkit-font-smoothing: antialiased; | 
				
			||||
 | 
					  -moz-osx-font-smoothing: grayscale; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					a { | 
				
			||||
 | 
					  font-weight: 500; | 
				
			||||
 | 
					  color: #646cff; | 
				
			||||
 | 
					  text-decoration: inherit; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					a:hover { | 
				
			||||
 | 
					  color: #535bf2; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					body { | 
				
			||||
 | 
					  margin: 0; | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					  place-items: center; | 
				
			||||
 | 
					  min-width: 320px; | 
				
			||||
 | 
					  min-height: 100vh; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					h1 { | 
				
			||||
 | 
					  font-size: 3.2em; | 
				
			||||
 | 
					  line-height: 1.1; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					button { | 
				
			||||
 | 
					  border-radius: 8px; | 
				
			||||
 | 
					  border: 1px solid transparent; | 
				
			||||
 | 
					  padding: 0.6em 1.2em; | 
				
			||||
 | 
					  font-size: 1em; | 
				
			||||
 | 
					  font-weight: 500; | 
				
			||||
 | 
					  font-family: inherit; | 
				
			||||
 | 
					  background-color: #1a1a1a; | 
				
			||||
 | 
					  cursor: pointer; | 
				
			||||
 | 
					  transition: border-color 0.25s; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					button:hover { | 
				
			||||
 | 
					  border-color: #646cff; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					button:focus, | 
				
			||||
 | 
					button:focus-visible { | 
				
			||||
 | 
					  outline: 4px auto -webkit-focus-ring-color; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.card { | 
				
			||||
 | 
					  padding: 2em; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#app { | 
				
			||||
 | 
					  max-width: 1280px; | 
				
			||||
 | 
					  margin: 0 auto; | 
				
			||||
 | 
					  padding: 2rem; | 
				
			||||
 | 
					  text-align: center; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					@media (prefers-color-scheme: light) { | 
				
			||||
 | 
					  :root { | 
				
			||||
 | 
					    color: #213547; | 
				
			||||
 | 
					    background-color: #ffffff; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  a:hover { | 
				
			||||
 | 
					    color: #747bff; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  button { | 
				
			||||
 | 
					    background-color: #f9f9f9; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,24 @@ | 
				
			|||||
 | 
					import { defineConfig } from 'vite' | 
				
			||||
 | 
					import vue from '@vitejs/plugin-vue' | 
				
			||||
 | 
					import { fileURLToPath, URL } from 'node:url' | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// https://vitejs.dev/config/
 | 
				
			||||
 | 
					export default defineConfig({ | 
				
			||||
 | 
					  plugins: [ | 
				
			||||
 | 
					    vue() | 
				
			||||
 | 
					  ], | 
				
			||||
 | 
					  server: { | 
				
			||||
 | 
					    proxy: { | 
				
			||||
 | 
					      '/api': { | 
				
			||||
 | 
					        target: ' http://localhost:8080', | 
				
			||||
 | 
					        changeOrigin: true, | 
				
			||||
 | 
					        rewrite: (path) => path.replace(/^\/api/, ''), // 去掉 /api 前缀
 | 
				
			||||
 | 
					      }, | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					  }, | 
				
			||||
 | 
					  resolve: { | 
				
			||||
 | 
					    alias: { | 
				
			||||
 | 
					      '@': fileURLToPath(new URL('./src', import.meta.url)), | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					  }, | 
				
			||||
 | 
					}) | 
				
			||||
					Loading…
					
					
				
		Reference in new issue