mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-05-09 08:30:09 +00:00
update workspace
This commit is contained in:
@@ -3,4 +3,5 @@ export const COMMAND_DIR = 'commands'
|
||||
export const CHAT_DIR = 'chats'
|
||||
export const CUSTOM_MODE_DIR = 'custom_modes'
|
||||
export const CONVERT_DATA_DIR = 'convert_data'
|
||||
export const WORKSPACE_DIR = 'workspaces'
|
||||
export const INITIAL_MIGRATION_MARKER = '.initial_migration_completed'
|
||||
|
||||
185
src/database/json/workspace/WorkspaceManager.ts
Normal file
185
src/database/json/workspace/WorkspaceManager.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { App } from 'obsidian'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { AbstractJsonRepository } from '../base'
|
||||
import { ROOT_DIR, WORKSPACE_DIR } from '../constants'
|
||||
|
||||
import {
|
||||
WORKSPACE_SCHEMA_VERSION,
|
||||
Workspace,
|
||||
WorkspaceMetadata
|
||||
} from './types'
|
||||
|
||||
export class WorkspaceManager extends AbstractJsonRepository<
|
||||
Workspace,
|
||||
WorkspaceMetadata
|
||||
> {
|
||||
constructor(app: App) {
|
||||
super(app, `${ROOT_DIR}/${WORKSPACE_DIR}`)
|
||||
}
|
||||
|
||||
protected generateFileName(workspace: Workspace): string {
|
||||
// Format: v{schemaVersion}_{name}_{updatedAt}_{id}.json
|
||||
const encodedName = encodeURIComponent(workspace.name)
|
||||
return `v${workspace.schemaVersion}_${encodedName}_${workspace.updatedAt}_${workspace.id}.json`
|
||||
}
|
||||
|
||||
protected parseFileName(fileName: string): WorkspaceMetadata | null {
|
||||
// Parse: v{schemaVersion}_{name}_{updatedAt}_{id}.json
|
||||
const regex = new RegExp(
|
||||
`^v${WORKSPACE_SCHEMA_VERSION}_(.+)_(\\d+)_([0-9a-f-]+)\\.json$`,
|
||||
)
|
||||
const match = fileName.match(regex)
|
||||
if (!match) return null
|
||||
|
||||
const name = decodeURIComponent(match[1])
|
||||
const updatedAt = parseInt(match[2], 10)
|
||||
const id = match[3]
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
updatedAt,
|
||||
createdAt: 0,
|
||||
schemaVersion: WORKSPACE_SCHEMA_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
public async createWorkspace(
|
||||
initialData: Partial<Workspace>,
|
||||
): Promise<Workspace> {
|
||||
const now = Date.now()
|
||||
const newWorkspace: Workspace = {
|
||||
id: uuidv4(),
|
||||
name: 'New Workspace',
|
||||
content: [],
|
||||
chatHistory: [],
|
||||
metadata: {},
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
schemaVersion: WORKSPACE_SCHEMA_VERSION,
|
||||
...initialData,
|
||||
}
|
||||
|
||||
await this.create(newWorkspace)
|
||||
return newWorkspace
|
||||
}
|
||||
|
||||
public async findById(id: string): Promise<Workspace | null> {
|
||||
const allMetadata = await this.listMetadata()
|
||||
const targetMetadata = allMetadata.find((meta) => meta.id === id)
|
||||
|
||||
if (!targetMetadata) return null
|
||||
|
||||
return this.read(targetMetadata.fileName)
|
||||
}
|
||||
|
||||
public async findByName(name: string): Promise<Workspace | null> {
|
||||
const allMetadata = await this.listMetadata()
|
||||
const targetMetadata = allMetadata.find((meta) => meta.name === name)
|
||||
|
||||
if (!targetMetadata) return null
|
||||
|
||||
return this.read(targetMetadata.fileName)
|
||||
}
|
||||
|
||||
public async updateWorkspace(
|
||||
id: string,
|
||||
updates: Partial<
|
||||
Omit<Workspace, 'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'>
|
||||
>,
|
||||
): Promise<Workspace | null> {
|
||||
const workspace = await this.findById(id)
|
||||
if (!workspace) return null
|
||||
|
||||
const updatedWorkspace: Workspace = {
|
||||
...workspace,
|
||||
...updates,
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
|
||||
await this.update(workspace, updatedWorkspace)
|
||||
return updatedWorkspace
|
||||
}
|
||||
|
||||
public async deleteWorkspace(id: string): Promise<boolean> {
|
||||
const allMetadata = await this.listMetadata()
|
||||
const targetMetadata = allMetadata.find((meta) => meta.id === id)
|
||||
if (!targetMetadata) return false
|
||||
|
||||
await this.delete(targetMetadata.fileName)
|
||||
return true
|
||||
}
|
||||
|
||||
public async listWorkspaces(): Promise<WorkspaceMetadata[]> {
|
||||
const metadata = await this.listMetadata()
|
||||
const sorted = metadata.sort((a, b) => b.updatedAt - a.updatedAt)
|
||||
return sorted
|
||||
}
|
||||
|
||||
public async addChatToWorkspace(
|
||||
workspaceId: string,
|
||||
chatId: string,
|
||||
chatTitle: string
|
||||
): Promise<Workspace | null> {
|
||||
const workspace = await this.findById(workspaceId)
|
||||
if (!workspace) return null
|
||||
|
||||
const existingChatIndex = workspace.chatHistory.findIndex(
|
||||
chat => chat.id === chatId
|
||||
)
|
||||
|
||||
if (existingChatIndex >= 0) {
|
||||
// 更新已存在的聊天标题
|
||||
workspace.chatHistory[existingChatIndex].title = chatTitle
|
||||
} else {
|
||||
// 添加新聊天
|
||||
workspace.chatHistory.push({ id: chatId, title: chatTitle })
|
||||
}
|
||||
|
||||
return this.updateWorkspace(workspaceId, {
|
||||
chatHistory: workspace.chatHistory
|
||||
})
|
||||
}
|
||||
|
||||
public async removeChatFromWorkspace(
|
||||
workspaceId: string,
|
||||
chatId: string
|
||||
): Promise<Workspace | null> {
|
||||
const workspace = await this.findById(workspaceId)
|
||||
if (!workspace) return null
|
||||
|
||||
workspace.chatHistory = workspace.chatHistory.filter(
|
||||
chat => chat.id !== chatId
|
||||
)
|
||||
|
||||
return this.updateWorkspace(workspaceId, {
|
||||
chatHistory: workspace.chatHistory
|
||||
})
|
||||
}
|
||||
|
||||
public async ensureDefaultVaultWorkspace(): Promise<Workspace> {
|
||||
// 检查是否已存在默认的 vault 工作区
|
||||
const existingVault = await this.findByName('vault')
|
||||
if (existingVault) {
|
||||
return existingVault
|
||||
}
|
||||
|
||||
// 创建默认的 vault 工作区
|
||||
const defaultWorkspace = await this.createWorkspace({
|
||||
name: 'vault',
|
||||
content: [
|
||||
{
|
||||
type: 'folder',
|
||||
content: '/' // 整个 vault 根目录
|
||||
}
|
||||
],
|
||||
metadata: {
|
||||
isDefault: true,
|
||||
description: 'all vault as workspace'
|
||||
}
|
||||
})
|
||||
|
||||
return defaultWorkspace
|
||||
}
|
||||
}
|
||||
2
src/database/json/workspace/index.ts
Normal file
2
src/database/json/workspace/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './types'
|
||||
export * from './WorkspaceManager'
|
||||
30
src/database/json/workspace/types.ts
Normal file
30
src/database/json/workspace/types.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export const WORKSPACE_SCHEMA_VERSION = 1
|
||||
|
||||
export interface WorkspaceContent {
|
||||
type: 'tag' | 'folder'
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface WorkspaceChatHistory {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export interface Workspace {
|
||||
id: string
|
||||
name: string
|
||||
content: WorkspaceContent[]
|
||||
chatHistory: WorkspaceChatHistory[]
|
||||
metadata: Record<string, any>
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
schemaVersion: number
|
||||
}
|
||||
|
||||
export interface WorkspaceMetadata {
|
||||
id: string
|
||||
name: string
|
||||
updatedAt: number
|
||||
createdAt: number
|
||||
schemaVersion: number
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { backOff } from 'exponential-backoff'
|
||||
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'
|
||||
import { MarkdownTextSplitter } from 'langchain/text_splitter'
|
||||
import { minimatch } from 'minimatch'
|
||||
import { App, Notice, TFile } from 'obsidian'
|
||||
import pLimit from 'p-limit'
|
||||
@@ -111,17 +111,10 @@ export class VectorManager {
|
||||
return
|
||||
}
|
||||
|
||||
const textSplitter = RecursiveCharacterTextSplitter.fromLanguage(
|
||||
'markdown',
|
||||
{
|
||||
chunkSize: options.chunkSize,
|
||||
// TODO: Use token-based chunking after migrating to WebAssembly-based tiktoken
|
||||
// Current token counting method is too slow for practical use
|
||||
// lengthFunction: async (text) => {
|
||||
// return await tokenCount(text)
|
||||
// },
|
||||
},
|
||||
)
|
||||
const textSplitter = new MarkdownTextSplitter({
|
||||
chunkSize: options.chunkSize,
|
||||
chunkOverlap: Math.floor(options.chunkSize * 0.15)
|
||||
})
|
||||
|
||||
const skippedFiles: string[] = []
|
||||
const contentChunks: InsertVector[] = (
|
||||
@@ -323,12 +316,10 @@ export class VectorManager {
|
||||
)
|
||||
|
||||
// Embed the files
|
||||
const textSplitter = RecursiveCharacterTextSplitter.fromLanguage(
|
||||
'markdown',
|
||||
{
|
||||
chunkSize,
|
||||
},
|
||||
)
|
||||
const textSplitter = new MarkdownTextSplitter({
|
||||
chunkSize: chunkSize,
|
||||
chunkOverlap: Math.floor(chunkSize * 0.15)
|
||||
});
|
||||
let fileContent = await this.app.vault.cachedRead(file)
|
||||
// 清理null字节,防止PostgreSQL UTF8编码错误
|
||||
fileContent = fileContent.replace(/\0/g, '')
|
||||
|
||||
Reference in New Issue
Block a user