@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,253 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useArtifactStore.js v2
|
|
3
|
-
*
|
|
4
|
-
* 对标 Claude Artifacts 体验:
|
|
5
|
-
* - 每个代码块 = 一个 Artifact(有文件名、语言、内容)
|
|
6
|
-
* - 右侧面板顶部 tab 栏展示当前会话所有 artifact,可切换
|
|
7
|
-
* - 聊天气泡里只显示轻量文件卡片
|
|
8
|
-
* - 流式生成时实时更新右侧面板内容
|
|
9
|
-
*/
|
|
10
|
-
import { ref, computed, provide, inject, shallowRef } from 'vue'
|
|
11
|
-
|
|
12
|
-
const STORE_KEY = Symbol('artifact-store')
|
|
13
|
-
|
|
14
|
-
// ── 常量 ─────────────────────────────────────────────────────────
|
|
15
|
-
export const ARTIFACT_MIN_LINES = 6
|
|
16
|
-
|
|
17
|
-
export const ARTIFACT_LANGS = new Set([
|
|
18
|
-
'vue','html','jsx','tsx','mermaid','svg',
|
|
19
|
-
])
|
|
20
|
-
|
|
21
|
-
export const LANG_LABEL = {
|
|
22
|
-
vue:'Vue SFC', html:'HTML', jsx:'React JSX', tsx:'React TSX',
|
|
23
|
-
javascript:'JavaScript', js:'JavaScript', typescript:'TypeScript', ts:'TypeScript',
|
|
24
|
-
python:'Python', go:'Go', rust:'Rust', java:'Java', kotlin:'Kotlin', swift:'Swift',
|
|
25
|
-
css:'CSS', scss:'SCSS', sass:'SASS', less:'LESS',
|
|
26
|
-
sql:'SQL', graphql:'GraphQL',
|
|
27
|
-
bash:'Shell', sh:'Shell', shell:'Shell',
|
|
28
|
-
json:'JSON', yaml:'YAML', yml:'YAML', toml:'TOML', xml:'XML',
|
|
29
|
-
markdown:'Markdown', md:'Markdown',
|
|
30
|
-
mermaid:'Mermaid 图', svg:'SVG', math:'Math', latex:'LaTeX',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const LANG_EXT = {
|
|
34
|
-
vue:'vue', html:'html', jsx:'jsx', tsx:'tsx',
|
|
35
|
-
javascript:'js', js:'js', typescript:'ts', ts:'ts',
|
|
36
|
-
python:'py', go:'go', rust:'rs', java:'java', kotlin:'kt', swift:'swift',
|
|
37
|
-
css:'css', scss:'scss', sass:'sass', less:'less',
|
|
38
|
-
sql:'sql', graphql:'graphql', bash:'sh', sh:'sh', shell:'sh',
|
|
39
|
-
json:'json', yaml:'yaml', yml:'yaml', toml:'toml', xml:'xml',
|
|
40
|
-
markdown:'md', md:'md', mermaid:'mmd', svg:'svg', math:'tex', latex:'tex',
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ── 判断是否应成为 Artifact(兜底规则,优先级低于 AI 的 [artifact] 显式标记)──
|
|
44
|
-
// 仅当代码行数 >= 50 且语言有意义时自动路由到面板
|
|
45
|
-
export function shouldBeArtifact(lang, code) {
|
|
46
|
-
if (!code?.trim()) return false
|
|
47
|
-
if (!lang) return false
|
|
48
|
-
return code.split('\n').length >= 50
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ── 推断文件名 ────────────────────────────────────────────────────
|
|
52
|
-
export function inferFileName(lang, code, index) {
|
|
53
|
-
const ext = LANG_EXT[lang] || 'txt'
|
|
54
|
-
|
|
55
|
-
if (lang === 'vue') {
|
|
56
|
-
const m = code.match(/(?:name|defineOptions\(\{[^}]*name):\s*['"]([^'"]+)['"]/)
|
|
57
|
-
|| code.match(/\/\/\s*([A-Z][a-zA-Z0-9]+)\.vue/)
|
|
58
|
-
if (m?.[1]) return `${m[1]}.vue`
|
|
59
|
-
return `Component${index > 1 ? index : ''}.vue`
|
|
60
|
-
}
|
|
61
|
-
if (lang === 'html') {
|
|
62
|
-
const m = code.match(/<title>([^<]{1,40})<\/title>/)
|
|
63
|
-
if (m?.[1]) return `${m[1].trim().replace(/\s+/g,'-').toLowerCase()}.html`
|
|
64
|
-
return `index.html`
|
|
65
|
-
}
|
|
66
|
-
if (lang === 'jsx' || lang === 'tsx') {
|
|
67
|
-
const m = code.match(/(?:function|const|class)\s+([A-Z][a-zA-Z0-9]+)/)
|
|
68
|
-
if (m?.[1]) return `${m[1]}.${ext}`
|
|
69
|
-
return `App.${ext}`
|
|
70
|
-
}
|
|
71
|
-
if (lang === 'mermaid') {
|
|
72
|
-
const first = code.trim().split('\n')[0]?.trim().replace(/\s+.*/,'').toLowerCase()
|
|
73
|
-
return first ? `${first}.mmd` : `diagram.mmd`
|
|
74
|
-
}
|
|
75
|
-
if (lang === 'python') {
|
|
76
|
-
const m = code.match(/^#\s*(.+)/m) || code.match(/def\s+(\w+)/)
|
|
77
|
-
if (m?.[1]) return `${m[1].trim().replace(/\s+/g,'_').toLowerCase()}.py`
|
|
78
|
-
return `script.py`
|
|
79
|
-
}
|
|
80
|
-
if (lang === 'json') {
|
|
81
|
-
try {
|
|
82
|
-
const obj = JSON.parse(code)
|
|
83
|
-
if (obj.name) return `${obj.name}.json`
|
|
84
|
-
} catch {}
|
|
85
|
-
return `data.json`
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return `${lang === 'javascript' || lang === 'js' ? 'script' : 'code'}-${index}.${ext}`
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ── Store 工厂 ───────────────────────────────────────────────────
|
|
92
|
-
export function createArtifactStore() {
|
|
93
|
-
// 当前激活 artifact 的 code 独立 ref,专门用于外部精确订阅
|
|
94
|
-
// 因为 artifactsMap 是 shallowRef,item 对象内部属性变化无法被 computed/watch 自动追踪
|
|
95
|
-
const activeCode = ref('')
|
|
96
|
-
|
|
97
|
-
// 正在 streaming 的 artifact id 集合,用 ref 包装保证 ArtifactCard 能响应式追踪
|
|
98
|
-
const streamingIds = ref(new Set())
|
|
99
|
-
|
|
100
|
-
// 同步 activeCode 到当前激活 artifact 的 code
|
|
101
|
-
function syncActiveCode() {
|
|
102
|
-
const art = artifactsMap.value.get(activeId.value)
|
|
103
|
-
|| [...artifactsMap.value.values()].at(-1)
|
|
104
|
-
|| null
|
|
105
|
-
activeCode.value = art?.code ?? ''
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 所有 artifact,shallowRef 避免深层响应式
|
|
109
|
-
const artifactsMap = shallowRef(new Map())
|
|
110
|
-
|
|
111
|
-
// 当前激活的 artifact id
|
|
112
|
-
const activeId = ref(null)
|
|
113
|
-
|
|
114
|
-
// 面板是否可见(有任何 artifact 就显示)
|
|
115
|
-
const panelVisible = computed(() => artifactsMap.value.size > 0)
|
|
116
|
-
|
|
117
|
-
// 所有 artifact 列表(按注册顺序,用于 tab 栏)
|
|
118
|
-
const allArtifacts = computed(() => {
|
|
119
|
-
return [...artifactsMap.value.values()].sort((a, b) => a._seq - b._seq)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
// 当前激活的 artifact
|
|
123
|
-
const activeArtifact = computed(() =>
|
|
124
|
-
artifactsMap.value.get(activeId.value) || allArtifacts.value[allArtifacts.value.length - 1] || null
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
// 按 messageId 分组(MessageBubble 用)
|
|
128
|
-
const byMessage = computed(() => {
|
|
129
|
-
const map = new Map()
|
|
130
|
-
for (const art of allArtifacts.value) {
|
|
131
|
-
const list = map.get(art.messageId) || []
|
|
132
|
-
list.push(art)
|
|
133
|
-
map.set(art.messageId, list)
|
|
134
|
-
}
|
|
135
|
-
return map
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
let _seq = 0
|
|
139
|
-
|
|
140
|
-
function preRegister({ messageId, lang, index }) {
|
|
141
|
-
const id = `art-${messageId}-${index}`
|
|
142
|
-
if (artifactsMap.value.has(id)) return id
|
|
143
|
-
const label = LANG_LABEL[lang] || lang?.toUpperCase() || 'Code'
|
|
144
|
-
const ext = LANG_EXT[lang] || 'txt'
|
|
145
|
-
const fileName = `generating.${ext}`
|
|
146
|
-
const item = {
|
|
147
|
-
id, messageId, lang, code: '', fileName, label, lines: 0,
|
|
148
|
-
_seq: ++_seq, streaming: true,
|
|
149
|
-
}
|
|
150
|
-
const next = new Map(artifactsMap.value)
|
|
151
|
-
next.set(id, item)
|
|
152
|
-
artifactsMap.value = next
|
|
153
|
-
activeId.value = id
|
|
154
|
-
syncActiveCode()
|
|
155
|
-
return id
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function register({ messageId, lang, code, index }) {
|
|
159
|
-
const id = `art-${messageId}-${index}`
|
|
160
|
-
const label = LANG_LABEL[lang] || lang?.toUpperCase() || 'Code'
|
|
161
|
-
const lines = code.split('\n').length
|
|
162
|
-
const inferred = inferFileName(lang, code, index)
|
|
163
|
-
const fileName = (inferred && !inferred.startsWith('generating')) ? inferred : `code-${index}.${LANG_EXT[lang] || 'txt'}`
|
|
164
|
-
|
|
165
|
-
const existing = artifactsMap.value.get(id)
|
|
166
|
-
const next = new Map(artifactsMap.value)
|
|
167
|
-
// 始终替换为新对象,确保引用变化
|
|
168
|
-
next.set(id, existing
|
|
169
|
-
? { ...existing, code, lines, fileName: (inferred && !inferred.startsWith('generating')) ? inferred : existing.fileName }
|
|
170
|
-
: { id, messageId, lang, code, fileName, label, lines, _seq: ++_seq, streaming: false }
|
|
171
|
-
)
|
|
172
|
-
artifactsMap.value = next
|
|
173
|
-
activeId.value = id
|
|
174
|
-
syncActiveCode()
|
|
175
|
-
return id
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function updateCode(artifactId, code) {
|
|
179
|
-
const item = artifactsMap.value.get(artifactId)
|
|
180
|
-
if (!item) return
|
|
181
|
-
const inferred = inferFileName(item.lang, code, item._seq)
|
|
182
|
-
const next = new Map(artifactsMap.value)
|
|
183
|
-
// 替换为新对象,确保引用变化能触发 ArtifactCard 重渲染
|
|
184
|
-
next.set(artifactId, {
|
|
185
|
-
...item,
|
|
186
|
-
code,
|
|
187
|
-
lines: code.split('\n').length,
|
|
188
|
-
streaming: true,
|
|
189
|
-
fileName: (inferred && !inferred.startsWith('generating')) ? inferred : item.fileName,
|
|
190
|
-
})
|
|
191
|
-
artifactsMap.value = next
|
|
192
|
-
syncActiveCode()
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function markDone(artifactId) {
|
|
196
|
-
const item = artifactsMap.value.get(artifactId)
|
|
197
|
-
console.log('[markDone] item found:', !!item, 'current streaming:', item?.streaming)
|
|
198
|
-
if (!item) return
|
|
199
|
-
const next = new Map(artifactsMap.value)
|
|
200
|
-
const newItem = { ...item, streaming: false }
|
|
201
|
-
next.set(artifactId, newItem)
|
|
202
|
-
artifactsMap.value = next
|
|
203
|
-
console.log('[markDone] done, newItem.streaming:', newItem.streaming)
|
|
204
|
-
syncActiveCode()
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function open(id) {
|
|
208
|
-
activeId.value = id
|
|
209
|
-
syncActiveCode()
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function close() {
|
|
213
|
-
// 不关闭面板,只清空 activeId(面板由 panelVisible 控制,有 artifact 就显示)
|
|
214
|
-
activeId.value = null
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function clearSession() {
|
|
218
|
-
artifactsMap.value = new Map()
|
|
219
|
-
activeId.value = null
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function clearByMessage(messageId) {
|
|
223
|
-
const next = new Map(artifactsMap.value)
|
|
224
|
-
for (const [id, art] of next) {
|
|
225
|
-
if (art.messageId === messageId) next.delete(id)
|
|
226
|
-
}
|
|
227
|
-
if (activeId.value && !next.has(activeId.value)) {
|
|
228
|
-
activeId.value = next.size > 0 ? [...next.values()].at(-1).id : null
|
|
229
|
-
}
|
|
230
|
-
artifactsMap.value = next
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return {
|
|
234
|
-
artifactsMap, activeId, activeArtifact, activeCode,
|
|
235
|
-
allArtifacts, panelVisible, byMessage,
|
|
236
|
-
preRegister, register, updateCode, markDone,
|
|
237
|
-
open, close, clearSession, clearByMessage,
|
|
238
|
-
LANG_LABEL, LANG_EXT,
|
|
239
|
-
shouldBeArtifact,
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function provideArtifactStore() {
|
|
244
|
-
const store = createArtifactStore()
|
|
245
|
-
provide(STORE_KEY, store)
|
|
246
|
-
return store
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export function useArtifactStore() {
|
|
250
|
-
const store = inject(STORE_KEY)
|
|
251
|
-
if (!store) throw new Error('useArtifactStore: missing provideArtifactStore()')
|
|
252
|
-
return store
|
|
253
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useAuth.js
|
|
3
|
-
* 全局用户认证状态管理(provide/inject 模式)
|
|
4
|
-
* 登录态持久化到 localStorage,页面刷新后自动恢复
|
|
5
|
-
*/
|
|
6
|
-
import { ref, computed, provide, inject } from 'vue'
|
|
7
|
-
import { login, logout } from '../services/authApi.js'
|
|
8
|
-
|
|
9
|
-
const AUTH_KEY = Symbol('auth')
|
|
10
|
-
const LS_USER_KEY = 'citic_auth_user'
|
|
11
|
-
const LS_TOKEN_KEY = 'citic_auth_token'
|
|
12
|
-
|
|
13
|
-
// ── localStorage 工具 ─────────────────────────────────────────────
|
|
14
|
-
function lsSave(user, token) {
|
|
15
|
-
try {
|
|
16
|
-
localStorage.setItem(LS_USER_KEY, JSON.stringify(user))
|
|
17
|
-
localStorage.setItem(LS_TOKEN_KEY, token)
|
|
18
|
-
} catch { /* 隐私模式下 localStorage 可能不可用,静默忽略 */ }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function lsClear() {
|
|
22
|
-
try {
|
|
23
|
-
localStorage.removeItem(LS_USER_KEY)
|
|
24
|
-
localStorage.removeItem(LS_TOKEN_KEY)
|
|
25
|
-
} catch {}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function lsRestore() {
|
|
29
|
-
try {
|
|
30
|
-
const rawUser = localStorage.getItem(LS_USER_KEY)
|
|
31
|
-
const rawToken = localStorage.getItem(LS_TOKEN_KEY)
|
|
32
|
-
if (rawUser && rawToken) {
|
|
33
|
-
return { user: JSON.parse(rawUser), token: rawToken }
|
|
34
|
-
}
|
|
35
|
-
} catch {}
|
|
36
|
-
return null
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function createAuth() {
|
|
40
|
-
// 初始化时从 localStorage 恢复登录态
|
|
41
|
-
const saved = lsRestore()
|
|
42
|
-
|
|
43
|
-
const user = ref(saved?.user || null)
|
|
44
|
-
const token = ref(saved?.token || null)
|
|
45
|
-
const isLoading = ref(false)
|
|
46
|
-
const error = ref('')
|
|
47
|
-
|
|
48
|
-
const isLoggedIn = computed(() => !!user.value)
|
|
49
|
-
|
|
50
|
-
async function doLogin(username, password) {
|
|
51
|
-
isLoading.value = true
|
|
52
|
-
error.value = ''
|
|
53
|
-
try {
|
|
54
|
-
const result = await login(username, password)
|
|
55
|
-
user.value = result.user
|
|
56
|
-
token.value = result.token
|
|
57
|
-
lsSave(result.user, result.token) // 持久化
|
|
58
|
-
return true
|
|
59
|
-
} catch (err) {
|
|
60
|
-
error.value = err.message || '登录失败,请重试'
|
|
61
|
-
return false
|
|
62
|
-
} finally {
|
|
63
|
-
isLoading.value = false
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function doLogout() {
|
|
68
|
-
await logout()
|
|
69
|
-
user.value = null
|
|
70
|
-
token.value = null
|
|
71
|
-
error.value = ''
|
|
72
|
-
lsClear() // 清除持久化
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return { user, token, isLoggedIn, isLoading, error, doLogin, doLogout }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function provideAuth() {
|
|
79
|
-
const auth = createAuth()
|
|
80
|
-
provide(AUTH_KEY, auth)
|
|
81
|
-
return auth
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function useAuth() {
|
|
85
|
-
const auth = inject(AUTH_KEY)
|
|
86
|
-
if (!auth) throw new Error('useAuth must be used inside a component with provideAuth()')
|
|
87
|
-
return auth
|
|
88
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useChatDB.js
|
|
3
|
-
* IndexedDB 持久化层,负责聊天 sessions 的存取
|
|
4
|
-
*
|
|
5
|
-
* 存储策略:
|
|
6
|
-
* - 仅存 displayText(msg.content)和 attachments 元信息(不存 base64 图片数据)
|
|
7
|
-
* - apiContent 含 image_url base64,体积大且无需持久化,不写入 DB
|
|
8
|
-
* - 每次写入整个 sessions 数组(简化实现,sessions 数量通常不超过几十条)
|
|
9
|
-
*
|
|
10
|
-
* DB 结构:
|
|
11
|
-
* db: citic_chat version: 1
|
|
12
|
-
* store: sessions keyPath: id
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const DB_NAME = 'citic_chat'
|
|
16
|
-
const DB_VERSION = 1
|
|
17
|
-
const STORE_NAME = 'sessions'
|
|
18
|
-
|
|
19
|
-
let _db = null
|
|
20
|
-
|
|
21
|
-
// ── 打开 / 初始化 DB ──────────────────────────────────────────────
|
|
22
|
-
function openDB() {
|
|
23
|
-
if (_db) return Promise.resolve(_db)
|
|
24
|
-
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
const req = indexedDB.open(DB_NAME, DB_VERSION)
|
|
27
|
-
|
|
28
|
-
req.onupgradeneeded = (e) => {
|
|
29
|
-
const db = e.target.result
|
|
30
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
31
|
-
db.createObjectStore(STORE_NAME, { keyPath: 'id' })
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
req.onsuccess = (e) => {
|
|
36
|
-
_db = e.target.result
|
|
37
|
-
resolve(_db)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
req.onerror = () => reject(req.error)
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ── 序列化:去除不需要持久化的大体积字段 ─────────────────────────
|
|
45
|
-
function serializeSession(session) {
|
|
46
|
-
return {
|
|
47
|
-
id: session.id,
|
|
48
|
-
title: session.title,
|
|
49
|
-
messages: session.messages.map(msg => {
|
|
50
|
-
const base = {
|
|
51
|
-
id: msg.id,
|
|
52
|
-
role: msg.role,
|
|
53
|
-
content: msg.content, // displayText,安全
|
|
54
|
-
error: msg.error || false,
|
|
55
|
-
streaming: false, // 持久化时流式已结束
|
|
56
|
-
}
|
|
57
|
-
// attachments 只保留元信息(无 base64 preview)
|
|
58
|
-
if (msg.attachments && msg.attachments.length > 0) {
|
|
59
|
-
base.attachments = msg.attachments.map(att => ({
|
|
60
|
-
id: att.id,
|
|
61
|
-
fileName: att.fileName,
|
|
62
|
-
type: att.type,
|
|
63
|
-
ext: att.ext,
|
|
64
|
-
// preview(图片 dataURL)体积大,不持久化
|
|
65
|
-
}))
|
|
66
|
-
}
|
|
67
|
-
// apiContent 完全不持久化(含 base64 / 文件原文)
|
|
68
|
-
return base
|
|
69
|
-
}),
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ── 保存所有 sessions ─────────────────────────────────────────────
|
|
74
|
-
export async function saveSessions(sessions) {
|
|
75
|
-
try {
|
|
76
|
-
const db = await openDB()
|
|
77
|
-
const tx = db.transaction(STORE_NAME, 'readwrite')
|
|
78
|
-
const store = tx.objectStore(STORE_NAME)
|
|
79
|
-
|
|
80
|
-
// 先清空旧数据,再写入当前全量(保持顺序)
|
|
81
|
-
store.clear()
|
|
82
|
-
for (const session of sessions) {
|
|
83
|
-
store.put(serializeSession(session))
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return new Promise((resolve, reject) => {
|
|
87
|
-
tx.oncomplete = () => resolve()
|
|
88
|
-
tx.onerror = () => reject(tx.error)
|
|
89
|
-
})
|
|
90
|
-
} catch (err) {
|
|
91
|
-
// DB 操作失败静默处理,不影响主流程
|
|
92
|
-
console.warn('[ChatDB] saveSessions failed:', err)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ── 读取所有 sessions ─────────────────────────────────────────────
|
|
97
|
-
export async function loadSessions() {
|
|
98
|
-
try {
|
|
99
|
-
const db = await openDB()
|
|
100
|
-
const tx = db.transaction(STORE_NAME, 'readonly')
|
|
101
|
-
const store = tx.objectStore(STORE_NAME)
|
|
102
|
-
|
|
103
|
-
return new Promise((resolve, reject) => {
|
|
104
|
-
const req = store.getAll()
|
|
105
|
-
req.onsuccess = () => {
|
|
106
|
-
const rows = req.result || []
|
|
107
|
-
// IndexedDB getAll 不保证顺序,按 id(时间戳)降序排列(新会话在前)
|
|
108
|
-
rows.sort((a, b) => b.id - a.id)
|
|
109
|
-
resolve(rows)
|
|
110
|
-
}
|
|
111
|
-
req.onerror = () => reject(req.error)
|
|
112
|
-
})
|
|
113
|
-
} catch (err) {
|
|
114
|
-
console.warn('[ChatDB] loadSessions failed:', err)
|
|
115
|
-
return []
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ── 删除单条 session ──────────────────────────────────────────────
|
|
120
|
-
export async function deleteSession(id) {
|
|
121
|
-
try {
|
|
122
|
-
const db = await openDB()
|
|
123
|
-
const tx = db.transaction(STORE_NAME, 'readwrite')
|
|
124
|
-
tx.objectStore(STORE_NAME).delete(id)
|
|
125
|
-
return new Promise((resolve, reject) => {
|
|
126
|
-
tx.oncomplete = () => resolve()
|
|
127
|
-
tx.onerror = () => reject(tx.error)
|
|
128
|
-
})
|
|
129
|
-
} catch (err) {
|
|
130
|
-
console.warn('[ChatDB] deleteSession failed:', err)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ── 清空所有 sessions ─────────────────────────────────────────────
|
|
135
|
-
export async function clearAllSessions() {
|
|
136
|
-
try {
|
|
137
|
-
const db = await openDB()
|
|
138
|
-
const tx = db.transaction(STORE_NAME, 'readwrite')
|
|
139
|
-
tx.objectStore(STORE_NAME).clear()
|
|
140
|
-
return new Promise((resolve, reject) => {
|
|
141
|
-
tx.oncomplete = () => resolve()
|
|
142
|
-
tx.onerror = () => reject(tx.error)
|
|
143
|
-
})
|
|
144
|
-
} catch (err) {
|
|
145
|
-
console.warn('[ChatDB] clearAllSessions failed:', err)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useCountUp.js
|
|
3
|
-
* 数字滚动动画
|
|
4
|
-
*/
|
|
5
|
-
import { ref } from 'vue'
|
|
6
|
-
|
|
7
|
-
export function useCountUp(target, duration = 1800) {
|
|
8
|
-
const current = ref(0)
|
|
9
|
-
let started = false
|
|
10
|
-
|
|
11
|
-
function start() {
|
|
12
|
-
if (started) return
|
|
13
|
-
started = true
|
|
14
|
-
const t0 = performance.now()
|
|
15
|
-
const tick = (now) => {
|
|
16
|
-
const p = Math.min((now - t0) / duration, 1)
|
|
17
|
-
current.value = Math.round((1 - Math.pow(1 - p, 3)) * target)
|
|
18
|
-
if (p < 1) requestAnimationFrame(tick)
|
|
19
|
-
}
|
|
20
|
-
requestAnimationFrame(tick)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return { current, start }
|
|
24
|
-
}
|