chore: 精简代码

This commit is contained in:
ViperEkura 2026-03-26 16:38:52 +08:00
parent f2866df2ea
commit 95e771cb61
11 changed files with 152 additions and 227 deletions

View File

@ -132,11 +132,7 @@ function scrollToBottom(smooth = true) {
})
}
watch(() => props.messages.length, () => {
scrollToBottom()
})
watch(() => props.streamingContent, () => {
watch([() => props.messages.length, () => props.streamingContent], () => {
scrollToBottom()
})
@ -298,52 +294,8 @@ watch(() => props.conversation?.id, () => {
padding: 0 16px;
}
.message-bubble {
display: flex;
gap: 12px;
margin-bottom: 16px;
width: 100%;
}
/* .message-bubble, .avatar, .message-body now in global.css */
.message-bubble .message-container {
display: flex;
flex-direction: column;
max-width: 85%;
min-width: 200px;
}
.message-bubble.user .message-container {
align-items: flex-end;
}
.message-bubble.assistant .message-container {
align-items: flex-start;
}
.message-bubble .avatar {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
letter-spacing: -0.3px;
flex-shrink: 0;
background: var(--avatar-gradient);
color: white;
}
.message-bubble .message-body {
flex: 1;
min-width: 0;
overflow: hidden;
padding: 16px;
border: 1px solid var(--border-light);
border-radius: 12px;
background: var(--bg-primary);
transition: background 0.2s, border-color 0.2s;
}
</style>

View File

@ -0,0 +1,12 @@
<template>
<button class="btn-close" @click="$emit('click')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</template>
<script setup>
defineEmits(['click'])
</script>

View File

@ -90,35 +90,7 @@ function copyContent() {
</script>
<style scoped>
.message-bubble {
display: flex;
gap: 12px;
margin-bottom: 16px;
width: 100%;
}
.message-bubble.user {
flex-direction: row-reverse;
}
.message-container {
display: flex;
flex-direction: column;
min-width: 200px;
width: 100%;
}
.message-bubble.user .message-container {
align-items: flex-end;
width: fit-content;
max-width: 85%;
}
.message-bubble.assistant .message-container {
align-items: flex-start;
flex: 1 1 auto;
min-width: 0;
}
/* .message-bubble, .avatar, .message-body now in global.css */
.attachments-list {
display: flex;
@ -154,48 +126,6 @@ function copyContent() {
font-weight: 500;
}
.message-bubble.assistant .message-body {
width: 100%;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
flex-shrink: 0;
}
.user .avatar {
background: linear-gradient(135deg, #2563eb, #3b82f6);
color: white;
font-size: 12px;
font-weight: 700;
letter-spacing: -0.3px;
}
.assistant .avatar {
background: var(--avatar-gradient);
color: white;
font-size: 12px;
font-weight: 700;
letter-spacing: -0.3px;
}
.message-body {
flex: 1;
min-width: 0;
padding: 16px;
border: 1px solid var(--border-light);
border-radius: 12px;
background: var(--bg-primary);
transition: background 0.2s, border-color 0.2s;
}
.message-footer {
display: flex;
align-items: center;

View File

@ -74,6 +74,7 @@
<script setup>
import { ref, computed, watch } from 'vue'
import { renderMarkdown } from '../utils/markdown'
import { formatJson, truncate } from '../utils/format'
import { useCodeEnhancement } from '../composables/useCodeEnhancement'
const props = defineProps({
@ -97,18 +98,6 @@ function toggleItem(key) {
expandedKeys.value[key] = !expandedKeys.value[key]
}
function formatJson(value) {
if (value == null) return ''
const str = typeof value === 'string' ? value : JSON.stringify(value)
try { return JSON.stringify(JSON.parse(str), null, 2) } catch { return str }
}
function truncate(text, max = 60) {
if (!text) return ''
const str = text.replace(/\s+/g, ' ').trim()
return str.length > max ? str.slice(0, max) + '…' : str
}
function getResultSummary(result) {
try {
const parsed = typeof result === 'string' ? JSON.parse(result) : result

View File

@ -53,7 +53,7 @@
<div class="modal">
<div class="modal-header">
<h3>创建项目</h3>
<button class="btn-close" @click="showCreateModal = false">&times;</button>
<CloseButton @click="showCreateModal = false" />
</div>
<div class="modal-body">
<div class="form-group">
@ -79,7 +79,7 @@
<div class="modal">
<div class="modal-header">
<h3>上传文件夹</h3>
<button class="btn-close" @click="closeUploadModal">&times;</button>
<CloseButton @click="closeUploadModal" />
</div>
<div class="modal-body">
<div class="form-group">
@ -124,7 +124,7 @@
<div class="modal">
<div class="modal-header">
<h3>确认删除</h3>
<button class="btn-close" @click="showDeleteModal = false">&times;</button>
<CloseButton @click="showDeleteModal = false" />
</div>
<div class="modal-body">
<p>确定要删除项目 <strong>{{ projectToDelete?.name }}</strong> </p>
@ -144,9 +144,10 @@
<script setup>
import { ref, onMounted } from 'vue'
import { projectApi } from '../api'
import CloseButton from './CloseButton.vue'
const props = defineProps({
currentProject: Object,
currentProject: { type: Object, default: null },
})
const emit = defineEmits(['select', 'created', 'deleted'])
@ -316,23 +317,7 @@ defineExpose({
color: var(--text-primary);
}
.btn-icon {
padding: 6px;
border: none;
background: transparent;
color: var(--text-secondary);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.btn-icon:hover {
background: var(--bg-hover);
color: var(--accent-primary);
}
/* .btn-icon is defined in global.css */
.btn-icon.danger:hover {
background: var(--danger-bg);

View File

@ -18,12 +18,7 @@
{{ t.label }}
</button>
</div>
<button class="btn-close" @click="$emit('close')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<CloseButton @click="$emit('close')" />
</div>
</div>
@ -132,6 +127,7 @@
import { reactive, ref, watch, onMounted } from 'vue'
import { modelApi, conversationApi } from '../api'
import { useTheme } from '../composables/useTheme'
import CloseButton from './CloseButton.vue'
const props = defineProps({
visible: { type: Boolean, default: false },
@ -251,34 +247,7 @@ onMounted(loadModels)
gap: 12px;
}
.period-tabs {
display: flex;
gap: 2px;
background: var(--bg-input);
padding: 3px;
border-radius: 8px;
}
.tab {
padding: 4px 12px;
border: none;
background: none;
color: var(--text-tertiary);
font-size: 12px;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
}
.tab:hover {
color: var(--text-secondary);
}
.tab.active {
background: var(--accent-primary);
color: white;
box-shadow: 0 1px 3px rgba(37, 99, 235, 0.3);
}
/* tab styles now in global.css */
.settings-body {
flex: 1;

View File

@ -109,7 +109,8 @@ function selectProject(project) {
}
function onProjectCreated(project) {
// Auto-select newly created project
// Auto-select newly created project and refresh list
projectManagerRef.value?.loadProjects()
emit('selectProject', project)
}
@ -132,7 +133,6 @@ function onScroll(e) {
.sidebar {
width: 20%;
min-width: 220px;
max-width: 320px;
flex-shrink: 0;
background: color-mix(in srgb, var(--bg-primary) 75%, transparent);
backdrop-filter: blur(40px);

View File

@ -20,12 +20,7 @@
{{ p.label }}
</button>
</div>
<button class="btn-close" @click="$emit('close')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<CloseButton @click="$emit('close')" />
</div>
</div>
@ -224,6 +219,7 @@ import { ref, computed, onMounted } from 'vue'
import { statsApi } from '../api'
import { useTheme } from '../composables/useTheme'
import { formatNumber } from '../utils/format'
import CloseButton from './CloseButton.vue'
const emit = defineEmits(['close'])
@ -377,34 +373,7 @@ onMounted(loadStats)
gap: 12px;
}
.period-tabs {
display: flex;
gap: 2px;
background: var(--bg-input);
padding: 3px;
border-radius: 8px;
}
.tab {
padding: 4px 12px;
border: none;
background: none;
color: var(--text-tertiary);
font-size: 12px;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
}
.tab:hover {
color: var(--text-secondary);
}
.tab.active {
background: var(--accent-primary);
color: white;
box-shadow: 0 1px 3px rgba(37, 99, 235, 0.3);
}
/* tab styles now in global.css */
.stats-loading {
display: flex;

View File

@ -509,6 +509,107 @@ input[type="range"]::-moz-range-thumb {
cursor: not-allowed;
}
/* ============ Tab Navigation ============ */
.period-tabs {
display: flex;
gap: 2px;
background: var(--bg-input);
padding: 3px;
border-radius: 8px;
}
.tab {
padding: 4px 12px;
border: none;
background: none;
color: var(--text-tertiary);
font-size: 12px;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
}
.tab:hover {
color: var(--text-secondary);
}
.tab.active {
background: var(--accent-primary);
color: white;
box-shadow: 0 1px 3px rgba(37, 99, 235, 0.3);
}
/* ============ Message Bubble Shared ============ */
.message-bubble {
display: flex;
gap: 12px;
margin-bottom: 16px;
width: 100%;
}
.message-bubble.user {
flex-direction: row-reverse;
}
.message-container {
display: flex;
flex-direction: column;
min-width: 200px;
width: 100%;
}
.message-bubble.user .message-container {
align-items: flex-end;
width: fit-content;
max-width: 85%;
}
.message-bubble.assistant .message-container {
align-items: flex-start;
flex: 1 1 auto;
width: 100%;
min-width: 0;
}
.message-bubble.assistant .message-body {
width: 100%;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
letter-spacing: -0.3px;
flex-shrink: 0;
}
.user .avatar {
background: linear-gradient(135deg, #2563eb, #3b82f6);
color: white;
font-size: 12px;
}
.assistant .avatar {
background: var(--avatar-gradient);
color: white;
font-size: 12px;
}
.message-body {
flex: 1;
min-width: 0;
padding: 16px;
border: 1px solid var(--border-light);
border-radius: 12px;
background: var(--bg-primary);
transition: background 0.2s, border-color 0.2s;
}
/* ============ Form ============ */
.form-group {
margin-bottom: 16px;

View File

@ -23,3 +23,21 @@ export function formatNumber(num) {
if (num >= 1000) return (num / 1000).toFixed(1) + 'K'
return num.toString()
}
/**
* Format a value as pretty-printed JSON string.
*/
export function formatJson(value) {
if (value == null) return ''
const str = typeof value === 'string' ? value : JSON.stringify(value)
try { return JSON.stringify(JSON.parse(str), null, 2) } catch { return str }
}
/**
* Truncate text to max characters with ellipsis.
*/
export function truncate(text, max = 60) {
if (!text) return ''
const str = text.replace(/\s+/g, ' ').trim()
return str.length > max ? str.slice(0, max) + '\u2026' : str
}

View File

@ -75,6 +75,9 @@ export function renderMarkdown(text) {
return marked.parse(text)
}
const COPY_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>'
const CHECK_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>'
/**
* 后处理 HTML为所有代码块包裹 .code-block 容器
* 添加语言标签和复制按钮在组件 onMounted / updated 中调用
@ -101,9 +104,6 @@ export function enhanceCodeBlocks(container) {
langSpan.className = 'code-lang'
langSpan.textContent = lang
const COPY_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>'
const CHECK_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>'
const copyBtn = document.createElement('button')
copyBtn.className = 'code-copy-btn'
copyBtn.title = '复制'