mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-26 10:11:36 +08:00
- 新增聊天页面(WebSocket 实时通信、会话管理、Markdown 渲染) - 新增 Agent 管理页面(CRUD、备份、工作区配置、模型选择) - 新增设备认证模块(Ed25519 签名) - 模型排序固化到配置文件(拖拽排序 + 下拉排序均写入底层数据) - 记忆文件页面支持 Agent 切换 - Gateway 配置页重构为选项卡片布局 - 页面路由加载动画 - 批量测试支持终止、实时刷新卡片 - 服务管理备份说明文案 - Modal 组件支持 readonly 字段 - 脱敏 mock 数据中的内部地址
113 lines
3.5 KiB
JavaScript
113 lines
3.5 KiB
JavaScript
/**
|
|
* 本地消息存储 - IndexedDB
|
|
* 从 clawapp 移植,适配 ClawPanel
|
|
*/
|
|
|
|
const DB_NAME = 'clawpanel-messages'
|
|
const DB_VERSION = 1
|
|
const STORE_NAME = 'messages'
|
|
const STORE_SESSIONS = 'sessions'
|
|
|
|
let _db = null
|
|
|
|
function openDB() {
|
|
return new Promise((resolve, reject) => {
|
|
if (_db) return resolve(_db)
|
|
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
|
request.onerror = () => reject(request.error)
|
|
request.onsuccess = () => { _db = request.result; resolve(_db) }
|
|
request.onupgradeneeded = (event) => {
|
|
const db = event.target.result
|
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
const msgStore = db.createObjectStore(STORE_NAME, { keyPath: 'id' })
|
|
msgStore.createIndex('sessionKey', 'sessionKey', { unique: false })
|
|
msgStore.createIndex('timestamp', 'timestamp', { unique: false })
|
|
msgStore.createIndex('sessionKey_timestamp', ['sessionKey', 'timestamp'], { unique: false })
|
|
}
|
|
if (!db.objectStoreNames.contains(STORE_SESSIONS)) {
|
|
db.createObjectStore(STORE_SESSIONS, { keyPath: 'sessionKey' })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
export async function saveMessage(message) {
|
|
if (!message || !message.id) return
|
|
try {
|
|
const db = await openDB()
|
|
const tx = db.transaction(STORE_NAME, 'readwrite')
|
|
tx.objectStore(STORE_NAME).put({
|
|
id: message.id,
|
|
sessionKey: message.sessionKey || '',
|
|
role: message.role || 'assistant',
|
|
content: message.content || message.text || '',
|
|
timestamp: message.timestamp || Date.now(),
|
|
sync: true
|
|
})
|
|
} catch (e) {
|
|
console.error('[db] saveMessage error:', e)
|
|
}
|
|
}
|
|
|
|
export async function saveMessages(messages) {
|
|
if (!messages?.length) return
|
|
try {
|
|
const db = await openDB()
|
|
const tx = db.transaction(STORE_NAME, 'readwrite')
|
|
const store = tx.objectStore(STORE_NAME)
|
|
messages.forEach(msg => {
|
|
if (!msg.id) return
|
|
store.put({
|
|
id: msg.id,
|
|
sessionKey: msg.sessionKey || '',
|
|
role: msg.role || 'assistant',
|
|
content: msg.content || msg.text || '',
|
|
timestamp: msg.timestamp || Date.now(),
|
|
sync: true
|
|
})
|
|
})
|
|
} catch (e) {
|
|
console.error('[db] saveMessages error:', e)
|
|
}
|
|
}
|
|
|
|
export async function getLocalMessages(sessionKey, limit = 200) {
|
|
try {
|
|
const db = await openDB()
|
|
return new Promise((resolve) => {
|
|
const tx = db.transaction(STORE_NAME, 'readonly')
|
|
const index = tx.objectStore(STORE_NAME).index('sessionKey_timestamp')
|
|
const range = IDBKeyRange.bound([sessionKey, 0], [sessionKey, Date.now() + 1])
|
|
const messages = []
|
|
const request = index.openCursor(range, 'prev')
|
|
request.onsuccess = (event) => {
|
|
const cursor = event.target.result
|
|
if (cursor && messages.length < limit) { messages.push(cursor.value); cursor.continue() }
|
|
}
|
|
tx.oncomplete = () => resolve(messages.reverse())
|
|
tx.onerror = () => resolve([])
|
|
})
|
|
} catch (e) {
|
|
console.error('[db] getLocalMessages error:', e)
|
|
return []
|
|
}
|
|
}
|
|
|
|
export async function clearSessionMessages(sessionKey) {
|
|
try {
|
|
const db = await openDB()
|
|
const tx = db.transaction(STORE_NAME, 'readwrite')
|
|
const request = tx.objectStore(STORE_NAME).index('sessionKey').openCursor(sessionKey)
|
|
request.onsuccess = (event) => {
|
|
const cursor = event.target.result
|
|
if (cursor) { cursor.delete(); cursor.continue() }
|
|
}
|
|
} catch (e) {
|
|
console.error('[db] clearSessionMessages error:', e)
|
|
}
|
|
}
|
|
|
|
export function isStorageAvailable() {
|
|
try { return 'indexedDB' in window && !!indexedDB } catch { return false }
|
|
}
|