feat(v031): SettingsService.{getVisionModel,setVisionModel,getVisionCapableCache,setVisionCapableCache}
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,11 @@ const SettingsSchema = z.object({
|
||||
// v0.3.0 Cut E — 양방향 git sync 설정. 모두 optional — 미구성 시 sync 비활성.
|
||||
sync_repo_url: z.string().nullable().optional(),
|
||||
sync_auto_enabled: z.boolean().optional(),
|
||||
sync_interval_min: z.number().int().min(5).optional()
|
||||
sync_interval_min: z.number().int().min(5).optional(),
|
||||
// v0.3.1 Cut F
|
||||
vision_model: z.string().nullable().optional(),
|
||||
vision_capable_cache: z.array(z.string()).optional(),
|
||||
vision_cache_at: z.string().optional()
|
||||
}).strict();
|
||||
|
||||
export type Settings = z.infer<typeof SettingsSchema>;
|
||||
@@ -127,6 +131,30 @@ export class SettingsService {
|
||||
await this.persist(next);
|
||||
}
|
||||
|
||||
/** v0.3.1 Cut F — 선택된 vision model. null = 미선택. */
|
||||
async getVisionModel(): Promise<string | null> {
|
||||
const s = await this.load();
|
||||
return s.vision_model ?? null;
|
||||
}
|
||||
|
||||
async setVisionModel(value: string | null): Promise<void> {
|
||||
const current = await this.load();
|
||||
const next: Settings = { ...current, vision_model: value };
|
||||
await this.persist(next);
|
||||
}
|
||||
|
||||
/** v0.3.1 Cut F — /api/tags 조회 결과 캐시. 기본 빈 배열 + null timestamp. */
|
||||
async getVisionCapableCache(): Promise<{ models: string[]; at: string | null }> {
|
||||
const s = await this.load();
|
||||
return { models: s.vision_capable_cache ?? [], at: s.vision_cache_at ?? null };
|
||||
}
|
||||
|
||||
async setVisionCapableCache(models: string[], now: Date): Promise<void> {
|
||||
const current = await this.load();
|
||||
const next: Settings = { ...current, vision_capable_cache: models, vision_cache_at: now.toISOString() };
|
||||
await this.persist(next);
|
||||
}
|
||||
|
||||
private async persist(next: Settings): Promise<void> {
|
||||
await mkdir(dirname(this.filePath), { recursive: true });
|
||||
const tmpPath = this.filePath + '.tmp';
|
||||
|
||||
@@ -90,4 +90,31 @@ describe('SettingsService', () => {
|
||||
await expect(svc.setSyncIntervalMin(10.5)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('v0.3.1 Cut F — vision settings', () => {
|
||||
it('getVisionModel() defaults to null', async () => {
|
||||
expect(await svc.getVisionModel()).toBeNull();
|
||||
});
|
||||
|
||||
it('setVisionModel() / getVisionModel() round-trip including null clear', async () => {
|
||||
await svc.setVisionModel('llava:13b');
|
||||
expect(await svc.getVisionModel()).toBe('llava:13b');
|
||||
await svc.setVisionModel(null);
|
||||
expect(await svc.getVisionModel()).toBeNull();
|
||||
});
|
||||
|
||||
it('getVisionCapableCache() defaults to empty models + null at', async () => {
|
||||
const cache = await svc.getVisionCapableCache();
|
||||
expect(cache.models).toEqual([]);
|
||||
expect(cache.at).toBeNull();
|
||||
});
|
||||
|
||||
it('setVisionCapableCache() persists models + ISO timestamp', async () => {
|
||||
const now = new Date('2026-05-09T12:00:00.000Z');
|
||||
await svc.setVisionCapableCache(['llava:13b', 'llava:7b'], now);
|
||||
const cache = await svc.getVisionCapableCache();
|
||||
expect(cache.models).toEqual(['llava:13b', 'llava:7b']);
|
||||
expect(cache.at).toBe('2026-05-09T12:00:00.000Z');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user