@huyooo/ai-chat-frontend-vue 0.2.13 → 0.2.14

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.
Files changed (142) hide show
  1. package/README.md +85 -77
  2. package/dist/_baseUniq-Berj5otS.js +477 -0
  3. package/dist/arc-D4b-De5i.js +83 -0
  4. package/dist/architecture-U656AL7Q-B1R4o7wt.js +5 -0
  5. package/dist/architectureDiagram-VXUJARFQ-0H1LzSrf.js +4668 -0
  6. package/dist/blockDiagram-VD42YOAC-Dd6DMjLf.js +2265 -0
  7. package/dist/c4Diagram-YG6GDRKO-c6mpIonb.js +1581 -0
  8. package/dist/channel-CQSBI1fo.js +5 -0
  9. package/dist/chunk-4BX2VUAB-CfWC7lTG.js +9 -0
  10. package/dist/chunk-55IACEB6-joM8m2Zv.js +8 -0
  11. package/dist/chunk-B4BG7PRW-DN94Y3mS.js +1376 -0
  12. package/dist/chunk-DI55MBZ5-CPRGoFPG.js +1382 -0
  13. package/dist/chunk-FMBD7UC4-dUKl2TcN.js +19 -0
  14. package/dist/chunk-QN33PNHL-o3WmqquO.js +20 -0
  15. package/dist/chunk-QZHKN3VN-Btf62lRF.js +15 -0
  16. package/dist/chunk-TZMSLE5B-QCPpnm8Y.js +64 -0
  17. package/dist/classDiagram-2ON5EDUG-ERF9RE9K.js +16 -0
  18. package/dist/classDiagram-v2-WZHVMYZB-ERF9RE9K.js +16 -0
  19. package/dist/clone-B_B1yyxj.js +8 -0
  20. package/dist/components/ChatPanel.vue.d.ts +11 -7
  21. package/dist/components/ChatPanel.vue.d.ts.map +1 -1
  22. package/dist/components/common/SettingsPanel.vue.d.ts +10 -1
  23. package/dist/components/common/SettingsPanel.vue.d.ts.map +1 -1
  24. package/dist/components/common/ToggleSwitch.vue.d.ts.map +1 -1
  25. package/dist/components/input/ChatInput.vue.d.ts +4 -1
  26. package/dist/components/input/ChatInput.vue.d.ts.map +1 -1
  27. package/dist/components/input/DropdownSelector.vue.d.ts +10 -0
  28. package/dist/components/input/DropdownSelector.vue.d.ts.map +1 -1
  29. package/dist/components/message/MessageBubble.vue.d.ts +2 -2
  30. package/dist/components/message/MessageBubble.vue.d.ts.map +1 -1
  31. package/dist/components/message/PartsRenderer.vue.d.ts +6 -0
  32. package/dist/components/message/PartsRenderer.vue.d.ts.map +1 -1
  33. package/dist/components/message/parts/CollapsibleCard.vue.d.ts.map +1 -1
  34. package/dist/components/message/parts/ImagePart.vue.d.ts.map +1 -1
  35. package/dist/components/message/parts/SearchPart.vue.d.ts.map +1 -1
  36. package/dist/components/message/parts/TextPart.vue.d.ts +3 -1
  37. package/dist/components/message/parts/TextPart.vue.d.ts.map +1 -1
  38. package/dist/components/message/parts/ToolCallPart.vue.d.ts +9 -4
  39. package/dist/components/message/parts/ToolCallPart.vue.d.ts.map +1 -1
  40. package/dist/components/message/parts/index.d.ts +5 -1
  41. package/dist/components/message/parts/index.d.ts.map +1 -1
  42. package/dist/components/message/parts/visual-predicate.d.ts +7 -0
  43. package/dist/components/message/parts/visual-predicate.d.ts.map +1 -0
  44. package/dist/components/message/parts/visual-render.d.ts +2 -0
  45. package/dist/components/message/parts/visual-render.d.ts.map +1 -0
  46. package/dist/components/message/parts/visual.d.ts +3 -0
  47. package/dist/components/message/parts/visual.d.ts.map +1 -0
  48. package/dist/composables/useChat.d.ts +34 -10
  49. package/dist/composables/useChat.d.ts.map +1 -1
  50. package/dist/composables/useImageUpload.d.ts +7 -0
  51. package/dist/composables/useImageUpload.d.ts.map +1 -1
  52. package/dist/cose-bilkent-S5V4N54A-C-CkEnIH.js +2608 -0
  53. package/dist/cytoscape.esm-CjI2IsL8.js +18735 -0
  54. package/dist/dagre-6UL2VRFP-BbmZysl9.js +446 -0
  55. package/dist/defaultLocale-D7EN2tov.js +171 -0
  56. package/dist/diagram-PSM6KHXK-CMnope5e.js +533 -0
  57. package/dist/diagram-QEK2KX5R-Bc87skpo.js +221 -0
  58. package/dist/diagram-S2PKOQOG-BtkzC5-o.js +143 -0
  59. package/dist/erDiagram-Q2GNP2WA-CwQLdjh9.js +842 -0
  60. package/dist/flowDiagram-NV44I4VS-8ehMD0M4.js +1627 -0
  61. package/dist/ganttDiagram-JELNMOA3-dLX8Pbvy.js +2670 -0
  62. package/dist/gitGraph-F6HP7TQM-BBk5qfnx.js +5 -0
  63. package/dist/gitGraphDiagram-NY62KEGX-DG_fvWrq.js +712 -0
  64. package/dist/graph-B7BnGhuR.js +381 -0
  65. package/dist/index-Cr3tTH72.js +83883 -0
  66. package/dist/index.d.ts +7 -7
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +19 -56433
  69. package/dist/info-NVLQJR56-eZr6nVr1.js +5 -0
  70. package/dist/infoDiagram-WHAUD3N6-BcVYTft_.js +24 -0
  71. package/dist/init-DjUOC4st.js +16 -0
  72. package/dist/journeyDiagram-XKPGCS4Q-D4C72gYB.js +834 -0
  73. package/dist/kanban-definition-3W4ZIXB7-BuSjAlm6.js +721 -0
  74. package/dist/layout-LjYblgUu.js +1441 -0
  75. package/dist/linear-DtPqj295.js +259 -0
  76. package/dist/mermaid-parser.core-DdOZxjrw.js +15189 -0
  77. package/dist/min-DYbgfxu0.js +38 -0
  78. package/dist/mindmap-definition-VGOIOE7T-BZxsr4M_.js +787 -0
  79. package/dist/ordinal-DfAQgscy.js +61 -0
  80. package/dist/packet-BFZMPI3H-XfNBjRzf.js +5 -0
  81. package/dist/pie-7BOR55EZ-pJDdOv19.js +5 -0
  82. package/dist/pieDiagram-ADFJNKIX-DDwfWBNn.js +161 -0
  83. package/dist/quadrantDiagram-AYHSOK5B-Ddpxuto0.js +1024 -0
  84. package/dist/radar-NHE76QYJ-jE07d-4Z.js +5 -0
  85. package/dist/requirementDiagram-UZGBJVZJ-B_TLWQ-N.js +852 -0
  86. package/dist/sankeyDiagram-TZEHDZUN-C1TWrUpG.js +810 -0
  87. package/dist/sequenceDiagram-WL72ISMW-B8pqb5YX.js +2518 -0
  88. package/dist/stateDiagram-FKZM4ZOC-DwNT1ej6.js +263 -0
  89. package/dist/stateDiagram-v2-4FDKWEC3-CZQUitzd.js +16 -0
  90. package/dist/style.css +1 -1
  91. package/dist/timeline-definition-IT6M3QCI-BGdS1wvf.js +799 -0
  92. package/dist/treemap-KMMF4GRG-B_eEU0Q4.js +5 -0
  93. package/dist/types/index.d.ts +36 -37
  94. package/dist/types/index.d.ts.map +1 -1
  95. package/dist/xychartDiagram-PRI3JC2R-DtwXQqN4.js +1340 -0
  96. package/package.json +3 -3
  97. package/src/components/ChatPanel.vue +75 -13
  98. package/src/components/common/SettingsPanel.vue +169 -0
  99. package/src/components/input/ChatInput.vue +49 -34
  100. package/src/components/input/DropdownSelector.vue +175 -18
  101. package/src/components/message/MessageBubble.vue +61 -40
  102. package/src/components/message/PartsRenderer.vue +106 -27
  103. package/src/components/message/parts/CollapsibleCard.vue +8 -3
  104. package/src/components/message/parts/ImagePart.vue +0 -1
  105. package/src/components/message/parts/SearchPart.vue +2 -0
  106. package/src/components/message/parts/TextPart.vue +753 -11
  107. package/src/components/message/parts/ToolCallPart.vue +315 -246
  108. package/src/components/message/parts/index.ts +5 -1
  109. package/src/components/message/parts/visual-predicate.ts +43 -0
  110. package/src/components/message/parts/visual-render.ts +19 -0
  111. package/src/components/message/parts/visual.ts +12 -0
  112. package/src/composables/useChat.ts +560 -92
  113. package/src/composables/useImageUpload.ts +115 -11
  114. package/src/index.ts +6 -8
  115. package/src/styles.css +124 -0
  116. package/src/types/index.ts +52 -44
  117. package/dist/components/message/blocks/CodeBlock.vue.d.ts +0 -11
  118. package/dist/components/message/blocks/CodeBlock.vue.d.ts.map +0 -1
  119. package/dist/components/message/blocks/TextBlock.vue.d.ts +0 -7
  120. package/dist/components/message/blocks/TextBlock.vue.d.ts.map +0 -1
  121. package/dist/components/message/blocks/index.d.ts +0 -6
  122. package/dist/components/message/blocks/index.d.ts.map +0 -1
  123. package/dist/components/message/parts/ToolResultPart.vue.d.ts +0 -14
  124. package/dist/components/message/parts/ToolResultPart.vue.d.ts.map +0 -1
  125. package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts +0 -4
  126. package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts.map +0 -1
  127. package/dist/components/message/tool-results/SearchResults.vue.d.ts +0 -4
  128. package/dist/components/message/tool-results/SearchResults.vue.d.ts.map +0 -1
  129. package/dist/components/message/tool-results/WeatherCard.vue.d.ts +0 -4
  130. package/dist/components/message/tool-results/WeatherCard.vue.d.ts.map +0 -1
  131. package/dist/components/message/tool-results/index.d.ts +0 -7
  132. package/dist/components/message/tool-results/index.d.ts.map +0 -1
  133. package/src/components/message/ContentRenderer.vue +0 -61
  134. package/src/components/message/ToolResultRenderer.vue +0 -27
  135. package/src/components/message/blocks/CodeBlock.vue +0 -113
  136. package/src/components/message/blocks/TextBlock.vue +0 -21
  137. package/src/components/message/blocks/index.ts +0 -6
  138. package/src/components/message/parts/ToolResultPart.vue +0 -78
  139. package/src/components/message/tool-results/DefaultToolResult.vue +0 -45
  140. package/src/components/message/tool-results/SearchResults.vue +0 -133
  141. package/src/components/message/tool-results/WeatherCard.vue +0 -139
  142. package/src/components/message/tool-results/index.ts +0 -7
