feat: implemented the v1 browser embedded LLM support (Gemma 4 E4B based)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { KGConfigStorage } from '../io/KGConfigStorage';
|
||||
import { CONFIG_UPGRADER_CONSTANTS } from '../../constants/coreConstants';
|
||||
import { upgradeConfigToV1 } from './upgradeConfigToV1';
|
||||
import { upgradeConfigToV2 } from './upgradeConfigToV2';
|
||||
|
||||
/**
|
||||
* KGConfigUpgrader — Orchestrates app-level migrations (e.g., storage backend changes).
|
||||
@@ -33,6 +34,10 @@ export class KGConfigUpgrader {
|
||||
await upgradeConfigToV1();
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
await upgradeConfigToV2();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`No config upgrader found for version ${nextVersion}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const configStore = new Map<string, { name: string; data: Record<string, unknown>; lastModified: number }>();
|
||||
|
||||
vi.mock('../io/KGConfigStorage', () => ({
|
||||
KGConfigStorage: {
|
||||
getInstance: () => ({
|
||||
getRaw: vi.fn(async (name: string) => configStore.get(name)?.data ?? null),
|
||||
saveRaw: vi.fn(async (name: string, data: Record<string, unknown>) => {
|
||||
configStore.set(name, { name, data, lastModified: Date.now() });
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
import { upgradeConfigToV2 } from './upgradeConfigToV2';
|
||||
|
||||
describe('upgradeConfigToV2', () => {
|
||||
beforeEach(() => {
|
||||
configStore.clear();
|
||||
});
|
||||
|
||||
it('pins legacy installs without an explicit provider to the old default provider', async () => {
|
||||
configStore.set('userConfig', {
|
||||
name: 'userConfig',
|
||||
data: {
|
||||
general: {
|
||||
openai: { api_key: '', model: 'gpt-5.4-mini', flex: false },
|
||||
},
|
||||
},
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
|
||||
await upgradeConfigToV2();
|
||||
|
||||
expect((configStore.get('userConfig')?.data.general as Record<string, unknown>).llm_provider).toBe('openai');
|
||||
});
|
||||
|
||||
it('leaves explicit providers unchanged', async () => {
|
||||
configStore.set('userConfig', {
|
||||
name: 'userConfig',
|
||||
data: {
|
||||
general: {
|
||||
llm_provider: 'openai_compatible',
|
||||
},
|
||||
},
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
|
||||
await upgradeConfigToV2();
|
||||
|
||||
expect((configStore.get('userConfig')?.data.general as Record<string, unknown>).llm_provider).toBe('openai_compatible');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { KGConfigStorage } from '../io/KGConfigStorage';
|
||||
|
||||
const CONFIG_KEY = 'userConfig';
|
||||
const LEGACY_DEFAULT_PROVIDER = 'openai';
|
||||
|
||||
export async function upgradeConfigToV2(): Promise<void> {
|
||||
const storage = KGConfigStorage.getInstance();
|
||||
const rawConfig = await storage.getRaw(CONFIG_KEY);
|
||||
if (!rawConfig || typeof rawConfig !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = rawConfig as Record<string, unknown>;
|
||||
const general = config.general;
|
||||
if (!general || typeof general !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('llm_provider' in (general as Record<string, unknown>)) {
|
||||
return;
|
||||
}
|
||||
|
||||
(general as Record<string, unknown>).llm_provider = LEGACY_DEFAULT_PROVIDER;
|
||||
await storage.saveRaw(CONFIG_KEY, config);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { KGConfigStorage } from '../io/KGConfigStorage';
|
||||
interface AppConfig {
|
||||
general: {
|
||||
language: string;
|
||||
llm_provider: 'openai' | 'gemini' | 'claude' | 'claude_openrouter' | 'openai_compatible';
|
||||
llm_provider: 'local_browser' | 'openai' | 'gemini' | 'claude' | 'claude_openrouter' | 'openai_compatible';
|
||||
persist_api_keys_non_localhost: boolean;
|
||||
openai: {
|
||||
api_key: string;
|
||||
@@ -183,7 +183,7 @@ export class ConfigManager {
|
||||
this.defaultConfig = {
|
||||
general: {
|
||||
language: 'en_us',
|
||||
llm_provider: 'openai',
|
||||
llm_provider: 'local_browser',
|
||||
persist_api_keys_non_localhost: false,
|
||||
openai: {
|
||||
api_key: '',
|
||||
|
||||
@@ -9,10 +9,12 @@ class MockWritableFileStream {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
async write(content: ArrayBuffer | ArrayBufferView): Promise<void> {
|
||||
const bytes = content instanceof ArrayBuffer
|
||||
? new Uint8Array(content)
|
||||
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
||||
async write(content: ArrayBuffer | ArrayBufferView | string): Promise<void> {
|
||||
const bytes = typeof content === 'string'
|
||||
? new TextEncoder().encode(content)
|
||||
: content instanceof ArrayBuffer
|
||||
? new Uint8Array(content)
|
||||
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
||||
this.chunks.push(new Uint8Array(bytes));
|
||||
}
|
||||
|
||||
@@ -44,6 +46,8 @@ class MockFileSystemFileHandle {
|
||||
|
||||
async getFile(): Promise<File> {
|
||||
return {
|
||||
size: this.content.byteLength,
|
||||
text: async () => new TextDecoder().decode(this.content),
|
||||
arrayBuffer: async () => this.content.buffer.slice(0),
|
||||
} as unknown as File;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user