diff --git a/frontend/package.json b/frontend/package.json index a906e28..d45477c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "@fontsource/jetbrains-mono": "^5.3.0", "@vicons/ionicons5": "^0.13.0", "axios": "^1.20.0", "echarts": "^6.1.0", diff --git a/frontend/src/api/ai.ts b/frontend/src/api/ai.ts index 92989f6..2ba0fd2 100644 --- a/frontend/src/api/ai.ts +++ b/frontend/src/api/ai.ts @@ -264,6 +264,23 @@ export function queryBySql(query: string) { return request.post('/ai/sql', { query }, { timeout: 120000 }) } +/** 智能查数历史记录 */ +export interface AiSqlHistoryItem { + id: number + query: string + createdAt: string +} + +/** 获取智能查数历史(按创建时间倒序) */ +export function getSqlHistory(limit = 100) { + return request.get('/ai/sql/history', { params: { limit } }) +} + +/** 清空智能查数历史 */ +export function clearSqlHistory() { + return request.delete('/ai/sql/history') +} + /** 自然语言记账解析结果 */ export interface AiRecordResult { summary: string diff --git a/frontend/src/composables/useAiEnabled.ts b/frontend/src/composables/useAiEnabled.ts index e548ed5..b783ea9 100644 --- a/frontend/src/composables/useAiEnabled.ts +++ b/frontend/src/composables/useAiEnabled.ts @@ -5,8 +5,8 @@ import { getAiConfig } from '../api/ai' /** 当前用户 AI 是否启用;读取失败一律按未启用处理 */ export const aiEnabled = ref(false) -/** 是否已成功加载过一次,避免重复请求 */ -let loaded = false +/** AI 启用状态是否已确定(首次读取完成前为 false,用于避免误显示「未启用」) */ +export const aiEnabledLoaded = ref(false) /** * 从后端刷新 AI 启用状态。 @@ -19,17 +19,17 @@ export async function refreshAiEnabled() { } catch (e) { aiEnabled.value = false } - loaded = true + aiEnabledLoaded.value = true } /** 确保至少加载过一次 AI 启用状态(已加载则不重复请求) */ export async function ensureAiEnabledLoaded() { - if (loaded) return + if (aiEnabledLoaded.value) return await refreshAiEnabled() } /** 重置为未启用(退出登录等场景使用) */ export function resetAiEnabled() { aiEnabled.value = false - loaded = false + aiEnabledLoaded.value = false } diff --git a/frontend/src/locales/messages.ts b/frontend/src/locales/messages.ts index 814cd45..afe4028 100644 --- a/frontend/src/locales/messages.ts +++ b/frontend/src/locales/messages.ts @@ -883,6 +883,9 @@ export default { examplesLabel: { zh: '示例', en: 'Examples', ja: '例' }, history: { zh: '历史提问', en: 'History', ja: '履歴' }, clearHistory: { zh: '清空历史', en: 'Clear history', ja: '履歴をクリア' }, + copyQuestion: { zh: '复制提问', en: 'Copy question', ja: '質問をコピー' }, + historySearch: { zh: '搜索历史提问', en: 'Search history', ja: '履歴を検索' }, + historyNoMatch: { zh: '无匹配结果', en: 'No matching history', ja: '一致する履歴がありません' }, needConfig: { zh: 'AI 未启用,请先在「设置」中开启 AI。', en: 'AI is not enabled. Please enable it in Settings first.', ja: 'AI が有効になっていません。「設定」で有効にしてください。' }, }, record: { diff --git a/frontend/src/store/user.ts b/frontend/src/store/user.ts index 27e56ab..1ae36dd 100644 --- a/frontend/src/store/user.ts +++ b/frontend/src/store/user.ts @@ -2,7 +2,8 @@ import { defineStore } from 'pinia' import { ref } from 'vue' import { getUserInfo } from '../api/user' -import { readString, removeKey, writeString } from '../utils/storage' +import { readString, removeKey, removeSessionKey, writeString } from '../utils/storage' +import { STORAGE_KEYS } from '../utils/storageKeys' /** 登录令牌存储键(认证凭据,不属于界面偏好,不纳入 ledgerly. 前缀) */ const TOKEN_KEY = 'token' @@ -41,6 +42,8 @@ export const useUserStore = defineStore('user', () => { nickname.value = '' userId.value = null removeKey(TOKEN_KEY) + // 清除智能查数上一次查询会话,避免同标签切换账号后看到上一用户的数据 + removeSessionKey(STORAGE_KEYS.sqlQuerySession) } return { token, nickname, userId, setToken, setUserInfo, fetchUserInfo, logout } diff --git a/frontend/src/utils/storage.ts b/frontend/src/utils/storage.ts index 91735da..ce0853c 100644 --- a/frontend/src/utils/storage.ts +++ b/frontend/src/utils/storage.ts @@ -64,6 +64,34 @@ export function writeSessionString(key: string, value: string): void { } } +/** 读取 sessionStorage JSON,解析失败或读取异常时回退 fallback */ +export function readSessionJson(key: string, fallback: T): T { + try { + const raw = sessionStorage.getItem(key) + return raw ? (JSON.parse(raw) as T) : fallback + } catch (e) { + return fallback + } +} + +/** 写入 sessionStorage JSON,忽略存储异常 */ +export function writeSessionJson(key: string, value: unknown): void { + try { + sessionStorage.setItem(key, JSON.stringify(value)) + } catch (e) { + // 忽略写入失败 + } +} + +/** 删除 sessionStorage 键,忽略存储异常 */ +export function removeSessionKey(key: string): void { + try { + sessionStorage.removeItem(key) + } catch (e) { + // 忽略删除失败 + } +} + /** * 迁移历史存储键:新键不存在且旧键存在时,将旧值搬运到新键并删除旧键。 * diff --git a/frontend/src/utils/storageKeys.ts b/frontend/src/utils/storageKeys.ts index b39fd3a..236e176 100644 --- a/frontend/src/utils/storageKeys.ts +++ b/frontend/src/utils/storageKeys.ts @@ -33,8 +33,8 @@ export const STORAGE_KEYS = { transactionAiQueryHistory: 'ledgerly.transaction.aiQueryHistory', // 收支记录 AI 查询浮窗位置尺寸 transactionAiQueryWindow: 'ledgerly.transaction.aiQueryWindow', - // 智能查数的历史提问 - sqlQueryHistory: 'ledgerly.sqlQuery.history', + // 智能查数上一次查询会话(sessionStorage,含输入与结果缓存) + sqlQuerySession: 'ledgerly.sqlQuery.session', // 内容区滚动位置(sessionStorage,按路由拼接后缀) scrollPrefix: 'ledgerly.scroll.', } as const diff --git a/frontend/src/views/SqlQuery.vue b/frontend/src/views/SqlQuery.vue index f3f9f6e..367c4d2 100644 --- a/frontend/src/views/SqlQuery.vue +++ b/frontend/src/views/SqlQuery.vue @@ -1,10 +1,10 @@