feat: implemented the v1 browser embedded LLM support (Gemma 4 E4B based)

This commit is contained in:
Xiaohan-Tian
2026-05-14 18:55:23 -07:00
parent 0e6e2736d0
commit 98c2b97413
30 changed files with 19462 additions and 153 deletions
+56 -3
View File
@@ -3,7 +3,8 @@ import './ChatBox.css';
import { FaPlus, FaBan, FaDownload } from 'react-icons/fa';
import { UserMessage, AssistantMessage } from './chat';
import { AgentCore } from '../agent/core/AgentCore';
import { LLMProvider } from '../agent/llm/LLMProvider';
import { OpenAICompatibleLLMProvider, type LLMProvider } from '../agent/llm/LLMProvider';
import { LocalBrowserLLMProvider } from '../agent/llm/LocalBrowserLLMProvider';
import { ConfigManager } from '../core/config/ConfigManager';
import { useProjectStore } from '../stores/projectStore';
import { SystemPrompts } from '../agent/core/SystemPrompts';
@@ -13,6 +14,8 @@ import { useStreamProcessor } from '../hooks/useStreamProcessor';
import { createMessage, addWelcomeMessage } from '../utils/chatMessageUtils';
import { formatLocalDateTime } from '../util/timeUtil';
import { downloadBlob, buildTimestampSuffix } from '../util/miscUtil';
import { LocalLLMModelManager, type LocalLLMModelState } from '../util/localLLMModelManager';
import { LOCAL_LLM_DISPLAY_NAME, LOCAL_LLM_PROVIDER_KEY } from '../util/localLLMConfig';
import KGDropdown from './common/KGDropdown';
import type { ChatMessage } from '../types/projectTypes';
@@ -32,6 +35,8 @@ const createLLMProviderFromConfig = (): LLMProvider => {
let baseURL: string | undefined;
switch (providerType) {
case LOCAL_LLM_PROVIDER_KEY:
return new LocalBrowserLLMProvider();
case 'openai':
apiKey = configManager.get('general.openai.api_key') as string;
model = configManager.get('general.openai.model') as string;
@@ -50,7 +55,7 @@ const createLLMProviderFromConfig = (): LLMProvider => {
break;
}
return new LLMProvider(apiKey, model, baseURL);
return new OpenAICompatibleLLMProvider(apiKey, model, baseURL);
};
interface ChatBoxProps {
@@ -64,6 +69,8 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [isProcessing, setIsProcessing] = useState(false);
const [lastUserMessage, setLastUserMessage] = useState<string>('');
const [localModelState, setLocalModelState] = useState<LocalLLMModelState>(LocalLLMModelManager.getState());
const [activeProvider, setActiveProvider] = useState<string>('openai');
// Track if this is the first message (for system prompt logging)
const [isFirstMessage, setIsFirstMessage] = useState(true);
@@ -180,9 +187,11 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
}
const applyProviderFromConfig = () => {
const providerType = (configManager.get('general.llm_provider') as string) || 'openai';
const provider = createLLMProviderFromConfig();
const agentCore = AgentCore.instance();
agentCore.setLLMProvider(provider);
setActiveProvider(providerType);
console.log('LLM provider configured');
};
@@ -204,6 +213,7 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
registerClearChatUICallback(clearChatUI);
const maybeUnsubscribePromise = initializeProvider();
const unsubscribeLocalModel = LocalLLMModelManager.subscribe(setLocalModelState);
(async () => {
if (hasShownWelcomeOnceInRuntime) return;
@@ -216,6 +226,7 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
})();
return () => {
unsubscribeLocalModel();
Promise.resolve(maybeUnsubscribePromise).then((cleanup) => {
if (typeof cleanup === 'function') cleanup();
}).catch(() => {});
@@ -266,7 +277,10 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
// Log system prompt only for first message
if (isFirstMessage) {
try {
const systemPrompt = await SystemPrompts.getSystemPromptWithContext();
const provider = AgentCore.instance().getLLMProvider();
const systemPrompt = await SystemPrompts.getSystemPromptWithContext(
provider?.getPreferredSystemPromptPath?.(),
);
console.log('------------ SYSTEM ------------');
console.log(systemPrompt);
console.log('--------------------------------');
@@ -372,6 +386,45 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
</div>
</div>
{activeProvider === LOCAL_LLM_PROVIDER_KEY && (
<div style={{ padding: '10px 14px 0 14px' }}>
<div className="settings-group" style={{ marginBottom: '12px', padding: '14px' }}>
<h4 style={{ marginBottom: '10px' }}>{LOCAL_LLM_DISPLAY_NAME} Local Runtime</h4>
{!localModelState.runtimeSupport.supported && (
<div className="settings-help" style={{ fontSize: '12px', color: '#d0a56b', marginBottom: '8px' }}>
{localModelState.runtimeSupport.reason}
</div>
)}
{!localModelState.isCached && !localModelState.isDownloading && localModelState.runtimeSupport.supported && (
<div className="settings-help" style={{ fontSize: '12px', color: '#b0b0b0', marginBottom: '8px' }}>
The local language model has not been downloaded yet. It will be downloaded automatically the next time you send a chat request with this provider.
</div>
)}
{(localModelState.isChecking || localModelState.isDownloading || localModelState.progressText) && (
<div className="settings-progress-block">
<div
className="settings-progress-track"
role="progressbar"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={Math.max(0, Math.min(100, localModelState.progressPercent))}
>
<div className="settings-progress-fill" style={{ width: `${Math.max(0, Math.min(100, localModelState.progressPercent))}%` }} />
</div>
<div className="settings-help" style={{ fontSize: '12px', color: '#b0b0b0', marginTop: '6px' }}>
{localModelState.isChecking ? 'Checking local model cache...' : localModelState.progressText}
</div>
</div>
)}
{localModelState.error && (
<div className="settings-help" style={{ fontSize: '12px', color: '#d45a5a' }}>
{localModelState.error}
</div>
)}
</div>
</div>
)}
<div className="chatbox-messages">
{messages.map((message) => (
message.role === 'user' ? (