drdata commited on
Commit
ac80eab
·
verified ·
1 Parent(s): fb0ae9b

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +55 -641
src/App.tsx CHANGED
@@ -60,7 +60,7 @@ interface BaseMessage {
60
  interface ToolMessage {
61
  role: "tool";
62
  content: string;
63
- renderInfo: RenderInfo[]; // Rich data for the UI
64
  }
65
  type Message = BaseMessage | ToolMessage;
66
 
@@ -121,29 +121,38 @@ const App: React.FC = () => {
121
  } = useMCP();
122
 
123
  const loadTools = useCallback(async (): Promise<void> => {
124
- const db = await getDB();
125
- const allTools: Tool[] = await db.getAll(STORE_NAME);
126
- if (allTools.length === 0) {
127
- const defaultTools: Tool[] = Object.entries(DEFAULT_TOOLS).map(
128
- ([name, code], id) => ({
129
- id,
130
- name,
131
- code,
132
- enabled: true,
133
- isCollapsed: false,
134
- })
135
- );
136
- const tx = db.transaction(STORE_NAME, "readwrite");
137
- await Promise.all(defaultTools.map((tool) => tx.store.put(tool)));
138
- await tx.done;
139
- setTools(defaultTools);
140
- } else {
141
- setTools(allTools.map((t) => ({ ...t, isCollapsed: false })));
142
- }
 
143
 
144
- // Load MCP tools and merge them
145
- const mcpTools = getMCPToolsAsOriginalTools();
146
- setTools((prevTools) => [...prevTools, ...mcpTools]);
 
 
 
 
 
 
 
 
147
  }, [getMCPToolsAsOriginalTools]);
148
 
149
  useEffect(() => {
@@ -180,6 +189,7 @@ const App: React.FC = () => {
180
  const clearChat = useCallback(() => {
181
  setMessages([]);
182
  clearPastKeyValues();
 
183
  }, [clearPastKeyValues]);
184
 
185
  const addTool = async (): Promise<void> => {
@@ -259,17 +269,31 @@ const App: React.FC = () => {
259
  };
260
 
261
  const executeToolCall = async (callString: string): Promise<string> => {
 
 
262
  const parsedCall = parsePythonicCalls(callString);
263
- if (!parsedCall) throw new Error(`Invalid tool call format: ${callString}`);
 
 
 
 
264
 
265
  const { name, positionalArgs, keywordArgs } = parsedCall;
266
  const toolToUse = tools.find((t) => t.name === name && t.enabled);
267
- if (!toolToUse) throw new Error(`Tool '${name}' not found or is disabled.`);
 
 
 
 
 
 
 
268
 
269
  // Check if this is an MCP tool
270
  const isMCPTool = toolToUse.code?.includes("mcpServerId:");
271
  if (isMCPTool) {
272
- // Extract MCP server ID and tool name from the code
 
273
  const mcpServerMatch = toolToUse.code?.match(/mcpServerId: "([^"]+)"/);
274
  const mcpToolMatch = toolToUse.code?.match(/toolName: "([^"]+)"/);
275
 
@@ -277,14 +301,12 @@ const App: React.FC = () => {
277
  const serverId = mcpServerMatch[1];
278
  const toolName = mcpToolMatch[1];
279
 
280
- // Convert positional and keyword args to a single args object
281
  const { functionCode } = extractFunctionAndRenderer(toolToUse.code);
282
  const schema = generateSchemaFromCode(functionCode);
283
  const paramNames = Object.keys(schema.parameters.properties);
284
 
285
  const args: Record<string, unknown> = {};
286
 
287
- // Map positional args
288
  for (
289
  let i = 0;
290
  i < Math.min(positionalArgs.length, paramNames.length);
@@ -293,18 +315,19 @@ const App: React.FC = () => {
293
  args[paramNames[i]] = positionalArgs[i];
294
  }
295
 
296
- // Map keyword args
297
  Object.entries(keywordArgs).forEach(([key, value]) => {
298
  args[key] = value;
299
  });
300
 
301
- // Call MCP tool
302
  const result = await callMCPTool(serverId, toolName, args);
 
303
  return JSON.stringify(result);
304
  }
305
  }
306
 
307
- // Handle local tools as before
 
308
  const { functionCode } = extractFunctionAndRenderer(toolToUse.code);
309
  const schema = generateSchemaFromCode(functionCode);
310
  const paramNames = Object.keys(schema.parameters.properties);
@@ -318,613 +341,4 @@ const App: React.FC = () => {
318
  finalArgs.push(positionalArgs[i]);
319
  } else if (Object.prototype.hasOwnProperty.call(keywordArgs, paramName)) {
320
  finalArgs.push(keywordArgs[paramName]);
321
- } else if (
322
- Object.prototype.hasOwnProperty.call(
323
- schema.parameters.properties[paramName],
324
- "default"
325
- )
326
- ) {
327
- finalArgs.push(schema.parameters.properties[paramName].default);
328
- } else if (!requiredParams.includes(paramName)) {
329
- finalArgs.push(undefined);
330
- } else {
331
- throw new Error(`Missing required argument: ${paramName}`);
332
- }
333
- }
334
-
335
- const bodyMatch = functionCode.match(/function[^{]+\{([\s\S]*)\}/);
336
- if (!bodyMatch) {
337
- throw new Error(
338
- "Could not parse function body. Ensure it's a standard `function` declaration."
339
- );
340
- }
341
- const body = bodyMatch[1];
342
- const AsyncFunction = Object.getPrototypeOf(
343
- async function () {}
344
- ).constructor;
345
- const func = new AsyncFunction(...paramNames, body);
346
- const result = await func(...finalArgs);
347
- return JSON.stringify(result);
348
- };
349
-
350
- const executeToolCalls = async (
351
- toolCallContent: string
352
- ): Promise<RenderInfo[]> => {
353
- const toolCalls = extractPythonicCalls(toolCallContent);
354
- if (toolCalls.length === 0)
355
- return [{ call: "", error: "No valid tool calls found." }];
356
-
357
- const results: RenderInfo[] = [];
358
- for (const call of toolCalls) {
359
- try {
360
- const result = await executeToolCall(call);
361
- const parsedCall = parsePythonicCalls(call);
362
- const toolUsed = parsedCall
363
- ? tools.find((t) => t.name === parsedCall.name && t.enabled)
364
- : null;
365
- const { rendererCode } = toolUsed
366
- ? extractFunctionAndRenderer(toolUsed.code)
367
- : { rendererCode: undefined };
368
-
369
- let parsedResult;
370
- try {
371
- parsedResult = JSON.parse(result);
372
- } catch {
373
- parsedResult = result;
374
- }
375
-
376
- let namedParams: Record<string, unknown> = Object.create(null);
377
- if (parsedCall && toolUsed) {
378
- const schema = generateSchemaFromCode(
379
- extractFunctionAndRenderer(toolUsed.code).functionCode
380
- );
381
- const paramNames = Object.keys(schema.parameters.properties);
382
- namedParams = mapArgsToNamedParams(
383
- paramNames,
384
- parsedCall.positionalArgs,
385
- parsedCall.keywordArgs
386
- );
387
- }
388
-
389
- results.push({
390
- call,
391
- result: parsedResult,
392
- renderer: rendererCode,
393
- input: namedParams,
394
- });
395
- } catch (error) {
396
- const errorMessage = getErrorMessage(error);
397
- results.push({ call, error: errorMessage });
398
- }
399
- }
400
- return results;
401
- };
402
-
403
- const handleSendMessage = async (): Promise<void> => {
404
- if (!input.trim() || !isReady) return;
405
-
406
- const userMessage: Message = { role: "user", content: input };
407
- const currentMessages: Message[] = [...messages, userMessage];
408
- setMessages(currentMessages);
409
- setInput("");
410
- setIsGenerating(true);
411
-
412
- try {
413
- const toolSchemas = tools
414
- .filter((tool) => tool.enabled)
415
- .map((tool) => generateSchemaFromCode(tool.code));
416
-
417
- while (true) {
418
- const messagesForGeneration = [
419
- { role: "system" as const, content: systemPrompt },
420
- ...currentMessages,
421
- ];
422
-
423
- setMessages([...currentMessages, { role: "assistant", content: "" }]);
424
-
425
- let accumulatedContent = "";
426
- const response = await generateResponse(
427
- messagesForGeneration,
428
- toolSchemas,
429
- (token: string) => {
430
- accumulatedContent += token;
431
- setMessages((current) => {
432
- const updated = [...current];
433
- updated[updated.length - 1] = {
434
- role: "assistant",
435
- content: accumulatedContent,
436
- };
437
- return updated;
438
- });
439
- }
440
- );
441
-
442
- currentMessages.push({ role: "assistant", content: response });
443
- const toolCallContent = extractToolCallContent(response);
444
-
445
- if (toolCallContent) {
446
- const toolResults = await executeToolCalls(toolCallContent);
447
-
448
- const toolMessage: ToolMessage = {
449
- role: "tool",
450
- content: JSON.stringify(toolResults.map((r) => r.result ?? null)),
451
- renderInfo: toolResults,
452
- };
453
- currentMessages.push(toolMessage);
454
- setMessages([...currentMessages]);
455
- continue;
456
- } else {
457
- setMessages(currentMessages);
458
- break;
459
- }
460
- }
461
- } catch (error) {
462
- const errorMessage = getErrorMessage(error);
463
- setMessages([
464
- ...currentMessages,
465
- {
466
- role: "assistant",
467
- content: `Error generating response: ${errorMessage}`,
468
- },
469
- ]);
470
- } finally {
471
- setIsGenerating(false);
472
- setTimeout(() => inputRef.current?.focus(), 0);
473
- }
474
- };
475
-
476
- const loadSystemPrompt = useCallback(async (): Promise<void> => {
477
- try {
478
- const db = await getDB();
479
- const stored = await db.get(SETTINGS_STORE_NAME, "systemPrompt");
480
- if (stored && stored.value) setSystemPrompt(stored.value);
481
- } catch (error) {
482
- console.error("Failed to load system prompt:", error);
483
- }
484
- }, []);
485
-
486
- const saveSystemPrompt = useCallback(
487
- async (prompt: string): Promise<void> => {
488
- try {
489
- const db = await getDB();
490
- await db.put(SETTINGS_STORE_NAME, {
491
- key: "systemPrompt",
492
- value: prompt,
493
- });
494
- } catch (error) {
495
- console.error("Failed to save system prompt:", error);
496
- }
497
- },
498
- []
499
- );
500
-
501
- const loadSelectedModel = useCallback(async (): Promise<void> => {
502
- try {
503
- await loadModel();
504
- } catch (error) {
505
- console.error("Failed to load model:", error);
506
- }
507
- }, [loadModel]);
508
-
509
- const loadSelectedModelId = useCallback(async (): Promise<void> => {
510
- try {
511
- const db = await getDB();
512
- const stored = await db.get(SETTINGS_STORE_NAME, "selectedModelId");
513
- if (stored && stored.value) {
514
- setSelectedModelId(stored.value);
515
- }
516
- } catch (error) {
517
- console.error("Failed to load selected model ID:", error);
518
- }
519
- }, []);
520
-
521
- useEffect(() => {
522
- loadSystemPrompt();
523
- }, [loadSystemPrompt]);
524
-
525
- const handleOpenSystemPromptModal = (): void => {
526
- setTempSystemPrompt(systemPrompt);
527
- setIsSystemPromptModalOpen(true);
528
- };
529
-
530
- const handleSaveSystemPrompt = (): void => {
531
- setSystemPrompt(tempSystemPrompt);
532
- saveSystemPrompt(tempSystemPrompt);
533
- setIsSystemPromptModalOpen(false);
534
- };
535
-
536
- const handleCancelSystemPrompt = (): void => {
537
- setTempSystemPrompt("");
538
- setIsSystemPromptModalOpen(false);
539
- };
540
-
541
- const handleResetSystemPrompt = (): void => {
542
- setTempSystemPrompt(DEFAULT_SYSTEM_PROMPT);
543
- };
544
-
545
- const saveSelectedModel = useCallback(
546
- async (modelId: string): Promise<void> => {
547
- try {
548
- const db = await getDB();
549
- await db.put(SETTINGS_STORE_NAME, {
550
- key: "selectedModelId",
551
- value: modelId,
552
- });
553
- } catch (error) {
554
- console.error("Failed to save selected model ID:", error);
555
- }
556
- },
557
- []
558
- );
559
-
560
- useEffect(() => {
561
- loadSystemPrompt();
562
- loadSelectedModelId();
563
- }, [loadSystemPrompt, loadSelectedModelId]);
564
-
565
- const handleModelSelect = async (modelId: string) => {
566
- setSelectedModelId(modelId);
567
- setIsModelDropdownOpen(false);
568
- await saveSelectedModel(modelId);
569
- };
570
-
571
- const handleExampleClick = async (messageText: string): Promise<void> => {
572
- if (!isReady || isGenerating) return;
573
- setInput(messageText);
574
-
575
- const userMessage: Message = { role: "user", content: messageText };
576
- const currentMessages: Message[] = [...messages, userMessage];
577
- setMessages(currentMessages);
578
- setInput("");
579
- setIsGenerating(true);
580
-
581
- try {
582
- const toolSchemas = tools
583
- .filter((tool) => tool.enabled)
584
- .map((tool) => generateSchemaFromCode(tool.code));
585
-
586
- while (true) {
587
- const messagesForGeneration = [
588
- { role: "system" as const, content: systemPrompt },
589
- ...currentMessages,
590
- ];
591
-
592
- setMessages([...currentMessages, { role: "assistant", content: "" }]);
593
-
594
- let accumulatedContent = "";
595
- const response = await generateResponse(
596
- messagesForGeneration,
597
- toolSchemas,
598
- (token: string) => {
599
- accumulatedContent += token;
600
- setMessages((current) => {
601
- const updated = [...current];
602
- updated[updated.length - 1] = {
603
- role: "assistant",
604
- content: accumulatedContent,
605
- };
606
- return updated;
607
- });
608
- }
609
- );
610
-
611
- currentMessages.push({ role: "assistant", content: response });
612
- const toolCallContent = extractToolCallContent(response);
613
-
614
- if (toolCallContent) {
615
- const toolResults = await executeToolCalls(toolCallContent);
616
-
617
- const toolMessage: ToolMessage = {
618
- role: "tool",
619
- content: JSON.stringify(toolResults.map((r) => r.result ?? null)),
620
- renderInfo: toolResults,
621
- };
622
- currentMessages.push(toolMessage);
623
- setMessages([...currentMessages]);
624
- continue;
625
- } else {
626
- setMessages(currentMessages);
627
- break;
628
- }
629
- }
630
- } catch (error) {
631
- const errorMessage = getErrorMessage(error);
632
- setMessages([
633
- ...currentMessages,
634
- {
635
- role: "assistant",
636
- content: `Error generating response: ${errorMessage}`,
637
- },
638
- ]);
639
- } finally {
640
- setIsGenerating(false);
641
- setTimeout(() => inputRef.current?.focus(), 0);
642
- }
643
- };
644
-
645
- return (
646
- <div className="font-sans bg-gray-900">
647
- {!isReady ? (
648
- <LoadingScreen
649
- isLoading={isLoading}
650
- progress={progress}
651
- error={error}
652
- loadSelectedModel={loadSelectedModel}
653
- selectedModelId={selectedModelId}
654
- isModelDropdownOpen={isModelDropdownOpen}
655
- setIsModelDropdownOpen={setIsModelDropdownOpen}
656
- handleModelSelect={handleModelSelect}
657
- />
658
- ) : (
659
- <div className="flex h-screen text-white">
660
- <div
661
- className={`flex flex-col p-4 transition-all duration-300 ${
662
- isToolsPanelVisible ? "w-1/2" : "w-full"
663
- }`}
664
- >
665
- <div className="flex items-center justify-between mb-4">
666
- <div className="flex items-center gap-3">
667
- <h1 className="text-3xl font-bold text-gray-200">
668
- LFM2 WebGPU
669
- </h1>
670
- </div>
671
- <div className="flex items-center gap-3">
672
- <div className="flex items-center text-green-400">
673
- <Zap size={16} className="mr-2" />
674
- Ready
675
- </div>
676
- <button
677
- disabled={isGenerating}
678
- onClick={clearChat}
679
- className={`h-10 flex items-center px-3 py-2 rounded-lg font-bold transition-colors text-sm ${
680
- isGenerating
681
- ? "bg-gray-600 cursor-not-allowed opacity-50"
682
- : "bg-gray-600 hover:bg-gray-700"
683
- }`}
684
- title="Clear chat"
685
- >
686
- <RotateCcw size={14} className="mr-2" /> Clear
687
- </button>
688
- <button
689
- onClick={handleOpenSystemPromptModal}
690
- className="h-10 flex items-center px-3 py-2 rounded-lg font-bold transition-colors bg-gray-600 hover:bg-gray-700 text-sm"
691
- title="Edit system prompt"
692
- >
693
- <Settings size={16} />
694
- </button>
695
- <button
696
- onClick={() => setIsMCPManagerOpen(true)}
697
- className="h-10 flex items-center px-3 py-2 rounded-lg font-bold transition-colors bg-blue-600 hover:bg-blue-700 text-sm"
698
- title="Manage MCP Servers"
699
- >
700
- 🌐
701
- </button>
702
- <button
703
- onClick={() => setIsToolsPanelVisible(!isToolsPanelVisible)}
704
- className="h-10 flex items-center px-3 py-2 rounded-lg font-bold transition-colors bg-gray-600 hover:bg-gray-700 text-sm"
705
- title={
706
- isToolsPanelVisible
707
- ? "Hide Tools Panel"
708
- : "Show Tools Panel"
709
- }
710
- >
711
- {isToolsPanelVisible ? (
712
- <PanelRightClose size={16} />
713
- ) : (
714
- <PanelRightOpen size={16} />
715
- )}
716
- </button>
717
- </div>
718
- </div>
719
-
720
- <div
721
- ref={chatContainerRef}
722
- className="flex-grow bg-gray-800 rounded-lg p-4 overflow-y-auto mb-4 space-y-4"
723
- >
724
- {messages.length === 0 && isReady ? (
725
- <ExamplePrompts onExampleClick={handleExampleClick} />
726
- ) : (
727
- messages.map((msg, index) => {
728
- const key = `${msg.role}-${index}`;
729
-
730
- if (msg.role === "user") {
731
- return (
732
- <div key={key} className="flex justify-end">
733
- <div className="p-3 rounded-lg max-w-md bg-indigo-600">
734
- <p className="text-sm whitespace-pre-wrap">
735
- {msg.content}
736
- </p>
737
- </div>
738
- </div>
739
- );
740
- } else if (msg.role === "assistant") {
741
- const isToolCall = msg.content.includes(
742
- "<|tool_call_start|>"
743
- );
744
-
745
- if (isToolCall) {
746
- const nextMessage = messages[index + 1];
747
- const isCompleted = nextMessage?.role === "tool";
748
- const hasError =
749
- isCompleted &&
750
- (nextMessage as ToolMessage).renderInfo.some(
751
- (info) => !!info.error
752
- );
753
-
754
- return (
755
- <div key={key} className="flex justify-start">
756
- <div className="p-3 rounded-lg bg-gray-700">
757
- <ToolCallIndicator
758
- content={msg.content}
759
- isRunning={!isCompleted}
760
- hasError={hasError}
761
- />
762
- </div>
763
- </div>
764
- );
765
- }
766
-
767
- return (
768
- <div key={key} className="flex justify-start">
769
- <div className="p-3 rounded-lg max-w-md bg-gray-700">
770
- <p className="text-sm whitespace-pre-wrap">
771
- {msg.content}
772
- </p>
773
- </div>
774
- </div>
775
- );
776
- } else if (msg.role === "tool") {
777
- const visibleToolResults = msg.renderInfo.filter(
778
- (info) =>
779
- info.error || (info.result != null && info.renderer)
780
- );
781
-
782
- if (visibleToolResults.length === 0) return null;
783
-
784
- return (
785
- <div key={key} className="flex justify-start">
786
- <div className="p-3 rounded-lg bg-gray-700 max-w-lg">
787
- <div className="space-y-3">
788
- {visibleToolResults.map((info, idx) => (
789
- <div className="flex flex-col gap-2" key={idx}>
790
- <div className="text-xs text-gray-400 font-mono">
791
- {info.call}
792
- </div>
793
- {info.error ? (
794
- <ResultBlock error={info.error} />
795
- ) : (
796
- <ToolResultRenderer
797
- result={info.result}
798
- rendererCode={info.renderer}
799
- input={info.input}
800
- />
801
- )}
802
- </div>
803
- ))}
804
- </div>
805
- </div>
806
- </div>
807
- );
808
- }
809
- return null;
810
- })
811
- )}
812
- </div>
813
-
814
- <div className="flex">
815
- <input
816
- ref={inputRef}
817
- type="text"
818
- value={input}
819
- onChange={(e) => setInput(e.target.value)}
820
- onKeyDown={(e) =>
821
- e.key === "Enter" &&
822
- !isGenerating &&
823
- isReady &&
824
- handleSendMessage()
825
- }
826
- disabled={isGenerating || !isReady}
827
- className="flex-grow bg-gray-700 rounded-l-lg p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50"
828
- placeholder={
829
- isReady
830
- ? "Type your message here..."
831
- : "Load model first to enable chat"
832
- }
833
- />
834
- <button
835
- onClick={handleSendMessage}
836
- disabled={isGenerating || !isReady}
837
- className="bg-indigo-600 hover:bg-indigo-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white font-bold p-3 rounded-r-lg transition-colors"
838
- >
839
- <Play size={20} />
840
- </button>
841
- </div>
842
- </div>
843
-
844
- {isToolsPanelVisible && (
845
- <div className="w-1/2 flex flex-col p-4 border-l border-gray-700 transition-all duration-300">
846
- <div className="flex justify-between items-center mb-4">
847
- <h2 className="text-2xl font-bold text-teal-400">Tools</h2>
848
- <button
849
- onClick={addTool}
850
- className="flex items-center bg-teal-600 hover:bg-teal-700 text-white font-bold py-2 px-4 rounded-lg transition-colors"
851
- >
852
- <Plus size={16} className="mr-2" /> Add Tool
853
- </button>
854
- </div>
855
- <div
856
- ref={toolsContainerRef}
857
- className="flex-grow bg-gray-800 rounded-lg p-4 overflow-y-auto space-y-3"
858
- >
859
- {tools.map((tool) => (
860
- <ToolItem
861
- key={tool.id}
862
- tool={tool}
863
- onToggleEnabled={() => toggleToolEnabled(tool.id)}
864
- onToggleCollapsed={() => toggleToolCollapsed(tool.id)}
865
- onExpand={() => expandTool(tool.id)}
866
- onDelete={() => deleteTool(tool.id)}
867
- onCodeChange={(newCode) =>
868
- handleToolCodeChange(tool.id, newCode)
869
- }
870
- />
871
- ))}
872
- </div>
873
- </div>
874
- )}
875
- </div>
876
- )}
877
-
878
- {isSystemPromptModalOpen && (
879
- <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
880
- <div className="bg-gray-800 rounded-lg p-6 w-3/4 max-w-4xl max-h-3/4 flex flex-col text-gray-100">
881
- <div className="flex justify-between items-center mb-4">
882
- <h2 className="text-xl font-bold text-indigo-400">
883
- Edit System Prompt
884
- </h2>
885
- <button
886
- onClick={handleCancelSystemPrompt}
887
- className="text-gray-400 hover:text-white"
888
- >
889
- <X size={20} />
890
- </button>
891
- </div>
892
- <div className="flex-grow mb-4">
893
- <textarea
894
- value={tempSystemPrompt}
895
- onChange={(e) => setTempSystemPrompt(e.target.value)}
896
- className="w-full h-full bg-gray-700 text-white p-4 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-indigo-500"
897
- placeholder="Enter your system prompt here..."
898
- style={{ minHeight: "300px" }}
899
- />
900
- </div>
901
- <div className="flex justify-between">
902
- <button
903
- onClick={handleResetSystemPrompt}
904
- className="px-4 py-2 bg-teal-600 hover:bg-teal-700 rounded-lg transition-colors"
905
- >
906
- Reset
907
- </button>
908
- <div className="flex gap-3">
909
- <button
910
- onClick={handleSaveSystemPrompt}
911
- className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 rounded-lg transition-colors"
912
- >
913
- Save
914
- </button>
915
- </div>
916
- </div>
917
- </div>
918
- </div>
919
- )}
920
-
921
- {/* MCP Server Manager Modal */}
922
- <MCPServerManager
923
- isOpen={isMCPManagerOpen}
924
- onClose={() => setIsMCPManagerOpen(false)}
925
- />
926
- </div>
927
- );
928
- };
929
-
930
- export default App;
 
60
  interface ToolMessage {
61
  role: "tool";
62
  content: string;
63
+ renderInfo: RenderInfo[];
64
  }
65
  type Message = BaseMessage | ToolMessage;
66
 
 
121
  } = useMCP();
122
 
123
  const loadTools = useCallback(async (): Promise<void> => {
124
+ try {
125
+ const db = await getDB();
126
+ const allTools: Tool[] = await db.getAll(STORE_NAME);
127
+ if (allTools.length === 0) {
128
+ const defaultTools: Tool[] = Object.entries(DEFAULT_TOOLS).map(
129
+ ([name, code], id) => ({
130
+ id,
131
+ name,
132
+ code,
133
+ enabled: true,
134
+ isCollapsed: false,
135
+ })
136
+ );
137
+ const tx = db.transaction(STORE_NAME, "readwrite");
138
+ await Promise.all(defaultTools.map((tool) => tx.store.put(tool)));
139
+ await tx.done;
140
+ setTools(defaultTools);
141
+ } else {
142
+ setTools(allTools.map((t) => ({ ...t, isCollapsed: false })));
143
+ }
144
 
145
+ // Load MCP tools and merge them
146
+ const mcpTools = getMCPToolsAsOriginalTools();
147
+ setTools((prevTools) => [...prevTools, ...mcpTools]);
148
+
149
+ console.log('✅ Tools loaded:', {
150
+ defaultToolCount: allTools.length,
151
+ mcpToolCount: mcpTools.length
152
+ });
153
+ } catch (error) {
154
+ console.error('❌ Failed to load tools:', error);
155
+ }
156
  }, [getMCPToolsAsOriginalTools]);
157
 
158
  useEffect(() => {
 
189
  const clearChat = useCallback(() => {
190
  setMessages([]);
191
  clearPastKeyValues();
192
+ console.log('🧹 Chat cleared');
193
  }, [clearPastKeyValues]);
194
 
195
  const addTool = async (): Promise<void> => {
 
269
  };
270
 
271
  const executeToolCall = async (callString: string): Promise<string> => {
272
+ console.log('🔧 Executing tool call:', callString);
273
+
274
  const parsedCall = parsePythonicCalls(callString);
275
+ if (!parsedCall) {
276
+ const error = `Invalid tool call format: ${callString}`;
277
+ console.error('❌', error);
278
+ throw new Error(error);
279
+ }
280
 
281
  const { name, positionalArgs, keywordArgs } = parsedCall;
282
  const toolToUse = tools.find((t) => t.name === name && t.enabled);
283
+
284
+ if (!toolToUse) {
285
+ const error = `Tool '${name}' not found or is disabled.`;
286
+ console.error('❌', error);
287
+ throw new Error(error);
288
+ }
289
+
290
+ console.log('🔧 Tool found:', { name, enabled: toolToUse.enabled });
291
 
292
  // Check if this is an MCP tool
293
  const isMCPTool = toolToUse.code?.includes("mcpServerId:");
294
  if (isMCPTool) {
295
+ console.log('🌐 MCP tool detected');
296
+
297
  const mcpServerMatch = toolToUse.code?.match(/mcpServerId: "([^"]+)"/);
298
  const mcpToolMatch = toolToUse.code?.match(/toolName: "([^"]+)"/);
299
 
 
301
  const serverId = mcpServerMatch[1];
302
  const toolName = mcpToolMatch[1];
303
 
 
304
  const { functionCode } = extractFunctionAndRenderer(toolToUse.code);
305
  const schema = generateSchemaFromCode(functionCode);
306
  const paramNames = Object.keys(schema.parameters.properties);
307
 
308
  const args: Record<string, unknown> = {};
309
 
 
310
  for (
311
  let i = 0;
312
  i < Math.min(positionalArgs.length, paramNames.length);
 
315
  args[paramNames[i]] = positionalArgs[i];
316
  }
317
 
 
318
  Object.entries(keywordArgs).forEach(([key, value]) => {
319
  args[key] = value;
320
  });
321
 
322
+ console.log('🌐 Calling MCP tool:', { serverId, toolName, args });
323
  const result = await callMCPTool(serverId, toolName, args);
324
+ console.log('✅ MCP tool result:', result);
325
  return JSON.stringify(result);
326
  }
327
  }
328
 
329
+ // Handle local tools
330
+ console.log('🔧 Executing local tool');
331
  const { functionCode } = extractFunctionAndRenderer(toolToUse.code);
332
  const schema = generateSchemaFromCode(functionCode);
333
  const paramNames = Object.keys(schema.parameters.properties);
 
341
  finalArgs.push(positionalArgs[i]);
342
  } else if (Object.prototype.hasOwnProperty.call(keywordArgs, paramName)) {
343
  finalArgs.push(keywordArgs[paramName]);
344
+ } els