first commit
This commit is contained in:
commit
0fe225c858
|
@ -0,0 +1,30 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
# wxproject
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "wxproject",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"echarts": "^5.6.0",
|
||||
"marked": "^15.0.8",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"vite": "^6.2.4",
|
||||
"vite-plugin-vue-devtools": "^7.7.2"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<title>智能对话系统</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,56 @@
|
|||
<script setup>
|
||||
import ChatInterface from './components/ChatInterface.vue'
|
||||
import ChatModeSelector from './components/ChatModeSelector.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const currentMode = ref({
|
||||
id: 'training',
|
||||
token: 'app-88ae2GN49aUyNO6qGg7tbTfX'
|
||||
})
|
||||
|
||||
const handleModeChange = (mode) => {
|
||||
currentMode.value = mode
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app">
|
||||
<div class="app-container">
|
||||
<ChatModeSelector @mode-changed="handleModeChange" />
|
||||
<div class="chat-wrapper">
|
||||
<ChatInterface :chatMode="currentMode" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-wrapper {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,86 @@
|
|||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
After Width: | Height: | Size: 276 B |
|
@ -0,0 +1,35 @@
|
|||
@import './base.css';
|
||||
|
||||
#app {
|
||||
max-width: 100vh;
|
||||
margin: 0 auto;
|
||||
/* padding: 2rem; */
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
/* grid-template-columns: 1fr 1fr; */
|
||||
/* padding: 0 2rem; */
|
||||
}
|
||||
}
|
|
@ -0,0 +1,933 @@
|
|||
<template>
|
||||
<div class="chat-container">
|
||||
<div class="chat-messages" ref="chatContainer">
|
||||
<div v-for="(message, index) in messages" :key="index"
|
||||
:class="['message', message.type === 'ai' ? 'ai-message' : 'user-message']">
|
||||
<div class="message-wrapper">
|
||||
<div class="avatar">
|
||||
<span class="emoji">{{ message.type === 'ai' ? '🤖' : '👤' }}</span>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message-text" v-html="formatMessage(message.content)"></div>
|
||||
<div v-if="message.type === 'user' && message.evaluation"
|
||||
class="evaluation-section">
|
||||
<div class="evaluation-title" @click="toggleEvaluation(index)">
|
||||
评价解析 {{ message.showEvaluation ? '▼' : '▶' }}
|
||||
</div>
|
||||
<div v-show="message.showEvaluation" class="evaluation-content">
|
||||
<div v-show="message.pingfen" class="pingfen" v-html="formatMessage(message.pingfen)"></div>
|
||||
<div v-show="message.zongjie" class="zongjie" v-html="formatMessage(message.zongjie)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isTyping" class="message ai-message">
|
||||
<div class="message-wrapper">
|
||||
<div class="avatar">
|
||||
<span class="emoji">🤖</span>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="typing-indicator">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showScore" class="score-overlay">
|
||||
<div class="score-container">
|
||||
<h2>成绩展示</h2>
|
||||
<div class="score-chart" ref="scoreChart"></div>
|
||||
<div class="score-value">{{ score }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<button class="new-chat-btn" @click="startNewChat" :disabled="loading">
|
||||
<span class="new-chat-icon">+</span>
|
||||
新会话
|
||||
</button>
|
||||
<textarea
|
||||
v-model="newMessage"
|
||||
@keyup.enter.exact="sendMessage"
|
||||
@keydown.enter.exact.prevent
|
||||
placeholder="请输入消息..."
|
||||
:disabled="loading"
|
||||
rows="1"
|
||||
ref="messageInput"
|
||||
@input="adjustTextareaHeight"
|
||||
></textarea>
|
||||
<button @click="sendMessage" :disabled="loading">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
import { marked } from 'marked';
|
||||
|
||||
export default {
|
||||
name: 'ChatInterface',
|
||||
props: {
|
||||
chatMode: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
id: 'training',
|
||||
token: 'app-88ae2GN49aUyNO6qGg7tbTfX'
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
messages: [],
|
||||
newMessage: '',
|
||||
loading: false,
|
||||
evaluation: null,
|
||||
showEvaluation: false,
|
||||
score: '',
|
||||
summary: '',
|
||||
chart: null,
|
||||
apiEndpoint: 'http://160.202.224.52:31002/v1/chat-messages',
|
||||
conversationId: '',
|
||||
isTyping: false,
|
||||
maxRows: 6
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 配置marked选项
|
||||
marked.setOptions({
|
||||
breaks: true, // 支持GitHub风格的换行
|
||||
gfm: true, // 启用GitHub风格的Markdown
|
||||
sanitize: false // 允许HTML标签
|
||||
});
|
||||
// 组件创建时从localStorage加载聊天记录
|
||||
this.loadChatHistory();
|
||||
},
|
||||
watch: {
|
||||
'chatMode.id': {
|
||||
handler(newModeId, oldModeId) {
|
||||
if (oldModeId) {
|
||||
// 保存当前模式的聊天记录
|
||||
this.saveChatHistory(oldModeId);
|
||||
}
|
||||
// 加载新模式的聊天记录
|
||||
this.loadChatHistory(newModeId);
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
messages: {
|
||||
handler(newMessages) {
|
||||
// 当消息更新时保存到localStorage
|
||||
if (this.chatMode.id) {
|
||||
this.saveChatHistory(this.chatMode.id);
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveChatHistory(modeId) {
|
||||
const history = {
|
||||
messages: this.messages,
|
||||
conversationId: this.conversationId
|
||||
};
|
||||
localStorage.setItem(`chat_history_${modeId}`, JSON.stringify(history));
|
||||
},
|
||||
loadChatHistory(modeId = this.chatMode.id) {
|
||||
const savedHistory = localStorage.getItem(`chat_history_${modeId}`);
|
||||
if (savedHistory) {
|
||||
const history = JSON.parse(savedHistory);
|
||||
this.messages = history.messages;
|
||||
this.conversationId = history.conversationId;
|
||||
} else {
|
||||
// 如果没有历史记录,清空当前对话
|
||||
this.messages = [];
|
||||
this.conversationId = '';
|
||||
}
|
||||
},
|
||||
async sendMessage() {
|
||||
if (!this.newMessage.trim()) return;
|
||||
|
||||
const userMessage = {
|
||||
type: 'user',
|
||||
content: this.newMessage
|
||||
};
|
||||
|
||||
this.messages.push(userMessage);
|
||||
this.loading = true;
|
||||
const currentMessage = this.newMessage;
|
||||
this.newMessage = '';
|
||||
this.$refs.messageInput.style.height = 'auto';
|
||||
|
||||
try {
|
||||
this.isTyping = true;
|
||||
const response = await fetch(this.apiEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.chatMode.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
inputs: {},
|
||||
query: currentMessage,
|
||||
response_mode: 'streaming',
|
||||
conversation_id: this.conversationId,
|
||||
user: 'sys123'
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('网络请求失败');
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
let aiMessage = null;
|
||||
let aiContent = '';
|
||||
let evaluationContent = {
|
||||
pingfen: '',
|
||||
zongjie: '',
|
||||
dafen: ''
|
||||
};
|
||||
let currentTag = null;
|
||||
let isFirstChunk = true;
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const text = new TextDecoder().decode(value);
|
||||
buffer += text;
|
||||
|
||||
// 处理完整的行
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || ''; // 保留最后一个不完整的行
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim() || !line.startsWith('data: ')) continue;
|
||||
|
||||
try {
|
||||
const jsonStr = line.substring(6);
|
||||
const jsonData = JSON.parse(jsonStr);
|
||||
|
||||
// 更新conversation_id
|
||||
if (jsonData.conversation_id && !this.conversationId) {
|
||||
this.conversationId = jsonData.conversation_id;
|
||||
}
|
||||
|
||||
if (jsonData.event === 'message' && jsonData.answer) {
|
||||
const answer = jsonData.answer;
|
||||
|
||||
// 检查标签开始
|
||||
if (answer.includes('<kehu>')) {
|
||||
currentTag = 'kehu';
|
||||
} else if (answer.includes('<pingfen>')) {
|
||||
currentTag = 'pingfen';
|
||||
} else if (answer.includes('<zongjie>')) {
|
||||
currentTag = 'zongjie';
|
||||
} else if (answer.includes('<dafen>')) {
|
||||
currentTag = 'dafen';
|
||||
}
|
||||
|
||||
// 检查标签结束
|
||||
if (answer.includes('</kehu>')) {
|
||||
currentTag = null;
|
||||
} else if (answer.includes('</pingfen>')) {
|
||||
currentTag = null;
|
||||
} else if (answer.includes('</zongjie>')) {
|
||||
currentTag = null;
|
||||
} else if (answer.includes('</dafen>')) {
|
||||
currentTag = null;
|
||||
}
|
||||
|
||||
// 根据当前标签处理内容
|
||||
if (currentTag === 'kehu' || !currentTag) {
|
||||
// 移除kehu标签
|
||||
const cleanedAnswer = answer.replace(/<\/?kehu>/g, '');
|
||||
if (cleanedAnswer.trim()) {
|
||||
if (isFirstChunk) {
|
||||
this.isTyping = false;
|
||||
aiMessage = {
|
||||
type: 'ai',
|
||||
content: ''
|
||||
};
|
||||
this.messages.push(aiMessage);
|
||||
isFirstChunk = false;
|
||||
}
|
||||
|
||||
aiContent += cleanedAnswer;
|
||||
if (aiMessage) {
|
||||
aiMessage.content = aiContent;
|
||||
// 使用requestAnimationFrame来优化渲染性能
|
||||
requestAnimationFrame(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (currentTag === 'pingfen') {
|
||||
evaluationContent.pingfen += answer.replace(/<\/?pingfen>/g, '');
|
||||
} else if (currentTag === 'zongjie') {
|
||||
evaluationContent.zongjie += answer.replace(/<\/?zongjie>/g, '');
|
||||
} else if (currentTag === 'dafen') {
|
||||
evaluationContent.dafen += answer.replace(/<\/?dafen>/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理消息结束事件
|
||||
if (jsonData.event === 'message_end' || jsonData.event === 'workflow_finished') {
|
||||
// 处理评价解析
|
||||
if (evaluationContent.pingfen || evaluationContent.zongjie) {
|
||||
const lastUserMessageIndex = this.messages.length - 2;
|
||||
if (lastUserMessageIndex >= 0) {
|
||||
this.messages[lastUserMessageIndex].evaluation = true;
|
||||
this.messages[lastUserMessageIndex].showEvaluation = false;
|
||||
this.messages[lastUserMessageIndex].pingfen = evaluationContent.pingfen;
|
||||
this.messages[lastUserMessageIndex].zongjie = evaluationContent.zongjie;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理成绩展示
|
||||
if (evaluationContent.dafen) {
|
||||
this.score = evaluationContent.dafen;
|
||||
if (this.$refs.scoreChart) {
|
||||
this.initChart();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing SSE message:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送消息失败:', error);
|
||||
this.messages.push({
|
||||
type: 'ai',
|
||||
content: '抱歉,发生了一些错误。请稍后重试。'
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.isTyping = false;
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
}
|
||||
},
|
||||
toggleEvaluation(index) {
|
||||
this.messages[index].showEvaluation = !this.messages[index].showEvaluation;
|
||||
},
|
||||
scrollToBottom() {
|
||||
this.$nextTick(() => {
|
||||
const container = this.$refs.chatContainer;
|
||||
container.scrollTop = container.scrollHeight;
|
||||
});
|
||||
},
|
||||
initChart() {
|
||||
if (this.chart) {
|
||||
this.chart.dispose();
|
||||
}
|
||||
|
||||
this.chart = echarts.init(this.$refs.scoreChart);
|
||||
|
||||
// 解析成绩数据,假设成绩格式为:维度1:分数1,维度2:分数2,...
|
||||
const scoreData = this.score.split(',').map(item => {
|
||||
const [name, value] = item.split(':');
|
||||
return { name, value: parseFloat(value) };
|
||||
});
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '成绩维度分析',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
radar: {
|
||||
indicator: scoreData.map(item => ({
|
||||
name: item.name,
|
||||
max: 100
|
||||
}))
|
||||
},
|
||||
series: [{
|
||||
type: 'radar',
|
||||
data: [{
|
||||
value: scoreData.map(item => item.value),
|
||||
name: '成绩',
|
||||
areaStyle: {
|
||||
color: 'rgba(76, 175, 80, 0.3)'
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#4CAF50'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#4CAF50'
|
||||
}
|
||||
}]
|
||||
}]
|
||||
};
|
||||
|
||||
this.chart.setOption(option);
|
||||
|
||||
// 监听窗口大小变化,调整图表大小
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
},
|
||||
handleResize() {
|
||||
if (this.chart) {
|
||||
this.chart.resize();
|
||||
}
|
||||
},
|
||||
formatMessage(text) {
|
||||
if (!text) return '';
|
||||
|
||||
// 移除特定标签但保留其内容
|
||||
text = text.replace(/<\/?(kehu|pingfen|zongjie|chengji|dafen)[^>]*>/g, '');
|
||||
|
||||
// 使用marked解析Markdown
|
||||
try {
|
||||
return marked(text);
|
||||
} catch (e) {
|
||||
console.error('Markdown parsing error:', e);
|
||||
return text;
|
||||
}
|
||||
},
|
||||
adjustTextareaHeight() {
|
||||
const textarea = this.$refs.messageInput;
|
||||
textarea.style.height = 'auto';
|
||||
const newHeight = Math.min(textarea.scrollHeight, this.maxRows * 24); // 24px 是单行高度
|
||||
textarea.style.height = newHeight + 'px';
|
||||
},
|
||||
startNewChat() {
|
||||
// 清空消息记录
|
||||
this.messages = [];
|
||||
// 重置会话ID
|
||||
this.conversationId = '';
|
||||
// 重置其他状态
|
||||
this.evaluation = null;
|
||||
this.showEvaluation = false;
|
||||
this.score = '';
|
||||
this.summary = '';
|
||||
// 保存空状态到本地存储
|
||||
this.saveChatHistory(this.chatMode.id);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
// 组件销毁前移除事件监听
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
if (this.chart) {
|
||||
this.chart.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ai-message {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.message-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
max-width: 66.666%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.emoji {
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.user-message .avatar {
|
||||
background: #e8f5e8;
|
||||
}
|
||||
|
||||
.ai-message .avatar {
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ai-message .message-wrapper {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.user-message .message-wrapper {
|
||||
margin-left: auto;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
line-height: 1.6;
|
||||
font-size: 15px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.ai-message .message-text {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.ai-message .message-text::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: 14px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 6px 8px 6px 0;
|
||||
border-color: transparent #fff transparent transparent;
|
||||
}
|
||||
|
||||
.user-message .message-text {
|
||||
background: #95ec69;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-message .message-text::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: 14px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 6px 0 6px 8px;
|
||||
border-color: transparent transparent transparent #95ec69;
|
||||
}
|
||||
|
||||
.evaluation-section {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.evaluation-title {
|
||||
cursor: pointer;
|
||||
color: #576b95;
|
||||
font-weight: normal;
|
||||
padding: 10px 16px;
|
||||
background: #f7f7f7;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.evaluation-title:hover {
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.evaluation-content {
|
||||
margin-top: 0;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(ul),
|
||||
.evaluation-content :deep(ol) {
|
||||
margin: 8px 0;
|
||||
padding-left: 24px;
|
||||
list-style-position: outside;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(li) {
|
||||
margin: 4px 0;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(blockquote) {
|
||||
margin: 12px 0;
|
||||
padding: 0 0 0 16px;
|
||||
border-left: 4px solid #ddd;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(pre) {
|
||||
margin: 12px 0;
|
||||
padding: 16px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(code) {
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 4px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(pre code) {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(p) {
|
||||
margin: 12px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(h1),
|
||||
.evaluation-content :deep(h2),
|
||||
.evaluation-content :deep(h3),
|
||||
.evaluation-content :deep(h4),
|
||||
.evaluation-content :deep(h5),
|
||||
.evaluation-content :deep(h6) {
|
||||
margin: 16px 0 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.evaluation-content :deep(h1) { font-size: 1.5em; margin-top: 24px; }
|
||||
.evaluation-content :deep(h2) { font-size: 1.3em; margin-top: 20px; }
|
||||
.evaluation-content :deep(h3) { font-size: 1.2em; margin-top: 16px; }
|
||||
.evaluation-content :deep(h4) { font-size: 1.1em; }
|
||||
.evaluation-content :deep(h5),
|
||||
.evaluation-content :deep(h6) { font-size: 1em; }
|
||||
|
||||
.evaluation-content .pingfen,
|
||||
.evaluation-content .zongjie {
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.evaluation-content .pingfen:last-child,
|
||||
.evaluation-content .zongjie:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #f5f5f5;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: all 0.3s;
|
||||
background: #fff;
|
||||
resize: none;
|
||||
min-height: 42px;
|
||||
max-height: calc(24px * 6);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
border-color: #07c160;
|
||||
box-shadow: 0 0 0 2px rgba(7, 193, 96, 0.1);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0 24px;
|
||||
background: #07c160;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #06ae56;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 优化滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0,0,0,0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.score-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.score-container {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.score-container h2 {
|
||||
margin: 0 0 20px;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.score-chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 16px;
|
||||
color: #07c160;
|
||||
margin-top: 16px;
|
||||
padding: 8px 16px;
|
||||
background: #f6ffed;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 16px;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.typing-indicator span {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #95ec69;
|
||||
border-radius: 50%;
|
||||
animation: typing 1s infinite ease-in-out;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.new-chat-btn {
|
||||
padding: 0 16px;
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 42px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.new-chat-btn:hover {
|
||||
background: #e8e8e8;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.new-chat-btn:disabled {
|
||||
background: #f5f5f5;
|
||||
border-color: #ddd;
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.new-chat-icon {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 修改输入区域样式以适应新按钮 */
|
||||
.input-area {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #f5f5f5;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* 发送按钮样式保持绿色 */
|
||||
.input-area button:last-child {
|
||||
background: #07c160;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.input-area button:last-child:hover {
|
||||
background: #06ae56;
|
||||
}
|
||||
|
||||
.input-area button:last-child:disabled {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
/* 添加全局样式以支持Markdown渲染 */
|
||||
.message-text :deep(h1),
|
||||
.message-text :deep(h2),
|
||||
.message-text :deep(h3),
|
||||
.message-text :deep(h4),
|
||||
.message-text :deep(h5),
|
||||
.message-text :deep(h6) {
|
||||
margin: 16px 0 8px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.message-text :deep(h1) { font-size: 1.5em; }
|
||||
.message-text :deep(h2) { font-size: 1.3em; }
|
||||
.message-text :deep(h3) { font-size: 1.2em; }
|
||||
.message-text :deep(h4) { font-size: 1.1em; }
|
||||
.message-text :deep(h5),
|
||||
.message-text :deep(h6) { font-size: 1em; }
|
||||
|
||||
.message-text :deep(p) {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.message-text :deep(ul),
|
||||
.message-text :deep(ol) {
|
||||
margin: 8px 0;
|
||||
padding-left: 24px;
|
||||
list-style-position: outside;
|
||||
}
|
||||
|
||||
.message-text :deep(li) {
|
||||
margin: 4px 0;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.message-text :deep(blockquote) {
|
||||
margin: 12px 0;
|
||||
padding: 0 0 0 16px;
|
||||
border-left: 4px solid #ddd;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.message-text :deep(pre) {
|
||||
margin: 12px 0;
|
||||
padding: 16px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.message-text :deep(code) {
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 4px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.message-text :deep(pre code) {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.message-text :deep(p) {
|
||||
margin: 12px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,131 @@
|
|||
<template>
|
||||
<div class="mode-selector">
|
||||
<h3 class="mode-title">对话场景</h3>
|
||||
<div class="mode-list">
|
||||
<div
|
||||
v-for="mode in chatModes"
|
||||
:key="mode.id"
|
||||
:class="['mode-item', { active: currentMode === mode.id }]"
|
||||
@click="selectMode(mode.id)"
|
||||
>
|
||||
<i :class="mode.icon"></i>
|
||||
<span>{{ mode.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChatModeSelector',
|
||||
data() {
|
||||
return {
|
||||
currentMode: 'training',
|
||||
chatModes: [
|
||||
{
|
||||
id: 'training',
|
||||
name: '寿险代理人AI陪练',
|
||||
icon: 'fas fa-user-tie',
|
||||
token: 'app-88ae2GN49aUyNO6qGg7tbTfX'
|
||||
},
|
||||
{
|
||||
id: 'quote_objection',
|
||||
name: '报价中异议',
|
||||
icon: 'fas fa-comments',
|
||||
token: 'app-88ae2GN49aUyNO6qGg7tbTfX'
|
||||
},
|
||||
{
|
||||
id: 'post_quote_objection',
|
||||
name: '报价后异议',
|
||||
icon: 'fas fa-comment-dollar',
|
||||
token: 'app-88ae2GN49aUyNO6qGg7tbTfX'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectMode(modeId) {
|
||||
this.currentMode = modeId;
|
||||
const selectedMode = this.chatModes.find(mode => mode.id === modeId);
|
||||
this.$emit('mode-changed', {
|
||||
id: modeId,
|
||||
token: selectedMode.token
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mode-selector {
|
||||
width: 240px;
|
||||
min-width: 240px;
|
||||
background: #ffffff;
|
||||
border-right: 1px solid #e0e0e0;
|
||||
height: 100%;
|
||||
padding: 20px 0;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.mode-title {
|
||||
padding: 0 20px;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mode-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.mode-item {
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mode-item:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.mode-item.active {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mode-item i {
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mode-item span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.mode-selector {
|
||||
background: #1e1e1e;
|
||||
border-right-color: #333;
|
||||
}
|
||||
|
||||
.mode-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mode-item {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mode-item:hover {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,19 @@
|
|||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
class="iconify iconify--mdi"
|
||||
width="24"
|
||||
height="24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,6 @@
|
|||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
|
@ -0,0 +1,18 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
})
|
Loading…
Reference in New Issue