From b2abd7e4faf56a3613dd128f656a3861f3c652f0 Mon Sep 17 00:00:00 2001 From: JinmingYang <2214962083@qq.com> Date: Mon, 4 Nov 2024 14:44:34 +0800 Subject: [PATCH] feat: add entities --- ...l.controller.ts => ai-model-controller.ts} | 10 +- ...ontroller.ts => ai-provider-controller.ts} | 2 +- ...pply.controller.ts => apply-controller.ts} | 0 ...{chat.controller.ts => chat-controller.ts} | 0 ...ntroller.ts => chat-session-controller.ts} | 3 +- ...e.controller.ts => codebase-controller.ts} | 0 .../{doc.controller.ts => doc-controller.ts} | 5 + ...{file.controller.ts => file-controller.ts} | 0 .../{git.controller.ts => git-controller.ts} | 0 .../webview-api/controllers/index.ts | 27 ++ ...s.controller.ts => settings-controller.ts} | 2 +- ...tem.controller.ts => system-controller.ts} | 0 src/extension/webview-api/index.ts | 27 +- .../webview-api/lowdb/ai-model-db.ts | 13 +- .../webview-api/lowdb/ai-provider-db.ts | 31 ++- src/extension/webview-api/lowdb/base-db.ts | 71 +++++- .../webview-api/lowdb/chat-sessions-db.ts | 25 +- .../webview-api/lowdb/doc-sites-db.ts | 35 ++- .../webview-api/lowdb/settings-db.ts | 23 +- src/shared/entities/ai-model-entity.ts | 51 ++++ .../ai-provider-entity.ts} | 72 +++--- src/shared/entities/base-entity.ts | 12 + src/shared/entities/chat-session-entity.ts | 37 +++ src/shared/entities/doc-site-entity.ts | 32 +++ src/shared/entities/index.ts | 6 + .../settings-entity.ts} | 33 +++ .../fs-chat-strategy-provider.ts | 7 +- .../convert-chat-context-to-chat-session.ts | 2 +- src/webview/components/chat/chat-ui.tsx | 3 +- .../components/chat/editor/chat-input.tsx | 7 +- .../chat/messages/roles/chat-ai-message.tsx | 3 +- .../messages/roles/chat-human-message.tsx | 3 +- .../chat/selectors/context-selector.tsx | 7 +- .../ai-model-management/index.tsx | 13 +- .../ai-model-management/manual-model-list.tsx | 2 +- .../model-feature-list.tsx | 2 +- .../ai-model-management/model-item.tsx | 2 +- .../ai-model-management/remote-model-list.tsx | 2 +- .../ai-provider-management/index.tsx | 2 +- .../ai-provider-management/provider-card.tsx | 6 +- .../ai-provider-management/provider-form.tsx | 2 +- .../ai-provider-management/utils.ts | 2 +- .../doc-management/doc-site-card.tsx | 142 +++++++++++ .../doc-management/doc-site-dialog.tsx | 64 +++++ .../custom-renders/doc-management/index.tsx | 241 ++++++++++++++++++ ...doc-management.tsx => doc-management2.tsx} | 0 .../components/settings/settings-config.tsx | 2 +- .../components/settings/settings-page.tsx | 2 +- src/webview/components/settings/settings.tsx | 2 +- src/webview/components/settings/types.ts | 2 +- .../contexts/global-search-context/types.ts | 2 +- .../use-search-categories.tsx | 3 +- src/webview/hooks/chat/use-conversation.ts | 2 +- src/webview/services/api-client/index.ts | 2 +- src/webview/stores/chat-store.ts | 10 +- src/webview/types/chat.ts | 3 - 56 files changed, 892 insertions(+), 167 deletions(-) rename src/extension/webview-api/controllers/{ai-model.controller.ts => ai-model-controller.ts} (94%) rename src/extension/webview-api/controllers/{ai-provider.controller.ts => ai-provider-controller.ts} (93%) rename src/extension/webview-api/controllers/{apply.controller.ts => apply-controller.ts} (100%) rename src/extension/webview-api/controllers/{chat.controller.ts => chat-controller.ts} (100%) rename src/extension/webview-api/controllers/{chat-session.controller.ts => chat-session-controller.ts} (96%) rename src/extension/webview-api/controllers/{codebase.controller.ts => codebase-controller.ts} (100%) rename src/extension/webview-api/controllers/{doc.controller.ts => doc-controller.ts} (96%) rename src/extension/webview-api/controllers/{file.controller.ts => file-controller.ts} (100%) rename src/extension/webview-api/controllers/{git.controller.ts => git-controller.ts} (100%) create mode 100644 src/extension/webview-api/controllers/index.ts rename src/extension/webview-api/controllers/{settings.controller.ts => settings-controller.ts} (96%) rename src/extension/webview-api/controllers/{system.controller.ts => system-controller.ts} (100%) create mode 100644 src/shared/entities/ai-model-entity.ts rename src/shared/{utils/ai-providers.ts => entities/ai-provider-entity.ts} (72%) create mode 100644 src/shared/entities/base-entity.ts create mode 100644 src/shared/entities/chat-session-entity.ts create mode 100644 src/shared/entities/doc-site-entity.ts create mode 100644 src/shared/entities/index.ts rename src/shared/{utils/settings-config.ts => entities/settings-entity.ts} (88%) create mode 100644 src/webview/components/settings/custom-renders/doc-management/doc-site-card.tsx create mode 100644 src/webview/components/settings/custom-renders/doc-management/doc-site-dialog.tsx create mode 100644 src/webview/components/settings/custom-renders/doc-management/index.tsx rename src/webview/components/settings/custom-renders/{doc-management.tsx => doc-management2.tsx} (100%) diff --git a/src/extension/webview-api/controllers/ai-model.controller.ts b/src/extension/webview-api/controllers/ai-model-controller.ts similarity index 94% rename from src/extension/webview-api/controllers/ai-model.controller.ts rename to src/extension/webview-api/controllers/ai-model-controller.ts index 4ab6c2a..0fc0458 100644 --- a/src/extension/webview-api/controllers/ai-model.controller.ts +++ b/src/extension/webview-api/controllers/ai-model-controller.ts @@ -1,10 +1,10 @@ import { logger } from '@extension/logger' -import { +import type { AIModel, - type AIModelFeature, - type AIProvider, - type AIProviderType -} from '@shared/utils/ai-providers' + AIModelFeature, + AIProvider, + AIProviderType +} from '@shared/entities' import { aiModelDB } from '../lowdb/ai-model-db' import { Controller } from '../types' diff --git a/src/extension/webview-api/controllers/ai-provider.controller.ts b/src/extension/webview-api/controllers/ai-provider-controller.ts similarity index 93% rename from src/extension/webview-api/controllers/ai-provider.controller.ts rename to src/extension/webview-api/controllers/ai-provider-controller.ts index 0378a49..5187076 100644 --- a/src/extension/webview-api/controllers/ai-provider.controller.ts +++ b/src/extension/webview-api/controllers/ai-provider-controller.ts @@ -1,4 +1,4 @@ -import { AIProvider } from '@shared/utils/ai-providers' +import type { AIProvider } from '@shared/entities' import { aiProviderDB } from '../lowdb/ai-provider-db' import { Controller } from '../types' diff --git a/src/extension/webview-api/controllers/apply.controller.ts b/src/extension/webview-api/controllers/apply-controller.ts similarity index 100% rename from src/extension/webview-api/controllers/apply.controller.ts rename to src/extension/webview-api/controllers/apply-controller.ts diff --git a/src/extension/webview-api/controllers/chat.controller.ts b/src/extension/webview-api/controllers/chat-controller.ts similarity index 100% rename from src/extension/webview-api/controllers/chat.controller.ts rename to src/extension/webview-api/controllers/chat-controller.ts diff --git a/src/extension/webview-api/controllers/chat-session.controller.ts b/src/extension/webview-api/controllers/chat-session-controller.ts similarity index 96% rename from src/extension/webview-api/controllers/chat-session.controller.ts rename to src/extension/webview-api/controllers/chat-session-controller.ts index 65ddd5e..9754b24 100644 --- a/src/extension/webview-api/controllers/chat-session.controller.ts +++ b/src/extension/webview-api/controllers/chat-session-controller.ts @@ -1,10 +1,11 @@ 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 type { ChatContext, Conversation } from '@shared/types/chat-context' import { convertChatContextToChatSession } from '@shared/utils/convert-chat-context-to-chat-session' -import { ChatSession, chatSessionsDB } from '../lowdb/chat-sessions-db' +import { chatSessionsDB } from '../lowdb/chat-sessions-db' import { Controller } from '../types' export class ChatSessionController extends Controller { diff --git a/src/extension/webview-api/controllers/codebase.controller.ts b/src/extension/webview-api/controllers/codebase-controller.ts similarity index 100% rename from src/extension/webview-api/controllers/codebase.controller.ts rename to src/extension/webview-api/controllers/codebase-controller.ts diff --git a/src/extension/webview-api/controllers/doc.controller.ts b/src/extension/webview-api/controllers/doc-controller.ts similarity index 96% rename from src/extension/webview-api/controllers/doc.controller.ts rename to src/extension/webview-api/controllers/doc-controller.ts index 25e68ad..4b13d79 100644 --- a/src/extension/webview-api/controllers/doc.controller.ts +++ b/src/extension/webview-api/controllers/doc-controller.ts @@ -31,6 +31,11 @@ export class DocController extends Controller { this.disposeResources(request.id) } + async removeDocSites(request: { ids: string[] }) { + await docSitesDB.batchRemove(request.ids) + request.ids.forEach(id => this.disposeResources(id)) + } + async *crawlDocs(request: { id: string options?: Partial diff --git a/src/extension/webview-api/controllers/file.controller.ts b/src/extension/webview-api/controllers/file-controller.ts similarity index 100% rename from src/extension/webview-api/controllers/file.controller.ts rename to src/extension/webview-api/controllers/file-controller.ts diff --git a/src/extension/webview-api/controllers/git.controller.ts b/src/extension/webview-api/controllers/git-controller.ts similarity index 100% rename from src/extension/webview-api/controllers/git.controller.ts rename to src/extension/webview-api/controllers/git-controller.ts diff --git a/src/extension/webview-api/controllers/index.ts b/src/extension/webview-api/controllers/index.ts new file mode 100644 index 0000000..a3c1aa6 --- /dev/null +++ b/src/extension/webview-api/controllers/index.ts @@ -0,0 +1,27 @@ +import type { Controller } from '../types' +import { AIModelController } from './ai-model-controller' +import { AIProviderController } from './ai-provider-controller' +import { ApplyController } from './apply-controller' +import { ChatController } from './chat-controller' +import { ChatSessionController } from './chat-session-controller' +import { CodebaseController } from './codebase-controller' +import { DocController } from './doc-controller' +import { FileController } from './file-controller' +import { GitController } from './git-controller' +import { SettingsController } from './settings-controller' +import { SystemController } from './system-controller' + +export const controllers = [ + ChatController, + CodebaseController, + FileController, + GitController, + SystemController, + DocController, + ChatSessionController, + ApplyController, + SettingsController, + AIProviderController, + AIModelController +] as const satisfies (typeof Controller)[] +export type Controllers = typeof controllers diff --git a/src/extension/webview-api/controllers/settings.controller.ts b/src/extension/webview-api/controllers/settings-controller.ts similarity index 96% rename from src/extension/webview-api/controllers/settings.controller.ts rename to src/extension/webview-api/controllers/settings-controller.ts index bb5ae49..ebddce6 100644 --- a/src/extension/webview-api/controllers/settings.controller.ts +++ b/src/extension/webview-api/controllers/settings-controller.ts @@ -1,4 +1,4 @@ -import type { SettingKey, SettingValue } from '@shared/utils/settings-config' +import type { SettingKey, SettingValue } from '@shared/entities' import { globalSettingsDB, workspaceSettingsDB } from '../lowdb/settings-db' import { Controller } from '../types' diff --git a/src/extension/webview-api/controllers/system.controller.ts b/src/extension/webview-api/controllers/system-controller.ts similarity index 100% rename from src/extension/webview-api/controllers/system.controller.ts rename to src/extension/webview-api/controllers/system-controller.ts diff --git a/src/extension/webview-api/index.ts b/src/extension/webview-api/index.ts index 0a7c42a..523af52 100644 --- a/src/extension/webview-api/index.ts +++ b/src/extension/webview-api/index.ts @@ -5,17 +5,7 @@ import findFreePorts from 'find-free-ports' import { Server } from 'socket.io' import * as vscode from 'vscode' -import { AIModelController } from './controllers/ai-model.controller' -import { AIProviderController } from './controllers/ai-provider.controller' -import { ApplyController } from './controllers/apply.controller' -import { ChatSessionController } from './controllers/chat-session.controller' -import { ChatController } from './controllers/chat.controller' -import { CodebaseController } from './controllers/codebase.controller' -import { DocController } from './controllers/doc.controller' -import { FileController } from './controllers/file.controller' -import { GitController } from './controllers/git.controller' -import { SettingsController } from './controllers/settings.controller' -import { SystemController } from './controllers/system.controller' +import { controllers } from './controllers' import type { Controller, ControllerClass, @@ -122,21 +112,6 @@ class APIManager { } } -export const controllers = [ - ChatController, - CodebaseController, - FileController, - GitController, - SystemController, - DocController, - ChatSessionController, - ApplyController, - SettingsController, - AIProviderController, - AIModelController -] as const -export type Controllers = typeof controllers - export const setupWebviewAPIManager = async ( context: vscode.ExtensionContext, panel: WebviewPanel, diff --git a/src/extension/webview-api/lowdb/ai-model-db.ts b/src/extension/webview-api/lowdb/ai-model-db.ts index 6dcd241..b4bde4a 100644 --- a/src/extension/webview-api/lowdb/ai-model-db.ts +++ b/src/extension/webview-api/lowdb/ai-model-db.ts @@ -1,12 +1,21 @@ import path from 'path' import { aidePaths } from '@extension/file-utils/paths' -import { AIModel } from '@shared/utils/ai-providers' +import { AIModelEntity, type AIModel } from '@shared/entities/ai-model-entity' import { BaseDB } from './base-db' class AIModelDB extends BaseDB { + static readonly schemaVersion = 1 + constructor() { - super(path.join(aidePaths.getGlobalLowdbPath(), 'ai-models.json')) + // Use entity's defaults + const defaults = new AIModelEntity().getDefaults() + + super( + path.join(aidePaths.getGlobalLowdbPath(), 'ai-models.json'), + defaults, + AIModelDB.schemaVersion + ) } } diff --git a/src/extension/webview-api/lowdb/ai-provider-db.ts b/src/extension/webview-api/lowdb/ai-provider-db.ts index 5ee4e3f..2cbac4e 100644 --- a/src/extension/webview-api/lowdb/ai-provider-db.ts +++ b/src/extension/webview-api/lowdb/ai-provider-db.ts @@ -1,11 +1,11 @@ import path from 'path' import { aidePaths } from '@extension/file-utils/paths' +import { AIModelEntity, type AIModel } from '@shared/entities/ai-model-entity' import { - AIProvider, + AIProviderEntity, AIProviderType, - getDefaultAIModel, - type AIModel -} from '@shared/utils/ai-providers' + type AIProvider +} from '@shared/entities/ai-provider-entity' import { aiModelDB } from './ai-model-db' import { BaseDB } from './base-db' @@ -26,12 +26,27 @@ const findNewModel = async ( // Filter out models that don't exist yet return modelsName .filter(name => !existingModelSet.has(name)) - .map(name => getDefaultAIModel(name, providerOrBaseUrl)) + .map( + name => + new AIModelEntity({ + name, + providerOrBaseUrl + }) + ) } class AIProviderDB extends BaseDB { + static readonly schemaVersion = 1 + constructor() { - super(path.join(aidePaths.getGlobalLowdbPath(), 'ai-providers.json')) + // Use entity's defaults + const defaults = new AIProviderEntity().getDefaults() + + super( + path.join(aidePaths.getGlobalLowdbPath(), 'ai-providers.json'), + defaults, + AIProviderDB.schemaVersion + ) } async add( @@ -53,9 +68,7 @@ class AIProviderDB extends BaseDB { ) // Add models one by one to avoid type error - for (const model of newModels) { - await aiModelDB.add(model) - } + await aiModelDB.batchAdd(newModels) return await super.add(item) } diff --git a/src/extension/webview-api/lowdb/base-db.ts b/src/extension/webview-api/lowdb/base-db.ts index 1353433..f8aa027 100644 --- a/src/extension/webview-api/lowdb/base-db.ts +++ b/src/extension/webview-api/lowdb/base-db.ts @@ -1,22 +1,58 @@ +import type { IBaseEntity } from '@shared/entities/base-entity' import { Low } from 'lowdb' import { JSONFile } from 'lowdb/node' import { v4 as uuidv4 } from 'uuid' -export interface BaseItem { - id: string -} +export class BaseDB { + protected db: Low<{ items: T[]; schemaVersion?: number }> + + protected currentVersion: number = 1 -export class BaseDB { - protected db: Low<{ items: T[] }> + protected defaults: Partial = {} - constructor(filePath: string) { - const adapter = new JSONFile<{ items: T[] }>(filePath) + constructor( + filePath: string, + defaults: Partial = {}, + currentVersion: number = 1 + ) { + const adapter = new JSONFile<{ items: T[]; schemaVersion?: number }>( + filePath + ) this.db = new Low(adapter, { items: [] }) + this.defaults = defaults + this.currentVersion = currentVersion } protected async load() { await this.db.read() - this.db.data ||= { items: [] } + this.db.data ||= { items: [], schemaVersion: this.currentVersion } + + if (this.db.data.schemaVersion !== this.currentVersion) { + await this.migrateData() + } + + this.db.data.items = this.db.data.items.map(item => ({ + ...this.defaults, + ...item + })) + } + + protected async migrateData() { + const currentVersion = this.db.data.schemaVersion || 1 + + if (currentVersion < this.currentVersion) { + for (let v = currentVersion; v < this.currentVersion; v++) { + await this.applyMigration(v) + } + } + + this.db.data.schemaVersion = this.currentVersion + await this.db.write() + } + + // eslint-disable-next-line unused-imports/no-unused-vars + protected async applyMigration(fromVersion: number) { + // Override this method in derived classes to implement specific migrations } async getAll(): Promise { @@ -32,12 +68,31 @@ export class BaseDB { return newItem } + async batchAdd(items: (Omit & { id?: string })[]): Promise { + await this.load() + const newItems = items.map(item => ({ + ...item, + id: item.id || uuidv4() + })) as T[] + this.db.data.items.push(...newItems) + await this.db.write() + return newItems + } + async remove(id: string): Promise { await this.load() this.db.data.items = this.db.data.items.filter(item => item.id !== id) await this.db.write() } + async batchRemove(ids: string[]): Promise { + await this.load() + this.db.data.items = this.db.data.items.filter( + item => !ids.includes(item.id) + ) + await this.db.write() + } + async update(id: string, updates: Partial): Promise { await this.load() const item = this.db.data.items.find(i => i.id === id) diff --git a/src/extension/webview-api/lowdb/chat-sessions-db.ts b/src/extension/webview-api/lowdb/chat-sessions-db.ts index 7b2e5cb..eb0d083 100644 --- a/src/extension/webview-api/lowdb/chat-sessions-db.ts +++ b/src/extension/webview-api/lowdb/chat-sessions-db.ts @@ -1,19 +1,24 @@ import path from 'path' import { aidePaths } from '@extension/file-utils/paths' -import { ChatContextType } from '@shared/types/chat-context' +import { + ChatSessionEntity, + type ChatSession +} from '@shared/entities/chat-session-entity' -import { BaseDB, BaseItem } from './base-db' - -export interface ChatSession extends BaseItem { - type: ChatContextType - createdAt: number - updatedAt: number - title: string -} +import { BaseDB } from './base-db' class ChatSessionsDB extends BaseDB { + static readonly schemaVersion = 1 + constructor() { - super(path.join(aidePaths.getWorkspaceLowdbPath(), 'sessions.json')) + // Use entity's defaults + const defaults = new ChatSessionEntity().getDefaults() + + super( + path.join(aidePaths.getWorkspaceLowdbPath(), 'sessions.json'), + defaults, + ChatSessionsDB.schemaVersion + ) } async search(query: string): Promise { diff --git a/src/extension/webview-api/lowdb/doc-sites-db.ts b/src/extension/webview-api/lowdb/doc-sites-db.ts index 6b05f81..9b4d6e0 100644 --- a/src/extension/webview-api/lowdb/doc-sites-db.ts +++ b/src/extension/webview-api/lowdb/doc-sites-db.ts @@ -1,18 +1,19 @@ import path from 'path' import { aidePaths } from '@extension/file-utils/paths' +import { DocSiteEntity, type DocSite } from '@shared/entities/doc-site-entity' -import { BaseDB, BaseItem } from './base-db' - -export interface DocSite extends BaseItem { - name: string - url: string - isCrawled: boolean - isIndexed: boolean -} +import { BaseDB } from './base-db' class DocSitesDB extends BaseDB { + static readonly schemaVersion = 1 + constructor() { - super(path.join(aidePaths.getGlobalLowdbPath(), 'doc-sites.json')) + const defaults = new DocSiteEntity().getDefaults() + super( + path.join(aidePaths.getGlobalLowdbPath(), 'doc-sites.json'), + defaults, + DocSitesDB.schemaVersion + ) } async add( @@ -20,11 +21,17 @@ class DocSitesDB extends BaseDB { id?: string } ): Promise { - return super.add({ - ...item, - isCrawled: false, - isIndexed: false - }) + const docSite = new DocSiteEntity(item) + return super.add(docSite) + } + + async batchAdd( + items: (Omit & { + id?: string + })[] + ): Promise { + const docSites = items.map(item => new DocSiteEntity(item)) + return super.batchAdd(docSites) } async updateStatus( diff --git a/src/extension/webview-api/lowdb/settings-db.ts b/src/extension/webview-api/lowdb/settings-db.ts index 686bc0e..8907179 100644 --- a/src/extension/webview-api/lowdb/settings-db.ts +++ b/src/extension/webview-api/lowdb/settings-db.ts @@ -2,21 +2,23 @@ import path from 'path' import { aidePaths } from '@extension/file-utils/paths' import { settingsConfig, + SettingsEntity, type SettingCategory, type SettingKey, + type Settings, type SettingValue -} from '@shared/utils/settings-config' +} from '@shared/entities/settings-entity' -import { BaseDB, BaseItem } from './base-db' - -export interface Settings extends BaseItem { - key: SettingKey - value: SettingValue - category: SettingCategory - updatedAt: number -} +import { BaseDB } from './base-db' class SettingsDB extends BaseDB { + static readonly schemaVersion = 1 + + constructor(filePath: string) { + const defaults = new SettingsEntity().getDefaults() + super(filePath, defaults, SettingsDB.schemaVersion) + } + async setSetting( key: K, value: SettingValue @@ -32,12 +34,13 @@ class SettingsDB extends BaseDB { }) as Promise } - return this.add({ + const setting = new SettingsEntity({ key, value, category: config.category, updatedAt: Date.now() }) + return this.add(setting) } async getSetting( diff --git a/src/shared/entities/ai-model-entity.ts b/src/shared/entities/ai-model-entity.ts new file mode 100644 index 0000000..0035158 --- /dev/null +++ b/src/shared/entities/ai-model-entity.ts @@ -0,0 +1,51 @@ +import { v4 as uuidv4 } from 'uuid' + +import { AIProviderType } from './ai-provider-entity' +import { BaseEntity, type IBaseEntity } from './base-entity' + +export interface AIModel extends IBaseEntity { + // if the provider is a third party OpenAI-like provider, the value is the base URL + providerOrBaseUrl: AIProviderType | string + name: string + imageSupport: AIModelSupport + audioSupport: AIModelSupport + toolsCallSupport: AIModelSupport +} + +export class AIModelEntity extends BaseEntity implements AIModel { + id!: string + + providerOrBaseUrl!: AIProviderType | string + + name!: string + + imageSupport!: AIModelSupport + + audioSupport!: AIModelSupport + + toolsCallSupport!: AIModelSupport + + getDefaults(): AIModel { + return { + id: uuidv4(), + providerOrBaseUrl: AIProviderType.OpenAI, + name: 'unknown', + imageSupport: 'unknown', + audioSupport: 'unknown', + toolsCallSupport: 'unknown' + } + } +} + +export type AIModelSupport = boolean | 'unknown' + +export type AIModelFeature = keyof Pick< + AIModel, + 'imageSupport' | 'audioSupport' | 'toolsCallSupport' +> + +export const aiModelFeatures = [ + 'imageSupport', + 'audioSupport', + 'toolsCallSupport' +] as const satisfies AIModelFeature[] diff --git a/src/shared/utils/ai-providers.ts b/src/shared/entities/ai-provider-entity.ts similarity index 72% rename from src/shared/utils/ai-providers.ts rename to src/shared/entities/ai-provider-entity.ts index 4f8d166..4ee56bc 100644 --- a/src/shared/utils/ai-providers.ts +++ b/src/shared/entities/ai-provider-entity.ts @@ -1,42 +1,8 @@ import { v4 as uuidv4 } from 'uuid' -export type AIModelSupport = boolean | 'unknown' -export interface AIModel { - id: string - // if the provider is a third party OpenAI-like provider, the value is the base URL - providerOrBaseUrl: AIProviderType | string - name: string - imageSupport: AIModelSupport - audioSupport: AIModelSupport - toolsCallSupport: AIModelSupport -} - -export const getDefaultAIModel = ( - name: string, - providerOrBaseUrl: AIProviderType | string -) => - ({ - id: uuidv4(), - providerOrBaseUrl, - name, - imageSupport: 'unknown', - audioSupport: 'unknown', - toolsCallSupport: 'unknown' - }) satisfies AIModel - -export type AIModelFeature = keyof Pick< - AIModel, - 'imageSupport' | 'audioSupport' | 'toolsCallSupport' -> +import { BaseEntity, type IBaseEntity } from './base-entity' -export const aiModelFeatures = [ - 'imageSupport', - 'audioSupport', - 'toolsCallSupport' -] as const satisfies AIModelFeature[] - -export interface AIProvider { - id: string +export interface AIProvider extends IBaseEntity { name: string type: AIProviderType order: number @@ -46,6 +12,40 @@ export interface AIProvider { 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 { + return { + id: uuidv4(), + name: 'unknown', + type: AIProviderType.OpenAI, + order: 0, + extraFields: {}, + allowRealTimeModels: true, + realTimeModels: [], + manualModels: [] + } + } +} + export enum AIProviderType { OpenAI = 'openai', AzureOpenAI = 'azure-openai', diff --git a/src/shared/entities/base-entity.ts b/src/shared/entities/base-entity.ts new file mode 100644 index 0000000..eaf309e --- /dev/null +++ b/src/shared/entities/base-entity.ts @@ -0,0 +1,12 @@ +export interface IBaseEntity { + id: string + schemaVersion?: number +} + +export abstract class BaseEntity { + constructor(data?: Partial) { + Object.assign(this, this.getDefaults(), data ?? {}) + } + + abstract getDefaults(): Partial +} diff --git a/src/shared/entities/chat-session-entity.ts b/src/shared/entities/chat-session-entity.ts new file mode 100644 index 0000000..020aff1 --- /dev/null +++ b/src/shared/entities/chat-session-entity.ts @@ -0,0 +1,37 @@ +import { ChatContextType } from '@shared/types/chat-context' +import { v4 as uuidv4 } from 'uuid' + +import { BaseEntity, type IBaseEntity } from './base-entity' + +export interface ChatSession extends IBaseEntity { + type: ChatContextType + createdAt: number + updatedAt: number + title: string +} + +export class ChatSessionEntity + extends BaseEntity + implements ChatSession +{ + id!: string + + type!: ChatContextType + + createdAt!: number + + updatedAt!: number + + title!: string + + getDefaults(): ChatSession { + const now = Date.now() + return { + id: uuidv4(), + type: ChatContextType.Chat, + createdAt: now, + updatedAt: now, + title: 'New Chat' + } + } +} diff --git a/src/shared/entities/doc-site-entity.ts b/src/shared/entities/doc-site-entity.ts new file mode 100644 index 0000000..e05fb84 --- /dev/null +++ b/src/shared/entities/doc-site-entity.ts @@ -0,0 +1,32 @@ +import { v4 as uuidv4 } from 'uuid' + +import { BaseEntity, type IBaseEntity } from './base-entity' + +export interface DocSite extends IBaseEntity { + name: string + url: string + isCrawled: boolean + isIndexed: boolean +} + +export class DocSiteEntity extends BaseEntity implements DocSite { + id!: string + + name!: string + + url!: string + + isCrawled!: boolean + + isIndexed!: boolean + + getDefaults(): DocSite { + return { + id: uuidv4(), + name: 'unknown', + url: '', + isCrawled: false, + isIndexed: false + } + } +} diff --git a/src/shared/entities/index.ts b/src/shared/entities/index.ts new file mode 100644 index 0000000..078a0cd --- /dev/null +++ b/src/shared/entities/index.ts @@ -0,0 +1,6 @@ +export * from './ai-model-entity' +export * from './ai-provider-entity' +export * from './base-entity' +export * from './chat-session-entity' +export * from './doc-site-entity' +export * from './settings-entity' diff --git a/src/shared/utils/settings-config.ts b/src/shared/entities/settings-entity.ts similarity index 88% rename from src/shared/utils/settings-config.ts rename to src/shared/entities/settings-entity.ts index e3256d7..add1f3b 100644 --- a/src/shared/utils/settings-config.ts +++ b/src/shared/entities/settings-entity.ts @@ -1,3 +1,36 @@ +import { v4 as uuidv4 } from 'uuid' + +import { BaseEntity, type IBaseEntity } from './base-entity' + +export interface Settings extends IBaseEntity { + key: SettingKey + value: SettingValue + category: SettingCategory + updatedAt: number +} + +export class SettingsEntity extends BaseEntity implements Settings { + id!: string + + key!: SettingKey + + value!: SettingValue + + category!: SettingCategory + + updatedAt!: number + + getDefaults(): Settings { + return { + id: uuidv4(), + key: 'unknown' as SettingKey, + value: 'unknown', + category: 'appearance' as SettingCategory, + updatedAt: Date.now() + } + } +} + export type SettingsSaveType = 'global' | 'workspace' export type SettingsOption = string | { label: string; value: string } diff --git a/src/shared/plugins/fs-plugin/server/chat-strategy/fs-chat-strategy-provider.ts b/src/shared/plugins/fs-plugin/server/chat-strategy/fs-chat-strategy-provider.ts index 741edf5..66a6be0 100644 --- a/src/shared/plugins/fs-plugin/server/chat-strategy/fs-chat-strategy-provider.ts +++ b/src/shared/plugins/fs-plugin/server/chat-strategy/fs-chat-strategy-provider.ts @@ -3,7 +3,10 @@ import { IGNORE_FILETYPES_WITHOUT_IMG } from '@extension/constants' import { createShouldIgnore } from '@extension/file-utils/ignore-patterns' -import { traverseFileOrFolders } from '@extension/file-utils/traverse-fs' +import { + traverseFileOrFolders, + type FileInfo +} from '@extension/file-utils/traverse-fs' import { VsCodeFS } from '@extension/file-utils/vscode-fs' import { logger } from '@extension/logger' import { getWorkspaceFolder } from '@extension/utils' @@ -18,9 +21,9 @@ import type { StructuredTool } from '@langchain/core/tools' import type { ChatStrategyProvider } from '@shared/plugins/base/server/create-provider-manager' import { PluginId } from '@shared/plugins/base/types' import { mergeCodeSnippets } from '@shared/plugins/fs-plugin/server/merge-code-snippets' +import type { ChatContext } from '@shared/types/chat-context' import type { Conversation } from '@shared/types/chat-context/conversation' import { removeDuplicates } from '@shared/utils/common' -import type { ChatContext, FileInfo } from '@webview/types/chat' import type { FsPluginState } from '../../types' import { diff --git a/src/shared/utils/convert-chat-context-to-chat-session.ts b/src/shared/utils/convert-chat-context-to-chat-session.ts index 3a0ee2a..20b129a 100644 --- a/src/shared/utils/convert-chat-context-to-chat-session.ts +++ b/src/shared/utils/convert-chat-context-to-chat-session.ts @@ -1,5 +1,5 @@ +import type { ChatSession } from '@shared/entities' import { type ChatContext } from '@shared/types/chat-context' -import type { ChatSession } from '@webview/types/chat' import { getTitleFromConversations } from './get-title-from-conversations' diff --git a/src/webview/components/chat/chat-ui.tsx b/src/webview/components/chat/chat-ui.tsx index 1910bb7..08294f4 100644 --- a/src/webview/components/chat/chat-ui.tsx +++ b/src/webview/components/chat/chat-ui.tsx @@ -1,11 +1,10 @@ import { useRef, type FC } from 'react' import { GearIcon, MagnifyingGlassIcon, PlusIcon } from '@radix-ui/react-icons' -import { ChatContextType } from '@shared/types/chat-context' +import { ChatContextType, type Conversation } from '@shared/types/chat-context' import { useChatContext } from '@webview/contexts/chat-context' import { useGlobalSearch } from '@webview/contexts/global-search-context' import { useChatState } from '@webview/hooks/chat/use-chat-state' import { api } from '@webview/services/api-client' -import type { Conversation } from '@webview/types/chat' import { logger } from '@webview/utils/logger' import { useNavigate } from 'react-router' diff --git a/src/webview/components/chat/editor/chat-input.tsx b/src/webview/components/chat/editor/chat-input.tsx index f32949a..33606eb 100644 --- a/src/webview/components/chat/editor/chat-input.tsx +++ b/src/webview/components/chat/editor/chat-input.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, type FC } from 'react' import type { PluginId } from '@shared/plugins/base/types' +import type { ChatContext, Conversation } from '@shared/types/chat-context' import { tryParseJSON, tryStringifyJSON } from '@shared/utils/common' import { convertToLangchainMessageContents } from '@shared/utils/convert-to-langchain-message-contents' import { getAllTextFromLangchainMessageContents } from '@shared/utils/get-all-text-from-langchain-message-contents' @@ -12,11 +13,7 @@ import { import { useMentionOptions } from '@webview/hooks/chat/use-mention-options' import { usePluginFilesSelectorProviders } from '@webview/hooks/chat/use-plugin-providers' import { useCloneState } from '@webview/hooks/use-clone-state' -import { - type ChatContext, - type Conversation, - type FileInfo -} from '@webview/types/chat' +import { type FileInfo } from '@webview/types/chat' import { cn } from '@webview/utils/common' import { updatePluginStatesFromEditorState } from '@webview/utils/plugin-states' import { diff --git a/src/webview/components/chat/messages/roles/chat-ai-message.tsx b/src/webview/components/chat/messages/roles/chat-ai-message.tsx index 7be78f3..ba9fa7a 100644 --- a/src/webview/components/chat/messages/roles/chat-ai-message.tsx +++ b/src/webview/components/chat/messages/roles/chat-ai-message.tsx @@ -1,7 +1,8 @@ import type { CSSProperties, FC } from 'react' +import type { Conversation } from '@shared/types/chat-context' import { getAllTextFromLangchainMessageContents } from '@shared/utils/get-all-text-from-langchain-message-contents' import { GlowingCard } from '@webview/components/glowing-card' -import type { Conversation, ConversationUIState } from '@webview/types/chat' +import type { ConversationUIState } from '@webview/types/chat' import { cn } from '@webview/utils/common' import { Markdown } from '../markdown' diff --git a/src/webview/components/chat/messages/roles/chat-human-message.tsx b/src/webview/components/chat/messages/roles/chat-human-message.tsx index 2e17147..7650e41 100644 --- a/src/webview/components/chat/messages/roles/chat-human-message.tsx +++ b/src/webview/components/chat/messages/roles/chat-human-message.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, type CSSProperties, type FC } from 'react' +import type { Conversation } from '@shared/types/chat-context' import { ChatInput, ChatInputMode, @@ -7,7 +8,7 @@ import { } from '@webview/components/chat/editor/chat-input' import { GlowingCard } from '@webview/components/glowing-card' import { useConversation } from '@webview/hooks/chat/use-conversation' -import type { Conversation, ConversationUIState } from '@webview/types/chat' +import type { ConversationUIState } from '@webview/types/chat' import { cn } from '@webview/utils/common' export interface ChatHumanMessageProps diff --git a/src/webview/components/chat/selectors/context-selector.tsx b/src/webview/components/chat/selectors/context-selector.tsx index c449e53..9303b53 100644 --- a/src/webview/components/chat/selectors/context-selector.tsx +++ b/src/webview/components/chat/selectors/context-selector.tsx @@ -1,13 +1,10 @@ /* eslint-disable unused-imports/no-unused-vars */ import React, { useState } from 'react' import { ImageIcon } from '@radix-ui/react-icons' +import type { ChatContext, Conversation } from '@shared/types/chat-context' import { Button } from '@webview/components/ui/button' import { usePluginImagesSelectorProviders } from '@webview/hooks/chat/use-plugin-providers' -import { - type ChatContext, - type Conversation, - type ModelOption -} from '@webview/types/chat' +import { type ModelOption } from '@webview/types/chat' import type { Updater } from 'use-immer' import { ModelSelector } from './model-selector' 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 5b03b2b..597c456 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 @@ -1,11 +1,11 @@ import { useState } from 'react' import { + AIModelEntity, AIProviderType, - getDefaultAIModel, type AIModel, type AIModelFeature, type AIProvider -} from '@shared/utils/ai-providers' +} from '@shared/entities' import { removeDuplicates } from '@shared/utils/common' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { api } from '@webview/services/api-client' @@ -16,6 +16,15 @@ import { CreateModelDialog } from './create-model-dialog' import { ManualModelList } from './manual-model-list' import { RemoteModelList } from './remote-model-list' +const getDefaultAIModel = ( + name: string, + providerOrBaseUrl: AIProviderType | string +): AIModel => + new AIModelEntity({ + name, + providerOrBaseUrl + }) + export const AIModelManagement = ({ className, provider, diff --git a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/manual-model-list.tsx b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/manual-model-list.tsx index ceb85fa..ce3b2bb 100644 --- a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/manual-model-list.tsx +++ b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/manual-model-list.tsx @@ -1,4 +1,4 @@ -import { type AIModel, type AIModelFeature } from '@shared/utils/ai-providers' +import type { AIModel, AIModelFeature } from '@shared/entities' import { CardList } from '@webview/components/ui/card-list' import { ModelFeatureList } from './model-feature-list' diff --git a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/model-feature-list.tsx b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/model-feature-list.tsx index 75b6007..d914223 100644 --- a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/model-feature-list.tsx +++ b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/model-feature-list.tsx @@ -10,7 +10,7 @@ import { aiModelFeatures, type AIModel, type AIModelFeature -} from '@shared/utils/ai-providers' +} from '@shared/entities' import { Button } from '@webview/components/ui/button' interface ModelFeatureListProps { diff --git a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/model-item.tsx b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/model-item.tsx index cc7b5dc..89ca597 100644 --- a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/model-item.tsx +++ b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/model-item.tsx @@ -1,6 +1,6 @@ import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities' import { DragHandleDots2Icon } from '@radix-ui/react-icons' -import { type AIModel } from '@shared/utils/ai-providers' +import type { AIModel } from '@shared/entities' import { Checkbox } from '@webview/components/ui/checkbox' interface ModelItemProps { diff --git a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/remote-model-list.tsx b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/remote-model-list.tsx index ca290b3..326aeaa 100644 --- a/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/remote-model-list.tsx +++ b/src/webview/components/settings/custom-renders/ai-provider-management/ai-model-management/remote-model-list.tsx @@ -1,5 +1,5 @@ import { ReloadIcon } from '@radix-ui/react-icons' -import { type AIModel, type AIModelFeature } from '@shared/utils/ai-providers' +import type { AIModel, AIModelFeature } from '@shared/entities' import { Button } from '@webview/components/ui/button' import { CardList } from '@webview/components/ui/card-list' import { Switch } from '@webview/components/ui/switch' diff --git a/src/webview/components/settings/custom-renders/ai-provider-management/index.tsx b/src/webview/components/settings/custom-renders/ai-provider-management/index.tsx index 318d5a1..8c75dbb 100644 --- a/src/webview/components/settings/custom-renders/ai-provider-management/index.tsx +++ b/src/webview/components/settings/custom-renders/ai-provider-management/index.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { type AIProvider } from '@shared/utils/ai-providers' +import type { AIProvider } from '@shared/entities' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { CardList } from '@webview/components/ui/card-list' import { diff --git a/src/webview/components/settings/custom-renders/ai-provider-management/provider-card.tsx b/src/webview/components/settings/custom-renders/ai-provider-management/provider-card.tsx index 0296c52..153fb0a 100644 --- a/src/webview/components/settings/custom-renders/ai-provider-management/provider-card.tsx +++ b/src/webview/components/settings/custom-renders/ai-provider-management/provider-card.tsx @@ -6,7 +6,7 @@ import { EyeOpenIcon, Pencil2Icon } from '@radix-ui/react-icons' -import { aiProviderConfigs, type AIProvider } from '@shared/utils/ai-providers' +import { aiProviderConfigs, type AIProvider } from '@shared/entities' import { Button } from '@webview/components/ui/button' import { Checkbox } from '@webview/components/ui/checkbox' @@ -54,7 +54,9 @@ export const ProviderCard = ({ )}
-

{provider.name}

+

+ {provider.name} +

+ + ) + + const renderField = (label: string, content: React.ReactNode) => ( +
+
{label}
+
{content}
+
+ ) + + return ( +
+
+
+ {onSelect && ( + + )} +

{site.name}

+
+
+ + +
+
+ +
+ {renderField( + 'URL', + + + + + {site.url} + + )} +
+ +
+ {renderProgressSection('Crawl', crawlingProgress, site.isCrawled, () => + onCrawl(site.id) + )} + {renderProgressSection('Index', indexingProgress, site.isIndexed, () => + onReindex(site.id) + )} +
+
+ ) +} diff --git a/src/webview/components/settings/custom-renders/doc-management/doc-site-dialog.tsx b/src/webview/components/settings/custom-renders/doc-management/doc-site-dialog.tsx new file mode 100644 index 0000000..ce89a1f --- /dev/null +++ b/src/webview/components/settings/custom-renders/doc-management/doc-site-dialog.tsx @@ -0,0 +1,64 @@ +import { ReloadIcon } from '@radix-ui/react-icons' +import type { DocSite } from '@shared/entities' +import { Button } from '@webview/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@webview/components/ui/dialog' +import { Input } from '@webview/components/ui/input' + +interface DocSiteDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + loading: boolean + siteName: string + siteUrl: string + onSiteNameChange: (value: string) => void + onSiteUrlChange: (value: string) => void + onSave: () => void + editingSite?: DocSite +} + +export const DocSiteDialog = ({ + open, + onOpenChange, + loading, + siteName, + siteUrl, + onSiteNameChange, + onSiteUrlChange, + onSave, + editingSite +}: DocSiteDialogProps) => ( + + + + + {editingSite ? 'Edit Doc Site' : 'Add New Doc Site'} + + + +
+ onSiteNameChange(e.target.value)} + className="text-sm" + /> + onSiteUrlChange(e.target.value)} + className="text-sm" + /> + +
+
+
+) diff --git a/src/webview/components/settings/custom-renders/doc-management/index.tsx b/src/webview/components/settings/custom-renders/doc-management/index.tsx new file mode 100644 index 0000000..32c03c9 --- /dev/null +++ b/src/webview/components/settings/custom-renders/doc-management/index.tsx @@ -0,0 +1,241 @@ +import { useState } from 'react' +import type { DocSite } from '@shared/entities' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { CardList } from '@webview/components/ui/card-list' +import { Input } from '@webview/components/ui/input' +import { api } from '@webview/services/api-client' +import type { ProgressInfo } from '@webview/types/chat' +import { logAndToastError } from '@webview/utils/common' +import { toast } from 'sonner' + +import { DocSiteCard } from './doc-site-card' +import { DocSiteDialog } from './doc-site-dialog' + +// Query key for doc sites +const docSitesQueryKey = ['docSites'] as const + +export const DocManagement = () => { + const queryClient = useQueryClient() + const [siteName, setSiteName] = useState('') + const [siteUrl, setSiteUrl] = useState('') + const [editingSiteId, setEditingSiteId] = useState(null) + const [crawlingProgress, setCrawlingProgress] = useState< + Record + >({}) + const [indexingProgress, setIndexingProgress] = useState< + Record + >({}) + const [searchQuery, setSearchQuery] = useState('') + const [isDialogOpen, setIsDialogOpen] = useState(false) + + // Queries + const { data: docSites = [] } = useQuery({ + queryKey: [...docSitesQueryKey, searchQuery], + queryFn: () => + searchQuery + ? api.doc.searchDocSites(searchQuery) + : api.doc.getDocSites({}) + }) + + // Mutations + const addSiteMutation = useMutation({ + mutationFn: (data: { name: string; url: string }) => + api.doc.addDocSite(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: docSitesQueryKey }) + toast.success('New doc site added successfully') + handleCloseDialog() + }, + onError: error => { + logAndToastError('Failed to add doc site', error) + } + }) + + const updateSiteMutation = useMutation({ + mutationFn: (data: { id: string; name: string; url: string }) => + api.doc.updateDocSite(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: docSitesQueryKey }) + toast.success('Doc site updated successfully') + handleCloseDialog() + }, + onError: error => { + logAndToastError('Failed to update doc site', error) + } + }) + + const removeSitesMutation = useMutation({ + mutationFn: (ids: string[]) => api.doc.removeDocSites({ ids }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: docSitesQueryKey }) + toast.success('Doc site removed successfully') + }, + onError: error => { + logAndToastError('Failed to remove doc site', error) + } + }) + + const crawlSiteMutation = useMutation({ + mutationFn: async ({ id }: { id: string }) => { + setCrawlingProgress(prev => ({ ...prev, [id]: 0 })) + await api.doc.crawlDocs({ id }, (progress: ProgressInfo) => + updateProgress(progress, id, setCrawlingProgress) + ) + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: docSitesQueryKey }) + toast.success('Document crawling completed') + }, + onError: error => { + logAndToastError('Crawling failed', error) + } + }) + + const reindexSiteMutation = useMutation({ + mutationFn: async ({ id }: { id: string }) => { + setIndexingProgress(prev => ({ ...prev, [id]: 0 })) + await api.doc.reindexDocs({ id }, (progress: ProgressInfo) => + updateProgress(progress, id, setIndexingProgress) + ) + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: docSitesQueryKey }) + toast.success('Document reindexing completed') + }, + onError: error => { + logAndToastError('Reindexing failed', error) + } + }) + + const handleSaveSite = async () => { + if (!siteName || !siteUrl) return + + if (editingSiteId) { + updateSiteMutation.mutate({ + id: editingSiteId, + name: siteName, + url: siteUrl + }) + } else { + addSiteMutation.mutate({ + name: siteName, + url: siteUrl + }) + } + } + + const handleEditSite = (site: DocSite) => { + setEditingSiteId(site.id) + setSiteName(site.name) + setSiteUrl(site.url) + setIsDialogOpen(true) + } + + const handleOpenDialog = () => { + clearSiteFields() + setIsDialogOpen(true) + } + + const handleCloseDialog = () => { + setIsDialogOpen(false) + clearSiteFields() + } + + const handleRemoveSites = (items: DocSite[]) => { + removeSitesMutation.mutate(items.map(item => item.id)) + } + + const handleSearch = (query: string) => { + setSearchQuery(query) + } + + const clearSiteFields = () => { + setSiteName('') + setSiteUrl('') + setEditingSiteId(null) + } + + const updateProgress = ( + progress: ProgressInfo, + id: string, + setProgress: React.Dispatch>> + ) => { + setProgress(prev => ({ + ...prev, + [id]: Math.round((progress.processedItems / progress.totalItems) * 100) + })) + } + + return ( +
+ {/*
+ handleSearch(e.target.value)} + className="text-xs h-8" + /> + +
*/} + + handleSearch(e.target.value)} + className="text-xs h-8" + /> + } + renderCard={({ item: site, isSelected, onSelect }) => ( + handleRemoveSites([site])} + onCrawl={() => crawlSiteMutation.mutate({ id: site.id })} + onReindex={() => reindexSiteMutation.mutate({ id: site.id })} + isSelected={isSelected} + onSelect={onSelect} + /> + )} + /> + + site.id === editingSiteId) + : undefined + } + /> +
+ ) +} diff --git a/src/webview/components/settings/custom-renders/doc-management.tsx b/src/webview/components/settings/custom-renders/doc-management2.tsx similarity index 100% rename from src/webview/components/settings/custom-renders/doc-management.tsx rename to src/webview/components/settings/custom-renders/doc-management2.tsx diff --git a/src/webview/components/settings/settings-config.tsx b/src/webview/components/settings/settings-config.tsx index b3d8c27..04890a3 100644 --- a/src/webview/components/settings/settings-config.tsx +++ b/src/webview/components/settings/settings-config.tsx @@ -2,7 +2,7 @@ import { settingsConfig as sharedSettingsConfig, type SettingCategory, type SettingsConfigItem -} from '@shared/utils/settings-config' +} from '@shared/entities' import { AIProviderManagement } from './custom-renders/ai-provider-management' import { CodebaseIndexing } from './custom-renders/codebase' diff --git a/src/webview/components/settings/settings-page.tsx b/src/webview/components/settings/settings-page.tsx index 8e9de46..6610c11 100644 --- a/src/webview/components/settings/settings-page.tsx +++ b/src/webview/components/settings/settings-page.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import type { SettingsSaveType } from '@shared/utils/settings-config' +import type { SettingsSaveType } from '@shared/entities' import { api } from '@webview/services/api-client' import { useNavigate, useSearchParams } from 'react-router-dom' diff --git a/src/webview/components/settings/settings.tsx b/src/webview/components/settings/settings.tsx index d411d8f..58e7b7a 100644 --- a/src/webview/components/settings/settings.tsx +++ b/src/webview/components/settings/settings.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, type FC } from 'react' import { ChevronDownIcon, ChevronRightIcon } from '@radix-ui/react-icons' -import type { SettingsSaveType } from '@shared/utils/settings-config' +import type { SettingsSaveType } from '@shared/entities' import { Button } from '@webview/components/ui/button' import { Input } from '@webview/components/ui/input' import { ScrollArea } from '@webview/components/ui/scroll-area' diff --git a/src/webview/components/settings/types.ts b/src/webview/components/settings/types.ts index 8897ae2..2fa620c 100644 --- a/src/webview/components/settings/types.ts +++ b/src/webview/components/settings/types.ts @@ -1,4 +1,4 @@ -import type { SettingsSaveType } from '@shared/utils/settings-config' +import type { SettingsSaveType } from '@shared/entities' export interface SettingItem { saveType: SettingsSaveType diff --git a/src/webview/contexts/global-search-context/types.ts b/src/webview/contexts/global-search-context/types.ts index c929d44..4f809a3 100644 --- a/src/webview/contexts/global-search-context/types.ts +++ b/src/webview/contexts/global-search-context/types.ts @@ -1,4 +1,4 @@ -import type { ChatSession } from '@extension/webview-api/lowdb/chat-sessions-db' +import type { ChatSession } from '@shared/entities' import type { SettingItem } from '@webview/components/settings/types' export interface SearchResult { diff --git a/src/webview/contexts/global-search-context/use-search-categories.tsx b/src/webview/contexts/global-search-context/use-search-categories.tsx index d03053b..0958a31 100644 --- a/src/webview/contexts/global-search-context/use-search-categories.tsx +++ b/src/webview/contexts/global-search-context/use-search-categories.tsx @@ -1,10 +1,11 @@ import { ChatBubbleIcon, GearIcon } from '@radix-ui/react-icons' +import type { ChatSession } from '@shared/entities' +import { ChatContextType } from '@shared/types/chat-context' import type { SearchCategory, SearchItem } from '@webview/components/global-search/global-search' import type { SettingItem } from '@webview/components/settings/types' -import { ChatContextType, type ChatSession } from '@webview/types/chat' import { useNavigate } from 'react-router' import { useChatContext } from '../chat-context' diff --git a/src/webview/hooks/chat/use-conversation.ts b/src/webview/hooks/chat/use-conversation.ts index 11eb0ba..878b894 100644 --- a/src/webview/hooks/chat/use-conversation.ts +++ b/src/webview/hooks/chat/use-conversation.ts @@ -1,5 +1,5 @@ +import type { Conversation } from '@shared/types/chat-context' import { getDefaultConversation } from '@shared/utils/get-default-conversation' -import type { Conversation } from '@webview/types/chat' import { useImmer } from 'use-immer' export const useConversation = ( diff --git a/src/webview/services/api-client/index.ts b/src/webview/services/api-client/index.ts index b9fa440..7207823 100644 --- a/src/webview/services/api-client/index.ts +++ b/src/webview/services/api-client/index.ts @@ -1,4 +1,4 @@ -import type { Controllers } from '@extension/webview-api' +import type { Controllers } from '@extension/webview-api/controllers' import type { ControllerClass } from '@extension/webview-api/types' import { io, Socket } from 'socket.io-client' diff --git a/src/webview/stores/chat-store.ts b/src/webview/stores/chat-store.ts index 18602f7..1daa728 100644 --- a/src/webview/stores/chat-store.ts +++ b/src/webview/stores/chat-store.ts @@ -1,10 +1,10 @@ -import { api } from '@webview/services/api-client' +import type { ChatSession } from '@shared/entities' import { - ChatContext, ChatContextType, - ChatSession, - Conversation -} from '@webview/types/chat' + 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' diff --git a/src/webview/types/chat.ts b/src/webview/types/chat.ts index 8e5c4e6..eb7a7a1 100644 --- a/src/webview/types/chat.ts +++ b/src/webview/types/chat.ts @@ -2,10 +2,7 @@ import type { FC } from 'react' import type { Conversation } from '@shared/types/chat-context' import type { MentionItemLayoutProps } from '@webview/components/chat/selectors/mention-selector/mention-item-layout' -export type { ChatSession } from '@extension/webview-api/lowdb/chat-sessions-db' -export type { DocSite } from '@extension/webview-api/lowdb/doc-sites-db' export type { ProgressInfo } from '@extension/webview-api/chat-context-processor/utils/progress-reporter' -export * from '@shared/types/chat-context' export type { FileInfo, FolderInfo } from '@extension/file-utils/traverse-fs' export interface ModelOption {