@@ -1,28 +1,770 @@
1
1
  <template>
2
- <div
3
- v-if="text"
4
- class="text-part markdown-content"
5
- v-html="html"
6
- />
2
+ <div v-if="text" ref="containerRef" class="text-part">
3
+ <!-- 渲染解析后的内容块 -->
4
+ <template v-for="block in blocks" :key="block.id">
5
+ <!-- 文本块:使用 markdown 渲染 -->
6
+ <div v-if="block.type === 'text'" class="text-block" v-html="renderMarkdown(block.content)"></div>
7
+ <!-- 代码块 -->
8
+ <div v-else-if="block.type === 'code'" class="code-block-wrapper">
9
+ <div class="code-header">
10
+ <span class="code-language">{{ block.language || 'plaintext' }}</span>
11
+ <div class="code-actions">
12
+ <button
13
+ v-if="shouldShowVisualToggle(block.language, block.content)"
14
+ class="action-btn"
15
+ type="button"
16
+ :title="visualShowMap[block.id] ? '切换为代码视图' : '切换为可视化视图'"
17
+ @click="toggleVisualView(block.id, block.language)"
18
+ >
19
+ <!-- 切换图标 -->
20
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
21
+ <polyline points="17 1 21 5 17 9"></polyline>
22
+ <path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
23
+ <polyline points="7 23 3 19 7 15"></polyline>
24
+ <path d="M21 13v2a4 4 0 0 1-4 4H3"></path>
25
+ </svg>
26
+ <span>切换视图</span>
27
+ </button>
28
+ <button
29
+ class="action-btn"
30
+ @click="copyCode(block.content, block.id)"
31
+ :title="copiedId === block.id ? '已复制' : '复制代码'"
32
+ >
33
+ <svg v-if="copiedId !== block.id" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
34
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
35
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
36
+ </svg>
37
+ <svg v-else width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
38
+ <polyline points="20 6 9 17 4 12"></polyline>
39
+ </svg>
40
+ <span>{{ copiedId === block.id ? '已复制' : '复制' }}</span>
41
+ </button>
42
+ </div>
43
+ </div>
44
+ <!-- Mermaid: 默认显示代码,点击"显示图表"后才渲染 -->
45
+ <div
46
+ v-if="isMermaidBlockLanguage(block.language) && visualShowMap[block.id]"
47
+ class="mermaid-placeholder"
48
+ :data-mermaid-code-b64="encodeMermaidCodeToBase64Url(block.content)"
49
+ >
50
+ <div class="mermaid-loading">加载图表中...</div>
51
+ </div>
52
+ <!-- LaTeX: 闭合后自动切换到渲染视图 -->
53
+ <div
54
+ v-if="isLatexBlockLanguage(block.language) && visualShowMap[block.id]"
55
+ class="latex-rendered"
56
+ v-html="renderFencedLatexToHtml(block.content)"
57
+ ></div>
58
+ <pre
59
+ v-if="(!(isMermaidBlockLanguage(block.language) || isLatexBlockLanguage(block.language))) || !visualShowMap[block.id] || (isLatexBlockLanguage(block.language) && !canVisualizeLatex(block.content))"
60
+ class="code-block"
61
+ ><code :class="block.language ? `language-${block.language}` : ''" v-html="highlightCode(block.content, block.language)"></code></pre>
62
+ </div>
63
+ </template>
64
+ </div>
7
65
  </template>
