๐ user-models.ts โข 7718 bytes
/**
* CmdCode V0.5 - ็จๆท่ชๅฎไนๆจกๅ้
็ฝฎ็ฎก็
*
* ๅ่ฝ๏ผ
* 1. ็จๆทๅฏๅๅปบๅคไธช่ชๅฎไนๆจกๅ้
็ฝฎ
* 2. ้
็ฝฎๅ ๅฏๅญๅจๅจ็จๆทไธๅฑ็ฎๅฝ
* 3. ๆฏๆๅๆข้ป่ฎคๆจกๅ
* 4. ๅฏๅจๆถ่ชๅจๅ ่ฝฝ็จๆท้ป่ฎคๆจกๅ
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'node:fs'
import { join } from 'node:path'
import { homedir } from 'node:os'
import { encrypt, decrypt } from './crypto-util.js'
import { testConnection } from './models.js'
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ็ฑปๅๅฎไน
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/** ็จๆท่ชๅฎไนๆจกๅ้
็ฝฎ */
export interface UserModelConfig {
id: string // ้
็ฝฎID๏ผๆไปถๅ๏ผไธๅซๆฉๅฑๅ๏ผ
name: string // ๆจกๅๆพ็คบๅ็งฐ
model: string // ๆจกๅID๏ผAPI่ฐ็จ็จ๏ผ
baseUrl: string // APIๅฐๅ
apiKey: string // APIๅฏ้ฅ๏ผๅ ๅฏๅญๅจ๏ผ
note1?: string // ๅคๆณจไฟกๆฏไธ
note2?: string // ๅคๆณจไฟกๆฏไบ
createdAt: string // ๅๅปบๆถ้ด
updatedAt: string // ๆดๆฐๆถ้ด
isDefault?: boolean // ๆฏๅฆไธบ็จๆท้ป่ฎคๆจกๅ
}
/** ๆจกๅๅ่กจ้กน๏ผไธๅซๆๆไฟกๆฏ๏ผ */
export interface ModelListItem {
id: string
name: string
model: string
baseUrl: string
note1?: string
note2?: string
createdAt: string
isDefault: boolean
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ้
็ฝฎ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const CMD_DIR = join(homedir(), '.cmdcode')
/** ่ทๅ็จๆทๆจกๅ้
็ฝฎ็ฎๅฝ */
function getUserModelsDir(username: string): string {
return join(CMD_DIR, 'workspaces', username, 'models')
}
/** ็กฎไฟ็ฎๅฝๅญๅจ */
function ensureDir(dir: string): void {
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
}
/** ่ฎพ็ฝฎๆไปถๆ้ */
function setPermission(filePath: string): void {
try { require('node:fs').chmodSync(filePath, 0o600) } catch { /* ignore */ }
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// CRUD ๆไฝ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/**
* ๅๅปบ็จๆทๆจกๅ้
็ฝฎ
*/
export function createUserModel(
username: string,
config: Omit<UserModelConfig, 'id' | 'createdAt' | 'updatedAt' | 'isDefault'>
): UserModelConfig {
const modelsDir = getUserModelsDir(username)
ensureDir(modelsDir)
// ็ๆๅฏไธID
const timestamp = Date.now().toString(36)
const random = Math.random().toString(36).slice(2, 6)
const id = `model_${timestamp}_${random}`
const now = new Date().toISOString()
const fullConfig: UserModelConfig = {
...config,
id,
createdAt: now,
updatedAt: now,
isDefault: false,
}
// ๆฃๆฅๆฏๅฆๆฏ็ฌฌไธไธช้
็ฝฎ๏ผ่ชๅจ่ฎพไธบ้ป่ฎค
const existing = listUserModels(username)
if (existing.length === 0) {
fullConfig.isDefault = true
}
// ๅ ๅฏๅญๅจ
const filePath = join(modelsDir, `${id}.enc`)
writeFileSync(filePath, encrypt(fullConfig), 'utf-8')
setPermission(filePath)
return fullConfig
}
/**
* ่ฏปๅ็จๆทๆจกๅ้
็ฝฎ
*/
export function loadUserModel(username: string, modelId: string): UserModelConfig | null {
const modelsDir = getUserModelsDir(username)
const filePath = join(modelsDir, `${modelId}.enc`)
if (!existsSync(filePath)) return null
try {
return decrypt<UserModelConfig>(readFileSync(filePath, 'utf-8'))
} catch {
return null
}
}
/**
* ๆดๆฐ็จๆทๆจกๅ้
็ฝฎ
*/
export function updateUserModel(
username: string,
modelId: string,
updates: Partial<Omit<UserModelConfig, 'id' | 'createdAt'>>
): UserModelConfig | null {
const existing = loadUserModel(username, modelId)
if (!existing) return null
const updated: UserModelConfig = {
...existing,
...updates,
id: modelId,
createdAt: existing.createdAt,
updatedAt: new Date().toISOString(),
}
const modelsDir = getUserModelsDir(username)
const filePath = join(modelsDir, `${modelId}.enc`)
writeFileSync(filePath, encrypt(updated), 'utf-8')
setPermission(filePath)
return updated
}
/**
* ๅ ้ค็จๆทๆจกๅ้
็ฝฎ
*/
export function deleteUserModel(username: string, modelId: string): boolean {
const modelsDir = getUserModelsDir(username)
const filePath = join(modelsDir, `${modelId}.enc`)
if (!existsSync(filePath)) return false
try {
unlinkSync(filePath)
return true
} catch {
return false
}
}
/**
* ๅๅบ็จๆทๆๆๆจกๅ้
็ฝฎ๏ผไธๅซAPI Key๏ผ
*/
export function listUserModels(username: string): ModelListItem[] {
const modelsDir = getUserModelsDir(username)
if (!existsSync(modelsDir)) return []
const files = readdirSync(modelsDir)
.filter(f => f.endsWith('.enc'))
.sort()
const models: ModelListItem[] = []
for (const f of files) {
try {
const config = decrypt<UserModelConfig>(
readFileSync(join(modelsDir, f), 'utf-8')
)
models.push({
id: config.id,
name: config.name,
model: config.model,
baseUrl: config.baseUrl,
note1: config.note1,
note2: config.note2,
createdAt: config.createdAt,
isDefault: config.isDefault || false,
})
} catch { /* skip broken files */ }
}
return models
}
/**
* ่ทๅ็จๆท้ป่ฎคๆจกๅ้
็ฝฎ๏ผๅซAPI Key๏ผ
*/
export function getUserDefaultModel(username: string): UserModelConfig | null {
const modelsDir = getUserModelsDir(username)
if (!existsSync(modelsDir)) return null
const files = readdirSync(modelsDir).filter(f => f.endsWith('.enc'))
for (const f of files) {
try {
const config = decrypt<UserModelConfig>(
readFileSync(join(modelsDir, f), 'utf-8')
)
if (config.isDefault) {
return config
}
} catch { /* skip */ }
}
// ๆฒกๆ้ป่ฎค็๏ผ่ฟๅ็ฌฌไธไธช
if (files.length > 0) {
try {
return decrypt<UserModelConfig>(
readFileSync(join(modelsDir, files[0]), 'utf-8')
)
} catch { /* skip */ }
}
return null
}
/**
* ่ฎพ็ฝฎ็จๆท้ป่ฎคๆจกๅ
*/
export function setUserDefaultModel(username: string, modelId: string): boolean {
const modelsDir = getUserModelsDir(username)
if (!existsSync(modelsDir)) return false
const files = readdirSync(modelsDir).filter(f => f.endsWith('.enc'))
let found = false
for (const f of files) {
try {
const config = decrypt<UserModelConfig>(
readFileSync(join(modelsDir, f), 'utf-8')
)
if (config.id === modelId) {
config.isDefault = true
found = true
} else {
config.isDefault = false
}
writeFileSync(join(modelsDir, f), encrypt(config), 'utf-8')
} catch { /* skip */ }
}
return found
}
/**
* ๆต่ฏๆจกๅ่ฟๆฅ
*/
export async function testUserModelConnection(config: UserModelConfig): Promise<{
success: boolean
latencyMs: number
error?: string
}> {
return testConnection(config.baseUrl, config.apiKey, config.model)
}
/**
* ็ป่ฎก็จๆทๆจกๅๆฐ้
*/
export function countUserModels(username: string): number {
const modelsDir = getUserModelsDir(username)
if (!existsSync(modelsDir)) return 0
return readdirSync(modelsDir).filter(f => f.endsWith('.enc')).length
}