chore: 精简代码
This commit is contained in:
parent
f2866df2ea
commit
95e771cb61
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3>创建项目</h3>
|
||||
<button class="btn-close" @click="showCreateModal = false">×</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">×</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">×</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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = '复制'
|
||||
|
|
|
|||
Loading…
Reference in New Issue