feat: added a welcome msg for local LLM users
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { processUserMessage } from './UserMessageFilter';
|
||||
import { addWelcomeMessage } from '../../utils/chatMessageUtils';
|
||||
|
||||
const configState = new Map<string, unknown>();
|
||||
|
||||
const configManagerMock = {
|
||||
getIsInitialized: vi.fn(() => true),
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
get: vi.fn((key: string) => configState.get(key)),
|
||||
};
|
||||
|
||||
vi.mock('../../core/config/ConfigManager', () => ({
|
||||
ConfigManager: {
|
||||
instance: () => configManagerMock,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../chatUtil', () => ({
|
||||
clearChatHistoryAndUI: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../stores/projectStore', () => ({
|
||||
useProjectStore: {
|
||||
getState: () => ({
|
||||
setStatus: vi.fn(),
|
||||
activeRegionId: null,
|
||||
selectedRegionIds: [],
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../agent/core/SystemPrompts', () => ({
|
||||
SystemPrompts: {
|
||||
getPromptWithContext: vi.fn(async (value: string) => value),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('processUserMessage /welcome', () => {
|
||||
beforeEach(() => {
|
||||
configState.clear();
|
||||
configState.set('general.llm_provider', 'local_browser');
|
||||
configState.set('general.openai.api_key', '');
|
||||
configState.set('general.gemini.api_key', '');
|
||||
configState.set('general.claude.api_key', '');
|
||||
configState.set('general.claude_openrouter.api_key', '');
|
||||
configState.set('general.openai_compatible.base_url', '');
|
||||
configState.set('general.openai_compatible.model', '');
|
||||
|
||||
configManagerMock.getIsInitialized.mockReturnValue(true);
|
||||
configManagerMock.initialize.mockClear();
|
||||
configManagerMock.get.mockClear();
|
||||
|
||||
vi.stubGlobal('fetch', vi.fn(async (input: string | URL | Request) => {
|
||||
const url = String(input);
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => `content:${url}`,
|
||||
};
|
||||
}));
|
||||
});
|
||||
|
||||
it('uses the local welcome for the local browser provider', async () => {
|
||||
configState.set('general.llm_provider', 'local_browser');
|
||||
configState.set('general.openai.api_key', '');
|
||||
configState.set('general.openai_compatible.base_url', '');
|
||||
configState.set('general.openai_compatible.model', '');
|
||||
|
||||
const result = await processUserMessage('/welcome');
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('chat/welcome_local_llm.md'));
|
||||
expect(result.metadata).toMatchObject({ command: 'welcome', variant: 'local' });
|
||||
expect(result.pseudoAssistantResponse).toContain('welcome_local_llm.md');
|
||||
});
|
||||
|
||||
it('uses the new-user welcome for non-local providers without required config', async () => {
|
||||
configState.set('general.llm_provider', 'openai');
|
||||
configState.set('general.openai.api_key', '');
|
||||
|
||||
const result = await processUserMessage('/welcome');
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('chat/welcome_new.md'));
|
||||
expect(result.metadata).toMatchObject({ command: 'welcome', variant: 'new' });
|
||||
expect(result.pseudoAssistantResponse).toContain('welcome_new.md');
|
||||
});
|
||||
|
||||
it('uses the returning-user welcome for configured non-local providers', async () => {
|
||||
configState.set('general.llm_provider', 'openai_compatible');
|
||||
configState.set('general.openai_compatible.base_url', 'https://openrouter.ai/api/v1');
|
||||
configState.set('general.openai_compatible.model', 'qwen/qwen3-30b-a3b:free');
|
||||
|
||||
const result = await processUserMessage('/welcome');
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('chat/welcome_again.md'));
|
||||
expect(result.metadata).toMatchObject({ command: 'welcome', variant: 'again' });
|
||||
expect(result.pseudoAssistantResponse).toContain('welcome_again.md');
|
||||
});
|
||||
|
||||
it('reuses the same welcome routing through addWelcomeMessage', async () => {
|
||||
configState.set('general.llm_provider', 'local_browser');
|
||||
|
||||
const message = await addWelcomeMessage();
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('chat/welcome_local_llm.md'));
|
||||
expect(message?.role).toBe('assistant');
|
||||
expect(message?.content).toContain('welcome_local_llm.md');
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,48 @@ export interface UserMessageFilterResult {
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
function hasText(value: unknown): boolean {
|
||||
return typeof value === 'string' && value.trim() !== '';
|
||||
}
|
||||
|
||||
function getWelcomeVariant(configManager: ConfigManager): 'local' | 'new' | 'again' {
|
||||
const provider = (configManager.get('general.llm_provider') as string) || LOCAL_LLM_PROVIDER_KEY;
|
||||
|
||||
if (provider === LOCAL_LLM_PROVIDER_KEY) {
|
||||
return 'local';
|
||||
}
|
||||
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
return hasText(configManager.get('general.openai.api_key')) ? 'again' : 'new';
|
||||
case 'gemini':
|
||||
return hasText(configManager.get('general.gemini.api_key')) ? 'again' : 'new';
|
||||
case 'claude':
|
||||
return hasText(configManager.get('general.claude.api_key')) ? 'again' : 'new';
|
||||
case 'claude_openrouter':
|
||||
return hasText(configManager.get('general.claude_openrouter.api_key')) ? 'again' : 'new';
|
||||
case 'openai_compatible':
|
||||
return hasText(configManager.get('general.openai_compatible.base_url'))
|
||||
&& hasText(configManager.get('general.openai_compatible.model'))
|
||||
? 'again'
|
||||
: 'new';
|
||||
default:
|
||||
return 'new';
|
||||
}
|
||||
}
|
||||
|
||||
function getWelcomeUrl(variant: 'local' | 'new' | 'again'): string {
|
||||
switch (variant) {
|
||||
case 'local':
|
||||
return `${import.meta.env.BASE_URL}chat/welcome_local_llm.md`;
|
||||
case 'again':
|
||||
return `${import.meta.env.BASE_URL}chat/welcome_again.md`;
|
||||
case 'new':
|
||||
default:
|
||||
return `${import.meta.env.BASE_URL}chat/welcome_new.md`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a user message before it is displayed or sent to the LLM.
|
||||
* Handles slash-commands and returns a structured decision.
|
||||
@@ -53,12 +95,8 @@ export async function processUserMessage(originalMessage: string): Promise<UserM
|
||||
await configManager.initialize();
|
||||
}
|
||||
|
||||
const openaiKey = (configManager.get('general.openai.api_key') as string) || '';
|
||||
const oaiCompatKey = (configManager.get('general.openai_compatible.api_key') as string) || '';
|
||||
const oaiCompatBaseUrl = (configManager.get('general.openai_compatible.base_url') as string) || '';
|
||||
const isNew = openaiKey.trim() === '' && oaiCompatKey.trim() === '' && oaiCompatBaseUrl.trim() === '';
|
||||
|
||||
const url = isNew ? `${import.meta.env.BASE_URL}chat/welcome_new.md` : `${import.meta.env.BASE_URL}chat/welcome_again.md`;
|
||||
const variant = getWelcomeVariant(configManager);
|
||||
const url = getWelcomeUrl(variant);
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) {
|
||||
throw new Error(`Failed to fetch ${url}: ${resp.status}`);
|
||||
@@ -69,7 +107,7 @@ export async function processUserMessage(originalMessage: string): Promise<UserM
|
||||
sendToLLM: false,
|
||||
finalMessageForLLM: null,
|
||||
pseudoAssistantResponse: md,
|
||||
metadata: { command: 'welcome', variant: isNew ? 'new' : 'again' }
|
||||
metadata: { command: 'welcome', variant }
|
||||
};
|
||||
} catch (err) {
|
||||
const fallback = 'Welcome to K.G.Studio Musician Assistant.';
|
||||
@@ -267,4 +305,3 @@ export async function processUserMessage(originalMessage: string): Promise<UserM
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user