diff --git a/src/extension/webview-api/chat-context-processor/utils/convert-langchain-message-to-conversation.ts b/src/extension/webview-api/chat-context-processor/utils/convert-langchain-message-to-conversation.ts index 27b4e70..ae0ac63 100644 --- a/src/extension/webview-api/chat-context-processor/utils/convert-langchain-message-to-conversation.ts +++ b/src/extension/webview-api/chat-context-processor/utils/convert-langchain-message-to-conversation.ts @@ -1,17 +1,18 @@ import type { MessageType } from '@langchain/core/messages' +import { ConversationEntity } from '@shared/entities/conversation-entity' import type { Conversation } from '@shared/types/chat-context' import type { LangchainMessage, LangchainMessageContents } from '@shared/types/chat-context/langchain-message' import { convertToLangchainMessageContents } from '@shared/utils/convert-to-langchain-message-contents' -import { getDefaultConversation } from '@shared/utils/get-default-conversation' export const convertLangchainMessageToConversation = ( message: LangchainMessage ): Conversation => { const messageType = message.toDict().type as MessageType - const defaultConversation = getDefaultConversation(messageType) + const defaultConversation = new ConversationEntity({ role: messageType }) + .entity const contents: LangchainMessageContents = convertToLangchainMessageContents( message.content ) diff --git a/src/extension/webview-api/controllers/chat-controller.ts b/src/extension/webview-api/controllers/chat-controller.ts index c4ab0a9..4f017d3 100644 --- a/src/extension/webview-api/controllers/chat-controller.ts +++ b/src/extension/webview-api/controllers/chat-controller.ts @@ -20,17 +20,4 @@ export class ChatController extends Controller { yield conversations } } - - // sendMessage(req: { message: string }): Promise { - // return Promise.resolve(`Hi, bro, I'm response to: ${req.message}`) - // } - - // async *streamChat(req: { - // prompt: string - // }): AsyncGenerator { - // for (let i = 0; i < 5; i++) { - // yield `Chunk ${i + 1} for prompt: ${req.prompt}` - // await new Promise(resolve => setTimeout(resolve, 1000)) - // } - // } } diff --git a/src/extension/webview-api/controllers/chat-session-controller.ts b/src/extension/webview-api/controllers/chat-session-controller.ts index 9754b24..204a1e3 100644 --- a/src/extension/webview-api/controllers/chat-session-controller.ts +++ b/src/extension/webview-api/controllers/chat-session-controller.ts @@ -1,9 +1,8 @@ import { aidePaths } from '@extension/file-utils/paths' import { VsCodeFS } from '@extension/file-utils/vscode-fs' import { logger } from '@extension/logger' -import type { ChatSession } from '@shared/entities' +import { ChatContextEntity, type ChatSession } from '@shared/entities' import type { ChatContext, Conversation } from '@shared/types/chat-context' -import { convertChatContextToChatSession } from '@shared/utils/convert-chat-context-to-chat-session' import { chatSessionsDB } from '../lowdb/chat-sessions-db' import { Controller } from '../types' @@ -16,7 +15,7 @@ export class ChatSessionController extends Controller { } async createSession(req: { chatContext: ChatContext }): Promise { - const chatSession = convertChatContextToChatSession(req.chatContext) + const chatSession = new ChatContextEntity(req.chatContext).toChatSession() const now = new Date().getTime() const session = await chatSessionsDB.add({ ...chatSession, @@ -48,7 +47,7 @@ export class ChatSessionController extends Controller { async updateSession(req: { chatContext: ChatContext }): Promise { const now = new Date().getTime() const session = await chatSessionsDB.update(req.chatContext.id, { - ...convertChatContextToChatSession(req.chatContext), + ...new ChatContextEntity(req.chatContext).toChatSession(), updatedAt: now }) @@ -65,7 +64,7 @@ export class ChatSessionController extends Controller { }): Promise { const now = new Date().getTime() const session = await chatSessionsDB.createOrUpdate({ - ...convertChatContextToChatSession(req.chatContext), + ...new ChatContextEntity(req.chatContext).toChatSession(), updatedAt: now }) diff --git a/src/extension/webview-api/lowdb/ai-model-db.ts b/src/extension/webview-api/lowdb/ai-model-db.ts index b4bde4a..20fe966 100644 --- a/src/extension/webview-api/lowdb/ai-model-db.ts +++ b/src/extension/webview-api/lowdb/ai-model-db.ts @@ -8,8 +8,7 @@ class AIModelDB extends BaseDB { static readonly schemaVersion = 1 constructor() { - // Use entity's defaults - const defaults = new AIModelEntity().getDefaults() + const defaults = new AIModelEntity().entity super( path.join(aidePaths.getGlobalLowdbPath(), 'ai-models.json'), diff --git a/src/extension/webview-api/lowdb/ai-provider-db.ts b/src/extension/webview-api/lowdb/ai-provider-db.ts index 2cbac4e..08418ec 100644 --- a/src/extension/webview-api/lowdb/ai-provider-db.ts +++ b/src/extension/webview-api/lowdb/ai-provider-db.ts @@ -31,7 +31,7 @@ const findNewModel = async ( new AIModelEntity({ name, providerOrBaseUrl - }) + }).entity ) } @@ -39,8 +39,7 @@ class AIProviderDB extends BaseDB { static readonly schemaVersion = 1 constructor() { - // Use entity's defaults - const defaults = new AIProviderEntity().getDefaults() + const defaults = new AIProviderEntity().entity super( path.join(aidePaths.getGlobalLowdbPath(), 'ai-providers.json'), diff --git a/src/extension/webview-api/lowdb/chat-sessions-db.ts b/src/extension/webview-api/lowdb/chat-sessions-db.ts index eb0d083..10c434c 100644 --- a/src/extension/webview-api/lowdb/chat-sessions-db.ts +++ b/src/extension/webview-api/lowdb/chat-sessions-db.ts @@ -11,8 +11,7 @@ class ChatSessionsDB extends BaseDB { static readonly schemaVersion = 1 constructor() { - // Use entity's defaults - const defaults = new ChatSessionEntity().getDefaults() + const defaults = new ChatSessionEntity().entity super( path.join(aidePaths.getWorkspaceLowdbPath(), 'sessions.json'), diff --git a/src/extension/webview-api/lowdb/doc-sites-db.ts b/src/extension/webview-api/lowdb/doc-sites-db.ts index 9b4d6e0..efd9a6b 100644 --- a/src/extension/webview-api/lowdb/doc-sites-db.ts +++ b/src/extension/webview-api/lowdb/doc-sites-db.ts @@ -8,7 +8,7 @@ class DocSitesDB extends BaseDB { static readonly schemaVersion = 1 constructor() { - const defaults = new DocSiteEntity().getDefaults() + const defaults = new DocSiteEntity().entity super( path.join(aidePaths.getGlobalLowdbPath(), 'doc-sites.json'), defaults, @@ -21,7 +21,7 @@ class DocSitesDB extends BaseDB { id?: string } ): Promise { - const docSite = new DocSiteEntity(item) + const docSite = new DocSiteEntity(item).entity return super.add(docSite) } @@ -30,7 +30,7 @@ class DocSitesDB extends BaseDB { id?: string })[] ): Promise { - const docSites = items.map(item => new DocSiteEntity(item)) + const docSites = items.map(item => new DocSiteEntity(item).entity) return super.batchAdd(docSites) } diff --git a/src/extension/webview-api/lowdb/settings-db.ts b/src/extension/webview-api/lowdb/settings-db.ts index 8907179..f2092c8 100644 --- a/src/extension/webview-api/lowdb/settings-db.ts +++ b/src/extension/webview-api/lowdb/settings-db.ts @@ -15,7 +15,7 @@ class SettingsDB extends BaseDB { static readonly schemaVersion = 1 constructor(filePath: string) { - const defaults = new SettingsEntity().getDefaults() + const defaults = new SettingsEntity().entity super(filePath, defaults, SettingsDB.schemaVersion) } @@ -39,7 +39,8 @@ class SettingsDB extends BaseDB { value, category: config.category, updatedAt: Date.now() - }) + }).entity + return this.add(setting) } diff --git a/src/shared/entities/ai-model-entity.ts b/src/shared/entities/ai-model-entity.ts index 0035158..2d38d3e 100644 --- a/src/shared/entities/ai-model-entity.ts +++ b/src/shared/entities/ai-model-entity.ts @@ -12,20 +12,8 @@ export interface AIModel extends IBaseEntity { toolsCallSupport: AIModelSupport } -export class AIModelEntity extends BaseEntity implements AIModel { - id!: string - - providerOrBaseUrl!: AIProviderType | string - - name!: string - - imageSupport!: AIModelSupport - - audioSupport!: AIModelSupport - - toolsCallSupport!: AIModelSupport - - getDefaults(): AIModel { +export class AIModelEntity extends BaseEntity { + protected getDefaults(): AIModel { return { id: uuidv4(), providerOrBaseUrl: AIProviderType.OpenAI, diff --git a/src/shared/entities/ai-provider-entity.ts b/src/shared/entities/ai-provider-entity.ts index 4ee56bc..c987c37 100644 --- a/src/shared/entities/ai-provider-entity.ts +++ b/src/shared/entities/ai-provider-entity.ts @@ -12,27 +12,8 @@ export interface AIProvider extends IBaseEntity { manualModels: string[] } -export class AIProviderEntity - extends BaseEntity - implements AIProvider -{ - id!: string - - name!: string - - type!: AIProviderType - - order!: number - - extraFields!: Record - - allowRealTimeModels!: boolean - - realTimeModels!: string[] - - manualModels!: string[] - - getDefaults(): AIProvider { +export class AIProviderEntity extends BaseEntity { + protected getDefaults(): AIProvider { return { id: uuidv4(), name: 'unknown', diff --git a/src/shared/entities/base-entity.ts b/src/shared/entities/base-entity.ts index eaf309e..a38cdca 100644 --- a/src/shared/entities/base-entity.ts +++ b/src/shared/entities/base-entity.ts @@ -4,9 +4,11 @@ export interface IBaseEntity { } export abstract class BaseEntity { + entity: T + constructor(data?: Partial) { - Object.assign(this, this.getDefaults(), data ?? {}) + this.entity = { ...this.getDefaults(), ...(data ?? {}) } } - abstract getDefaults(): Partial + protected abstract getDefaults(): T } diff --git a/src/shared/entities/chat-context-entity.ts b/src/shared/entities/chat-context-entity.ts new file mode 100644 index 0000000..cd9084f --- /dev/null +++ b/src/shared/entities/chat-context-entity.ts @@ -0,0 +1,65 @@ +import { ChatContextType, type Conversation } from '@shared/types/chat-context' +import type { SettingsContext } from '@shared/types/chat-context/settings-context' +import { v4 as uuidv4 } from 'uuid' + +import { BaseEntity, type IBaseEntity } from './base-entity' +import { ChatSessionEntity, type ChatSession } from './chat-session-entity' + +export interface ChatContext extends IBaseEntity { + type: ChatContextType + createdAt: number + updatedAt: number + conversations: Conversation[] + settings: SettingsContext +} + +export class ChatContextEntity extends BaseEntity { + getDefaults(): ChatContext { + const now = Date.now() + return { + id: uuidv4(), + type: ChatContextType.Chat, + createdAt: now, + updatedAt: now, + conversations: [], + settings: { + allowLongFileScan: false, + explicitContext: '总是用中文回复', + fastApplyModelName: 'gpt-4o-mini', + modelName: 'gpt-4o', + useFastApply: true + } + } + } + + toChatSession(): ChatSession { + const { entity } = this + + return new ChatSessionEntity({ + id: entity.id, + title: this.getTitleFromConversations(entity.conversations), + type: entity.type, + createdAt: entity.createdAt, + updatedAt: entity.updatedAt + }).entity + } + + private getTitleFromConversations = ( + conversations: Conversation[], + defaultTitle = 'New Chat' + ) => { + let firstHumanMessageText = '' + + conversations + .filter(conversation => conversation.role === 'human') + .forEach(conversation => + conversation.contents.forEach(content => { + if (content.type === 'text' && !firstHumanMessageText) { + firstHumanMessageText = content.text + } + }) + ) + + return firstHumanMessageText || defaultTitle + } +} diff --git a/src/shared/entities/chat-session-entity.ts b/src/shared/entities/chat-session-entity.ts index 020aff1..018e1e0 100644 --- a/src/shared/entities/chat-session-entity.ts +++ b/src/shared/entities/chat-session-entity.ts @@ -10,21 +10,8 @@ export interface ChatSession extends IBaseEntity { title: string } -export class ChatSessionEntity - extends BaseEntity - implements ChatSession -{ - id!: string - - type!: ChatContextType - - createdAt!: number - - updatedAt!: number - - title!: string - - getDefaults(): ChatSession { +export class ChatSessionEntity extends BaseEntity { + protected getDefaults(): ChatSession { const now = Date.now() return { id: uuidv4(), diff --git a/src/shared/entities/conversation-entity.ts b/src/shared/entities/conversation-entity.ts new file mode 100644 index 0000000..6fc3a32 --- /dev/null +++ b/src/shared/entities/conversation-entity.ts @@ -0,0 +1,26 @@ +import type { MessageType } from '@langchain/core/messages' +import type { PluginState } from '@shared/plugins/base/types' +import type { LangchainMessageContents } from '@shared/types/chat-context/langchain-message' +import { v4 as uuidv4 } from 'uuid' + +import { BaseEntity, type IBaseEntity } from './base-entity' + +export interface Conversation extends IBaseEntity { + createdAt: number + role: MessageType + contents: LangchainMessageContents + richText?: string + pluginStates: Record +} + +export class ConversationEntity extends BaseEntity { + protected getDefaults(): Conversation { + return { + id: uuidv4(), + createdAt: Date.now(), + role: 'human', + contents: [], + pluginStates: {} + } + } +} diff --git a/src/shared/entities/doc-site-entity.ts b/src/shared/entities/doc-site-entity.ts index e05fb84..0d38197 100644 --- a/src/shared/entities/doc-site-entity.ts +++ b/src/shared/entities/doc-site-entity.ts @@ -9,18 +9,8 @@ export interface DocSite extends IBaseEntity { isIndexed: boolean } -export class DocSiteEntity extends BaseEntity implements DocSite { - id!: string - - name!: string - - url!: string - - isCrawled!: boolean - - isIndexed!: boolean - - getDefaults(): DocSite { +export class DocSiteEntity extends BaseEntity { + protected getDefaults(): DocSite { return { id: uuidv4(), name: 'unknown', diff --git a/src/shared/entities/index.ts b/src/shared/entities/index.ts index 078a0cd..b60b32f 100644 --- a/src/shared/entities/index.ts +++ b/src/shared/entities/index.ts @@ -1,6 +1,8 @@ export * from './ai-model-entity' export * from './ai-provider-entity' export * from './base-entity' +export * from './chat-context-entity' export * from './chat-session-entity' +export * from './conversation-entity' export * from './doc-site-entity' export * from './settings-entity' diff --git a/src/shared/entities/settings-entity.ts b/src/shared/entities/settings-entity.ts index add1f3b..4dbfa53 100644 --- a/src/shared/entities/settings-entity.ts +++ b/src/shared/entities/settings-entity.ts @@ -9,18 +9,8 @@ export interface Settings extends IBaseEntity { updatedAt: number } -export class SettingsEntity extends BaseEntity implements Settings { - id!: string - - key!: SettingKey - - value!: SettingValue - - category!: SettingCategory - - updatedAt!: number - - getDefaults(): Settings { +export class SettingsEntity extends BaseEntity { + protected getDefaults(): Settings { return { id: uuidv4(), key: 'unknown' as SettingKey, diff --git a/src/shared/utils/convert-chat-context-to-chat-session.ts b/src/shared/utils/convert-chat-context-to-chat-session.ts deleted file mode 100644 index 20b129a..0000000 --- a/src/shared/utils/convert-chat-context-to-chat-session.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ChatSession } from '@shared/entities' -import { type ChatContext } from '@shared/types/chat-context' - -import { getTitleFromConversations } from './get-title-from-conversations' - -export const convertChatContextToChatSession = ( - chatContext: ChatContext -): ChatSession => ({ - id: chatContext.id, - title: getTitleFromConversations(chatContext.conversations), - type: chatContext.type, - createdAt: chatContext.createdAt, - updatedAt: chatContext.updatedAt -}) diff --git a/src/shared/utils/get-default-conversation.ts b/src/shared/utils/get-default-conversation.ts deleted file mode 100644 index 9254f13..0000000 --- a/src/shared/utils/get-default-conversation.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Conversation } from '@shared/types/chat-context' -import { v4 as uuidv4 } from 'uuid' - -export const getDefaultConversation = ( - role: Conversation['role'] -): Conversation => ({ - id: uuidv4(), - createdAt: Date.now(), - role, - contents: [], - pluginStates: {} -}) diff --git a/src/shared/utils/get-title-from-conversations.ts b/src/shared/utils/get-title-from-conversations.ts deleted file mode 100644 index 8d0e587..0000000 --- a/src/shared/utils/get-title-from-conversations.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Conversation } from '@shared/types/chat-context' - -export const getTitleFromConversations = ( - conversations: Conversation[], - defaultTitle = 'New Chat' -) => { - let firstHumanMessageText = '' - - conversations - .filter(conversation => conversation.role === 'human') - .forEach(conversation => - conversation.contents.forEach(content => { - if (content.type === 'text' && !firstHumanMessageText) { - firstHumanMessageText = content.text - } - }) - ) - - return firstHumanMessageText || defaultTitle -} diff --git a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/index.tsx b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/index.tsx index 597c456..80e9f0b 100644 --- a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/index.tsx +++ b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/index.tsx @@ -23,7 +23,7 @@ const getDefaultAIModel = ( new AIModelEntity({ name, providerOrBaseUrl - }) + }).entity export const AIModelManagement = ({ className, diff --git a/src/webview/hooks/chat/use-chat-sessions-ui.ts b/src/webview/hooks/chat/use-chat-sessions-ui.ts index 691c047..a947617 100644 --- a/src/webview/hooks/chat/use-chat-sessions-ui.ts +++ b/src/webview/hooks/chat/use-chat-sessions-ui.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import { convertChatContextToChatSession } from '@shared/utils/convert-chat-context-to-chat-session' +import { ChatContextEntity } from '@shared/entities' import { useChatContext } from '@webview/contexts/chat-context' export const useChatSessionsUI = () => { @@ -8,7 +8,7 @@ export const useChatSessionsUI = () => { const isCurrentSessionInChatSessions = chatSessions?.some(session => session.id === context.id) ?? false - const currentSession = convertChatContextToChatSession(context) + const currentSession = new ChatContextEntity(context).toChatSession() const chatSessionForRender = ( isCurrentSessionInChatSessions ? [...chatSessions]! : [currentSession] diff --git a/src/webview/hooks/chat/use-conversation.ts b/src/webview/hooks/chat/use-conversation.ts index 878b894..b95cbf5 100644 --- a/src/webview/hooks/chat/use-conversation.ts +++ b/src/webview/hooks/chat/use-conversation.ts @@ -1,5 +1,5 @@ +import { ConversationEntity } from '@shared/entities/conversation-entity' import type { Conversation } from '@shared/types/chat-context' -import { getDefaultConversation } from '@shared/utils/get-default-conversation' import { useImmer } from 'use-immer' export const useConversation = ( @@ -7,11 +7,11 @@ export const useConversation = ( initConversation?: Conversation ) => { const [conversation, setConversation] = useImmer( - () => initConversation ?? getDefaultConversation(role) + () => initConversation ?? new ConversationEntity({ role }).entity ) const resetConversation = () => { - setConversation(getDefaultConversation(role)) + setConversation(new ConversationEntity({ role }).entity) } return { diff --git a/src/webview/stores/chat-store.ts b/src/webview/stores/chat-store.ts index 1daa728..39f327e 100644 --- a/src/webview/stores/chat-store.ts +++ b/src/webview/stores/chat-store.ts @@ -1,33 +1,14 @@ import type { ChatSession } from '@shared/entities' -import { - ChatContextType, - type ChatContext, - type Conversation -} from '@shared/types/chat-context' +import { ChatContextEntity } from '@shared/entities/chat-context-entity' +import { type ChatContext, type Conversation } from '@shared/types/chat-context' import { api } from '@webview/services/api-client' import { logAndToastError } from '@webview/utils/common' import { logger } from '@webview/utils/logger' import { produce } from 'immer' import type { DraftFunction } from 'use-immer' -import { v4 as uuidv4 } from 'uuid' import { create } from 'zustand' import { immer } from 'zustand/middleware/immer' -const createDefaultContext = (): ChatContext => ({ - id: uuidv4(), - type: ChatContextType.Chat, - createdAt: Date.now(), - updatedAt: Date.now(), - settings: { - allowLongFileScan: false, - explicitContext: '总是用中文回复', - fastApplyModelName: 'gpt-4o-mini', - modelName: 'gpt-4o', - useFastApply: true - }, - conversations: [] -}) - export type ChatStore = { context: ChatContext chatSessions: ChatSession[] @@ -48,7 +29,7 @@ export type ChatStore = { export const useChatStore = create()( immer((set, get) => ({ - context: createDefaultContext(), + context: new ChatContextEntity().entity, chatSessions: [] as ChatSession[], setContext: contextOrUpdater => { if (typeof contextOrUpdater === 'function') { @@ -81,7 +62,7 @@ export const useChatStore = create()( c => c.id !== id ) }), - resetContext: () => set({ context: createDefaultContext() }), + resetContext: () => set({ context: new ChatContextEntity().entity }), saveSession: async () => { try { await api.chatSession.createOrUpdateSession({ @@ -106,7 +87,7 @@ export const useChatStore = create()( }, createAndSwitchToNewSession: async () => { try { - const newContext = createDefaultContext() + const newContext = new ChatContextEntity().entity const newSession = await api.chatSession.createSession({ chatContext: newContext })