refactor: add topics and settings table
dexie
This commit is contained in:
parent
cee373bb6f
commit
fa1f00f4f5
@ -130,6 +130,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
pre + pre {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 0;
|
||||
padding-left: 1em;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import LocalStorage from '@renderer/services/storage'
|
||||
import ImageStorage from '@renderer/services/storage'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setAvatar } from '@renderer/store/runtime'
|
||||
import { setUserName } from '@renderer/store/settings'
|
||||
@ -55,8 +55,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
try {
|
||||
const _file = file.originFileObj as File
|
||||
const compressedFile = await compressImage(_file)
|
||||
await LocalStorage.storeImage('avatar', compressedFile)
|
||||
dispatch(setAvatar(await LocalStorage.getImage('avatar')))
|
||||
await ImageStorage.set('avatar', compressedFile)
|
||||
dispatch(setAvatar(await ImageStorage.get('avatar')))
|
||||
} catch (error: any) {
|
||||
window.message.error(error.message)
|
||||
}
|
||||
|
||||
@ -1,13 +1,27 @@
|
||||
import { FileType } from '@renderer/types'
|
||||
import { FileType, Topic } from '@renderer/types'
|
||||
import { Dexie, type EntityTable } from 'dexie'
|
||||
|
||||
import { populateTopics } from './populate'
|
||||
|
||||
// Database declaration (move this to its own module also)
|
||||
export const db = new Dexie('CherryStudio') as Dexie & {
|
||||
files: EntityTable<FileType, 'id'>
|
||||
topics: EntityTable<Pick<Topic, 'id' | 'messages'>, 'id'>
|
||||
settings: EntityTable<{ id: string; value: any }, 'id'>
|
||||
}
|
||||
|
||||
db.version(1).stores({
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count'
|
||||
})
|
||||
|
||||
db.version(2).stores({
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||
topics: '&id, messages',
|
||||
settings: '&id, value'
|
||||
})
|
||||
|
||||
db.on('populate', async (trans) => {
|
||||
populateTopics(trans)
|
||||
})
|
||||
|
||||
export default db
|
||||
|
||||
24
src/renderer/src/databases/populate.ts
Normal file
24
src/renderer/src/databases/populate.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import i18n from '@renderer/i18n'
|
||||
import { Transaction } from 'dexie'
|
||||
import localforage from 'localforage'
|
||||
|
||||
export async function populateTopics(trans: Transaction) {
|
||||
const indexedKeys = await localforage.keys()
|
||||
for (const key of indexedKeys) {
|
||||
const value: any = await localforage.getItem(key)
|
||||
if (key.startsWith('topic:')) {
|
||||
await trans.db.table('topics').add({ id: value.id, messages: value.messages })
|
||||
}
|
||||
if (key === 'image://avatar') {
|
||||
await trans.db.table('settings').add({ id: key, value: await localforage.getItem(key) })
|
||||
}
|
||||
}
|
||||
|
||||
window.modal.success({
|
||||
title: i18n.t('message.upgrade.success.title'),
|
||||
content: i18n.t('message.upgrade.success.content'),
|
||||
okText: i18n.t('message.upgrade.success.button'),
|
||||
centered: true,
|
||||
onOk: () => window.api.reload()
|
||||
})
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
import { isLocalAi } from '@renderer/config/env'
|
||||
import db from '@renderer/databases'
|
||||
import i18n from '@renderer/i18n'
|
||||
import LocalStorage from '@renderer/services/storage'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setAvatar } from '@renderer/store/runtime'
|
||||
import { runAsyncFunction } from '@renderer/utils'
|
||||
import { useLiveQuery } from 'dexie-react-hooks'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useDefaultModel } from './useAssistant'
|
||||
@ -13,13 +14,11 @@ export function useAppInit() {
|
||||
const dispatch = useAppDispatch()
|
||||
const { proxyUrl, language } = useSettings()
|
||||
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
|
||||
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
|
||||
|
||||
useEffect(() => {
|
||||
runAsyncFunction(async () => {
|
||||
const storedImage = await LocalStorage.getImage('avatar')
|
||||
storedImage && dispatch(setAvatar(storedImage))
|
||||
})
|
||||
}, [dispatch])
|
||||
avatar?.value && dispatch(setAvatar(avatar.value))
|
||||
}, [avatar, dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
runAsyncFunction(async () => {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||
import LocalStorage from '@renderer/services/storage'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import {
|
||||
addAssistant,
|
||||
@ -18,6 +17,8 @@ import {
|
||||
import { setDefaultModel, setTopicNamingModel, setTranslateModel } from '@renderer/store/llm'
|
||||
import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
|
||||
|
||||
import { TopicManager } from './useTopic'
|
||||
|
||||
export function useAssistants() {
|
||||
const { assistants } = useAppSelector((state) => state.assistants)
|
||||
const dispatch = useAppDispatch()
|
||||
@ -30,7 +31,7 @@ export function useAssistants() {
|
||||
dispatch(removeAssistant({ id }))
|
||||
const assistant = assistants.find((a) => a.id === id)
|
||||
const topics = assistant?.topics || []
|
||||
topics.forEach(({ id }) => LocalStorage.removeTopic(id))
|
||||
topics.forEach(({ id }) => TopicManager.removeTopic(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,7 +46,7 @@ export function useAssistant(id: string) {
|
||||
model: assistant?.model ?? defaultModel,
|
||||
addTopic: (topic: Topic) => dispatch(addTopic({ assistantId: assistant.id, topic })),
|
||||
removeTopic: (topic: Topic) => {
|
||||
LocalStorage.removeTopic(topic.id)
|
||||
TopicManager.removeTopic(topic.id)
|
||||
dispatch(removeTopic({ assistantId: assistant.id, topic }))
|
||||
},
|
||||
updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })),
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import db from '@renderer/databases'
|
||||
import { deleteMessageFiles } from '@renderer/services/messages'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { find } from 'lodash'
|
||||
import { useEffect, useState } from 'react'
|
||||
@ -25,3 +27,38 @@ export function useActiveTopic(_assistant: Assistant) {
|
||||
export function getTopic(assistant: Assistant, topicId: string) {
|
||||
return assistant?.topics.find((topic) => topic.id === topicId)
|
||||
}
|
||||
|
||||
export class TopicManager {
|
||||
static async getTopic(id: string) {
|
||||
return await db.topics.get(id)
|
||||
}
|
||||
|
||||
static async getTopicMessages(id: string) {
|
||||
const topic = await this.getTopic(id)
|
||||
return topic ? topic.messages : []
|
||||
}
|
||||
|
||||
static async removeTopic(id: string) {
|
||||
const messages = await this.getTopicMessages(id)
|
||||
|
||||
for (const message of messages) {
|
||||
await deleteMessageFiles(message)
|
||||
}
|
||||
|
||||
db.topics.delete(id)
|
||||
}
|
||||
|
||||
static async clearTopicMessages(id: string) {
|
||||
const topic = await this.getTopic(id)
|
||||
|
||||
if (topic) {
|
||||
for (const message of topic?.messages ?? []) {
|
||||
await deleteMessageFiles(message)
|
||||
}
|
||||
|
||||
topic.messages = []
|
||||
|
||||
await db.topics.update(id, topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,10 @@ const resources = {
|
||||
'restore.success': 'Restored successfully',
|
||||
'reset.confirm.content': 'Are you sure you want to clear all data?',
|
||||
'reset.double.confirm.title': 'DATA LOST !!!',
|
||||
'reset.double.confirm.content': 'All data will be lost, do you want to continue?'
|
||||
'reset.double.confirm.content': 'All data will be lost, do you want to continue?',
|
||||
'upgrade.success.title': 'Upgrade successfully',
|
||||
'upgrade.success.content': 'Please restart the application to complete the upgrade',
|
||||
'upgrade.success.button': 'Restart'
|
||||
},
|
||||
chat: {
|
||||
save: 'Save',
|
||||
@ -319,7 +322,10 @@ const resources = {
|
||||
'restore.success': '恢复成功',
|
||||
'reset.confirm.content': '确定要重置所有数据吗?',
|
||||
'reset.double.confirm.title': '数据丢失!!!',
|
||||
'reset.double.confirm.content': '你的全部数据都会丢失,如果没有备份数据,将无法恢复,确定要继续吗?'
|
||||
'reset.double.confirm.content': '你的全部数据都会丢失,如果没有备份数据,将无法恢复,确定要继续吗?',
|
||||
'upgrade.success.title': '升级成功',
|
||||
'upgrade.success.content': '重启应用以完成升级',
|
||||
'upgrade.success.button': '重启'
|
||||
},
|
||||
chat: {
|
||||
save: '保存',
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import db from '@renderer/databases'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
|
||||
import { getTopic } from '@renderer/hooks/useTopic'
|
||||
import { getTopic, TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import {
|
||||
@ -9,11 +10,9 @@ import {
|
||||
filterMessages,
|
||||
getContextCount
|
||||
} from '@renderer/services/messages'
|
||||
import LocalStorage from '@renderer/services/storage'
|
||||
import { Assistant, Message, Model, Topic } from '@renderer/types'
|
||||
import { getBriefInfo, runAsyncFunction, uuid } from '@renderer/utils'
|
||||
import { t } from 'i18next'
|
||||
import localforage from 'localforage'
|
||||
import { last, reverse } from 'lodash'
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
@ -39,7 +38,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
(message: Message) => {
|
||||
const _messages = [...messages, message]
|
||||
setMessages(_messages)
|
||||
localforage.setItem(`topic:${topic.id}`, { id: topic.id, messages: _messages })
|
||||
db.topics.add({ id: topic.id, messages: _messages })
|
||||
},
|
||||
[messages, topic]
|
||||
)
|
||||
@ -60,7 +59,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
(message: Message) => {
|
||||
const _messages = messages.filter((m) => m.id !== message.id)
|
||||
setMessages(_messages)
|
||||
localforage.setItem(`topic:${topic.id}`, { id: topic.id, messages: _messages })
|
||||
db.topics.update(topic.id, { messages: _messages })
|
||||
deleteMessageFiles(message)
|
||||
},
|
||||
[messages, topic.id]
|
||||
@ -94,7 +93,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
EventEmitter.on(EVENT_NAMES.CLEAR_MESSAGES, () => {
|
||||
setMessages([])
|
||||
updateTopic({ ...topic, messages: [] })
|
||||
LocalStorage.clearTopicMessages(topic.id)
|
||||
TopicManager.clearTopicMessages(topic.id)
|
||||
}),
|
||||
EventEmitter.on(EVENT_NAMES.NEW_CONTEXT, () => {
|
||||
const lastMessage = last(messages)
|
||||
@ -124,7 +123,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
|
||||
useEffect(() => {
|
||||
runAsyncFunction(async () => {
|
||||
const messages = (await LocalStorage.getTopicMessages(topic.id)) || []
|
||||
const messages = (await TopicManager.getTopicMessages(topic.id)) || []
|
||||
setMessages(messages)
|
||||
})
|
||||
}, [topic.id])
|
||||
|
||||
@ -2,8 +2,8 @@ import { CloseOutlined, DeleteOutlined, EditOutlined, OpenAIOutlined } from '@an
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { fetchMessagesSummary } from '@renderer/services/api'
|
||||
import LocalStorage from '@renderer/services/storage'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Dropdown, MenuProps } from 'antd'
|
||||
@ -53,7 +53,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
key: 'auto-rename',
|
||||
icon: <OpenAIOutlined />,
|
||||
async onClick() {
|
||||
const messages = await LocalStorage.getTopicMessages(topic.id)
|
||||
const messages = await TopicManager.getTopicMessages(topic.id)
|
||||
if (messages.length >= 2) {
|
||||
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
||||
if (summaryText) {
|
||||
|
||||
@ -1,60 +1,27 @@
|
||||
import { Topic } from '@renderer/types'
|
||||
import db from '@renderer/databases'
|
||||
import { convertToBase64 } from '@renderer/utils'
|
||||
import localforage from 'localforage'
|
||||
|
||||
import { deleteMessageFiles } from './messages'
|
||||
|
||||
const IMAGE_PREFIX = 'image://'
|
||||
|
||||
export default class LocalStorage {
|
||||
static async getTopic(id: string) {
|
||||
return localforage.getItem<Topic>(`topic:${id}`)
|
||||
}
|
||||
|
||||
static async getTopicMessages(id: string) {
|
||||
const topic = await this.getTopic(id)
|
||||
return topic ? topic.messages : []
|
||||
}
|
||||
|
||||
static async removeTopic(id: string) {
|
||||
const messages = await this.getTopicMessages(id)
|
||||
|
||||
for (const message of messages) {
|
||||
await deleteMessageFiles(message)
|
||||
}
|
||||
|
||||
localforage.removeItem(`topic:${id}`)
|
||||
}
|
||||
|
||||
static async clearTopicMessages(id: string) {
|
||||
const topic = await this.getTopic(id)
|
||||
|
||||
if (topic) {
|
||||
for (const message of topic?.messages ?? []) {
|
||||
await deleteMessageFiles(message)
|
||||
}
|
||||
|
||||
topic.messages = []
|
||||
await localforage.setItem(`topic:${id}`, topic)
|
||||
}
|
||||
}
|
||||
|
||||
static async storeImage(name: string, file: File) {
|
||||
export default class ImageStorage {
|
||||
static async set(key: string, file: File) {
|
||||
const id = IMAGE_PREFIX + key
|
||||
try {
|
||||
const base64Image = await convertToBase64(file)
|
||||
if (typeof base64Image === 'string') {
|
||||
await localforage.setItem(IMAGE_PREFIX + name, base64Image)
|
||||
if (await db.settings.get(id)) {
|
||||
db.settings.update(id, { value: base64Image })
|
||||
return
|
||||
}
|
||||
await db.settings.add({ id, value: base64Image })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error storing the image', error)
|
||||
}
|
||||
}
|
||||
|
||||
static async getImage(name: string) {
|
||||
return localforage.getItem<string>(IMAGE_PREFIX + name)
|
||||
}
|
||||
|
||||
static async removeImage(name: string) {
|
||||
await localforage.removeItem(IMAGE_PREFIX + name)
|
||||
static async get(key: string): Promise<string> {
|
||||
const id = IMAGE_PREFIX + key
|
||||
return (await db.settings.get(id))?.value
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { getDefaultAssistant, getDefaultTopic } from '@renderer/services/assistant'
|
||||
import LocalStorage from '@renderer/services/storage'
|
||||
import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
|
||||
import { uniqBy } from 'lodash'
|
||||
|
||||
@ -91,7 +91,7 @@ const assistantsSlice = createSlice({
|
||||
removeAllTopics: (state, action: PayloadAction<{ assistantId: string }>) => {
|
||||
state.assistants = state.assistants.map((assistant) => {
|
||||
if (assistant.id === action.payload.assistantId) {
|
||||
assistant.topics.forEach((topic) => LocalStorage.removeTopic(topic.id))
|
||||
assistant.topics.forEach((topic) => TopicManager.removeTopic(topic.id))
|
||||
return {
|
||||
...assistant,
|
||||
topics: [getDefaultTopic()]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user