8
66
 
9
67
  <script setup lang="ts">
10
- import { computed } from 'vue'
11
- import { renderMarkdown } from '@huyooo/ai-chat-shared'
68
+ import { ref, watch, nextTick } from 'vue'
69
+ import {
70
+ createStreamParseState,
71
+ finishStreamParse,
72
+ parseContentStream,
73
+ highlightCode,
74
+ renderMarkdown,
75
+ initMermaid,
76
+ renderMermaidDiagrams,
77
+ encodeMermaidCodeToBase64Url,
78
+ type ContentBlock,
79
+ type StreamParseState,
80
+ } from '@huyooo/ai-chat-shared'
81
+ import {
82
+ canVisualizeLatex,
83
+ isLatexLanguage,
84
+ isMermaidLanguage,
85
+ renderFencedLatexToHtml,
86
+ shouldShowVisualToggle,
87
+ } from './visual'
88
+ // KaTeX CSS for LaTeX rendering
89
+ import 'katex/dist/katex.min.css'
90
+
91
+ // 初始化 Mermaid
92
+ initMermaid()
12
93
 
13
94
  const props = defineProps<{
14
95
  text: string
15
96
  }>()
16
97
 
17
- const html = computed(() => renderMarkdown(props.text))
98
+ const containerRef = ref<HTMLDivElement | null>(null)
99
+ const streamState = ref<StreamParseState>(createStreamParseState())
100
+ const lastText = ref('')
101
+ const blocks = ref<ContentBlock[]>([])
102
+ // 记录哪些可视化 block 要显示“可视化视图”(mermaid / latex),默认 undefined/false = 显示代码
103
+ const visualShowMap = ref<Record<string, boolean>>({})
104
+ const autoSwitchedVisual = ref<Set<string>>(new Set())
105
+
106
+ // 复制状态
107
+ const copiedId = ref<string | null>(null)
108
+
109
+ // 复制代码
110
+ async function copyCode(content: string, id: string) {
111
+ try {
112
+ await navigator.clipboard.writeText(content)
113
+ copiedId.value = id
114
+ setTimeout(() => {
115
+ copiedId.value = null
116
+ }, 2000)
117
+ } catch (err) {
118
+ console.error('复制失败:', err)
119
+ }
120
+ }
121
+
122
+ function isMermaidBlockLanguage(lang?: string) {
123
+ return isMermaidLanguage(lang)
124
+ }
125
+
126
+ function isLatexBlockLanguage(lang?: string) {
127
+ return isLatexLanguage(lang)
128
+ }
129
+
130
+ async function toggleVisualView(id: string, lang?: string) {
131
+ const willShowVisual = !visualShowMap.value[id]
132
+ visualShowMap.value = {
133
+ ...visualShowMap.value,
134
+ [id]: willShowVisual,
135
+ }
136
+
137
+ // Mermaid 切换到可视化时,需要在 DOM 更新后触发渲染
138
+ const lower = (lang || '').toLowerCase()
139
+ if (willShowVisual && lower === 'mermaid') {
140
+ await nextTick()
141
+ if (containerRef.value) renderMermaidDiagrams(containerRef.value)
142
+ }
143
+ }
144
+
145
+ function updateBlocksFromStream(newText: string) {
146
+ const prev = lastText.value
147
+ const current = newText || ''
148
+
149
+ if (prev && current.startsWith(prev)) {
150
+ const delta = current.slice(prev.length)
151
+ if (delta) streamState.value = parseContentStream(delta, streamState.value)
152
+ } else {
153
+ streamState.value = createStreamParseState()
154
+ streamState.value = parseContentStream(current, streamState.value)
155
+ }
156
+
157
+ lastText.value = current
158
+ blocks.value = finishStreamParse(streamState.value)
159
+
160
+ const inProgressVisualId =
161
+ streamState.value.inCodeBlock &&
162
+ (isMermaidBlockLanguage(streamState.value.codeLanguage) || isLatexBlockLanguage(streamState.value.codeLanguage))
163
+ ? streamState.value.codeBlockId
164
+ : null
165
+
166
+ const completedVisualIds: string[] = []
167
+ for (const b of streamState.value.blocks) {
168
+ if (b.type !== 'code') continue
169
+ if (!(isMermaidBlockLanguage(b.language) || isLatexBlockLanguage(b.language))) continue
170
+ if (inProgressVisualId && b.id === inProgressVisualId) continue
171
+ if (isLatexBlockLanguage(b.language) && !canVisualizeLatex(b.content)) continue
172
+ if (!autoSwitchedVisual.value.has(b.id)) completedVisualIds.push(b.id)
173
+ }
174
+
175
+ if (completedVisualIds.length > 0) {
176
+ for (const id of completedVisualIds) autoSwitchedVisual.value.add(id)
177
+ for (const id of completedVisualIds) {
178
+ if (visualShowMap.value[id] === undefined) visualShowMap.value[id] = true
179
+ }
180
+ window.setTimeout(() => {
181
+ if (containerRef.value) renderMermaidDiagrams(containerRef.value)
182
+ }, 0)
183
+ }
184
+ }
185
+
186
+ watch(
187
+ () => props.text,
188
+ (v) => updateBlocksFromStream(v),
189
+ { immediate: true }
190
+ )
18
191
  </script>
