@templmf/temp-solf-lmf 0.0.55 → 0.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fe-flow.md +348 -0
- package/package.json +1 -1
- package/ui-parse.md +308 -0
- package//345/242/236/351/207/217/351/234/200/346/261/202prompt +72 -0
- package/guanwang/README.md +0 -95
- package/guanwang/docs/changelog.md +0 -145
- package/guanwang/docs/doc-maintenance.md +0 -229
- package/guanwang/docs/product.md +0 -181
- package/guanwang/docs/test-cases.md +0 -395
- package/guanwang/docs/usage.md +0 -291
- package/guanwang/env.example +0 -27
- package/guanwang/index.html +0 -13
- package/guanwang/package-lock.json +0 -3825
- package/guanwang/package.json +0 -32
- package/guanwang/public/favicon.svg +0 -4
- package/guanwang/public/react-runtime/babel.min.js +0 -4
- package/guanwang/public/react-runtime/react-dom.min.js +0 -267
- package/guanwang/public/react-runtime/react.min.js +0 -31
- package/guanwang/public/vue-repl-assets/compiler-sfc.esm-browser.js +0 -50795
- package/guanwang/public/vue-repl-assets/runtime-dom.esm-browser.js +0 -12758
- package/guanwang/public/vue-repl-assets/server-renderer.esm-browser.js +0 -8600
- package/guanwang/public/vue-repl-assets/vue.esm-browser.js +0 -18672
- package/guanwang/src/App.vue +0 -61
- package/guanwang/src/chat-sdk/core/components/ChatBox.vue +0 -305
- package/guanwang/src/chat-sdk/core/components/ChatSidebar.vue +0 -84
- package/guanwang/src/chat-sdk/core/components/InputBar.vue +0 -354
- package/guanwang/src/chat-sdk/core/components/MessageBubble.vue +0 -703
- package/guanwang/src/chat-sdk/core/useTheme.js +0 -31
- package/guanwang/src/chat-sdk/features/artifact/ArtifactCard.vue +0 -172
- package/guanwang/src/chat-sdk/features/artifact/ArtifactPanel.vue +0 -963
- package/guanwang/src/chat-sdk/features/artifact/index.js +0 -13
- package/guanwang/src/chat-sdk/features/artifact/useArtifactStore.js +0 -275
- package/guanwang/src/chat-sdk/features/codepreview/CodePreview.vue +0 -523
- package/guanwang/src/chat-sdk/features/codepreview/index.js +0 -7
- package/guanwang/src/chat-sdk/features/markdown/index.js +0 -13
- package/guanwang/src/chat-sdk/features/markdown/useMarkdown.js +0 -724
- package/guanwang/src/chat-sdk/features/mermaid/MermaidZoom.vue +0 -254
- package/guanwang/src/chat-sdk/features/upload/FileAttachment.vue +0 -142
- package/guanwang/src/chat-sdk/features/upload/index.js +0 -17
- package/guanwang/src/chat-sdk/features/upload/useFileHandler.js +0 -336
- package/guanwang/src/chat-sdk/headless/api/adapters/openai.js +0 -76
- package/guanwang/src/chat-sdk/headless/api/chatApi.js +0 -126
- package/guanwang/src/chat-sdk/headless/buildSystemPrompt.js +0 -351
- package/guanwang/src/chat-sdk/headless/index.js +0 -15
- package/guanwang/src/chat-sdk/headless/useChat.js +0 -77
- package/guanwang/src/chat-sdk/headless/useChatDB.js +0 -147
- package/guanwang/src/chat-sdk/headless/useChatStore.js +0 -529
- package/guanwang/src/chat-sdk/index.js +0 -79
- package/guanwang/src/chat-sdk/modes/architect.js +0 -27
- package/guanwang/src/chat-sdk/modes/ask.js +0 -26
- package/guanwang/src/chat-sdk/modes/code.js +0 -25
- package/guanwang/src/chat-sdk/modes/index.js +0 -36
- package/guanwang/src/chat-sdk/modes/requirements.js +0 -175
- package/guanwang/src/chat-sdk/settings/SettingsPanel.vue +0 -170
- package/guanwang/src/chat-sdk/settings/index.js +0 -9
- package/guanwang/src/chat-sdk/settings/useSettings.js +0 -122
- package/guanwang/src/chat-sdk/tools/defaults.js +0 -89
- package/guanwang/src/chat-sdk/tools/index.js +0 -16
- package/guanwang/src/chat-sdk/tools/parser.js +0 -116
- package/guanwang/src/components/CustomCursor.vue +0 -69
- package/guanwang/src/components/Footer.vue +0 -24
- package/guanwang/src/components/LoginModal.vue +0 -109
- package/guanwang/src/components/Navbar.vue +0 -193
- package/guanwang/src/components/ThemeToggle.vue +0 -25
- package/guanwang/src/composables/useArtifactStore.js +0 -253
- package/guanwang/src/composables/useAuth.js +0 -88
- package/guanwang/src/composables/useChatDB.js +0 -147
- package/guanwang/src/composables/useCountUp.js +0 -24
- package/guanwang/src/composables/useFileHandler.js +0 -345
- package/guanwang/src/composables/useTheme.js +0 -31
- package/guanwang/src/config/api.js +0 -71
- package/guanwang/src/main.js +0 -23
- package/guanwang/src/router/index.js +0 -23
- package/guanwang/src/services/authApi.js +0 -27
- package/guanwang/src/services/chatApi.js +0 -66
- package/guanwang/src/styles/global.css +0 -478
- package/guanwang/src/tracker/analyze.js +0 -73
- package/guanwang/src/tracker/config.js +0 -82
- package/guanwang/src/tracker/index.js +0 -18
- package/guanwang/src/tracker/service.js +0 -102
- package/guanwang/src/tracker/useChatTracker.js +0 -179
- package/guanwang/src/tracker/useTracker.js +0 -45
- package/guanwang/src/views/ChatView.vue +0 -65
- package/guanwang/src/views/HomeView.vue +0 -156
- package/guanwang/src/views/MarketView.vue +0 -143
- package/guanwang/src/views/PracticesView.vue +0 -190
- package/guanwang/src/views/SkillsView.vue +0 -129
- package/guanwang/temp +0 -19
- package/guanwang/vite.config.js +0 -6
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Teleport to="body">
|
|
3
|
-
<div
|
|
4
|
-
v-if="visible"
|
|
5
|
-
class="mermaid-zoom-overlay"
|
|
6
|
-
@click.self="emit('close')"
|
|
7
|
-
@keydown.esc="emit('close')"
|
|
8
|
-
>
|
|
9
|
-
<div class="mermaid-zoom-box">
|
|
10
|
-
<div class="mermaid-zoom-toolbar">
|
|
11
|
-
<button class="mermaid-zoom-scale-btn" @click="zoomOut">-</button>
|
|
12
|
-
<span class="mermaid-zoom-scale-label">{{ Math.round(scale * 100) }}%</span>
|
|
13
|
-
<button class="mermaid-zoom-scale-btn" @click="zoomIn">+</button>
|
|
14
|
-
<button class="mermaid-zoom-scale-btn" @click="resetScale">重置</button>
|
|
15
|
-
<button class="mermaid-zoom-dl-btn" :disabled="downloading" @click="downloadPng">
|
|
16
|
-
<span v-if="downloading">导出中...</span>
|
|
17
|
-
<span v-else>下载 PNG</span>
|
|
18
|
-
</button>
|
|
19
|
-
<button class="mermaid-zoom-close" @click="emit('close')">✕</button>
|
|
20
|
-
</div>
|
|
21
|
-
<div ref="scrollRef" class="mermaid-zoom-scroll">
|
|
22
|
-
<div
|
|
23
|
-
ref="svgWrapRef"
|
|
24
|
-
class="mermaid-zoom-svg"
|
|
25
|
-
:style="{ width: svgWidth }"
|
|
26
|
-
v-html="normalizedSvg"
|
|
27
|
-
/>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</Teleport>
|
|
32
|
-
</template>
|
|
33
|
-
|
|
34
|
-
<script setup>
|
|
35
|
-
import { ref, computed, watch, nextTick } from 'vue'
|
|
36
|
-
|
|
37
|
-
const props = defineProps({
|
|
38
|
-
visible: { type: Boolean, default: false },
|
|
39
|
-
svgHtml: { type: String, default: '' },
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const emit = defineEmits(['close'])
|
|
43
|
-
|
|
44
|
-
const scale = ref(1)
|
|
45
|
-
const scrollRef = ref(null)
|
|
46
|
-
const svgWrapRef = ref(null)
|
|
47
|
-
const naturalW = ref(0) // SVG 自然宽度(px),从 viewBox 或 width 属性读取
|
|
48
|
-
|
|
49
|
-
// 去掉 SVG 的固定 width/height/style,只保留 viewBox,让 CSS 控制尺寸
|
|
50
|
-
const normalizedSvg = computed(() => {
|
|
51
|
-
if (!props.svgHtml) return ''
|
|
52
|
-
return props.svgHtml
|
|
53
|
-
.replace(/(<svg[^>]*)\swidth="[^"]*"/, '$1')
|
|
54
|
-
.replace(/(<svg[^>]*)\sheight="[^"]*"/, '$1')
|
|
55
|
-
.replace(/(<svg[^>]*)\sstyle="[^"]*"/, '$1')
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// 从原始 svgHtml 解析 SVG 自然宽度:优先 width 属性,其次 viewBox 第3列
|
|
59
|
-
function parseSvgNaturalWidth(html) {
|
|
60
|
-
const wMatch = html.match(/\swidth="([^"]+)"/)
|
|
61
|
-
if (wMatch) {
|
|
62
|
-
const v = parseFloat(wMatch[1])
|
|
63
|
-
if (v > 0) return v
|
|
64
|
-
}
|
|
65
|
-
const vbMatch = html.match(/\sviewBox="[^"]*?[\s,]([0-9.]+)[\s,]([0-9.]+)"/)
|
|
66
|
-
if (vbMatch) {
|
|
67
|
-
const v = parseFloat(vbMatch[2]) // viewBox 第3列 = width
|
|
68
|
-
if (v > 0) return v
|
|
69
|
-
}
|
|
70
|
-
return 0
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// SVG 渲染宽度:以自然宽度为基准 × scale
|
|
74
|
-
// 若自然宽度超过容器,scale=1 时 fit 到容器宽度(不超出)
|
|
75
|
-
const svgWidth = computed(() => {
|
|
76
|
-
const containerW = scrollRef.value?.clientWidth || 800
|
|
77
|
-
const base = naturalW.value > 0
|
|
78
|
-
? Math.min(naturalW.value, containerW) // scale=1 时不超过容器
|
|
79
|
-
: containerW
|
|
80
|
-
const w = Math.round(base * scale.value)
|
|
81
|
-
return `${w}px`
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const downloading = ref(false)
|
|
85
|
-
|
|
86
|
-
async function downloadPng() {
|
|
87
|
-
debugger
|
|
88
|
-
const svgEl = svgWrapRef.value?.querySelector('svg')
|
|
89
|
-
if (!svgEl || downloading.value) return
|
|
90
|
-
downloading.value = true
|
|
91
|
-
try {
|
|
92
|
-
const vb = svgEl.viewBox?.baseVal
|
|
93
|
-
const natW = (vb?.width > 0 ? vb.width : svgEl.clientWidth) || 800
|
|
94
|
-
const natH = (vb?.height > 0 ? vb.height : svgEl.clientHeight) || 600
|
|
95
|
-
const dpr = window.devicePixelRatio || 1
|
|
96
|
-
const outW = Math.round(natW * scale.value * dpr)
|
|
97
|
-
const outH = Math.round(natH * scale.value * dpr)
|
|
98
|
-
|
|
99
|
-
// 克隆 SVG:只保留自身 <style>,不内联外部字体,避免 Canvas 污染
|
|
100
|
-
const clone = svgEl.cloneNode(true)
|
|
101
|
-
clone.setAttribute('width', outW)
|
|
102
|
-
clone.setAttribute('height', outH)
|
|
103
|
-
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
|
104
|
-
|
|
105
|
-
// 清理可能导致 Canvas 污染的外部资源引用
|
|
106
|
-
// 1. 去除 href 指向外部 URL 的属性
|
|
107
|
-
clone.querySelectorAll('[href],[xlink\:href]').forEach(el => {
|
|
108
|
-
const href = el.getAttribute('href') || el.getAttribute('xlink:href') || ''
|
|
109
|
-
if (/^https?:\/\//.test(href)) el.removeAttribute('href')
|
|
110
|
-
})
|
|
111
|
-
// 2. 去除 <style> 里的 @font-face 及 url() 外链
|
|
112
|
-
clone.querySelectorAll('style').forEach(s => {
|
|
113
|
-
s.textContent = s.textContent
|
|
114
|
-
.replace(/@font-face\s*\{[^}]*\}/g, '')
|
|
115
|
-
.replace(/url\(['"]?https?:\/\/[^)]*['"]?\)/g, 'none')
|
|
116
|
-
})
|
|
117
|
-
// 3. 用纯系统字体覆盖,确保不触发任何网络字体请求
|
|
118
|
-
const safeFont = 'Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif'
|
|
119
|
-
clone.style.fontFamily = safeFont
|
|
120
|
-
clone.querySelectorAll('[style*="font-family"]').forEach(el => {
|
|
121
|
-
el.style.fontFamily = safeFont
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
const svgStr = new XMLSerializer().serializeToString(clone)
|
|
125
|
-
// 用 data URI 而非 Blob URL,规避跨域污染
|
|
126
|
-
const dataUri = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgStr)
|
|
127
|
-
|
|
128
|
-
await new Promise((resolve, reject) => {
|
|
129
|
-
const img = new Image()
|
|
130
|
-
img.onload = () => {
|
|
131
|
-
const canvas = document.createElement('canvas')
|
|
132
|
-
canvas.width = outW
|
|
133
|
-
canvas.height = outH
|
|
134
|
-
const ctx = canvas.getContext('2d')
|
|
135
|
-
ctx.fillStyle = '#ffffff'
|
|
136
|
-
ctx.fillRect(0, 0, outW, outH)
|
|
137
|
-
ctx.drawImage(img, 0, 0, outW, outH)
|
|
138
|
-
canvas.toBlob(pngBlob => {
|
|
139
|
-
if (!pngBlob) { reject(new Error('toBlob failed')); return }
|
|
140
|
-
const a = document.createElement('a')
|
|
141
|
-
a.href = URL.createObjectURL(pngBlob)
|
|
142
|
-
a.download = `mermaid-${Math.round(scale.value * 100)}pct.png`
|
|
143
|
-
a.click()
|
|
144
|
-
setTimeout(() => URL.revokeObjectURL(a.href), 3000)
|
|
145
|
-
resolve()
|
|
146
|
-
}, 'image/png')
|
|
147
|
-
}
|
|
148
|
-
img.onerror = () => {
|
|
149
|
-
// Canvas 仍被污染时降级为直接下载 SVG
|
|
150
|
-
console.warn('[MermaidZoom] Canvas 污染,降级下载 SVG')
|
|
151
|
-
const a = document.createElement('a')
|
|
152
|
-
a.href = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgStr)
|
|
153
|
-
a.download = `mermaid-${Math.round(scale.value * 100)}pct.svg`
|
|
154
|
-
a.click()
|
|
155
|
-
resolve()
|
|
156
|
-
}
|
|
157
|
-
img.src = dataUri
|
|
158
|
-
})
|
|
159
|
-
} catch (e) {
|
|
160
|
-
console.warn('[MermaidZoom] 导出失败', e)
|
|
161
|
-
} finally {
|
|
162
|
-
downloading.value = false
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function zoomIn() { scale.value = Math.min(4, +(scale.value + 0.2).toFixed(1)) }
|
|
167
|
-
function zoomOut() { scale.value = Math.max(0.2, +(scale.value - 0.2).toFixed(1)) }
|
|
168
|
-
function resetScale(){ scale.value = 1 }
|
|
169
|
-
|
|
170
|
-
// 打开时重置 scale 并解析自然宽度
|
|
171
|
-
watch(() => props.visible, async v => {
|
|
172
|
-
if (v) {
|
|
173
|
-
scale.value = 1
|
|
174
|
-
naturalW.value = parseSvgNaturalWidth(props.svgHtml)
|
|
175
|
-
await nextTick()
|
|
176
|
-
// DOM 挂载后用实际渲染宽度修正(更准确)
|
|
177
|
-
const svgEl = svgWrapRef.value?.querySelector('svg')
|
|
178
|
-
if (svgEl?.viewBox?.baseVal?.width > 0) {
|
|
179
|
-
naturalW.value = svgEl.viewBox.baseVal.width
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
</script>
|
|
184
|
-
|
|
185
|
-
<style scoped>
|
|
186
|
-
.mermaid-zoom-overlay {
|
|
187
|
-
position: fixed; inset: 0; z-index: 9999;
|
|
188
|
-
background: rgba(0,0,0,0.65); backdrop-filter: blur(4px);
|
|
189
|
-
display: flex; align-items: center; justify-content: center;
|
|
190
|
-
padding: 24px;
|
|
191
|
-
}
|
|
192
|
-
.mermaid-zoom-box {
|
|
193
|
-
background: var(--bg-primary);
|
|
194
|
-
border-radius: 14px;
|
|
195
|
-
display: flex; flex-direction: column;
|
|
196
|
-
width: 90vw; height: 80vh;
|
|
197
|
-
box-shadow: 0 24px 80px rgba(0,0,0,0.4);
|
|
198
|
-
overflow: hidden;
|
|
199
|
-
}
|
|
200
|
-
.mermaid-zoom-toolbar {
|
|
201
|
-
display: flex; align-items: center; gap: 6px;
|
|
202
|
-
padding: 10px 14px;
|
|
203
|
-
border-bottom: 1px solid var(--border-subtle);
|
|
204
|
-
background: var(--bg-secondary);
|
|
205
|
-
border-radius: 14px 14px 0 0;
|
|
206
|
-
flex-shrink: 0;
|
|
207
|
-
}
|
|
208
|
-
.mermaid-zoom-scale-btn {
|
|
209
|
-
min-width: 28px; height: 26px; padding: 0 8px;
|
|
210
|
-
border: 1px solid var(--border-default); border-radius: 5px;
|
|
211
|
-
background: transparent; color: var(--text-muted);
|
|
212
|
-
font-size: 13px; cursor: pointer; transition: all 0.12s;
|
|
213
|
-
}
|
|
214
|
-
.mermaid-zoom-scale-btn:hover { border-color: var(--brand-500); color: var(--brand-400); }
|
|
215
|
-
.mermaid-zoom-dl-btn {
|
|
216
|
-
height: 26px; padding: 0 12px;
|
|
217
|
-
border: 1px solid var(--brand-500); border-radius: 5px;
|
|
218
|
-
background: var(--brand-500); color: #fff;
|
|
219
|
-
font-size: 12px; cursor: pointer; transition: all 0.12s;
|
|
220
|
-
white-space: nowrap;
|
|
221
|
-
}
|
|
222
|
-
.mermaid-zoom-dl-btn:hover:not(:disabled) { background: var(--brand-600); border-color: var(--brand-600); }
|
|
223
|
-
.mermaid-zoom-dl-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
224
|
-
.mermaid-zoom-scale-label {
|
|
225
|
-
min-width: 44px; text-align: center;
|
|
226
|
-
font-size: 12px; font-family: 'Cascadia Code', monospace;
|
|
227
|
-
color: var(--text-secondary);
|
|
228
|
-
}
|
|
229
|
-
.mermaid-zoom-close {
|
|
230
|
-
margin-left: auto;
|
|
231
|
-
width: 26px; height: 26px; border-radius: 50%;
|
|
232
|
-
border: 1px solid var(--border-default); background: transparent;
|
|
233
|
-
color: var(--text-muted); font-size: 13px;
|
|
234
|
-
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
235
|
-
transition: background 0.15s, color 0.15s;
|
|
236
|
-
}
|
|
237
|
-
.mermaid-zoom-close:hover { background: var(--border-subtle); color: var(--text-primary); }
|
|
238
|
-
.mermaid-zoom-scroll {
|
|
239
|
-
flex: 1;
|
|
240
|
-
overflow: auto;
|
|
241
|
-
padding: 24px;
|
|
242
|
-
min-width: 0;
|
|
243
|
-
}
|
|
244
|
-
.mermaid-zoom-svg {
|
|
245
|
-
/* width 由 :style 动态设置;margin auto 实现水平居中 */
|
|
246
|
-
margin: 0 auto;
|
|
247
|
-
transition: width 0.15s ease;
|
|
248
|
-
}
|
|
249
|
-
.mermaid-zoom-svg :deep(svg) {
|
|
250
|
-
display: block;
|
|
251
|
-
width: 100%;
|
|
252
|
-
height: auto;
|
|
253
|
-
}
|
|
254
|
-
</style>
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<!-- 附件预览条:显示在输入框上方 -->
|
|
3
|
-
<div v-if="attachments.length > 0" class="attachment-bar">
|
|
4
|
-
<div
|
|
5
|
-
v-for="att in attachments"
|
|
6
|
-
:key="att.id"
|
|
7
|
-
class="att-chip"
|
|
8
|
-
:class="{ 'att-chip--visual': att.type === 'visual' }"
|
|
9
|
-
>
|
|
10
|
-
<!-- 图片缩略图 -->
|
|
11
|
-
<img
|
|
12
|
-
v-if="att.preview"
|
|
13
|
-
:src="att.preview"
|
|
14
|
-
class="att-thumb"
|
|
15
|
-
:alt="att.fileName"
|
|
16
|
-
/>
|
|
17
|
-
<!-- 文件图标 -->
|
|
18
|
-
<div v-else class="att-icon">
|
|
19
|
-
<component :is="fileIcon(att.ext)" :size="16" color="var(--brand-400)" />
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<!-- 文件信息 -->
|
|
23
|
-
<div class="att-info">
|
|
24
|
-
<span class="att-name">{{ truncateName(att.fileName) }}</span>
|
|
25
|
-
<span class="att-meta">
|
|
26
|
-
{{ att.type === 'visual' ? '视觉模型' : '文本模型' }}
|
|
27
|
-
· {{ att.fileSize }}
|
|
28
|
-
<template v-if="att.pageCount"> · {{ att.pageCount }}页</template>
|
|
29
|
-
</span>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<!-- 删除按钮 -->
|
|
33
|
-
<el-button
|
|
34
|
-
text
|
|
35
|
-
circle
|
|
36
|
-
size="small"
|
|
37
|
-
class="att-remove"
|
|
38
|
-
@click="$emit('remove', att.id)"
|
|
39
|
-
>
|
|
40
|
-
<X :size="13" />
|
|
41
|
-
</el-button>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
</template>
|
|
45
|
-
|
|
46
|
-
<script setup>
|
|
47
|
-
import { X, FileText, FileCode, FileSpreadsheet, Image, File } from 'lucide-vue-next'
|
|
48
|
-
|
|
49
|
-
defineProps({
|
|
50
|
-
attachments: { type: Array, default: () => [] },
|
|
51
|
-
})
|
|
52
|
-
defineEmits(['remove'])
|
|
53
|
-
|
|
54
|
-
function truncateName(name) {
|
|
55
|
-
return name.length > 20 ? name.slice(0, 17) + '...' : name
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function fileIcon(ext) {
|
|
59
|
-
const codeExts = new Set(['js','ts','jsx','tsx','vue','html','css','py','go','rs','java','sh','json','sql'])
|
|
60
|
-
const sheetExts = new Set(['xlsx','xls','csv'])
|
|
61
|
-
const imageExts = new Set(['jpg','jpeg','png','webp','gif','svg'])
|
|
62
|
-
if (imageExts.has(ext)) return Image
|
|
63
|
-
if (codeExts.has(ext)) return FileCode
|
|
64
|
-
if (sheetExts.has(ext)) return FileSpreadsheet
|
|
65
|
-
if (ext === 'pdf' || ext === 'docx' || ext === 'txt' || ext === 'md') return FileText
|
|
66
|
-
return File
|
|
67
|
-
}
|
|
68
|
-
</script>
|
|
69
|
-
|
|
70
|
-
<style scoped>
|
|
71
|
-
.attachment-bar {
|
|
72
|
-
display: flex;
|
|
73
|
-
flex-wrap: wrap;
|
|
74
|
-
gap: 8px;
|
|
75
|
-
padding: 10px 14px 6px;
|
|
76
|
-
border-bottom: 1px solid var(--border-subtle);
|
|
77
|
-
background: var(--bg-secondary);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.att-chip {
|
|
81
|
-
display: flex;
|
|
82
|
-
align-items: center;
|
|
83
|
-
gap: 8px;
|
|
84
|
-
padding: 6px 8px 6px 6px;
|
|
85
|
-
border-radius: 10px;
|
|
86
|
-
border: 1px solid var(--border-default);
|
|
87
|
-
background: var(--bg-tertiary);
|
|
88
|
-
max-width: 240px;
|
|
89
|
-
transition: border-color 0.15s;
|
|
90
|
-
}
|
|
91
|
-
.att-chip:hover { border-color: rgba(26,111,196,0.4); }
|
|
92
|
-
.att-chip--visual { border-color: rgba(26,111,196,0.3); background: var(--tag-bg); }
|
|
93
|
-
|
|
94
|
-
.att-thumb {
|
|
95
|
-
width: 36px;
|
|
96
|
-
height: 36px;
|
|
97
|
-
object-fit: cover;
|
|
98
|
-
border-radius: 6px;
|
|
99
|
-
flex-shrink: 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.att-icon {
|
|
103
|
-
width: 36px;
|
|
104
|
-
height: 36px;
|
|
105
|
-
border-radius: 6px;
|
|
106
|
-
background: var(--bg-secondary);
|
|
107
|
-
border: 1px solid var(--border-subtle);
|
|
108
|
-
display: flex;
|
|
109
|
-
align-items: center;
|
|
110
|
-
justify-content: center;
|
|
111
|
-
flex-shrink: 0;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.att-info {
|
|
115
|
-
display: flex;
|
|
116
|
-
flex-direction: column;
|
|
117
|
-
gap: 2px;
|
|
118
|
-
min-width: 0;
|
|
119
|
-
flex: 1;
|
|
120
|
-
}
|
|
121
|
-
.att-name {
|
|
122
|
-
font-size: 12px;
|
|
123
|
-
font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
|
124
|
-
color: var(--text-primary);
|
|
125
|
-
white-space: nowrap;
|
|
126
|
-
overflow: hidden;
|
|
127
|
-
text-overflow: ellipsis;
|
|
128
|
-
}
|
|
129
|
-
.att-meta {
|
|
130
|
-
font-size: 10px;
|
|
131
|
-
font-family: 'Cascadia Code', 'Consolas', monospace;
|
|
132
|
-
color: var(--brand-400);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.att-remove {
|
|
136
|
-
flex-shrink: 0;
|
|
137
|
-
color: var(--text-muted) !important;
|
|
138
|
-
opacity: 0.6;
|
|
139
|
-
transition: opacity 0.15s !important;
|
|
140
|
-
}
|
|
141
|
-
.att-remove:hover { opacity: 1 !important; color: #f87171 !important; }
|
|
142
|
-
</style>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* features/upload
|
|
3
|
-
*
|
|
4
|
-
* core — 纯能力:文件处理、类型判断、base64 转换
|
|
5
|
-
* UI — FileAttachment.vue 附件预览组件
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// ── core ──────────────────────────────────────────────────────────
|
|
9
|
-
export {
|
|
10
|
-
processFile,
|
|
11
|
-
buildMessageContent,
|
|
12
|
-
ACCEPT_TYPES,
|
|
13
|
-
FILE_TYPE_HINT,
|
|
14
|
-
} from './useFileHandler.js'
|
|
15
|
-
|
|
16
|
-
// ── UI ────────────────────────────────────────────────────────────
|
|
17
|
-
export const FileAttachment = () => import('./FileAttachment.vue')
|