update commnad name

This commit is contained in:
duanfuxiang 2025-04-24 16:22:46 +08:00
parent 96b9fcef3b
commit ecbe1725aa
9 changed files with 191 additions and 191 deletions

View File

@ -12,9 +12,9 @@ export class DatabaseNotInitializedException extends DatabaseException {
}
}
export class DuplicateTemplateException extends DatabaseException {
constructor(templateName: string) {
super(`Template with name "${templateName}" already exists`)
this.name = 'DuplicateTemplateException'
export class DuplicateCommandException extends DatabaseException {
constructor(commandName: string) {
super(`Command with name "${commandName}" already exists`)
this.name = 'DuplicateCommandException'
}
}

View File

@ -0,0 +1,148 @@
import fuzzysort from 'fuzzysort'
import { App } from 'obsidian'
import { v4 as uuidv4 } from 'uuid'
import { AbstractJsonRepository } from '../base'
import { COMMAND_DIR, ROOT_DIR } from '../constants'
import {
DuplicateCommandException,
EmptyCommandNameException,
} from '../exception'
import { COMMAND_SCHEMA_VERSION, Command, CommandMetadata } from './types'
export class CommandManager extends AbstractJsonRepository<
Command,
CommandMetadata
> {
constructor(app: App) {
super(app, `${ROOT_DIR}/${COMMAND_DIR}`)
}
protected generateFileName(template: Command): string {
// Format: v{schemaVersion}_name_id.json (with name encoded)
const encodedName = encodeURIComponent(template.name)
return `v${COMMAND_SCHEMA_VERSION}_${encodedName}_${template.id}.json`
}
protected parseFileName(fileName: string): CommandMetadata | null {
const match = fileName.match(
new RegExp(`^v${COMMAND_SCHEMA_VERSION}_(.+)_([0-9a-f-]+)\\.json$`),
)
if (!match) return null
const encodedName = match[1]
const id = match[2]
const name = decodeURIComponent(encodedName)
return { id, name, schemaVersion: COMMAND_SCHEMA_VERSION }
}
public async createCommand(
command: Omit<
Command,
'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'
>,
): Promise<Command> {
if (command.name !== undefined && command.name.length === 0) {
throw new EmptyCommandNameException()
}
const existingCommand = await this.findByName(command.name)
if (existingCommand) {
throw new DuplicateCommandException(command.name)
}
const newCommand: Command = {
id: uuidv4(),
...command,
createdAt: Date.now(),
updatedAt: Date.now(),
schemaVersion: COMMAND_SCHEMA_VERSION,
}
await this.create(newCommand)
return newCommand
}
public async ListCommands(): Promise<Command[]> {
const allMetadata = await this.listMetadata()
const allCommands = await Promise.all(allMetadata.map(async (meta) => this.read(meta.fileName)))
return allCommands.sort((a, b) => b.updatedAt - a.updatedAt)
}
public async findById(id: string): Promise<Command | 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<Command | 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 updateCommand(
id: string,
updates: Partial<
Omit<Command, 'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'>
>,
): Promise<Command | null> {
if (updates.name !== undefined && updates.name.length === 0) {
throw new EmptyCommandNameException()
}
const command = await this.findById(id)
if (!command) return null
if (updates.name && updates.name !== command.name) {
const existingCommand = await this.findByName(updates.name)
if (existingCommand) {
throw new DuplicateCommandException(updates.name)
}
}
const updatedCommand: Command = {
...command,
...updates,
updatedAt: Date.now(),
}
await this.update(command, updatedCommand)
return updatedCommand
}
public async deleteCommand(id: string): Promise<boolean> {
const command = await this.findById(id)
if (!command) return false
const fileName = this.generateFileName(command)
await this.delete(fileName)
return true
}
public async searchCommands(query: string): Promise<Command[]> {
const allMetadata = await this.listMetadata()
const results = fuzzysort.go(query, allMetadata, {
keys: ['name'],
threshold: 0.2,
limit: 20,
all: true,
})
const commands = (
await Promise.all(
results.map(async (result) => this.read(result.obj.fileName)),
)
).filter((command): command is Command => command !== null)
return commands
}
}

View File

@ -1,148 +0,0 @@
import fuzzysort from 'fuzzysort'
import { App } from 'obsidian'
import { v4 as uuidv4 } from 'uuid'
import { AbstractJsonRepository } from '../base'
import { ROOT_DIR, TEMPLATE_DIR } from '../constants'
import {
DuplicateTemplateException,
EmptyTemplateNameException,
} from '../exception'
import { TEMPLATE_SCHEMA_VERSION, Template, TemplateMetadata } from './types'
export class TemplateManager extends AbstractJsonRepository<
Template,
TemplateMetadata
> {
constructor(app: App) {
super(app, `${ROOT_DIR}/${TEMPLATE_DIR}`)
}
protected generateFileName(template: Template): string {
// Format: v{schemaVersion}_name_id.json (with name encoded)
const encodedName = encodeURIComponent(template.name)
return `v${TEMPLATE_SCHEMA_VERSION}_${encodedName}_${template.id}.json`
}
protected parseFileName(fileName: string): TemplateMetadata | null {
const match = fileName.match(
new RegExp(`^v${TEMPLATE_SCHEMA_VERSION}_(.+)_([0-9a-f-]+)\\.json$`),
)
if (!match) return null
const encodedName = match[1]
const id = match[2]
const name = decodeURIComponent(encodedName)
return { id, name, schemaVersion: TEMPLATE_SCHEMA_VERSION }
}
public async createTemplate(
template: Omit<
Template,
'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'
>,
): Promise<Template> {
if (template.name !== undefined && template.name.length === 0) {
throw new EmptyTemplateNameException()
}
const existingTemplate = await this.findByName(template.name)
if (existingTemplate) {
throw new DuplicateTemplateException(template.name)
}
const newTemplate: Template = {
id: uuidv4(),
...template,
createdAt: Date.now(),
updatedAt: Date.now(),
schemaVersion: TEMPLATE_SCHEMA_VERSION,
}
await this.create(newTemplate)
return newTemplate
}
public async ListTemplates(): Promise<Template[]> {
const allMetadata = await this.listMetadata()
const allTemplates = await Promise.all(allMetadata.map(async (meta) => this.read(meta.fileName)))
return allTemplates.sort((a, b) => b.updatedAt - a.updatedAt)
}
public async findById(id: string): Promise<Template | 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<Template | 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 updateTemplate(
id: string,
updates: Partial<
Omit<Template, 'id' | 'createdAt' | 'updatedAt' | 'schemaVersion'>
>,
): Promise<Template | null> {
if (updates.name !== undefined && updates.name.length === 0) {
throw new EmptyTemplateNameException()
}
const template = await this.findById(id)
if (!template) return null
if (updates.name && updates.name !== template.name) {
const existingTemplate = await this.findByName(updates.name)
if (existingTemplate) {
throw new DuplicateTemplateException(updates.name)
}
}
const updatedTemplate: Template = {
...template,
...updates,
updatedAt: Date.now(),
}
await this.update(template, updatedTemplate)
return updatedTemplate
}
public async deleteTemplate(id: string): Promise<boolean> {
const template = await this.findById(id)
if (!template) return false
const fileName = this.generateFileName(template)
await this.delete(fileName)
return true
}
public async searchTemplates(query: string): Promise<Template[]> {
const allMetadata = await this.listMetadata()
const results = fuzzysort.go(query, allMetadata, {
keys: ['name'],
threshold: 0.2,
limit: 20,
all: true,
})
const templates = (
await Promise.all(
results.map(async (result) => this.read(result.obj.fileName)),
)
).filter((template): template is Template => template !== null)
return templates
}
}

View File

@ -1,8 +1,8 @@
import { SerializedLexicalNode } from 'lexical'
export const TEMPLATE_SCHEMA_VERSION = 1
export const COMMAND_SCHEMA_VERSION = 1
export type Template = {
export type Command = {
id: string
name: string
content: { nodes: SerializedLexicalNode[] }
@ -11,7 +11,7 @@ export type Template = {
schemaVersion: number
}
export type TemplateMetadata = {
export type CommandMetadata = {
id: string
name: string
schemaVersion: number

View File

@ -1,4 +1,4 @@
export const ROOT_DIR = '.infio_json_db'
export const TEMPLATE_DIR = 'templates'
export const COMMAND_DIR = 'commands'
export const CHAT_DIR = 'chats'
export const INITIAL_MIGRATION_MARKER = '.initial_migration_completed'

View File

@ -1,14 +1,14 @@
export class DuplicateTemplateException extends Error {
constructor(templateName: string) {
super(`Template with name "${templateName}" already exists`)
this.name = 'DuplicateTemplateException'
export class DuplicateCommandException extends Error {
constructor(commandName: string) {
super(`Command with name "${commandName}" already exists`)
this.name = 'DuplicateCommandException'
}
}
export class EmptyTemplateNameException extends Error {
export class EmptyCommandNameException extends Error {
constructor() {
super('Template name cannot be empty')
this.name = 'EmptyTemplateNameException'
super('Command name cannot be empty')
this.name = 'EmptyCommandNameException'
}
}

View File

@ -1,12 +1,12 @@
import { App, normalizePath } from 'obsidian'
import { DBManager } from '../database-manager'
import { DuplicateTemplateException } from '../exception'
import { DuplicateCommandException } from '../exception'
import { ConversationManager } from '../modules/conversation/conversation-manager'
import { ChatManager } from './chat/ChatManager'
import { CommandManager } from './command/CommandManager'
import { INITIAL_MIGRATION_MARKER, ROOT_DIR } from './constants'
import { TemplateManager } from './command/TemplateManager'
import { serializeChatMessage } from './utils'
async function hasMigrationCompleted(app: App): Promise<boolean> {
@ -62,44 +62,44 @@ async function transferChatHistory(app: App, dbManager: DBManager): Promise<void
console.log('Chat history migration to JSON database completed')
}
async function transferTemplates(
async function transferCommands(
app: App,
dbManager: DBManager,
): Promise<void> {
const jsonTemplateManager = new TemplateManager(app)
const templateManager = dbManager.getCommandManager()
const jsonCommandManager = new CommandManager(app)
const commandManager = dbManager.getCommandManager()
const templates = await templateManager.findAllCommands()
const commands = await commandManager.findAllCommands()
for (const template of templates) {
for (const command of commands) {
try {
if (await jsonTemplateManager.findByName(template.name)) {
if (await jsonCommandManager.findByName(command.name)) {
// Template already exists, skip
continue
}
await jsonTemplateManager.createTemplate({
name: template.name,
content: template.content,
await jsonCommandManager.createCommand({
name: command.name,
content: command.content,
})
const verifyTemplate = await jsonTemplateManager.findByName(template.name)
if (!verifyTemplate) {
const verifyCommand = await jsonCommandManager.findByName(command.name)
if (!verifyCommand) {
throw new Error(
`Failed to verify migration of template ${template.name}`,
`Failed to verify migration of command ${command.name}`,
)
}
await templateManager.deleteCommand(template.id)
await commandManager.deleteCommand(command.id)
} catch (error) {
if (error instanceof DuplicateTemplateException) {
console.log(`Duplicate template found: ${template.name}. Skipping...`)
if (error instanceof DuplicateCommandException) {
console.log(`Duplicate command found: ${command.name}. Skipping...`)
} else {
console.error(`Error migrating template ${template.name}:`, error)
console.error(`Error migrating command ${command.name}:`, error)
}
}
}
console.log('Templates migration to JSON database completed')
console.log('Commands migration to JSON database completed')
}
export async function migrateToJsonDatabase(
@ -112,7 +112,7 @@ export async function migrateToJsonDatabase(
}
await transferChatHistory(app, dbManager)
await transferTemplates(app, dbManager)
await transferCommands(app, dbManager)
await markMigrationCompleted(app)
onMigrationComplete?.()
}

View File

@ -2,7 +2,7 @@ import fuzzysort from 'fuzzysort'
import { App } from 'obsidian'
import { DBManager } from '../../database-manager'
import { DuplicateTemplateException } from '../../exception'
import { DuplicateCommandException } from '../../exception'
import { InsertTemplate, SelectTemplate, UpdateTemplate } from '../../schema'
import { CommandRepository } from './command-repository'
@ -21,7 +21,7 @@ export class CommandManager {
async createCommand(command: InsertTemplate): Promise<SelectTemplate> {
const existingTemplate = await this.repository.findByName(command.name)
if (existingTemplate) {
throw new DuplicateTemplateException(command.name)
throw new DuplicateCommandException(command.name)
}
const created = await this.repository.create(command)
return created

View File

@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { lexicalNodeToPlainText } from '../components/chat-view/chat-input/utils/editor-state-to-plain-text'
import { useApp } from '../contexts/AppContext'
import { TemplateManager } from '../database/json/command/TemplateManager'
import { CommandManager } from '../database/json/command/CommandManager'
import { TemplateContent } from '../database/schema'
@ -28,10 +28,10 @@ export function useCommands(): UseCommands {
const app = useApp()
const templateManager = useMemo(() => new TemplateManager(app), [app])
const templateManager = useMemo(() => new CommandManager(app), [app])
const fetchCommandList = useCallback(async () => {
templateManager.ListTemplates().then((rows) => {
templateManager.ListCommands().then((rows) => {
setCommandList(rows.map((row) => ({
id: row.id,
name: row.name,
@ -49,7 +49,7 @@ export function useCommands(): UseCommands {
const createCommand = useCallback(
async (name: string, content: TemplateContent): Promise<void> => {
await templateManager.createTemplate({
await templateManager.createCommand({
name,
content,
})
@ -60,7 +60,7 @@ export function useCommands(): UseCommands {
const deleteCommand = useCallback(
async (id: string): Promise<void> => {
await templateManager.deleteTemplate(id)
await templateManager.deleteCommand(id)
fetchCommandList()
},
[templateManager, fetchCommandList],
@ -68,7 +68,7 @@ export function useCommands(): UseCommands {
const updateCommand = useCallback(
async (id: string, name: string, content: TemplateContent): Promise<void> => {
await templateManager.updateTemplate(id, {
await templateManager.updateCommand(id, {
name,
content,
})