19
192
 
20
193
  <style scoped>
194
+ /* ============================================
195
+ Vercel + GitHub 混合风格 Markdown 主题
196
+ ============================================ */
197
+
21
198
  .text-part {
199
+ max-width: 100%;
200
+ }
201
+
202
+ /* 文本块基础样式 */
203
+ .text-block {
22
204
  font-size: 14px;
23
- line-height: 1.7;
24
- color: var(--chat-text, #ccc);
205
+ line-height: 1.75;
206
+ color: var(--chat-text, #e5e7eb);
25
207
  word-break: break-word;
26
- overflow-wrap: break-word;
208
+ letter-spacing: 0.01em;
209
+ }
210
+
211
+ /* 段落 */
212
+ .text-block :deep(p) {
213
+ margin: 0.75em 0;
214
+ }
215
+
216
+ .text-block :deep(p:first-child) {
217
+ margin-top: 0;
218
+ }
219
+
220
+ .text-block :deep(p:last-child) {
221
+ margin-bottom: 0;
222
+ }
223
+
224
+ /* 标题 - GitHub 风格层级 + Vercel 现代感 */
225
+ .text-block :deep(h1) {
226
+ font-size: 1.5em;
227
+ font-weight: 700;
228
+ margin: 1.5em 0 0.75em;
229
+ padding-bottom: 0.4em;
230
+ border-bottom: 1px solid var(--chat-border, rgba(255, 255, 255, 0.1));
231
+ color: var(--chat-text-strong, #fff);
232
+ letter-spacing: -0.02em;
233
+ }
234
+
235
+ .text-block :deep(h2) {
236
+ font-size: 1.3em;
237
+ font-weight: 600;
238
+ margin: 1.25em 0 0.6em;
239
+ padding-bottom: 0.3em;
240
+ border-bottom: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
241
+ color: var(--chat-text-strong, #fff);
242
+ letter-spacing: -0.01em;
243
+ }
244
+
245
+ .text-block :deep(h3) {
246
+ font-size: 1.15em;
247
+ font-weight: 600;
248
+ margin: 1em 0 0.5em;
249
+ color: var(--chat-text-strong, #fff);
250
+ }
251
+
252
+ .text-block :deep(h4),
253
+ .text-block :deep(h5),
254
+ .text-block :deep(h6) {
255
+ font-size: 1em;
256
+ font-weight: 600;
257
+ margin: 0.8em 0 0.4em;
258
+ color: var(--chat-text, #e5e7eb);
259
+ }
260
+
261
+ .text-block :deep(h1:first-child),
262
+ .text-block :deep(h2:first-child),
263
+ .text-block :deep(h3:first-child) {
264
+ margin-top: 0;
265
+ }
266
+
267
+ /* 列表 */
268
+ .text-block :deep(ul),
269
+ .text-block :deep(ol) {
270
+ margin: 0.75em 0;
271
+ padding-left: 1.5em;
272
+ }
273
+
274
+ .text-block :deep(li) {
275
+ margin: 0.4em 0;
276
+ line-height: 1.6;
277
+ }
278
+
279
+ .text-block :deep(li::marker) {
280
+ color: var(--chat-text-muted, #9ca3af);
281
+ }
282
+
283
+ /* 嵌套列表 */
284
+ .text-block :deep(li ul),
285
+ .text-block :deep(li ol) {
286
+ margin: 0.25em 0;
287
+ }
288
+
289
+ /* 任务列表 */
290
+ .text-block :deep(.markdown-task-item) {
291
+ list-style: none;
292
+ margin-left: -1.5em;
293
+ padding-left: 0;
294
+ }
295
+
296
+ .text-block :deep(.markdown-task-checkbox) {
297
+ display: inline-block;
298
+ width: 1.2em;
299
+ margin-right: 0.4em;
300
+ font-family: 'Monaco', 'Menlo', monospace;
301
+ font-size: 0.85em;
302
+ color: var(--chat-accent, #60a5fa);
303
+ }
304
+
305
+ /* 链接 - Vercel 风格下划线动画 */
306
+ .text-block :deep(a) {
307
+ color: var(--chat-accent, #60a5fa);
308
+ text-decoration: none;
309
+ background-image: linear-gradient(var(--chat-accent, #60a5fa), var(--chat-accent, #60a5fa));
310
+ background-size: 0% 1px;
311
+ background-position: 0 100%;
312
+ background-repeat: no-repeat;
313
+ transition: background-size 0.25s ease-out;
314
+ }
315
+
316
+ .text-block :deep(a:hover) {
317
+ background-size: 100% 1px;
318
+ }
319
+
320
+ /* 粗体和斜体 */
321
+ .text-block :deep(strong) {
322
+ font-weight: 600;
323
+ color: var(--chat-text-strong, #fff);
324
+ }
325
+
326
+ .text-block :deep(em) {
327
+ font-style: italic;
328
+ color: var(--chat-text, #e5e7eb);
329
+ }
330
+
331
+ /* 删除线 */
332
+ .text-block :deep(s),
333
+ .text-block :deep(del) {
334
+ text-decoration: line-through;
335
+ color: var(--chat-text-muted, #9ca3af);
336
+ }
337
+
338
+ /* 行内代码 - 突出显示 */
339
+ .text-block :deep(code) {
340
+ font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', 'Consolas', monospace;
341
+ font-size: 0.875em;
342
+ background: var(--chat-inline-code-bg, rgba(110, 118, 129, 0.25));
343
+ padding: 0.2em 0.45em;
344
+ border-radius: 6px;
345
+ color: var(--chat-inline-code-text, #f472b6);
346
+ font-weight: 500;
347
+ }
348
+
349
+ /* 引用块 - VitePress 风格 */
350
+ .text-block :deep(blockquote) {
351
+ margin: 1em 0;
352
+ padding: 0.75em 1em;
353
+ border-left: 4px solid var(--chat-accent, #60a5fa);
354
+ background: var(--chat-blockquote-bg, rgba(96, 165, 250, 0.08));
355
+ border-radius: 0 8px 8px 0;
356
+ color: var(--chat-text-muted, #d1d5db);
357
+ }
358
+
359
+ .text-block :deep(blockquote p) {
360
+ margin: 0.25em 0;
361
+ }
362
+
363
+ .text-block :deep(blockquote p:first-child) {
364
+ margin-top: 0;
365
+ }
366
+
367
+ .text-block :deep(blockquote p:last-child) {
368
+ margin-bottom: 0;
369
+ }
370
+
371
+ /* 分隔线 - 渐变效果 */
372
+ .text-block :deep(hr) {
373
+ border: none;
374
+ height: 1px;
375
+ margin: 1.5em 0;
376
+ background: linear-gradient(
377
+ 90deg,
378
+ transparent,
379
+ var(--chat-border, rgba(255, 255, 255, 0.15)) 20%,
380
+ var(--chat-border, rgba(255, 255, 255, 0.15)) 80%,
381
+ transparent
382
+ );
383
+ }
384
+
385
+ /* 表格 - GitHub 风格 + 现代感 */
386
+ .text-block :deep(.markdown-table-wrapper) {
387
+ margin: 1em 0;
388
+ overflow-x: auto;
389
+ border-radius: 8px;
390
+ border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.1));
391
+ scrollbar-gutter: stable;
392
+ }
393
+
394
+ .text-block :deep(.markdown-table) {
395
+ min-width: 100%;
396
+ width: max-content;
397
+ border-collapse: collapse;
398
+ font-size: 0.9em;
399
+ }
400
+
401
+ .text-block :deep(.markdown-table th) {
402
+ background: var(--chat-table-header-bg, rgba(255, 255, 255, 0.06));
403
+ font-weight: 600;
404
+ text-align: left;
405
+ padding: 0.75em 1em;
406
+ border-bottom: 1px solid var(--chat-border, rgba(255, 255, 255, 0.1));
407
+ color: var(--chat-text-strong, #fff);
408
+ }
409
+
410
+ .text-block :deep(.markdown-table td) {
411
+ padding: 0.65em 1em;
412
+ border-bottom: 1px solid var(--chat-border, rgba(255, 255, 255, 0.06));
413
+ }
414
+
415
+ .text-block :deep(.markdown-table tr:last-child td) {
416
+ border-bottom: none;
417
+ }
418
+
419
+ .text-block :deep(.markdown-table tbody tr:hover) {
420
+ background: var(--chat-table-row-hover-bg, rgba(255, 255, 255, 0.05));
421
+ }
422
+
423
+ /* 图片 */
424
+ .text-block :deep(img) {
425
+ max-width: 100%;
426
+ height: auto;
427
+ border-radius: 8px;
428
+ margin: 0.75em 0;
429
+ }
430
+
431
+ /* ============================================
432
+ 代码块样式 - 简洁边框风格
433
+ ============================================ */
434
+
435
+ .code-block-wrapper {
436
+ margin: 1em 0;
437
+ border-radius: 8px;
438
+ overflow: hidden;
439
+ border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.15));
440
+ background: transparent;
441
+ }
442
+
443
+ .code-header {
444
+ display: flex;
445
+ justify-content: space-between;
446
+ align-items: center;
447
+ padding: 8px 14px;
448
+ border-bottom: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
449
+ }
450
+
451
+ .code-language {
452
+ font-size: 12px;
453
+ font-weight: 500;
454
+ color: var(--chat-text-muted, #8b949e);
455
+ text-transform: lowercase;
456
+ letter-spacing: 0.03em;
457
+ }
458
+
459
+ /* 按钮组容器 */
460
+ .code-actions {
461
+ display: flex;
462
+ align-items: center;
463
+ gap: 4px;
464
+ }
465
+
466
+ /* 统一的操作按钮样式 */
467
+ .action-btn {
468
+ display: flex;
469
+ align-items: center;
470
+ gap: 5px;
471
+ padding: 4px 8px;
472
+ font-size: 12px;
473
+ font-weight: 500;
474
+ color: var(--chat-text-muted, #8b949e);
475
+ background: transparent;
476
+ border: 1px solid transparent;
477
+ border-radius: 5px;
478
+ cursor: pointer;
479
+ transition: all 0.15s ease;
480
+ }
481
+
482
+ .action-btn:hover {
483
+ color: var(--chat-text, #e5e7eb);
484
+ background: var(--chat-hover-bg, rgba(255, 255, 255, 0.08));
485
+ border-color: var(--chat-border, rgba(255, 255, 255, 0.1));
486
+ }
487
+
488
+ .action-btn:active {
489
+ transform: scale(0.97);
490
+ }
491
+
492
+ .action-btn svg {
493
+ flex-shrink: 0;
494
+ width: 14px;
495
+ height: 14px;
496
+ }
497
+
498
+ .code-block {
499
+ margin: 0;
500
+ padding: 14px 18px;
501
+ overflow-x: auto;
502
+ font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', 'Consolas', monospace;
503
+ font-size: 13px;
504
+ line-height: 1.6;
505
+ color: var(--chat-code-text, #e6edf3);
506
+ background: transparent;
507
+ border: none;
508
+ border-radius: 0;
509
+ -webkit-font-smoothing: antialiased;
510
+ }
511
+
512
+ .code-block code {
513
+ display: block;
514
+ white-space: pre;
515
+ tab-size: 2;
516
+ }
517
+
518
+ /* 代码块滚动条 */
519
+ .code-block::-webkit-scrollbar {
520
+ height: 6px;
521
+ }
522
+
523
+ .code-block::-webkit-scrollbar-track {
524
+ background: transparent;
525
+ }
526
+
527
+ .code-block::-webkit-scrollbar-thumb {
528
+ background: var(--chat-scrollbar, rgba(255, 255, 255, 0.2));
529
+ border-radius: 3px;
530
+ }
531
+
532
+ .code-block::-webkit-scrollbar-thumb:hover {
533
+ background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
534
+ }
535
+
536
+ /* ============================================
537
+ 代码高亮主题 - 跟随主题变化
538
+ 使用 CSS 变量,支持浅色/深色主题
539
+ ============================================ */
540
+
541
+ /* 关键字: if, const, function, return, import, export 等 */
542
+ .code-block :deep(.hljs-keyword),
543
+ .code-block :deep(.hljs-selector-tag) {
544
+ color: var(--chat-hljs-keyword, #ff7b72);
545
+ }
546
+
547
+ /* 内置: console, document, window, Array, Object 等 */
548
+ .code-block :deep(.hljs-built_in) {
549
+ color: var(--chat-hljs-built-in, #ffa657);
550
+ }
551
+
552
+ /* 类型: string, number, boolean, interface, type 等 */
553
+ .code-block :deep(.hljs-type),
554
+ .code-block :deep(.hljs-class) {
555
+ color: var(--chat-hljs-type, #ffa657);
556
+ }
557
+
558
+ /* 函数名 */
559
+ .code-block :deep(.hljs-title),
560
+ .code-block :deep(.hljs-title.function_),
561
+ .code-block :deep(.hljs-function) {
562
+ color: var(--chat-hljs-function, #d2a8ff);
563
+ }
564
+
565
+ /* 字符串 */
566
+ .code-block :deep(.hljs-string),
567
+ .code-block :deep(.hljs-template-variable) {
568
+ color: var(--chat-hljs-string, #a5d6ff);
569
+ }
570
+
571
+ /* 数字 */
572
+ .code-block :deep(.hljs-number) {
573
+ color: var(--chat-hljs-number, #79c0ff);
574
+ }
575
+
576
+ /* 布尔值、null、undefined */
577
+ .code-block :deep(.hljs-literal),
578
+ .code-block :deep(.hljs-null) {
579
+ color: var(--chat-hljs-literal, #79c0ff);
580
+ }
581
+
582
+ /* 注释 */
583
+ .code-block :deep(.hljs-comment),
584
+ .code-block :deep(.hljs-quote) {
585
+ color: var(--chat-hljs-comment, #8b949e);
586
+ font-style: italic;
587
+ }
588
+
589
+ /* 变量 */
590
+ .code-block :deep(.hljs-variable),
591
+ .code-block :deep(.hljs-template-tag) {
592
+ color: var(--chat-hljs-variable, #ffa657);
593
+ }
594
+
595
+ /* 属性 */
596
+ .code-block :deep(.hljs-attr),
597
+ .code-block :deep(.hljs-attribute) {
598
+ color: var(--chat-hljs-attr, #79c0ff);
599
+ }
600
+
601
+ /* 对象属性名 */
602
+ .code-block :deep(.hljs-property) {
603
+ color: var(--chat-hljs-property, #7ee787);
604
+ }
605
+
606
+ /* 操作符 */
607
+ .code-block :deep(.hljs-operator) {
608
+ color: var(--chat-hljs-operator, #ff7b72);
609
+ }
610
+
611
+ /* 标点符号 */
612
+ .code-block :deep(.hljs-punctuation) {
613
+ color: var(--chat-hljs-punctuation, #e6edf3);
614
+ }
615
+
616
+ /* 参数 */
617
+ .code-block :deep(.hljs-params) {
618
+ color: var(--chat-hljs-params, #e6edf3);
619
+ }
620
+
621
+ /* 正则表达式 */
622
+ .code-block :deep(.hljs-regexp) {
623
+ color: var(--chat-hljs-regexp, #7ee787);
624
+ }
625
+
626
+ /* 选择器 (CSS) */
627
+ .code-block :deep(.hljs-selector-class),
628
+ .code-block :deep(.hljs-selector-id) {
629
+ color: var(--chat-hljs-selector, #7ee787);
630
+ }
631
+
632
+ /* 标签 (HTML/XML) */
633
+ .code-block :deep(.hljs-tag) {
634
+ color: var(--chat-hljs-tag, #7ee787);
635
+ }
636
+
637
+ .code-block :deep(.hljs-name) {
638
+ color: var(--chat-hljs-name, #7ee787);
639
+ }
640
+
641
+ /* 删除 (diff) */
642
+ .code-block :deep(.hljs-deletion) {
643
+ color: var(--chat-hljs-deletion, #ffa198);
644
+ background: var(--chat-hljs-deletion-bg, rgba(248, 81, 73, 0.15));
645
+ }
646
+
647
+ /* 添加 (diff) */
648
+ .code-block :deep(.hljs-addition) {
649
+ color: var(--chat-hljs-addition, #7ee787);
650
+ background: var(--chat-hljs-addition-bg, rgba(46, 160, 67, 0.15));
651
+ }
652
+
653
+ /* meta 信息 */
654
+ .code-block :deep(.hljs-meta) {
655
+ color: var(--chat-hljs-meta, #8b949e);
656
+ }
657
+
658
+ /* 链接 */
659
+ .code-block :deep(.hljs-link) {
660
+ color: var(--chat-hljs-link, #a5d6ff);
661
+ text-decoration: underline;
662
+ }
663
+
664
+ /* 符号 */
665
+ .code-block :deep(.hljs-symbol) {
666
+ color: var(--chat-hljs-symbol, #79c0ff);
667
+ }
668
+
669
+ /* 子字符串 (模板字符串中的 ${}) */
670
+ .code-block :deep(.hljs-subst) {
671
+ color: var(--chat-hljs-subst, #e6edf3);
672
+ }
673
+
674
+ /* section (markdown 标题等) */
675
+ .code-block :deep(.hljs-section) {
676
+ color: var(--chat-hljs-section, #79c0ff);
677
+ font-weight: bold;
678
+ }
679
+
680
+ /* bullet (markdown 列表) */
681
+ .code-block :deep(.hljs-bullet) {
682
+ color: var(--chat-hljs-bullet, #ffa657);
683
+ }
684
+
685
+ /* emphasis */
686
+ .code-block :deep(.hljs-emphasis) {
687
+ font-style: italic;
688
+ }
689
+
690
+ /* strong */
691
+ .code-block :deep(.hljs-strong) {
692
+ font-weight: bold;
693
+ }
694
+
695
+ /* ============================================
696
+ LaTeX 数学公式样式
697
+ ============================================ */
698
+
699
+ /* 块级公式 */
700
+ .text-block :deep(.latex-block) {
701
+ display: block;
702
+ margin: 1em 0;
703
+ padding: 0.75em 1em;
704
+ overflow-x: auto;
705
+ background: var(--chat-latex-bg, rgba(255, 255, 255, 0.02));
706
+ border-radius: 8px;
707
+ border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
708
+ }
709
+
710
+ .text-block :deep(.latex-block .katex-display) {
711
+ margin: 0;
712
+ overflow-x: auto;
713
+ overflow-y: hidden;
714
+ text-align: left;
715
+ }
716
+
717
+ .text-block :deep(.latex-block .katex) {
718
+ text-align: left;
719
+ }
720
+
721
+ /* 行内公式 */
722
+ .text-block :deep(.latex-inline) {
723
+ display: inline;
724
+ padding: 0 0.15em;
725
+ }
726
+
727
+ /* 公式错误提示 */
728
+ .text-block :deep(.latex-error) {
729
+ color: var(--chat-error, #ef4444);
730
+ background: rgba(239, 68, 68, 0.1);
731
+ padding: 0.2em 0.4em;
732
+ border-radius: 4px;
733
+ font-family: monospace;
734
+ font-size: 0.9em;
735
+ }
736
+
737
+ /* KaTeX 样式覆盖 - 适配暗色主题 */
738
+ .text-block :deep(.katex) {
739
+ font-size: 1.1em;
740
+ color: var(--chat-text, #e5e7eb);
741
+ }
742
+
743
+ /* KaTeX 分数线颜色 */
744
+ .text-block :deep(.katex .frac-line) {
745
+ background: var(--chat-text, #e5e7eb);
746
+ }
747
+
748
+ /* KaTeX 根号 */
749
+ .text-block :deep(.katex .sqrt > .sqrt-sign) {
750
+ color: var(--chat-text, #e5e7eb);
751
+ }
752
+
753
+ /* 滚动条样式 */
754
+ .text-block :deep(.latex-block::-webkit-scrollbar) {
755
+ height: 6px;
756
+ }
757
+
758
+ .text-block :deep(.latex-block::-webkit-scrollbar-track) {
759
+ background: transparent;
760
+ }
761
+
762
+ .text-block :deep(.latex-block::-webkit-scrollbar-thumb) {
763
+ background: var(--chat-scrollbar, rgba(255, 255, 255, 0.2));
764
+ border-radius: 3px;
765
+ }
766
+
767
+ .text-block :deep(.latex-block::-webkit-scrollbar-thumb:hover) {
768
+ background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
27
769
  }
28
770
  </style>