Create demo (#1)
Browse files- [WIP] do not merge yet (0fb17e108386b0d4175564b8f6d1baf9b55ab328)
- Delete index.html (2ea66b78a61812e89c3c0b39a831847eebae54ef)
- Delete style.css (2d84b4823957e674885571e16558b0e5c6b8377c)
- Upload 33 files (5ed8a7ec3ef4619a9bd8e25b887e84ce188d7200)
Co-authored-by: Joshua <Xenova@users.noreply.huggingface.co>
- eslint.config.js +23 -0
 - index.html +13 -26
 - index.js +0 -76
 - package.json +35 -0
 - src/App.tsx +758 -0
 - src/components/ExamplePrompts.tsx +46 -0
 - src/components/LoadingScreen.tsx +291 -0
 - src/components/ResultBlock.tsx +21 -0
 - src/components/ToolCallIndicator.tsx +104 -0
 - src/components/ToolItem.tsx +146 -0
 - src/components/ToolResultRenderer.tsx +42 -0
 - src/components/icons/HfLogo.tsx +35 -0
 - src/components/icons/IBMLogo.tsx +12 -0
 - src/constants/db.ts +3 -0
 - src/constants/examples.ts +39 -0
 - src/constants/models.ts +23 -0
 - src/hooks/useLLM.ts +234 -0
 - src/index.css +1 -0
 - src/main.tsx +10 -0
 - src/tools/get_location.js +84 -0
 - src/tools/get_time.js +51 -0
 - src/tools/index.ts +19 -0
 - src/tools/math_eval.js +54 -0
 - src/tools/open_webpage.js +49 -0
 - src/tools/random_number.js +51 -0
 - src/tools/sleep.js +45 -0
 - src/tools/speak.js +59 -0
 - src/tools/template.js +47 -0
 - src/utils.ts +249 -0
 - src/vite-env.d.ts +1 -0
 - style.css +0 -76
 - tsconfig.app.json +27 -0
 - tsconfig.json +7 -0
 - tsconfig.node.json +25 -0
 - vite.config.ts +8 -0
 
    	
        eslint.config.js
    ADDED
    
    | 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import js from "@eslint/js";
         
     | 
| 2 | 
         
            +
            import globals from "globals";
         
     | 
| 3 | 
         
            +
            import reactHooks from "eslint-plugin-react-hooks";
         
     | 
| 4 | 
         
            +
            import reactRefresh from "eslint-plugin-react-refresh";
         
     | 
| 5 | 
         
            +
            import tseslint from "typescript-eslint";
         
     | 
| 6 | 
         
            +
            import { globalIgnores } from "eslint/config";
         
     | 
| 7 | 
         
            +
             
     | 
| 8 | 
         
            +
            export default tseslint.config([
         
     | 
| 9 | 
         
            +
              globalIgnores(["dist"]),
         
     | 
| 10 | 
         
            +
              {
         
     | 
| 11 | 
         
            +
                files: ["**/*.{ts,tsx}"],
         
     | 
| 12 | 
         
            +
                extends: [
         
     | 
| 13 | 
         
            +
                  js.configs.recommended,
         
     | 
| 14 | 
         
            +
                  tseslint.configs.recommended,
         
     | 
| 15 | 
         
            +
                  reactHooks.configs["recommended-latest"],
         
     | 
| 16 | 
         
            +
                  reactRefresh.configs.vite,
         
     | 
| 17 | 
         
            +
                ],
         
     | 
| 18 | 
         
            +
                languageOptions: {
         
     | 
| 19 | 
         
            +
                  ecmaVersion: 2020,
         
     | 
| 20 | 
         
            +
                  globals: globals.browser,
         
     | 
| 21 | 
         
            +
                },
         
     | 
| 22 | 
         
            +
              },
         
     | 
| 23 | 
         
            +
            ]);
         
     | 
    	
        index.html
    CHANGED
    
    | 
         @@ -1,29 +1,16 @@ 
     | 
|
| 1 | 
         
            -
            <! 
     | 
| 2 | 
         
             
            <html lang="en">
         
     | 
| 3 | 
         
            -
             
     | 
| 4 | 
         
            -
            <head>
         
     | 
| 5 | 
         
             
                <meta charset="UTF-8" />
         
     | 
| 6 | 
         
            -
                <link 
     | 
| 7 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 8 | 
         
             
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         
     | 
| 9 | 
         
            -
                <title> 
     | 
| 10 | 
         
            -
            </head>
         
     | 
| 11 | 
         
            -
             
     | 
| 12 | 
         
            -
            < 
     | 
| 13 | 
         
            -
                < 
     | 
| 14 | 
         
            -
             
     | 
| 15 | 
         
            -
             
     | 
| 16 | 
         
            -
                        <path fill="#000"
         
     | 
| 17 | 
         
            -
                            d="M3.5 24.3a3 3 0 0 1-1.9-.8c-.5-.5-.8-1.2-.8-1.9V2.9c0-.7.3-1.3.8-1.9.6-.5 1.2-.7 2-.7h18.6c.7 0 1.3.2 1.9.7.5.6.7 1.2.7 2v18.6c0 .7-.2 1.4-.7 1.9a3 3 0 0 1-2 .8H3.6Zm0-2.7h18.7V2.9H3.5v18.7Zm2.7-2.7h13.3c.3 0 .5 0 .6-.3v-.7l-3.7-5a.6.6 0 0 0-.6-.2c-.2 0-.4 0-.5.3l-3.5 4.6-2.4-3.3a.6.6 0 0 0-.6-.3c-.2 0-.4.1-.5.3l-2.7 3.6c-.1.2-.2.4 0 .7.1.2.3.3.6.3Z">
         
     | 
| 18 | 
         
            -
                        </path>
         
     | 
| 19 | 
         
            -
                    </svg>
         
     | 
| 20 | 
         
            -
                    Click to upload image
         
     | 
| 21 | 
         
            -
                    <label id="example">(or try example)</label>
         
     | 
| 22 | 
         
            -
                </label>
         
     | 
| 23 | 
         
            -
                <label id="status">Loading model...</label>
         
     | 
| 24 | 
         
            -
                <input id="upload" type="file" accept="image/*" />
         
     | 
| 25 | 
         
            -
             
     | 
| 26 | 
         
            -
                <script src="index.js" type="module"></script>
         
     | 
| 27 | 
         
            -
            </body>
         
     | 
| 28 | 
         
            -
             
     | 
| 29 | 
         
            -
            </html>
         
     | 
| 
         | 
|
| 1 | 
         
            +
            <!doctype html>
         
     | 
| 2 | 
         
             
            <html lang="en">
         
     | 
| 3 | 
         
            +
              <head>
         
     | 
| 
         | 
|
| 4 | 
         
             
                <meta charset="UTF-8" />
         
     | 
| 5 | 
         
            +
                <link
         
     | 
| 6 | 
         
            +
                  rel="icon"
         
     | 
| 7 | 
         
            +
                  href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛠️</text></svg>"
         
     | 
| 8 | 
         
            +
                />
         
     | 
| 9 | 
         
             
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         
     | 
| 10 | 
         
            +
                <title>Granite WebGPU - In-Browser Tool Calling</title>
         
     | 
| 11 | 
         
            +
              </head>
         
     | 
| 12 | 
         
            +
              <body>
         
     | 
| 13 | 
         
            +
                <div id="root"></div>
         
     | 
| 14 | 
         
            +
                <script type="module" src="/src/main.tsx"></script>
         
     | 
| 15 | 
         
            +
              </body>
         
     | 
| 16 | 
         
            +
            </html>
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
    	
        index.js
    DELETED
    
    | 
         @@ -1,76 +0,0 @@ 
     | 
|
| 1 | 
         
            -
            import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.4.1';
         
     | 
| 2 | 
         
            -
             
     | 
| 3 | 
         
            -
            // Reference the elements that we will need
         
     | 
| 4 | 
         
            -
            const status = document.getElementById('status');
         
     | 
| 5 | 
         
            -
            const fileUpload = document.getElementById('upload');
         
     | 
| 6 | 
         
            -
            const imageContainer = document.getElementById('container');
         
     | 
| 7 | 
         
            -
            const example = document.getElementById('example');
         
     | 
| 8 | 
         
            -
             
     | 
| 9 | 
         
            -
            const EXAMPLE_URL = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/city-streets.jpg';
         
     | 
| 10 | 
         
            -
             
     | 
| 11 | 
         
            -
            // Create a new object detection pipeline
         
     | 
| 12 | 
         
            -
            status.textContent = 'Loading model...';
         
     | 
| 13 | 
         
            -
            const detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
         
     | 
| 14 | 
         
            -
            status.textContent = 'Ready';
         
     | 
| 15 | 
         
            -
             
     | 
| 16 | 
         
            -
            example.addEventListener('click', (e) => {
         
     | 
| 17 | 
         
            -
                e.preventDefault();
         
     | 
| 18 | 
         
            -
                detect(EXAMPLE_URL);
         
     | 
| 19 | 
         
            -
            });
         
     | 
| 20 | 
         
            -
             
     | 
| 21 | 
         
            -
            fileUpload.addEventListener('change', function (e) {
         
     | 
| 22 | 
         
            -
                const file = e.target.files[0];
         
     | 
| 23 | 
         
            -
                if (!file) {
         
     | 
| 24 | 
         
            -
                    return;
         
     | 
| 25 | 
         
            -
                }
         
     | 
| 26 | 
         
            -
             
     | 
| 27 | 
         
            -
                const reader = new FileReader();
         
     | 
| 28 | 
         
            -
             
     | 
| 29 | 
         
            -
                // Set up a callback when the file is loaded
         
     | 
| 30 | 
         
            -
                reader.onload = e2 => detect(e2.target.result);
         
     | 
| 31 | 
         
            -
             
     | 
| 32 | 
         
            -
                reader.readAsDataURL(file);
         
     | 
| 33 | 
         
            -
            });
         
     | 
| 34 | 
         
            -
             
     | 
| 35 | 
         
            -
             
     | 
| 36 | 
         
            -
            // Detect objects in the image
         
     | 
| 37 | 
         
            -
            async function detect(img) {
         
     | 
| 38 | 
         
            -
                imageContainer.innerHTML = '';
         
     | 
| 39 | 
         
            -
                imageContainer.style.backgroundImage = `url(${img})`;
         
     | 
| 40 | 
         
            -
             
     | 
| 41 | 
         
            -
                status.textContent = 'Analysing...';
         
     | 
| 42 | 
         
            -
                const output = await detector(img, {
         
     | 
| 43 | 
         
            -
                    threshold: 0.5,
         
     | 
| 44 | 
         
            -
                    percentage: true,
         
     | 
| 45 | 
         
            -
                });
         
     | 
| 46 | 
         
            -
                status.textContent = '';
         
     | 
| 47 | 
         
            -
                output.forEach(renderBox);
         
     | 
| 48 | 
         
            -
            }
         
     | 
| 49 | 
         
            -
             
     | 
| 50 | 
         
            -
            // Render a bounding box and label on the image
         
     | 
| 51 | 
         
            -
            function renderBox({ box, label }) {
         
     | 
| 52 | 
         
            -
                const { xmax, xmin, ymax, ymin } = box;
         
     | 
| 53 | 
         
            -
             
     | 
| 54 | 
         
            -
                // Generate a random color for the box
         
     | 
| 55 | 
         
            -
                const color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, 0);
         
     | 
| 56 | 
         
            -
             
     | 
| 57 | 
         
            -
                // Draw the box
         
     | 
| 58 | 
         
            -
                const boxElement = document.createElement('div');
         
     | 
| 59 | 
         
            -
                boxElement.className = 'bounding-box';
         
     | 
| 60 | 
         
            -
                Object.assign(boxElement.style, {
         
     | 
| 61 | 
         
            -
                    borderColor: color,
         
     | 
| 62 | 
         
            -
                    left: 100 * xmin + '%',
         
     | 
| 63 | 
         
            -
                    top: 100 * ymin + '%',
         
     | 
| 64 | 
         
            -
                    width: 100 * (xmax - xmin) + '%',
         
     | 
| 65 | 
         
            -
                    height: 100 * (ymax - ymin) + '%',
         
     | 
| 66 | 
         
            -
                })
         
     | 
| 67 | 
         
            -
             
     | 
| 68 | 
         
            -
                // Draw label
         
     | 
| 69 | 
         
            -
                const labelElement = document.createElement('span');
         
     | 
| 70 | 
         
            -
                labelElement.textContent = label;
         
     | 
| 71 | 
         
            -
                labelElement.className = 'bounding-box-label';
         
     | 
| 72 | 
         
            -
                labelElement.style.backgroundColor = color;
         
     | 
| 73 | 
         
            -
             
     | 
| 74 | 
         
            -
                boxElement.appendChild(labelElement);
         
     | 
| 75 | 
         
            -
                imageContainer.appendChild(boxElement);
         
     | 
| 76 | 
         
            -
            }
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
    	
        package.json
    ADDED
    
    | 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            {
         
     | 
| 2 | 
         
            +
              "name": "granite-tool-calling",
         
     | 
| 3 | 
         
            +
              "private": true,
         
     | 
| 4 | 
         
            +
              "version": "0.0.0",
         
     | 
| 5 | 
         
            +
              "type": "module",
         
     | 
| 6 | 
         
            +
              "scripts": {
         
     | 
| 7 | 
         
            +
                "dev": "vite",
         
     | 
| 8 | 
         
            +
                "build": "tsc -b && vite build",
         
     | 
| 9 | 
         
            +
                "lint": "eslint .",
         
     | 
| 10 | 
         
            +
                "preview": "vite preview"
         
     | 
| 11 | 
         
            +
              },
         
     | 
| 12 | 
         
            +
              "dependencies": {
         
     | 
| 13 | 
         
            +
                "@huggingface/transformers": "^3.7.6",
         
     | 
| 14 | 
         
            +
                "@monaco-editor/react": "^4.7.0",
         
     | 
| 15 | 
         
            +
                "@tailwindcss/vite": "^4.1.11",
         
     | 
| 16 | 
         
            +
                "idb": "^8.0.3",
         
     | 
| 17 | 
         
            +
                "lucide-react": "^0.535.0",
         
     | 
| 18 | 
         
            +
                "react": "^19.1.0",
         
     | 
| 19 | 
         
            +
                "react-dom": "^19.1.0",
         
     | 
| 20 | 
         
            +
                "tailwindcss": "^4.1.11"
         
     | 
| 21 | 
         
            +
              },
         
     | 
| 22 | 
         
            +
              "devDependencies": {
         
     | 
| 23 | 
         
            +
                "@eslint/js": "^9.30.1",
         
     | 
| 24 | 
         
            +
                "@types/react": "^19.1.8",
         
     | 
| 25 | 
         
            +
                "@types/react-dom": "^19.1.6",
         
     | 
| 26 | 
         
            +
                "@vitejs/plugin-react": "^4.6.0",
         
     | 
| 27 | 
         
            +
                "eslint": "^9.30.1",
         
     | 
| 28 | 
         
            +
                "eslint-plugin-react-hooks": "^5.2.0",
         
     | 
| 29 | 
         
            +
                "eslint-plugin-react-refresh": "^0.4.20",
         
     | 
| 30 | 
         
            +
                "globals": "^16.3.0",
         
     | 
| 31 | 
         
            +
                "typescript": "~5.8.3",
         
     | 
| 32 | 
         
            +
                "typescript-eslint": "^8.35.1",
         
     | 
| 33 | 
         
            +
                "vite": "^7.0.4"
         
     | 
| 34 | 
         
            +
              }
         
     | 
| 35 | 
         
            +
            }
         
     | 
    	
        src/App.tsx
    ADDED
    
    | 
         @@ -0,0 +1,758 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import React, {
         
     | 
| 2 | 
         
            +
              useState,
         
     | 
| 3 | 
         
            +
              useEffect,
         
     | 
| 4 | 
         
            +
              useCallback,
         
     | 
| 5 | 
         
            +
              useRef,
         
     | 
| 6 | 
         
            +
              useMemo,
         
     | 
| 7 | 
         
            +
            } from "react";
         
     | 
| 8 | 
         
            +
            import { openDB, type IDBPDatabase } from "idb";
         
     | 
| 9 | 
         
            +
            import { Play, Plus, RotateCcw, Wrench } from "lucide-react";
         
     | 
| 10 | 
         
            +
            import { useLLM } from "./hooks/useLLM";
         
     | 
| 11 | 
         
            +
             
     | 
| 12 | 
         
            +
            import type { Tool } from "./components/ToolItem";
         
     | 
| 13 | 
         
            +
             
     | 
| 14 | 
         
            +
            import {
         
     | 
| 15 | 
         
            +
              extractFunctionAndRenderer,
         
     | 
| 16 | 
         
            +
              generateSchemaFromCode,
         
     | 
| 17 | 
         
            +
              getErrorMessage,
         
     | 
| 18 | 
         
            +
              isMobileOrTablet,
         
     | 
| 19 | 
         
            +
            } from "./utils";
         
     | 
| 20 | 
         
            +
             
     | 
| 21 | 
         
            +
            import { DB_NAME, STORE_NAME, SETTINGS_STORE_NAME } from "./constants/db";
         
     | 
| 22 | 
         
            +
             
     | 
| 23 | 
         
            +
            import { DEFAULT_TOOLS, TEMPLATE } from "./tools";
         
     | 
| 24 | 
         
            +
            import ToolResultRenderer from "./components/ToolResultRenderer";
         
     | 
| 25 | 
         
            +
            import ToolCallIndicator from "./components/ToolCallIndicator";
         
     | 
| 26 | 
         
            +
            import ToolItem from "./components/ToolItem";
         
     | 
| 27 | 
         
            +
            import ResultBlock from "./components/ResultBlock";
         
     | 
| 28 | 
         
            +
            import ExamplePrompts from "./components/ExamplePrompts";
         
     | 
| 29 | 
         
            +
             
     | 
| 30 | 
         
            +
            import { LoadingScreen } from "./components/LoadingScreen";
         
     | 
| 31 | 
         
            +
             
     | 
| 32 | 
         
            +
            interface RenderInfo {
         
     | 
| 33 | 
         
            +
              call: string;
         
     | 
| 34 | 
         
            +
              result?: any;
         
     | 
| 35 | 
         
            +
              renderer?: string;
         
     | 
| 36 | 
         
            +
              input?: Record<string, any>;
         
     | 
| 37 | 
         
            +
              error?: string;
         
     | 
| 38 | 
         
            +
            }
         
     | 
| 39 | 
         
            +
             
     | 
| 40 | 
         
            +
            interface BaseMessage {
         
     | 
| 41 | 
         
            +
              role: "system" | "user" | "assistant";
         
     | 
| 42 | 
         
            +
              content: string;
         
     | 
| 43 | 
         
            +
            }
         
     | 
| 44 | 
         
            +
            interface ToolMessage {
         
     | 
| 45 | 
         
            +
              role: "tool";
         
     | 
| 46 | 
         
            +
              content: string;
         
     | 
| 47 | 
         
            +
              renderInfo: RenderInfo[]; // Rich data for the UI
         
     | 
| 48 | 
         
            +
            }
         
     | 
| 49 | 
         
            +
            type Message = BaseMessage | ToolMessage;
         
     | 
| 50 | 
         
            +
             
     | 
| 51 | 
         
            +
            async function getDB(): Promise<IDBPDatabase> {
         
     | 
| 52 | 
         
            +
              return openDB(DB_NAME, 1, {
         
     | 
| 53 | 
         
            +
                upgrade(db) {
         
     | 
| 54 | 
         
            +
                  if (!db.objectStoreNames.contains(STORE_NAME)) {
         
     | 
| 55 | 
         
            +
                    db.createObjectStore(STORE_NAME, {
         
     | 
| 56 | 
         
            +
                      keyPath: "id",
         
     | 
| 57 | 
         
            +
                      autoIncrement: true,
         
     | 
| 58 | 
         
            +
                    });
         
     | 
| 59 | 
         
            +
                  }
         
     | 
| 60 | 
         
            +
                  if (!db.objectStoreNames.contains(SETTINGS_STORE_NAME)) {
         
     | 
| 61 | 
         
            +
                    db.createObjectStore(SETTINGS_STORE_NAME, { keyPath: "key" });
         
     | 
| 62 | 
         
            +
                  }
         
     | 
| 63 | 
         
            +
                },
         
     | 
| 64 | 
         
            +
              });
         
     | 
| 65 | 
         
            +
            }
         
     | 
| 66 | 
         
            +
             
     | 
| 67 | 
         
            +
            const App: React.FC = () => {
         
     | 
| 68 | 
         
            +
              const [messages, setMessages] = useState<Message[]>([]);
         
     | 
| 69 | 
         
            +
              const [tools, setTools] = useState<Tool[]>([]);
         
     | 
| 70 | 
         
            +
              const [input, setInput] = useState<string>("");
         
     | 
| 71 | 
         
            +
              const [isGenerating, setIsGenerating] = useState<boolean>(false);
         
     | 
| 72 | 
         
            +
              const isMobile = useMemo(isMobileOrTablet, []);
         
     | 
| 73 | 
         
            +
              const [selectedModel, setSelectedModel] = useState<string>(
         
     | 
| 74 | 
         
            +
                isMobile ? "350M" : "1B",
         
     | 
| 75 | 
         
            +
              );
         
     | 
| 76 | 
         
            +
              const [isModelDropdownOpen, setIsModelDropdownOpen] =
         
     | 
| 77 | 
         
            +
                useState<boolean>(false);
         
     | 
| 78 | 
         
            +
              const [isToolsPanelVisible, setIsToolsPanelVisible] =
         
     | 
| 79 | 
         
            +
                useState<boolean>(false);
         
     | 
| 80 | 
         
            +
              const chatContainerRef = useRef<HTMLDivElement>(null);
         
     | 
| 81 | 
         
            +
              const debounceTimers = useRef<Record<number, NodeJS.Timeout>>({});
         
     | 
| 82 | 
         
            +
              const toolsContainerRef = useRef<HTMLDivElement>(null);
         
     | 
| 83 | 
         
            +
              const inputRef = useRef<HTMLInputElement>(null);
         
     | 
| 84 | 
         
            +
              const {
         
     | 
| 85 | 
         
            +
                isLoading,
         
     | 
| 86 | 
         
            +
                isReady,
         
     | 
| 87 | 
         
            +
                error,
         
     | 
| 88 | 
         
            +
                progress,
         
     | 
| 89 | 
         
            +
                loadModel,
         
     | 
| 90 | 
         
            +
                generateResponse,
         
     | 
| 91 | 
         
            +
                clearPastKeyValues,
         
     | 
| 92 | 
         
            +
              } = useLLM(selectedModel);
         
     | 
| 93 | 
         
            +
             
     | 
| 94 | 
         
            +
              const loadTools = useCallback(async (): Promise<void> => {
         
     | 
| 95 | 
         
            +
                const db = await getDB();
         
     | 
| 96 | 
         
            +
                const allTools: Tool[] = await db.getAll(STORE_NAME);
         
     | 
| 97 | 
         
            +
                if (allTools.length === 0) {
         
     | 
| 98 | 
         
            +
                  const defaultTools: Tool[] = Object.entries(DEFAULT_TOOLS).map(
         
     | 
| 99 | 
         
            +
                    ([name, code], id) => ({
         
     | 
| 100 | 
         
            +
                      id,
         
     | 
| 101 | 
         
            +
                      name,
         
     | 
| 102 | 
         
            +
                      code,
         
     | 
| 103 | 
         
            +
                      enabled: true,
         
     | 
| 104 | 
         
            +
                      isCollapsed: false,
         
     | 
| 105 | 
         
            +
                    }),
         
     | 
| 106 | 
         
            +
                  );
         
     | 
| 107 | 
         
            +
                  const tx = db.transaction(STORE_NAME, "readwrite");
         
     | 
| 108 | 
         
            +
                  await Promise.all(defaultTools.map((tool) => tx.store.put(tool)));
         
     | 
| 109 | 
         
            +
                  await tx.done;
         
     | 
| 110 | 
         
            +
                  setTools(defaultTools);
         
     | 
| 111 | 
         
            +
                } else {
         
     | 
| 112 | 
         
            +
                  setTools(allTools.map((t) => ({ ...t, isCollapsed: false })));
         
     | 
| 113 | 
         
            +
                }
         
     | 
| 114 | 
         
            +
              }, []);
         
     | 
| 115 | 
         
            +
             
     | 
| 116 | 
         
            +
              useEffect(() => {
         
     | 
| 117 | 
         
            +
                loadTools();
         
     | 
| 118 | 
         
            +
              }, [loadTools]);
         
     | 
| 119 | 
         
            +
             
     | 
| 120 | 
         
            +
              useEffect(() => {
         
     | 
| 121 | 
         
            +
                if (chatContainerRef.current) {
         
     | 
| 122 | 
         
            +
                  chatContainerRef.current.scrollTop =
         
     | 
| 123 | 
         
            +
                    chatContainerRef.current.scrollHeight;
         
     | 
| 124 | 
         
            +
                }
         
     | 
| 125 | 
         
            +
              }, [messages]);
         
     | 
| 126 | 
         
            +
             
     | 
| 127 | 
         
            +
              const updateToolInDB = async (tool: Tool): Promise<void> => {
         
     | 
| 128 | 
         
            +
                const db = await getDB();
         
     | 
| 129 | 
         
            +
                await db.put(STORE_NAME, tool);
         
     | 
| 130 | 
         
            +
              };
         
     | 
| 131 | 
         
            +
             
     | 
| 132 | 
         
            +
              const saveToolDebounced = (tool: Tool): void => {
         
     | 
| 133 | 
         
            +
                if (tool.id !== undefined && debounceTimers.current[tool.id]) {
         
     | 
| 134 | 
         
            +
                  clearTimeout(debounceTimers.current[tool.id]);
         
     | 
| 135 | 
         
            +
                }
         
     | 
| 136 | 
         
            +
                if (tool.id !== undefined) {
         
     | 
| 137 | 
         
            +
                  debounceTimers.current[tool.id] = setTimeout(() => {
         
     | 
| 138 | 
         
            +
                    updateToolInDB(tool);
         
     | 
| 139 | 
         
            +
                  }, 300);
         
     | 
| 140 | 
         
            +
                }
         
     | 
| 141 | 
         
            +
              };
         
     | 
| 142 | 
         
            +
             
     | 
| 143 | 
         
            +
              const clearChat = useCallback(() => {
         
     | 
| 144 | 
         
            +
                setMessages([]);
         
     | 
| 145 | 
         
            +
                clearPastKeyValues();
         
     | 
| 146 | 
         
            +
              }, [clearPastKeyValues]);
         
     | 
| 147 | 
         
            +
             
     | 
| 148 | 
         
            +
              const addTool = async (): Promise<void> => {
         
     | 
| 149 | 
         
            +
                const newTool: Omit<Tool, "id"> = {
         
     | 
| 150 | 
         
            +
                  name: "new_tool",
         
     | 
| 151 | 
         
            +
                  code: TEMPLATE,
         
     | 
| 152 | 
         
            +
                  enabled: true,
         
     | 
| 153 | 
         
            +
                  isCollapsed: false,
         
     | 
| 154 | 
         
            +
                };
         
     | 
| 155 | 
         
            +
                const db = await getDB();
         
     | 
| 156 | 
         
            +
                const id = await db.add(STORE_NAME, newTool);
         
     | 
| 157 | 
         
            +
                setTools((prev) => {
         
     | 
| 158 | 
         
            +
                  const updated = [...prev, { ...newTool, id: id as number }];
         
     | 
| 159 | 
         
            +
                  setTimeout(() => {
         
     | 
| 160 | 
         
            +
                    if (toolsContainerRef.current) {
         
     | 
| 161 | 
         
            +
                      toolsContainerRef.current.scrollTop =
         
     | 
| 162 | 
         
            +
                        toolsContainerRef.current.scrollHeight;
         
     | 
| 163 | 
         
            +
                    }
         
     | 
| 164 | 
         
            +
                  }, 0);
         
     | 
| 165 | 
         
            +
                  return updated;
         
     | 
| 166 | 
         
            +
                });
         
     | 
| 167 | 
         
            +
                clearChat();
         
     | 
| 168 | 
         
            +
              };
         
     | 
| 169 | 
         
            +
             
     | 
| 170 | 
         
            +
              const deleteTool = async (id: number): Promise<void> => {
         
     | 
| 171 | 
         
            +
                if (debounceTimers.current[id]) {
         
     | 
| 172 | 
         
            +
                  clearTimeout(debounceTimers.current[id]);
         
     | 
| 173 | 
         
            +
                }
         
     | 
| 174 | 
         
            +
                const db = await getDB();
         
     | 
| 175 | 
         
            +
                await db.delete(STORE_NAME, id);
         
     | 
| 176 | 
         
            +
                setTools(tools.filter((tool) => tool.id !== id));
         
     | 
| 177 | 
         
            +
                clearChat();
         
     | 
| 178 | 
         
            +
              };
         
     | 
| 179 | 
         
            +
             
     | 
| 180 | 
         
            +
              const toggleToolEnabled = (id: number): void => {
         
     | 
| 181 | 
         
            +
                let changedTool: Tool | undefined;
         
     | 
| 182 | 
         
            +
                const newTools = tools.map((tool) => {
         
     | 
| 183 | 
         
            +
                  if (tool.id === id) {
         
     | 
| 184 | 
         
            +
                    changedTool = { ...tool, enabled: !tool.enabled };
         
     | 
| 185 | 
         
            +
                    return changedTool;
         
     | 
| 186 | 
         
            +
                  }
         
     | 
| 187 | 
         
            +
                  return tool;
         
     | 
| 188 | 
         
            +
                });
         
     | 
| 189 | 
         
            +
                setTools(newTools);
         
     | 
| 190 | 
         
            +
                if (changedTool) saveToolDebounced(changedTool);
         
     | 
| 191 | 
         
            +
              };
         
     | 
| 192 | 
         
            +
             
     | 
| 193 | 
         
            +
              const toggleToolCollapsed = (id: number): void => {
         
     | 
| 194 | 
         
            +
                setTools(
         
     | 
| 195 | 
         
            +
                  tools.map((tool) =>
         
     | 
| 196 | 
         
            +
                    tool.id === id ? { ...tool, isCollapsed: !tool.isCollapsed } : tool,
         
     | 
| 197 | 
         
            +
                  ),
         
     | 
| 198 | 
         
            +
                );
         
     | 
| 199 | 
         
            +
              };
         
     | 
| 200 | 
         
            +
             
     | 
| 201 | 
         
            +
              const expandTool = (id: number): void => {
         
     | 
| 202 | 
         
            +
                setTools(
         
     | 
| 203 | 
         
            +
                  tools.map((tool) =>
         
     | 
| 204 | 
         
            +
                    tool.id === id ? { ...tool, isCollapsed: false } : tool,
         
     | 
| 205 | 
         
            +
                  ),
         
     | 
| 206 | 
         
            +
                );
         
     | 
| 207 | 
         
            +
              };
         
     | 
| 208 | 
         
            +
             
     | 
| 209 | 
         
            +
              const handleToolCodeChange = (id: number, newCode: string): void => {
         
     | 
| 210 | 
         
            +
                let changedTool: Tool | undefined;
         
     | 
| 211 | 
         
            +
                const newTools = tools.map((tool) => {
         
     | 
| 212 | 
         
            +
                  if (tool.id === id) {
         
     | 
| 213 | 
         
            +
                    const { functionCode } = extractFunctionAndRenderer(newCode);
         
     | 
| 214 | 
         
            +
                    const schema = generateSchemaFromCode(functionCode);
         
     | 
| 215 | 
         
            +
                    changedTool = { ...tool, code: newCode, name: schema.name };
         
     | 
| 216 | 
         
            +
                    return changedTool;
         
     | 
| 217 | 
         
            +
                  }
         
     | 
| 218 | 
         
            +
                  return tool;
         
     | 
| 219 | 
         
            +
                });
         
     | 
| 220 | 
         
            +
                setTools(newTools);
         
     | 
| 221 | 
         
            +
                if (changedTool) saveToolDebounced(changedTool);
         
     | 
| 222 | 
         
            +
              };
         
     | 
| 223 | 
         
            +
             
     | 
| 224 | 
         
            +
              interface ToolCallPayload {
         
     | 
| 225 | 
         
            +
                name: string;
         
     | 
| 226 | 
         
            +
                arguments?: Record<string, any>;
         
     | 
| 227 | 
         
            +
              }
         
     | 
| 228 | 
         
            +
             
     | 
| 229 | 
         
            +
              const extractToolCalls = (text: string): ToolCallPayload[] => {
         
     | 
| 230 | 
         
            +
                const matches = Array.from(
         
     | 
| 231 | 
         
            +
                  text.matchAll(/<tool_call>([\s\S]*?)<\/tool_call>/g),
         
     | 
| 232 | 
         
            +
                );
         
     | 
| 233 | 
         
            +
                const toolCalls: ToolCallPayload[] = [];
         
     | 
| 234 | 
         
            +
             
     | 
| 235 | 
         
            +
                for (const match of matches) {
         
     | 
| 236 | 
         
            +
                  try {
         
     | 
| 237 | 
         
            +
                    const parsed = JSON.parse(match[1].trim());
         
     | 
| 238 | 
         
            +
                    if (parsed && typeof parsed.name === "string") {
         
     | 
| 239 | 
         
            +
                      toolCalls.push({
         
     | 
| 240 | 
         
            +
                        name: parsed.name,
         
     | 
| 241 | 
         
            +
                        arguments: parsed.arguments ?? {},
         
     | 
| 242 | 
         
            +
                      });
         
     | 
| 243 | 
         
            +
                    }
         
     | 
| 244 | 
         
            +
                  } catch {
         
     | 
| 245 | 
         
            +
                    // ignore malformed tool call payloads
         
     | 
| 246 | 
         
            +
                  }
         
     | 
| 247 | 
         
            +
                }
         
     | 
| 248 | 
         
            +
             
     | 
| 249 | 
         
            +
                return toolCalls;
         
     | 
| 250 | 
         
            +
              };
         
     | 
| 251 | 
         
            +
             
     | 
| 252 | 
         
            +
              const executeToolCall = async (
         
     | 
| 253 | 
         
            +
                toolCall: ToolCallPayload,
         
     | 
| 254 | 
         
            +
              ): Promise<{
         
     | 
| 255 | 
         
            +
                serializedResult: string;
         
     | 
| 256 | 
         
            +
                rendererCode?: string;
         
     | 
| 257 | 
         
            +
                input: Record<string, any>;
         
     | 
| 258 | 
         
            +
              }> => {
         
     | 
| 259 | 
         
            +
                const toolToUse = tools.find((t) => t.name === toolCall.name && t.enabled);
         
     | 
| 260 | 
         
            +
                if (!toolToUse)
         
     | 
| 261 | 
         
            +
                  throw new Error(`Tool '${toolCall.name}' not found or is disabled.`);
         
     | 
| 262 | 
         
            +
             
     | 
| 263 | 
         
            +
                const { functionCode, rendererCode } = extractFunctionAndRenderer(
         
     | 
| 264 | 
         
            +
                  toolToUse.code,
         
     | 
| 265 | 
         
            +
                );
         
     | 
| 266 | 
         
            +
                const schema = generateSchemaFromCode(functionCode);
         
     | 
| 267 | 
         
            +
                const properties = schema.parameters?.properties ?? {};
         
     | 
| 268 | 
         
            +
                const paramNames = Object.keys(properties);
         
     | 
| 269 | 
         
            +
                const requiredParams = schema.parameters?.required ?? [];
         
     | 
| 270 | 
         
            +
                const callArgs = toolCall.arguments ?? {};
         
     | 
| 271 | 
         
            +
             
     | 
| 272 | 
         
            +
                const finalArgs: any[] = [];
         
     | 
| 273 | 
         
            +
                const resolvedArgs: Record<string, any> = Object.create(null);
         
     | 
| 274 | 
         
            +
             
     | 
| 275 | 
         
            +
                for (const paramName of paramNames) {
         
     | 
| 276 | 
         
            +
                  const propertyConfig = properties[paramName] ?? {};
         
     | 
| 277 | 
         
            +
                  if (Object.prototype.hasOwnProperty.call(callArgs, paramName)) {
         
     | 
| 278 | 
         
            +
                    const value = callArgs[paramName];
         
     | 
| 279 | 
         
            +
                    finalArgs.push(value);
         
     | 
| 280 | 
         
            +
                    resolvedArgs[paramName] = value;
         
     | 
| 281 | 
         
            +
                  } else if (
         
     | 
| 282 | 
         
            +
                    Object.prototype.hasOwnProperty.call(propertyConfig, "default")
         
     | 
| 283 | 
         
            +
                  ) {
         
     | 
| 284 | 
         
            +
                    const value = propertyConfig.default;
         
     | 
| 285 | 
         
            +
                    finalArgs.push(value);
         
     | 
| 286 | 
         
            +
                    resolvedArgs[paramName] = value;
         
     | 
| 287 | 
         
            +
                  } else if (!requiredParams.includes(paramName)) {
         
     | 
| 288 | 
         
            +
                    finalArgs.push(undefined);
         
     | 
| 289 | 
         
            +
                    resolvedArgs[paramName] = undefined;
         
     | 
| 290 | 
         
            +
                  } else {
         
     | 
| 291 | 
         
            +
                    throw new Error(`Missing required argument: ${paramName}`);
         
     | 
| 292 | 
         
            +
                  }
         
     | 
| 293 | 
         
            +
                }
         
     | 
| 294 | 
         
            +
             
     | 
| 295 | 
         
            +
                for (const extraKey of Object.keys(callArgs)) {
         
     | 
| 296 | 
         
            +
                  if (!Object.prototype.hasOwnProperty.call(resolvedArgs, extraKey)) {
         
     | 
| 297 | 
         
            +
                    resolvedArgs[extraKey] = callArgs[extraKey];
         
     | 
| 298 | 
         
            +
                  }
         
     | 
| 299 | 
         
            +
                }
         
     | 
| 300 | 
         
            +
             
     | 
| 301 | 
         
            +
                const bodyMatch = functionCode.match(/function[^{]+\{([\s\S]*)\}/);
         
     | 
| 302 | 
         
            +
                if (!bodyMatch) {
         
     | 
| 303 | 
         
            +
                  throw new Error(
         
     | 
| 304 | 
         
            +
                    "Could not parse function body. Ensure it's a standard `function` declaration.",
         
     | 
| 305 | 
         
            +
                  );
         
     | 
| 306 | 
         
            +
                }
         
     | 
| 307 | 
         
            +
                const body = bodyMatch[1];
         
     | 
| 308 | 
         
            +
                const AsyncFunction = Object.getPrototypeOf(
         
     | 
| 309 | 
         
            +
                  async function () {},
         
     | 
| 310 | 
         
            +
                ).constructor;
         
     | 
| 311 | 
         
            +
                const func = new AsyncFunction(...paramNames, body);
         
     | 
| 312 | 
         
            +
                const result = await func(...finalArgs);
         
     | 
| 313 | 
         
            +
             
     | 
| 314 | 
         
            +
                return {
         
     | 
| 315 | 
         
            +
                  serializedResult: JSON.stringify(result),
         
     | 
| 316 | 
         
            +
                  rendererCode,
         
     | 
| 317 | 
         
            +
                  input: resolvedArgs,
         
     | 
| 318 | 
         
            +
                };
         
     | 
| 319 | 
         
            +
              };
         
     | 
| 320 | 
         
            +
             
     | 
| 321 | 
         
            +
              const executeToolCalls = async (
         
     | 
| 322 | 
         
            +
                toolCalls: ToolCallPayload[],
         
     | 
| 323 | 
         
            +
              ): Promise<RenderInfo[]> => {
         
     | 
| 324 | 
         
            +
                if (toolCalls.length === 0) {
         
     | 
| 325 | 
         
            +
                  return [{ call: "", error: "No valid tool calls found." }];
         
     | 
| 326 | 
         
            +
                }
         
     | 
| 327 | 
         
            +
             
     | 
| 328 | 
         
            +
                const results: RenderInfo[] = [];
         
     | 
| 329 | 
         
            +
             
     | 
| 330 | 
         
            +
                for (const toolCall of toolCalls) {
         
     | 
| 331 | 
         
            +
                  const callDisplay = `<tool_call>${JSON.stringify(toolCall)}</tool_call>`;
         
     | 
| 332 | 
         
            +
                  try {
         
     | 
| 333 | 
         
            +
                    const { serializedResult, rendererCode, input } =
         
     | 
| 334 | 
         
            +
                      await executeToolCall(toolCall);
         
     | 
| 335 | 
         
            +
             
     | 
| 336 | 
         
            +
                    let parsedResult: unknown;
         
     | 
| 337 | 
         
            +
                    try {
         
     | 
| 338 | 
         
            +
                      parsedResult = JSON.parse(serializedResult);
         
     | 
| 339 | 
         
            +
                    } catch {
         
     | 
| 340 | 
         
            +
                      parsedResult = serializedResult;
         
     | 
| 341 | 
         
            +
                    }
         
     | 
| 342 | 
         
            +
             
     | 
| 343 | 
         
            +
                    results.push({
         
     | 
| 344 | 
         
            +
                      call: callDisplay,
         
     | 
| 345 | 
         
            +
                      result: parsedResult,
         
     | 
| 346 | 
         
            +
                      renderer: rendererCode,
         
     | 
| 347 | 
         
            +
                      input,
         
     | 
| 348 | 
         
            +
                    });
         
     | 
| 349 | 
         
            +
                  } catch (error) {
         
     | 
| 350 | 
         
            +
                    results.push({
         
     | 
| 351 | 
         
            +
                      call: callDisplay,
         
     | 
| 352 | 
         
            +
                      error: getErrorMessage(error),
         
     | 
| 353 | 
         
            +
                    });
         
     | 
| 354 | 
         
            +
                  }
         
     | 
| 355 | 
         
            +
                }
         
     | 
| 356 | 
         
            +
             
     | 
| 357 | 
         
            +
                return results;
         
     | 
| 358 | 
         
            +
              };
         
     | 
| 359 | 
         
            +
             
     | 
| 360 | 
         
            +
              const handleSendMessage = async (): Promise<void> => {
         
     | 
| 361 | 
         
            +
                if (!input.trim() || !isReady) return;
         
     | 
| 362 | 
         
            +
             
     | 
| 363 | 
         
            +
                const userMessage: Message = { role: "user", content: input };
         
     | 
| 364 | 
         
            +
                let currentMessages: Message[] = [...messages, userMessage];
         
     | 
| 365 | 
         
            +
                setMessages(currentMessages);
         
     | 
| 366 | 
         
            +
                setInput("");
         
     | 
| 367 | 
         
            +
                setIsGenerating(true);
         
     | 
| 368 | 
         
            +
             
     | 
| 369 | 
         
            +
                try {
         
     | 
| 370 | 
         
            +
                  const toolSchemas = tools
         
     | 
| 371 | 
         
            +
                    .filter((tool) => tool.enabled)
         
     | 
| 372 | 
         
            +
                    .map((tool) => generateSchemaFromCode(tool.code));
         
     | 
| 373 | 
         
            +
             
     | 
| 374 | 
         
            +
                  while (true) {
         
     | 
| 375 | 
         
            +
                    const messagesForGeneration = [...currentMessages];
         
     | 
| 376 | 
         
            +
             
     | 
| 377 | 
         
            +
                    setMessages([...currentMessages, { role: "assistant", content: "" }]);
         
     | 
| 378 | 
         
            +
             
     | 
| 379 | 
         
            +
                    let accumulatedContent = "";
         
     | 
| 380 | 
         
            +
                    const response = await generateResponse(
         
     | 
| 381 | 
         
            +
                      messagesForGeneration,
         
     | 
| 382 | 
         
            +
                      toolSchemas,
         
     | 
| 383 | 
         
            +
                      (token: string) => {
         
     | 
| 384 | 
         
            +
                        accumulatedContent += token;
         
     | 
| 385 | 
         
            +
                        setMessages((current) => {
         
     | 
| 386 | 
         
            +
                          const updated = [...current];
         
     | 
| 387 | 
         
            +
                          updated[updated.length - 1] = {
         
     | 
| 388 | 
         
            +
                            role: "assistant",
         
     | 
| 389 | 
         
            +
                            content: accumulatedContent,
         
     | 
| 390 | 
         
            +
                          };
         
     | 
| 391 | 
         
            +
                          return updated;
         
     | 
| 392 | 
         
            +
                        });
         
     | 
| 393 | 
         
            +
                      },
         
     | 
| 394 | 
         
            +
                    );
         
     | 
| 395 | 
         
            +
             
     | 
| 396 | 
         
            +
                    currentMessages.push({ role: "assistant", content: response });
         
     | 
| 397 | 
         
            +
                    const toolCalls = extractToolCalls(response);
         
     | 
| 398 | 
         
            +
             
     | 
| 399 | 
         
            +
                    if (toolCalls.length > 0) {
         
     | 
| 400 | 
         
            +
                      const toolResults = await executeToolCalls(toolCalls);
         
     | 
| 401 | 
         
            +
             
     | 
| 402 | 
         
            +
                      const toolMessage: ToolMessage = {
         
     | 
| 403 | 
         
            +
                        role: "tool",
         
     | 
| 404 | 
         
            +
                        content: JSON.stringify(toolResults.map((r) => r.result ?? null)),
         
     | 
| 405 | 
         
            +
                        renderInfo: toolResults,
         
     | 
| 406 | 
         
            +
                      };
         
     | 
| 407 | 
         
            +
                      currentMessages.push(toolMessage);
         
     | 
| 408 | 
         
            +
                      setMessages([...currentMessages]);
         
     | 
| 409 | 
         
            +
                      continue;
         
     | 
| 410 | 
         
            +
                    } else {
         
     | 
| 411 | 
         
            +
                      setMessages(currentMessages);
         
     | 
| 412 | 
         
            +
                      break;
         
     | 
| 413 | 
         
            +
                    }
         
     | 
| 414 | 
         
            +
                  }
         
     | 
| 415 | 
         
            +
                } catch (error) {
         
     | 
| 416 | 
         
            +
                  const errorMessage = getErrorMessage(error);
         
     | 
| 417 | 
         
            +
                  setMessages([
         
     | 
| 418 | 
         
            +
                    ...currentMessages,
         
     | 
| 419 | 
         
            +
                    {
         
     | 
| 420 | 
         
            +
                      role: "assistant",
         
     | 
| 421 | 
         
            +
                      content: `Error generating response: ${errorMessage}`,
         
     | 
| 422 | 
         
            +
                    },
         
     | 
| 423 | 
         
            +
                  ]);
         
     | 
| 424 | 
         
            +
                } finally {
         
     | 
| 425 | 
         
            +
                  setIsGenerating(false);
         
     | 
| 426 | 
         
            +
                  setTimeout(() => inputRef.current?.focus(), 0);
         
     | 
| 427 | 
         
            +
                }
         
     | 
| 428 | 
         
            +
              };
         
     | 
| 429 | 
         
            +
             
     | 
| 430 | 
         
            +
              const loadSelectedModel = useCallback(async (): Promise<void> => {
         
     | 
| 431 | 
         
            +
                try {
         
     | 
| 432 | 
         
            +
                  await loadModel();
         
     | 
| 433 | 
         
            +
                } catch (error) {
         
     | 
| 434 | 
         
            +
                  console.error("Failed to load model:", error);
         
     | 
| 435 | 
         
            +
                }
         
     | 
| 436 | 
         
            +
              }, [selectedModel, loadModel]);
         
     | 
| 437 | 
         
            +
             
     | 
| 438 | 
         
            +
              const saveSelectedModel = useCallback(async (modelId: string) => {
         
     | 
| 439 | 
         
            +
                try {
         
     | 
| 440 | 
         
            +
                  const db = await getDB();
         
     | 
| 441 | 
         
            +
                  await db.put(SETTINGS_STORE_NAME, {
         
     | 
| 442 | 
         
            +
                    key: "selectedModelId",
         
     | 
| 443 | 
         
            +
                    value: modelId,
         
     | 
| 444 | 
         
            +
                  });
         
     | 
| 445 | 
         
            +
                } catch (error) {
         
     | 
| 446 | 
         
            +
                  console.error("Failed to save selected model ID:", error);
         
     | 
| 447 | 
         
            +
                }
         
     | 
| 448 | 
         
            +
              }, []);
         
     | 
| 449 | 
         
            +
             
     | 
| 450 | 
         
            +
              const loadSelectedModelId = useCallback(async (): Promise<void> => {
         
     | 
| 451 | 
         
            +
                try {
         
     | 
| 452 | 
         
            +
                  const db = await getDB();
         
     | 
| 453 | 
         
            +
                  const stored = await db.get(SETTINGS_STORE_NAME, "selectedModelId");
         
     | 
| 454 | 
         
            +
                  if (stored && stored.value) {
         
     | 
| 455 | 
         
            +
                    setSelectedModel(stored.value);
         
     | 
| 456 | 
         
            +
                  }
         
     | 
| 457 | 
         
            +
                } catch (error) {
         
     | 
| 458 | 
         
            +
                  console.error("Failed to load selected model ID:", error);
         
     | 
| 459 | 
         
            +
                }
         
     | 
| 460 | 
         
            +
              }, []);
         
     | 
| 461 | 
         
            +
             
     | 
| 462 | 
         
            +
              useEffect(() => {
         
     | 
| 463 | 
         
            +
                loadSelectedModelId();
         
     | 
| 464 | 
         
            +
              }, [loadSelectedModelId]);
         
     | 
| 465 | 
         
            +
             
     | 
| 466 | 
         
            +
              const handleModelSelect = async (modelId: string) => {
         
     | 
| 467 | 
         
            +
                setSelectedModel(modelId);
         
     | 
| 468 | 
         
            +
                setIsModelDropdownOpen(false);
         
     | 
| 469 | 
         
            +
                await saveSelectedModel(modelId);
         
     | 
| 470 | 
         
            +
              };
         
     | 
| 471 | 
         
            +
             
     | 
| 472 | 
         
            +
              const handleExampleClick = async (messageText: string): Promise<void> => {
         
     | 
| 473 | 
         
            +
                if (!isReady || isGenerating) return;
         
     | 
| 474 | 
         
            +
                setInput(messageText);
         
     | 
| 475 | 
         
            +
             
     | 
| 476 | 
         
            +
                const userMessage: Message = { role: "user", content: messageText };
         
     | 
| 477 | 
         
            +
                const currentMessages: Message[] = [...messages, userMessage];
         
     | 
| 478 | 
         
            +
                setMessages(currentMessages);
         
     | 
| 479 | 
         
            +
                setInput("");
         
     | 
| 480 | 
         
            +
                setIsGenerating(true);
         
     | 
| 481 | 
         
            +
             
     | 
| 482 | 
         
            +
                try {
         
     | 
| 483 | 
         
            +
                  const toolSchemas = tools
         
     | 
| 484 | 
         
            +
                    .filter((tool) => tool.enabled)
         
     | 
| 485 | 
         
            +
                    .map((tool) => generateSchemaFromCode(tool.code));
         
     | 
| 486 | 
         
            +
             
     | 
| 487 | 
         
            +
                  while (true) {
         
     | 
| 488 | 
         
            +
                    const messagesForGeneration = [...currentMessages];
         
     | 
| 489 | 
         
            +
             
     | 
| 490 | 
         
            +
                    setMessages([...currentMessages, { role: "assistant", content: "" }]);
         
     | 
| 491 | 
         
            +
             
     | 
| 492 | 
         
            +
                    let accumulatedContent = "";
         
     | 
| 493 | 
         
            +
                    const response = await generateResponse(
         
     | 
| 494 | 
         
            +
                      messagesForGeneration,
         
     | 
| 495 | 
         
            +
                      toolSchemas,
         
     | 
| 496 | 
         
            +
                      (token: string) => {
         
     | 
| 497 | 
         
            +
                        accumulatedContent += token;
         
     | 
| 498 | 
         
            +
                        setMessages((current) => {
         
     | 
| 499 | 
         
            +
                          const updated = [...current];
         
     | 
| 500 | 
         
            +
                          updated[updated.length - 1] = {
         
     | 
| 501 | 
         
            +
                            role: "assistant",
         
     | 
| 502 | 
         
            +
                            content: accumulatedContent,
         
     | 
| 503 | 
         
            +
                          };
         
     | 
| 504 | 
         
            +
                          return updated;
         
     | 
| 505 | 
         
            +
                        });
         
     | 
| 506 | 
         
            +
                      },
         
     | 
| 507 | 
         
            +
                    );
         
     | 
| 508 | 
         
            +
             
     | 
| 509 | 
         
            +
                    currentMessages.push({ role: "assistant", content: response });
         
     | 
| 510 | 
         
            +
                    const toolCalls = extractToolCalls(response);
         
     | 
| 511 | 
         
            +
             
     | 
| 512 | 
         
            +
                    if (toolCalls.length > 0) {
         
     | 
| 513 | 
         
            +
                      const toolResults = await executeToolCalls(toolCalls);
         
     | 
| 514 | 
         
            +
             
     | 
| 515 | 
         
            +
                      const toolMessage: ToolMessage = {
         
     | 
| 516 | 
         
            +
                        role: "tool",
         
     | 
| 517 | 
         
            +
                        content: JSON.stringify(toolResults.map((r) => r.result ?? null)),
         
     | 
| 518 | 
         
            +
                        renderInfo: toolResults,
         
     | 
| 519 | 
         
            +
                      };
         
     | 
| 520 | 
         
            +
                      currentMessages.push(toolMessage);
         
     | 
| 521 | 
         
            +
                      setMessages([...currentMessages]);
         
     | 
| 522 | 
         
            +
                      continue;
         
     | 
| 523 | 
         
            +
                    } else {
         
     | 
| 524 | 
         
            +
                      setMessages(currentMessages);
         
     | 
| 525 | 
         
            +
                      break;
         
     | 
| 526 | 
         
            +
                    }
         
     | 
| 527 | 
         
            +
                  }
         
     | 
| 528 | 
         
            +
                } catch (error) {
         
     | 
| 529 | 
         
            +
                  const errorMessage = getErrorMessage(error);
         
     | 
| 530 | 
         
            +
                  setMessages([
         
     | 
| 531 | 
         
            +
                    ...currentMessages,
         
     | 
| 532 | 
         
            +
                    {
         
     | 
| 533 | 
         
            +
                      role: "assistant",
         
     | 
| 534 | 
         
            +
                      content: `Error generating response: ${errorMessage}`,
         
     | 
| 535 | 
         
            +
                    },
         
     | 
| 536 | 
         
            +
                  ]);
         
     | 
| 537 | 
         
            +
                } finally {
         
     | 
| 538 | 
         
            +
                  setIsGenerating(false);
         
     | 
| 539 | 
         
            +
                  setTimeout(() => inputRef.current?.focus(), 0);
         
     | 
| 540 | 
         
            +
                }
         
     | 
| 541 | 
         
            +
              };
         
     | 
| 542 | 
         
            +
             
     | 
| 543 | 
         
            +
              return (
         
     | 
| 544 | 
         
            +
                <div className="font-sans min-h-screen bg-gradient-to-br from-[#031b4e] via-[#06183d] to-[#010409] text-gray-100 text-[16px] md:text-[17px]">
         
     | 
| 545 | 
         
            +
                  {!isReady ? (
         
     | 
| 546 | 
         
            +
                    <LoadingScreen
         
     | 
| 547 | 
         
            +
                      isLoading={isLoading}
         
     | 
| 548 | 
         
            +
                      progress={progress}
         
     | 
| 549 | 
         
            +
                      error={error}
         
     | 
| 550 | 
         
            +
                      loadSelectedModel={loadSelectedModel}
         
     | 
| 551 | 
         
            +
                      selectedModelId={selectedModel}
         
     | 
| 552 | 
         
            +
                      isModelDropdownOpen={isModelDropdownOpen}
         
     | 
| 553 | 
         
            +
                      setIsModelDropdownOpen={setIsModelDropdownOpen}
         
     | 
| 554 | 
         
            +
                      handleModelSelect={handleModelSelect}
         
     | 
| 555 | 
         
            +
                    />
         
     | 
| 556 | 
         
            +
                  ) : (
         
     | 
| 557 | 
         
            +
                    <div className="flex h-screen text-gray-100 w-full gap-6 py-10 px-8">
         
     | 
| 558 | 
         
            +
                      <div className="flex-1 flex flex-col p-6 bg-white/5 backdrop-blur-lg border border-white/10 rounded-3xl shadow-[0_35px_65px_rgba(3,27,78,0.55)] min-h-0">
         
     | 
| 559 | 
         
            +
                        <div className="flex items-center justify-between mb-6">
         
     | 
| 560 | 
         
            +
                          <div className="space-y-1">
         
     | 
| 561 | 
         
            +
                            <span className="inline-flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.35em] text-[#78a9ff]">
         
     | 
| 562 | 
         
            +
                              IBM Granite
         
     | 
| 563 | 
         
            +
                            </span>
         
     | 
| 564 | 
         
            +
                            <h1 className="text-3xl font-semibold text-white">
         
     | 
| 565 | 
         
            +
                              Granite-4.0 Tool Studio
         
     | 
| 566 | 
         
            +
                            </h1>
         
     | 
| 567 | 
         
            +
                          </div>
         
     | 
| 568 | 
         
            +
                          <div className="flex items-center gap-3">
         
     | 
| 569 | 
         
            +
                            <button
         
     | 
| 570 | 
         
            +
                              disabled={isGenerating}
         
     | 
| 571 | 
         
            +
                              onClick={clearChat}
         
     | 
| 572 | 
         
            +
                              className={`h-10 flex items-center px-4 rounded-full font-semibold text-sm transition-all border ${
         
     | 
| 573 | 
         
            +
                                isGenerating
         
     | 
| 574 | 
         
            +
                                  ? "border-white/15 bg-white/10 text-[#a6c8ff] opacity-50 cursor-not-allowed"
         
     | 
| 575 | 
         
            +
                                  : "border-white/20 bg-white/8 text-[#d0e2ff] hover:border-[#78a9ff]/50 hover:bg-[#0f62fe]/15"
         
     | 
| 576 | 
         
            +
                              }`}
         
     | 
| 577 | 
         
            +
                              title="Clear chat"
         
     | 
| 578 | 
         
            +
                            >
         
     | 
| 579 | 
         
            +
                              <RotateCcw size={14} className="mr-2" /> Reset Thread
         
     | 
| 580 | 
         
            +
                            </button>
         
     | 
| 581 | 
         
            +
                            <button
         
     | 
| 582 | 
         
            +
                              onClick={() =>
         
     | 
| 583 | 
         
            +
                                setIsToolsPanelVisible((previous) => !previous)
         
     | 
| 584 | 
         
            +
                              }
         
     | 
| 585 | 
         
            +
                              className={`h-10 flex items-center px-4 rounded-full font-semibold text-sm transition-all border ${
         
     | 
| 586 | 
         
            +
                                isToolsPanelVisible
         
     | 
| 587 | 
         
            +
                                  ? "border-[#78a9ff]/60 bg-[#0f62fe]/25 text-white shadow-[0_10px_25px_rgba(15,98,254,0.25)]"
         
     | 
| 588 | 
         
            +
                                  : "border-white/20 bg-white/8 text-[#d0e2ff] hover:border-[#78a9ff]/50 hover:bg-[#0f62fe]/15"
         
     | 
| 589 | 
         
            +
                              }`}
         
     | 
| 590 | 
         
            +
                            >
         
     | 
| 591 | 
         
            +
                              <Wrench size={16} className="mr-2" />
         
     | 
| 592 | 
         
            +
                              {isToolsPanelVisible ? "Hide Tools" : "Show Tools"}
         
     | 
| 593 | 
         
            +
                            </button>
         
     | 
| 594 | 
         
            +
                          </div>
         
     | 
| 595 | 
         
            +
                        </div>
         
     | 
| 596 | 
         
            +
                        <div
         
     | 
| 597 | 
         
            +
                          ref={chatContainerRef}
         
     | 
| 598 | 
         
            +
                          className="flex-grow bg-[#0b1e3f]/80 border border-white/10 rounded-2xl p-6 overflow-y-auto mb-6 space-y-5 shadow-inner min-h-0"
         
     | 
| 599 | 
         
            +
                        >
         
     | 
| 600 | 
         
            +
                          {messages.length === 0 && isReady ? (
         
     | 
| 601 | 
         
            +
                            <ExamplePrompts onExampleClick={handleExampleClick} />
         
     | 
| 602 | 
         
            +
                          ) : (
         
     | 
| 603 | 
         
            +
                            messages.map((msg, index) => {
         
     | 
| 604 | 
         
            +
                              const key = `${msg.role}-${index}`;
         
     | 
| 605 | 
         
            +
                              if (msg.role === "user") {
         
     | 
| 606 | 
         
            +
                                return (
         
     | 
| 607 | 
         
            +
                                  <div key={key} className="flex justify-end">
         
     | 
| 608 | 
         
            +
                                    <div className="px-4 py-3 rounded-2xl max-w-md bg-[#0f62fe]/30 border border-[#78a9ff]/40 shadow-[0_20px_45px_rgba(10,49,140,0.25)]">
         
     | 
| 609 | 
         
            +
                                      <p className="text-md text-white whitespace-pre-wrap">
         
     | 
| 610 | 
         
            +
                                        {msg.content}
         
     | 
| 611 | 
         
            +
                                      </p>
         
     | 
| 612 | 
         
            +
                                    </div>
         
     | 
| 613 | 
         
            +
                                  </div>
         
     | 
| 614 | 
         
            +
                                );
         
     | 
| 615 | 
         
            +
                              }
         
     | 
| 616 | 
         
            +
                              if (msg.role === "assistant") {
         
     | 
| 617 | 
         
            +
                                const isToolCall = msg.content.includes("<tool_call>");
         
     | 
| 618 | 
         
            +
                                if (isToolCall) {
         
     | 
| 619 | 
         
            +
                                  const nextMessage = messages[index + 1];
         
     | 
| 620 | 
         
            +
                                  const isCompleted = nextMessage?.role === "tool";
         
     | 
| 621 | 
         
            +
                                  const hasError =
         
     | 
| 622 | 
         
            +
                                    isCompleted &&
         
     | 
| 623 | 
         
            +
                                    (nextMessage as ToolMessage).renderInfo.some(
         
     | 
| 624 | 
         
            +
                                      (info) => !!info.error,
         
     | 
| 625 | 
         
            +
                                    );
         
     | 
| 626 | 
         
            +
                                  return (
         
     | 
| 627 | 
         
            +
                                    <div key={key} className="flex justify-start">
         
     | 
| 628 | 
         
            +
                                      <div className="px-4 py-3 rounded-2xl bg-white/8 border border-[#0f62fe]/30 shadow-[0_18px_50px_rgba(0,0,0,0.35)]">
         
     | 
| 629 | 
         
            +
                                        <ToolCallIndicator
         
     | 
| 630 | 
         
            +
                                          content={msg.content}
         
     | 
| 631 | 
         
            +
                                          isRunning={!isCompleted}
         
     | 
| 632 | 
         
            +
                                          hasError={hasError}
         
     | 
| 633 | 
         
            +
                                        />
         
     | 
| 634 | 
         
            +
                                      </div>
         
     | 
| 635 | 
         
            +
                                    </div>
         
     | 
| 636 | 
         
            +
                                  );
         
     | 
| 637 | 
         
            +
                                }
         
     | 
| 638 | 
         
            +
                                return (
         
     | 
| 639 | 
         
            +
                                  <div key={key} className="flex justify-start">
         
     | 
| 640 | 
         
            +
                                    <div className="px-4 py-3 rounded-2xl max-w-md bg-white/8 border border-white/15 shadow-[0_18px_50px_rgba(0,0,0,0.35)]">
         
     | 
| 641 | 
         
            +
                                      <p className="text-md text-[#d0e2ff] whitespace-pre-wrap">
         
     | 
| 642 | 
         
            +
                                        {msg.content}
         
     | 
| 643 | 
         
            +
                                      </p>
         
     | 
| 644 | 
         
            +
                                    </div>
         
     | 
| 645 | 
         
            +
                                  </div>
         
     | 
| 646 | 
         
            +
                                );
         
     | 
| 647 | 
         
            +
                              }
         
     | 
| 648 | 
         
            +
                              if (msg.role === "tool") {
         
     | 
| 649 | 
         
            +
                                const visibleToolResults = msg.renderInfo.filter(
         
     | 
| 650 | 
         
            +
                                  (info) =>
         
     | 
| 651 | 
         
            +
                                    info.error || (info.result != null && info.renderer),
         
     | 
| 652 | 
         
            +
                                );
         
     | 
| 653 | 
         
            +
                                if (visibleToolResults.length === 0) return null;
         
     | 
| 654 | 
         
            +
                                return (
         
     | 
| 655 | 
         
            +
                                  <div key={key} className="flex justify-start">
         
     | 
| 656 | 
         
            +
                                    <div className="p-4 rounded-2xl bg-white/8 border border-white/15 max-w-lg shadow-[0_18px_50px_rgba(0,0,0,0.35)]">
         
     | 
| 657 | 
         
            +
                                      <div className="space-y-4">
         
     | 
| 658 | 
         
            +
                                        {visibleToolResults.map((info, idx) => (
         
     | 
| 659 | 
         
            +
                                          <div className="flex flex-col gap-2" key={idx}>
         
     | 
| 660 | 
         
            +
                                            <div className="text-xs text-[#a6c8ff] font-mono">
         
     | 
| 661 | 
         
            +
                                              {info.call}
         
     | 
| 662 | 
         
            +
                                            </div>
         
     | 
| 663 | 
         
            +
                                            {info.error ? (
         
     | 
| 664 | 
         
            +
                                              <ResultBlock error={info.error} />
         
     | 
| 665 | 
         
            +
                                            ) : (
         
     | 
| 666 | 
         
            +
                                              <ToolResultRenderer
         
     | 
| 667 | 
         
            +
                                                result={info.result}
         
     | 
| 668 | 
         
            +
                                                rendererCode={info.renderer}
         
     | 
| 669 | 
         
            +
                                                input={info.input}
         
     | 
| 670 | 
         
            +
                                              />
         
     | 
| 671 | 
         
            +
                                            )}
         
     | 
| 672 | 
         
            +
                                          </div>
         
     | 
| 673 | 
         
            +
                                        ))}
         
     | 
| 674 | 
         
            +
                                      </div>
         
     | 
| 675 | 
         
            +
                                    </div>
         
     | 
| 676 | 
         
            +
                                  </div>
         
     | 
| 677 | 
         
            +
                                );
         
     | 
| 678 | 
         
            +
                              }
         
     | 
| 679 | 
         
            +
                              return null;
         
     | 
| 680 | 
         
            +
                            })
         
     | 
| 681 | 
         
            +
                          )}
         
     | 
| 682 | 
         
            +
                        </div>
         
     | 
| 683 | 
         
            +
                        <div className="flex items-center gap-3">
         
     | 
| 684 | 
         
            +
                          <div className="flex flex-1 items-center bg-white/5 border border-white/10 rounded-2xl overflow-hidden shadow-[0_15px_45px_rgba(0,0,0,0.35)]">
         
     | 
| 685 | 
         
            +
                            <input
         
     | 
| 686 | 
         
            +
                              ref={inputRef}
         
     | 
| 687 | 
         
            +
                              type="text"
         
     | 
| 688 | 
         
            +
                              value={input}
         
     | 
| 689 | 
         
            +
                              onChange={(e) => setInput(e.target.value)}
         
     | 
| 690 | 
         
            +
                              onKeyDown={(e) =>
         
     | 
| 691 | 
         
            +
                                e.key === "Enter" &&
         
     | 
| 692 | 
         
            +
                                !isGenerating &&
         
     | 
| 693 | 
         
            +
                                isReady &&
         
     | 
| 694 | 
         
            +
                                handleSendMessage()
         
     | 
| 695 | 
         
            +
                              }
         
     | 
| 696 | 
         
            +
                              disabled={isGenerating || !isReady}
         
     | 
| 697 | 
         
            +
                              className="flex-grow bg-transparent px-5 py-3 text-lg text-white placeholder:text-[#a6c8ff]/70 focus:outline-none disabled:opacity-40"
         
     | 
| 698 | 
         
            +
                              placeholder={
         
     | 
| 699 | 
         
            +
                                isReady
         
     | 
| 700 | 
         
            +
                                  ? "Type your message here..."
         
     | 
| 701 | 
         
            +
                                  : "Load a Granite model to enable chat"
         
     | 
| 702 | 
         
            +
                              }
         
     | 
| 703 | 
         
            +
                            />
         
     | 
| 704 | 
         
            +
                            <button
         
     | 
| 705 | 
         
            +
                              onClick={handleSendMessage}
         
     | 
| 706 | 
         
            +
                              disabled={isGenerating || !isReady}
         
     | 
| 707 | 
         
            +
                              className="h-full px-5 py-3 bg-[#0f62fe] hover:bg-[#0043ce] disabled:bg-[#0f62fe]/40 disabled:cursor-not-allowed text-white font-semibold transition-all"
         
     | 
| 708 | 
         
            +
                            >
         
     | 
| 709 | 
         
            +
                              <Play size={28} />
         
     | 
| 710 | 
         
            +
                            </button>
         
     | 
| 711 | 
         
            +
                          </div>
         
     | 
| 712 | 
         
            +
                        </div>
         
     | 
| 713 | 
         
            +
                      </div>
         
     | 
| 714 | 
         
            +
                      {isToolsPanelVisible && (
         
     | 
| 715 | 
         
            +
                        <div className="w-full md:w-1/2 flex flex-col p-6 bg-white/5 backdrop-blur-lg border border-white/10 rounded-3xl shadow-[0_35px_65px_rgba(3,27,78,0.55)] min-h-0">
         
     | 
| 716 | 
         
            +
                          <div className="flex justify-between items-center mb-6">
         
     | 
| 717 | 
         
            +
                            <div>
         
     | 
| 718 | 
         
            +
                              <span className="text-xs font-semibold uppercase tracking-[0.25em] text-[#78a9ff]">
         
     | 
| 719 | 
         
            +
                                Tool Workspace
         
     | 
| 720 | 
         
            +
                              </span>
         
     | 
| 721 | 
         
            +
                              <h2 className="text-2xl font-semibold text-white mt-1">
         
     | 
| 722 | 
         
            +
                                Tools
         
     | 
| 723 | 
         
            +
                              </h2>
         
     | 
| 724 | 
         
            +
                            </div>
         
     | 
| 725 | 
         
            +
                            <button
         
     | 
| 726 | 
         
            +
                              onClick={addTool}
         
     | 
| 727 | 
         
            +
                              className="flex items-center bg-gradient-to-r from-[#0f62fe] to-[#4589ff] hover:brightness-110 text-white font-semibold py-2 px-4 rounded-full transition-all shadow-[0_15px_35px_rgba(15,98,254,0.35)]"
         
     | 
| 728 | 
         
            +
                            >
         
     | 
| 729 | 
         
            +
                              <Plus size={16} className="mr-2" /> Add Tool
         
     | 
| 730 | 
         
            +
                            </button>
         
     | 
| 731 | 
         
            +
                          </div>
         
     | 
| 732 | 
         
            +
                          <div
         
     | 
| 733 | 
         
            +
                            ref={toolsContainerRef}
         
     | 
| 734 | 
         
            +
                            className="flex-grow bg-[#0b1e3f]/60 border border-white/10 rounded-2xl p-4 overflow-y-auto space-y-3"
         
     | 
| 735 | 
         
            +
                          >
         
     | 
| 736 | 
         
            +
                            {tools.map((tool) => (
         
     | 
| 737 | 
         
            +
                              <ToolItem
         
     | 
| 738 | 
         
            +
                                key={tool.id}
         
     | 
| 739 | 
         
            +
                                tool={tool}
         
     | 
| 740 | 
         
            +
                                onToggleEnabled={() => toggleToolEnabled(tool.id)}
         
     | 
| 741 | 
         
            +
                                onToggleCollapsed={() => toggleToolCollapsed(tool.id)}
         
     | 
| 742 | 
         
            +
                                onExpand={() => expandTool(tool.id)}
         
     | 
| 743 | 
         
            +
                                onDelete={() => deleteTool(tool.id)}
         
     | 
| 744 | 
         
            +
                                onCodeChange={(newCode) =>
         
     | 
| 745 | 
         
            +
                                  handleToolCodeChange(tool.id, newCode)
         
     | 
| 746 | 
         
            +
                                }
         
     | 
| 747 | 
         
            +
                              />
         
     | 
| 748 | 
         
            +
                            ))}
         
     | 
| 749 | 
         
            +
                          </div>
         
     | 
| 750 | 
         
            +
                        </div>
         
     | 
| 751 | 
         
            +
                      )}
         
     | 
| 752 | 
         
            +
                    </div>
         
     | 
| 753 | 
         
            +
                  )}
         
     | 
| 754 | 
         
            +
                </div>
         
     | 
| 755 | 
         
            +
              );
         
     | 
| 756 | 
         
            +
            };
         
     | 
| 757 | 
         
            +
             
     | 
| 758 | 
         
            +
            export default App;
         
     | 
    	
        src/components/ExamplePrompts.tsx
    ADDED
    
    | 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import type React from "react";
         
     | 
| 2 | 
         
            +
            import { DEFAULT_EXAMPLES, type Example } from "../constants/examples";
         
     | 
| 3 | 
         
            +
             
     | 
| 4 | 
         
            +
            interface ExamplePromptsProps {
         
     | 
| 5 | 
         
            +
              examples?: Example[];
         
     | 
| 6 | 
         
            +
              onExampleClick: (messageText: string) => void;
         
     | 
| 7 | 
         
            +
            }
         
     | 
| 8 | 
         
            +
             
     | 
| 9 | 
         
            +
            const ExamplePrompts: React.FC<ExamplePromptsProps> = ({
         
     | 
| 10 | 
         
            +
              examples,
         
     | 
| 11 | 
         
            +
              onExampleClick,
         
     | 
| 12 | 
         
            +
            }) => (
         
     | 
| 13 | 
         
            +
              <div className="flex flex-col items-center justify-center h-full space-y-6">
         
     | 
| 14 | 
         
            +
                <div className="text-center mb-6">
         
     | 
| 15 | 
         
            +
                  <h2 className="text-3xl font-semibold text-white mb-1">Try an example</h2>
         
     | 
| 16 | 
         
            +
                  <p className="text-md text-[#9bb5ff]">Click one to get started</p>
         
     | 
| 17 | 
         
            +
                </div>
         
     | 
| 18 | 
         
            +
             
     | 
| 19 | 
         
            +
                <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 max-w-4xl w-full px-4">
         
     | 
| 20 | 
         
            +
                  {(examples || DEFAULT_EXAMPLES).map((example, index) => (
         
     | 
| 21 | 
         
            +
                    <button
         
     | 
| 22 | 
         
            +
                      key={index}
         
     | 
| 23 | 
         
            +
                      onClick={() => onExampleClick(example.messageText)}
         
     | 
| 24 | 
         
            +
                      className="group relative overflow-hidden rounded-2xl border border-white/12 bg-white/8 text-left shadow-[0_22px_55px_rgba(3,27,78,0.35)] transition-all hover:-translate-y-0.5 hover:shadow-[0_28px_65px_rgba(15,98,254,0.35)]"
         
     | 
| 25 | 
         
            +
                    >
         
     | 
| 26 | 
         
            +
                      <span className="pointer-events-none absolute inset-0 bg-gradient-to-r from-[#0f62fe]/25 via-transparent to-transparent opacity-0 transition-opacity group-hover:opacity-100" />
         
     | 
| 27 | 
         
            +
                      <div className="relative flex items-center gap-4 px-5 py-4">
         
     | 
| 28 | 
         
            +
                        <span className="flex size-12 flex-shrink-0 items-center justify-center rounded-full border border-[#78a9ff]/30 bg-[#0f62fe]/15 text-xl text-[#a6c8ff] transition-all group-hover:scale-105 group-hover:bg-[#0f62fe]/25 group-hover:text-white">
         
     | 
| 29 | 
         
            +
                          {example.icon}
         
     | 
| 30 | 
         
            +
                        </span>
         
     | 
| 31 | 
         
            +
                        <div className="flex flex-1 items-center">
         
     | 
| 32 | 
         
            +
                          <span className="text-md font-medium text-white">
         
     | 
| 33 | 
         
            +
                            {example.displayText}
         
     | 
| 34 | 
         
            +
                          </span>
         
     | 
| 35 | 
         
            +
                        </div>
         
     | 
| 36 | 
         
            +
                        <span className="flex size-10 items-center justify-center rounded-full border border-white/10 bg-white/10 text-[#9bb5ff] transition-all group-hover:border-[#78a9ff]/40 group-hover:bg-[#0f62fe]/35 group-hover:text-white">
         
     | 
| 37 | 
         
            +
                          →
         
     | 
| 38 | 
         
            +
                        </span>
         
     | 
| 39 | 
         
            +
                      </div>
         
     | 
| 40 | 
         
            +
                    </button>
         
     | 
| 41 | 
         
            +
                  ))}
         
     | 
| 42 | 
         
            +
                </div>
         
     | 
| 43 | 
         
            +
              </div>
         
     | 
| 44 | 
         
            +
            );
         
     | 
| 45 | 
         
            +
             
     | 
| 46 | 
         
            +
            export default ExamplePrompts;
         
     | 
    	
        src/components/LoadingScreen.tsx
    ADDED
    
    | 
         @@ -0,0 +1,291 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import { ChevronDown } from "lucide-react";
         
     | 
| 2 | 
         
            +
             
     | 
| 3 | 
         
            +
            import { MODEL_OPTIONS } from "../constants/models";
         
     | 
| 4 | 
         
            +
            import IBMLogo from "./icons/IBMLogo";
         
     | 
| 5 | 
         
            +
            import HfLogo from "./icons/HfLogo";
         
     | 
| 6 | 
         
            +
             
     | 
| 7 | 
         
            +
            import { useEffect, useRef } from "react";
         
     | 
| 8 | 
         
            +
             
     | 
| 9 | 
         
            +
            // Define the structure of our animated dots
         
     | 
| 10 | 
         
            +
            interface Dot {
         
     | 
| 11 | 
         
            +
              x: number;
         
     | 
| 12 | 
         
            +
              y: number;
         
     | 
| 13 | 
         
            +
              vx: number;
         
     | 
| 14 | 
         
            +
              vy: number;
         
     | 
| 15 | 
         
            +
              radius: number;
         
     | 
| 16 | 
         
            +
              opacity: number;
         
     | 
| 17 | 
         
            +
            }
         
     | 
| 18 | 
         
            +
             
     | 
| 19 | 
         
            +
            export const LoadingScreen = ({
         
     | 
| 20 | 
         
            +
              isLoading,
         
     | 
| 21 | 
         
            +
              progress,
         
     | 
| 22 | 
         
            +
              error,
         
     | 
| 23 | 
         
            +
              loadSelectedModel,
         
     | 
| 24 | 
         
            +
              selectedModelId,
         
     | 
| 25 | 
         
            +
              isModelDropdownOpen,
         
     | 
| 26 | 
         
            +
              setIsModelDropdownOpen,
         
     | 
| 27 | 
         
            +
              handleModelSelect,
         
     | 
| 28 | 
         
            +
            }: {
         
     | 
| 29 | 
         
            +
              isLoading: boolean;
         
     | 
| 30 | 
         
            +
              progress: number;
         
     | 
| 31 | 
         
            +
              error: string | null;
         
     | 
| 32 | 
         
            +
              loadSelectedModel: () => void;
         
     | 
| 33 | 
         
            +
              selectedModelId: string;
         
     | 
| 34 | 
         
            +
              isModelDropdownOpen: boolean;
         
     | 
| 35 | 
         
            +
              setIsModelDropdownOpen: (isOpen: boolean) => void;
         
     | 
| 36 | 
         
            +
              handleModelSelect: (modelId: string) => void;
         
     | 
| 37 | 
         
            +
            }) => {
         
     | 
| 38 | 
         
            +
              const model = MODEL_OPTIONS.find((opt) => opt.id === selectedModelId);
         
     | 
| 39 | 
         
            +
              const canvasRef = useRef<HTMLCanvasElement>(null);
         
     | 
| 40 | 
         
            +
             
     | 
| 41 | 
         
            +
              // Background Animation Effect
         
     | 
| 42 | 
         
            +
              useEffect(() => {
         
     | 
| 43 | 
         
            +
                const canvas = canvasRef.current;
         
     | 
| 44 | 
         
            +
                if (!canvas) return;
         
     | 
| 45 | 
         
            +
             
     | 
| 46 | 
         
            +
                const ctx = canvas.getContext("2d");
         
     | 
| 47 | 
         
            +
                if (!ctx) return;
         
     | 
| 48 | 
         
            +
             
     | 
| 49 | 
         
            +
                let animationFrameId: number;
         
     | 
| 50 | 
         
            +
                let dots: Dot[] = [];
         
     | 
| 51 | 
         
            +
                const maxConnectionDistance = 130; // Max distance to draw a line between dots
         
     | 
| 52 | 
         
            +
                const dotSpeed = 0.3; // How fast dots move
         
     | 
| 53 | 
         
            +
             
     | 
| 54 | 
         
            +
                const setup = () => {
         
     | 
| 55 | 
         
            +
                  canvas.width = window.innerWidth;
         
     | 
| 56 | 
         
            +
                  canvas.height = window.innerHeight;
         
     | 
| 57 | 
         
            +
                  dots = [];
         
     | 
| 58 | 
         
            +
                  // Adjust dot density based on screen area
         
     | 
| 59 | 
         
            +
                  const numDots = Math.floor((canvas.width * canvas.height) / 20000);
         
     | 
| 60 | 
         
            +
             
     | 
| 61 | 
         
            +
                  for (let i = 0; i < numDots; ++i) {
         
     | 
| 62 | 
         
            +
                    dots.push({
         
     | 
| 63 | 
         
            +
                      x: Math.random() * canvas.width,
         
     | 
| 64 | 
         
            +
                      y: Math.random() * canvas.height,
         
     | 
| 65 | 
         
            +
                      vx: (Math.random() - 0.5) * dotSpeed, // Random horizontal velocity
         
     | 
| 66 | 
         
            +
                      vy: (Math.random() - 0.5) * dotSpeed, // Random vertical velocity
         
     | 
| 67 | 
         
            +
                      radius: Math.random() * 1.5 + 0.5,
         
     | 
| 68 | 
         
            +
                      opacity: Math.random() * 0.5 + 0.2,
         
     | 
| 69 | 
         
            +
                    });
         
     | 
| 70 | 
         
            +
                  }
         
     | 
| 71 | 
         
            +
                };
         
     | 
| 72 | 
         
            +
             
     | 
| 73 | 
         
            +
                const draw = () => {
         
     | 
| 74 | 
         
            +
                  if (!ctx) return;
         
     | 
| 75 | 
         
            +
                  ctx.clearRect(0, 0, canvas.width, canvas.height);
         
     | 
| 76 | 
         
            +
             
     | 
| 77 | 
         
            +
                  // 1. Update and draw dots
         
     | 
| 78 | 
         
            +
                  dots.forEach((dot) => {
         
     | 
| 79 | 
         
            +
                    // Update position
         
     | 
| 80 | 
         
            +
                    dot.x += dot.vx;
         
     | 
| 81 | 
         
            +
                    dot.y += dot.vy;
         
     | 
| 82 | 
         
            +
             
     | 
| 83 | 
         
            +
                    // Bounce off edges
         
     | 
| 84 | 
         
            +
                    if (dot.x <= 0 || dot.x >= canvas.width) dot.vx *= -1;
         
     | 
| 85 | 
         
            +
                    if (dot.y <= 0 || dot.y >= canvas.height) dot.vy *= -1;
         
     | 
| 86 | 
         
            +
             
     | 
| 87 | 
         
            +
                    // Draw dot
         
     | 
| 88 | 
         
            +
                    ctx.beginPath();
         
     | 
| 89 | 
         
            +
                    ctx.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2);
         
     | 
| 90 | 
         
            +
                    ctx.fillStyle = `rgba(255, 255, 255, ${dot.opacity})`;
         
     | 
| 91 | 
         
            +
                    ctx.fill();
         
     | 
| 92 | 
         
            +
                  });
         
     | 
| 93 | 
         
            +
             
     | 
| 94 | 
         
            +
                  // 2. Draw connecting lines
         
     | 
| 95 | 
         
            +
                  ctx.lineWidth = 0.5; // Use a thin line for connections
         
     | 
| 96 | 
         
            +
                  for (let i = 0; i < dots.length; i++) {
         
     | 
| 97 | 
         
            +
                    for (let j = i + 1; j < dots.length; j++) {
         
     | 
| 98 | 
         
            +
                      const dot1 = dots[i];
         
     | 
| 99 | 
         
            +
                      const dot2 = dots[j];
         
     | 
| 100 | 
         
            +
                      const dx = dot1.x - dot2.x;
         
     | 
| 101 | 
         
            +
                      const dy = dot1.y - dot2.y;
         
     | 
| 102 | 
         
            +
                      const distance = Math.sqrt(dx * dx + dy * dy);
         
     | 
| 103 | 
         
            +
             
     | 
| 104 | 
         
            +
                      // If dots are close enough, draw a line
         
     | 
| 105 | 
         
            +
                      if (distance < maxConnectionDistance) {
         
     | 
| 106 | 
         
            +
                        // Calculate opacity based on distance (closer = more opaque)
         
     | 
| 107 | 
         
            +
                        const opacity = 1 - distance / maxConnectionDistance;
         
     | 
| 108 | 
         
            +
                        ctx.strokeStyle = `rgba(255, 255, 255, ${opacity * 0.3})`; // Faint white lines
         
     | 
| 109 | 
         
            +
                        ctx.beginPath();
         
     | 
| 110 | 
         
            +
                        ctx.moveTo(dot1.x, dot1.y);
         
     | 
| 111 | 
         
            +
                        ctx.lineTo(dot2.x, dot2.y);
         
     | 
| 112 | 
         
            +
                        ctx.stroke();
         
     | 
| 113 | 
         
            +
                      }
         
     | 
| 114 | 
         
            +
                    }
         
     | 
| 115 | 
         
            +
                  }
         
     | 
| 116 | 
         
            +
             
     | 
| 117 | 
         
            +
                  animationFrameId = requestAnimationFrame(draw);
         
     | 
| 118 | 
         
            +
                };
         
     | 
| 119 | 
         
            +
             
     | 
| 120 | 
         
            +
                const handleResize = () => {
         
     | 
| 121 | 
         
            +
                  cancelAnimationFrame(animationFrameId);
         
     | 
| 122 | 
         
            +
                  setup();
         
     | 
| 123 | 
         
            +
                  draw();
         
     | 
| 124 | 
         
            +
                };
         
     | 
| 125 | 
         
            +
             
     | 
| 126 | 
         
            +
                setup();
         
     | 
| 127 | 
         
            +
                draw();
         
     | 
| 128 | 
         
            +
             
     | 
| 129 | 
         
            +
                window.addEventListener("resize", handleResize);
         
     | 
| 130 | 
         
            +
             
     | 
| 131 | 
         
            +
                return () => {
         
     | 
| 132 | 
         
            +
                  window.removeEventListener("resize", handleResize);
         
     | 
| 133 | 
         
            +
                  cancelAnimationFrame(animationFrameId);
         
     | 
| 134 | 
         
            +
                };
         
     | 
| 135 | 
         
            +
              }, []);
         
     | 
| 136 | 
         
            +
             
     | 
| 137 | 
         
            +
              return (
         
     | 
| 138 | 
         
            +
                <div className="relative flex flex-col items-center justify-center h-screen bg-gradient-to-br from-[#031b4e] via-[#06183d] to-[#010409] text-gray-100 text-[16px] md:text-[17px] p-8 overflow-hidden">
         
     | 
| 139 | 
         
            +
                  {/* Background Canvas for Animation */}
         
     | 
| 140 | 
         
            +
                  <canvas
         
     | 
| 141 | 
         
            +
                    ref={canvasRef}
         
     | 
| 142 | 
         
            +
                    className="absolute top-0 left-0 w-full h-full z-0"
         
     | 
| 143 | 
         
            +
                  />
         
     | 
| 144 | 
         
            +
             
     | 
| 145 | 
         
            +
                  {/* Vignette Overlay */}
         
     | 
| 146 | 
         
            +
                  <div className="absolute top-0 left-0 w-full h-full z-10 bg-[radial-gradient(ellipse_at_center,_rgba(3,27,78,0)_30%,_rgba(1,4,9,0.85)_95%)]"></div>
         
     | 
| 147 | 
         
            +
             
     | 
| 148 | 
         
            +
                  {/* Main Content */}
         
     | 
| 149 | 
         
            +
                  <div className="relative z-20 max-w-3xl w-full flex flex-col items-center bg-white/5 border border-white/10 backdrop-blur-xl rounded-3xl p-10 shadow-[0_35px_65px_rgba(3,27,78,0.55)] space-y-8">
         
     | 
| 150 | 
         
            +
                    <div className="flex items-center justify-center gap-6 text-5xl md:text-6xl">
         
     | 
| 151 | 
         
            +
                      <a
         
     | 
| 152 | 
         
            +
                        href="https://huggingface.co/ibm-granite"
         
     | 
| 153 | 
         
            +
                        target="_blank"
         
     | 
| 154 | 
         
            +
                        rel="noopener noreferrer"
         
     | 
| 155 | 
         
            +
                        title="IBM Granite"
         
     | 
| 156 | 
         
            +
                      >
         
     | 
| 157 | 
         
            +
                        <div className="size-24 md:size-28 bg-blue-500 rounded-sm p-2 flex items-center justify-center">
         
     | 
| 158 | 
         
            +
                          <IBMLogo className="text-white" />
         
     | 
| 159 | 
         
            +
                        </div>
         
     | 
| 160 | 
         
            +
                      </a>
         
     | 
| 161 | 
         
            +
                      <span className="text-[#78a9ff]">×</span>
         
     | 
| 162 | 
         
            +
                      <a
         
     | 
| 163 | 
         
            +
                        href="https://huggingface.co/docs/transformers.js"
         
     | 
| 164 | 
         
            +
                        target="_blank"
         
     | 
| 165 | 
         
            +
                        rel="noopener noreferrer"
         
     | 
| 166 | 
         
            +
                        title="Transformers.js"
         
     | 
| 167 | 
         
            +
                      >
         
     | 
| 168 | 
         
            +
                        <HfLogo className="h-24 md:h-28 text-gray-300 hover:text-white transition-colors" />
         
     | 
| 169 | 
         
            +
                      </a>
         
     | 
| 170 | 
         
            +
                    </div>
         
     | 
| 171 | 
         
            +
             
     | 
| 172 | 
         
            +
                    <div className="w-full text-center">
         
     | 
| 173 | 
         
            +
                      <h1 className="text-5xl font-semibold mb-2 text-white tracking-tight">
         
     | 
| 174 | 
         
            +
                        Granite-4.0 WebGPU
         
     | 
| 175 | 
         
            +
                      </h1>
         
     | 
| 176 | 
         
            +
                      <p className="text-md md:text-lg text-[#a6c8ff]">
         
     | 
| 177 | 
         
            +
                        In-browser tool calling, powered by Transformers.js
         
     | 
| 178 | 
         
            +
                      </p>
         
     | 
| 179 | 
         
            +
                    </div>
         
     | 
| 180 | 
         
            +
             
     | 
| 181 | 
         
            +
                    <div className="w-full text-left text-[#d0e2ff] space-y-4 text-xl">
         
     | 
| 182 | 
         
            +
                      <p>
         
     | 
| 183 | 
         
            +
                        This demo showcases in-browser tool calling with Granite-4.0, a new
         
     | 
| 184 | 
         
            +
                        series of models by{" "}
         
     | 
| 185 | 
         
            +
                        <a
         
     | 
| 186 | 
         
            +
                          href="https://huggingface.co/ibm-granite"
         
     | 
| 187 | 
         
            +
                          target="_blank"
         
     | 
| 188 | 
         
            +
                          rel="noopener noreferrer"
         
     | 
| 189 | 
         
            +
                          className="text-[#78a9ff] hover:underline font-medium"
         
     | 
| 190 | 
         
            +
                        >
         
     | 
| 191 | 
         
            +
                          IBM Granite
         
     | 
| 192 | 
         
            +
                        </a>{" "}
         
     | 
| 193 | 
         
            +
                        designed for edge AI and on-device deployment.
         
     | 
| 194 | 
         
            +
                      </p>
         
     | 
| 195 | 
         
            +
                      <p>
         
     | 
| 196 | 
         
            +
                        Everything runs entirely in your browser with{" "}
         
     | 
| 197 | 
         
            +
                        <a
         
     | 
| 198 | 
         
            +
                          href="https://huggingface.co/docs/transformers.js"
         
     | 
| 199 | 
         
            +
                          target="_blank"
         
     | 
| 200 | 
         
            +
                          rel="noopener noreferrer"
         
     | 
| 201 | 
         
            +
                          className="text-[#78a9ff] hover:underline font-medium"
         
     | 
| 202 | 
         
            +
                        >
         
     | 
| 203 | 
         
            +
                          Transformers.js
         
     | 
| 204 | 
         
            +
                        </a>{" "}
         
     | 
| 205 | 
         
            +
                        and ONNX Runtime Web, meaning no data is sent to a server. It can
         
     | 
| 206 | 
         
            +
                        even run offline!
         
     | 
| 207 | 
         
            +
                      </p>
         
     | 
| 208 | 
         
            +
                    </div>
         
     | 
| 209 | 
         
            +
             
     | 
| 210 | 
         
            +
                    <p className="text-[#a6c8ff]">
         
     | 
| 211 | 
         
            +
                      Select a model and click load to get started.
         
     | 
| 212 | 
         
            +
                    </p>
         
     | 
| 213 | 
         
            +
             
     | 
| 214 | 
         
            +
                    <div className="relative w-full max-w-lg">
         
     | 
| 215 | 
         
            +
                      <div className="flex rounded-2xl border border-white/12 bg-white/10 overflow-hidden shadow-[0_18px_45px_rgba(3,27,78,0.45)]">
         
     | 
| 216 | 
         
            +
                        <button
         
     | 
| 217 | 
         
            +
                          onClick={isLoading ? undefined : loadSelectedModel}
         
     | 
| 218 | 
         
            +
                          disabled={isLoading}
         
     | 
| 219 | 
         
            +
                          className={`flex-1 flex items-center justify-center font-semibold transition-all text-lg ${isLoading ? "bg-white/5 text-[#8da2d8] cursor-not-allowed" : "bg-[#0f62fe] hover:bg-[#0043ce] text-white"}`}
         
     | 
| 220 | 
         
            +
                        >
         
     | 
| 221 | 
         
            +
                          <div className="px-6 py-3">
         
     | 
| 222 | 
         
            +
                            {isLoading ? (
         
     | 
| 223 | 
         
            +
                              <div className="flex items-center">
         
     | 
| 224 | 
         
            +
                                <span className="inline-block w-5 h-5 border-2 border-white/80 border-t-transparent rounded-full animate-spin"></span>
         
     | 
| 225 | 
         
            +
                                <span className="ml-3 text-md font-medium">
         
     | 
| 226 | 
         
            +
                                  Loading... ({progress}%)
         
     | 
| 227 | 
         
            +
                                </span>
         
     | 
| 228 | 
         
            +
                              </div>
         
     | 
| 229 | 
         
            +
                            ) : (
         
     | 
| 230 | 
         
            +
                              `Load ${model?.label}`
         
     | 
| 231 | 
         
            +
                            )}
         
     | 
| 232 | 
         
            +
                          </div>
         
     | 
| 233 | 
         
            +
                        </button>
         
     | 
| 234 | 
         
            +
                        <button
         
     | 
| 235 | 
         
            +
                          onClick={(e) => {
         
     | 
| 236 | 
         
            +
                            if (!isLoading) {
         
     | 
| 237 | 
         
            +
                              e.stopPropagation();
         
     | 
| 238 | 
         
            +
                              setIsModelDropdownOpen(!isModelDropdownOpen);
         
     | 
| 239 | 
         
            +
                            }
         
     | 
| 240 | 
         
            +
                          }}
         
     | 
| 241 | 
         
            +
                          aria-label="Select model"
         
     | 
| 242 | 
         
            +
                          className="px-4 py-3 border-l border-white/15 bg-[#0f62fe] hover:bg-[#0043ce] transition-colors text-white disabled:cursor-not-allowed disabled:bg-white/5"
         
     | 
| 243 | 
         
            +
                          disabled={isLoading}
         
     | 
| 244 | 
         
            +
                        >
         
     | 
| 245 | 
         
            +
                          <ChevronDown size={24} />
         
     | 
| 246 | 
         
            +
                        </button>
         
     | 
| 247 | 
         
            +
                      </div>
         
     | 
| 248 | 
         
            +
             
     | 
| 249 | 
         
            +
                      {isModelDropdownOpen && (
         
     | 
| 250 | 
         
            +
                        <div className="absolute left-0 right-0 bottom-full mb-3 bg-[#02102c]/98 border border-white/12 rounded-xl shadow-[0_22px_55px_rgba(3,27,78,0.55)] z-10 w-full overflow-visible backdrop-blur-2xl">
         
     | 
| 251 | 
         
            +
                          {MODEL_OPTIONS.map((option) => (
         
     | 
| 252 | 
         
            +
                            <button
         
     | 
| 253 | 
         
            +
                              key={option.id}
         
     | 
| 254 | 
         
            +
                              onClick={() => handleModelSelect(option.id)}
         
     | 
| 255 | 
         
            +
                              className={`w-full px-5 py-3 text-left text-sm font-medium rounded-lg border transition-all ${
         
     | 
| 256 | 
         
            +
                                selectedModelId === option.id
         
     | 
| 257 | 
         
            +
                                  ? "border-[#78a9ff]/60 bg-[#0f62fe]/25 text-white shadow-[0_10px_25px_rgba(15,98,254,0.25)]"
         
     | 
| 258 | 
         
            +
                                  : "border-transparent text-[#d0e2ff] hover:border-[#78a9ff]/30 hover:bg-white/12 hover:text-white"
         
     | 
| 259 | 
         
            +
                              }`}
         
     | 
| 260 | 
         
            +
                            >
         
     | 
| 261 | 
         
            +
                              <div className="font-medium">{option.label}</div>
         
     | 
| 262 | 
         
            +
                              <div className="text-md text-[#95a8dd]">{option.size}</div>
         
     | 
| 263 | 
         
            +
                            </button>
         
     | 
| 264 | 
         
            +
                          ))}
         
     | 
| 265 | 
         
            +
                        </div>
         
     | 
| 266 | 
         
            +
                      )}
         
     | 
| 267 | 
         
            +
                    </div>
         
     | 
| 268 | 
         
            +
             
     | 
| 269 | 
         
            +
                    {error && (
         
     | 
| 270 | 
         
            +
                      <div className="bg-[#2d0709]/70 border border-[#ff8389]/40 rounded-2xl p-4 w-full max-w-md text-center shadow-[0_15px_35px_rgba(45,7,9,0.4)]">
         
     | 
| 271 | 
         
            +
                        <p className="text-sm text-[#ffb3b8]">Error: {error}</p>
         
     | 
| 272 | 
         
            +
                        <button
         
     | 
| 273 | 
         
            +
                          onClick={loadSelectedModel}
         
     | 
| 274 | 
         
            +
                          className="mt-3 text-sm px-4 py-2 rounded-full bg-white/15 hover:bg-white/25 border border-white/20 text-white font-semibold transition-all"
         
     | 
| 275 | 
         
            +
                        >
         
     | 
| 276 | 
         
            +
                          Retry
         
     | 
| 277 | 
         
            +
                        </button>
         
     | 
| 278 | 
         
            +
                      </div>
         
     | 
| 279 | 
         
            +
                    )}
         
     | 
| 280 | 
         
            +
                  </div>
         
     | 
| 281 | 
         
            +
             
     | 
| 282 | 
         
            +
                  {/* Click-away listener for dropdown */}
         
     | 
| 283 | 
         
            +
                  {isModelDropdownOpen && (
         
     | 
| 284 | 
         
            +
                    <div
         
     | 
| 285 | 
         
            +
                      className="fixed inset-0 z-5"
         
     | 
| 286 | 
         
            +
                      onClick={() => setIsModelDropdownOpen(false)}
         
     | 
| 287 | 
         
            +
                    />
         
     | 
| 288 | 
         
            +
                  )}
         
     | 
| 289 | 
         
            +
                </div>
         
     | 
| 290 | 
         
            +
              );
         
     | 
| 291 | 
         
            +
            };
         
     | 
    	
        src/components/ResultBlock.tsx
    ADDED
    
    | 
         @@ -0,0 +1,21 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import type React from "react";
         
     | 
| 2 | 
         
            +
             
     | 
| 3 | 
         
            +
            const ResultBlock: React.FC<{ error?: string; result?: any }> = ({
         
     | 
| 4 | 
         
            +
              error,
         
     | 
| 5 | 
         
            +
              result,
         
     | 
| 6 | 
         
            +
            }) => (
         
     | 
| 7 | 
         
            +
              <div
         
     | 
| 8 | 
         
            +
                className={
         
     | 
| 9 | 
         
            +
                  error
         
     | 
| 10 | 
         
            +
                    ? "bg-red-900 border border-red-600 rounded p-3"
         
     | 
| 11 | 
         
            +
                    : "bg-gray-700 border border-gray-600 rounded p-3"
         
     | 
| 12 | 
         
            +
                }
         
     | 
| 13 | 
         
            +
              >
         
     | 
| 14 | 
         
            +
                {error ? <p className="text-red-300 text-sm">Error: {error}</p> : null}
         
     | 
| 15 | 
         
            +
                <pre className="text-sm text-gray-300 whitespace-pre-wrap overflow-auto mt-2">
         
     | 
| 16 | 
         
            +
                  {typeof result === "object" ? JSON.stringify(result, null, 2) : result}
         
     | 
| 17 | 
         
            +
                </pre>
         
     | 
| 18 | 
         
            +
              </div>
         
     | 
| 19 | 
         
            +
            );
         
     | 
| 20 | 
         
            +
             
     | 
| 21 | 
         
            +
            export default ResultBlock;
         
     | 
    	
        src/components/ToolCallIndicator.tsx
    ADDED
    
    | 
         @@ -0,0 +1,104 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import type React from "react";
         
     | 
| 2 | 
         
            +
            import { extractToolCallContent } from "../utils";
         
     | 
| 3 | 
         
            +
             
     | 
| 4 | 
         
            +
            const ToolCallIndicator: React.FC<{
         
     | 
| 5 | 
         
            +
              content: string;
         
     | 
| 6 | 
         
            +
              isRunning: boolean;
         
     | 
| 7 | 
         
            +
              hasError: boolean;
         
     | 
| 8 | 
         
            +
            }> = ({ content, isRunning, hasError }) => {
         
     | 
| 9 | 
         
            +
              const toolCalls = extractToolCallContent(content);
         
     | 
| 10 | 
         
            +
              const displayContent = toolCalls?.join("\n") ?? "...";
         
     | 
| 11 | 
         
            +
             
     | 
| 12 | 
         
            +
              return (
         
     | 
| 13 | 
         
            +
                <div
         
     | 
| 14 | 
         
            +
                  className={`transition-all duration-500 ease-in-out rounded-lg p-4 ${
         
     | 
| 15 | 
         
            +
                    isRunning
         
     | 
| 16 | 
         
            +
                      ? "bg-gradient-to-r from-yellow-900/30 to-orange-900/30 border border-yellow-600/50"
         
     | 
| 17 | 
         
            +
                      : hasError
         
     | 
| 18 | 
         
            +
                        ? "bg-gradient-to-r from-red-900/30 to-rose-900/30 border border-red-600/50"
         
     | 
| 19 | 
         
            +
                        : "bg-gradient-to-r from-green-900/30 to-emerald-900/30 border border-green-600/50"
         
     | 
| 20 | 
         
            +
                  }`}
         
     | 
| 21 | 
         
            +
                >
         
     | 
| 22 | 
         
            +
                  <div className="flex items-start space-x-3">
         
     | 
| 23 | 
         
            +
                    <div className="flex-shrink-0">
         
     | 
| 24 | 
         
            +
                      <div className="relative size-8">
         
     | 
| 25 | 
         
            +
                        {/* Spinner for running */}
         
     | 
| 26 | 
         
            +
                        <div
         
     | 
| 27 | 
         
            +
                          className={`absolute inset-0 flex items-center justify-center transition-opacity duration-500 ${
         
     | 
| 28 | 
         
            +
                            isRunning ? "opacity-100" : "opacity-0 pointer-events-none"
         
     | 
| 29 | 
         
            +
                          }`}
         
     | 
| 30 | 
         
            +
                        >
         
     | 
| 31 | 
         
            +
                          <div className="size-8 bg-green-400/0 border-2 border-yellow-400 border-t-transparent rounded-full animate-spin"></div>
         
     | 
| 32 | 
         
            +
                        </div>
         
     | 
| 33 | 
         
            +
             
     | 
| 34 | 
         
            +
                        {/* Cross for error */}
         
     | 
| 35 | 
         
            +
                        <div
         
     | 
| 36 | 
         
            +
                          className={`absolute inset-0 flex items-center justify-center transition-opacity duration-500 ${
         
     | 
| 37 | 
         
            +
                            hasError ? "opacity-100" : "opacity-0 pointer-events-none"
         
     | 
| 38 | 
         
            +
                          }`}
         
     | 
| 39 | 
         
            +
                        >
         
     | 
| 40 | 
         
            +
                          <div className="size-8 bg-red-400/100 rounded-full flex items-center justify-center transition-colors duration-500 ease-in-out">
         
     | 
| 41 | 
         
            +
                            <span className="text-md text-gray-900 font-bold">✗</span>
         
     | 
| 42 | 
         
            +
                          </div>
         
     | 
| 43 | 
         
            +
                        </div>
         
     | 
| 44 | 
         
            +
             
     | 
| 45 | 
         
            +
                        {/* Tick for success */}
         
     | 
| 46 | 
         
            +
                        <div
         
     | 
| 47 | 
         
            +
                          className={`absolute inset-0 flex items-center justify-center transition-opacity duration-500 ${
         
     | 
| 48 | 
         
            +
                            !isRunning && !hasError
         
     | 
| 49 | 
         
            +
                              ? "opacity-100"
         
     | 
| 50 | 
         
            +
                              : "opacity-0 pointer-events-none"
         
     | 
| 51 | 
         
            +
                          }`}
         
     | 
| 52 | 
         
            +
                        >
         
     | 
| 53 | 
         
            +
                          <div className="size-8 bg-green-400/100 rounded-full flex items-center justify-center transition-colors duration-500 ease-in-out">
         
     | 
| 54 | 
         
            +
                            <span className="text-md text-gray-900 font-bold">✓</span>
         
     | 
| 55 | 
         
            +
                          </div>
         
     | 
| 56 | 
         
            +
                        </div>
         
     | 
| 57 | 
         
            +
                      </div>
         
     | 
| 58 | 
         
            +
                    </div>
         
     | 
| 59 | 
         
            +
                    <div className="flex-grow min-w-0">
         
     | 
| 60 | 
         
            +
                      <div className="flex items-center space-x-2 mb-2">
         
     | 
| 61 | 
         
            +
                        <span
         
     | 
| 62 | 
         
            +
                          className={`font-semibold text-sm transition-colors duration-500 ease-in-out ${
         
     | 
| 63 | 
         
            +
                            isRunning
         
     | 
| 64 | 
         
            +
                              ? "text-yellow-400"
         
     | 
| 65 | 
         
            +
                              : hasError
         
     | 
| 66 | 
         
            +
                                ? "text-red-400"
         
     | 
| 67 | 
         
            +
                                : "text-green-400"
         
     | 
| 68 | 
         
            +
                          }`}
         
     | 
| 69 | 
         
            +
                        >
         
     | 
| 70 | 
         
            +
                          🔧 Tool Call
         
     | 
| 71 | 
         
            +
                        </span>
         
     | 
| 72 | 
         
            +
                        {isRunning && (
         
     | 
| 73 | 
         
            +
                          <span className="text-yellow-300 text-xs animate-pulse">
         
     | 
| 74 | 
         
            +
                            Running...
         
     | 
| 75 | 
         
            +
                          </span>
         
     | 
| 76 | 
         
            +
                        )}
         
     | 
| 77 | 
         
            +
                      </div>
         
     | 
| 78 | 
         
            +
                      <div className="bg-gray-800/50 rounded p-2 mb-2">
         
     | 
| 79 | 
         
            +
                        <code className="text-sm text-gray-300 font-mono break-all">
         
     | 
| 80 | 
         
            +
                          {displayContent}
         
     | 
| 81 | 
         
            +
                        </code>
         
     | 
| 82 | 
         
            +
                      </div>
         
     | 
| 83 | 
         
            +
                      <p
         
     | 
| 84 | 
         
            +
                        className={`text-sm transition-colors duration-500 ease-in-out ${
         
     | 
| 85 | 
         
            +
                          isRunning
         
     | 
| 86 | 
         
            +
                            ? "text-yellow-200"
         
     | 
| 87 | 
         
            +
                            : hasError
         
     | 
| 88 | 
         
            +
                              ? "text-red-200"
         
     | 
| 89 | 
         
            +
                              : "text-green-200"
         
     | 
| 90 | 
         
            +
                        }`}
         
     | 
| 91 | 
         
            +
                      >
         
     | 
| 92 | 
         
            +
                        {isRunning
         
     | 
| 93 | 
         
            +
                          ? "Executing tool call..."
         
     | 
| 94 | 
         
            +
                          : hasError
         
     | 
| 95 | 
         
            +
                            ? "Tool call failed"
         
     | 
| 96 | 
         
            +
                            : "Tool call completed"}
         
     | 
| 97 | 
         
            +
                      </p>
         
     | 
| 98 | 
         
            +
                    </div>
         
     | 
| 99 | 
         
            +
                  </div>
         
     | 
| 100 | 
         
            +
                </div>
         
     | 
| 101 | 
         
            +
              );
         
     | 
| 102 | 
         
            +
            };
         
     | 
| 103 | 
         
            +
             
     | 
| 104 | 
         
            +
            export default ToolCallIndicator;
         
     | 
    	
        src/components/ToolItem.tsx
    ADDED
    
    | 
         @@ -0,0 +1,146 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import Editor from "@monaco-editor/react";
         
     | 
| 2 | 
         
            +
            import { ChevronUp, ChevronDown, Trash2, Power } from "lucide-react";
         
     | 
| 3 | 
         
            +
            import { useMemo } from "react";
         
     | 
| 4 | 
         
            +
             
     | 
| 5 | 
         
            +
            import { extractFunctionAndRenderer, generateSchemaFromCode } from "../utils";
         
     | 
| 6 | 
         
            +
             
     | 
| 7 | 
         
            +
            export interface Tool {
         
     | 
| 8 | 
         
            +
              id: number;
         
     | 
| 9 | 
         
            +
              name: string;
         
     | 
| 10 | 
         
            +
              code: string;
         
     | 
| 11 | 
         
            +
              enabled: boolean;
         
     | 
| 12 | 
         
            +
              isCollapsed?: boolean;
         
     | 
| 13 | 
         
            +
              renderer?: string;
         
     | 
| 14 | 
         
            +
            }
         
     | 
| 15 | 
         
            +
             
     | 
| 16 | 
         
            +
            interface ToolItemProps {
         
     | 
| 17 | 
         
            +
              tool: Tool;
         
     | 
| 18 | 
         
            +
              onToggleEnabled: () => void;
         
     | 
| 19 | 
         
            +
              onToggleCollapsed: () => void;
         
     | 
| 20 | 
         
            +
              onExpand: () => void;
         
     | 
| 21 | 
         
            +
              onDelete: () => void;
         
     | 
| 22 | 
         
            +
              onCodeChange: (newCode: string) => void;
         
     | 
| 23 | 
         
            +
            }
         
     | 
| 24 | 
         
            +
             
     | 
| 25 | 
         
            +
            const ToolItem: React.FC<ToolItemProps> = ({
         
     | 
| 26 | 
         
            +
              tool,
         
     | 
| 27 | 
         
            +
              onToggleEnabled,
         
     | 
| 28 | 
         
            +
              onToggleCollapsed,
         
     | 
| 29 | 
         
            +
              onDelete,
         
     | 
| 30 | 
         
            +
              onCodeChange,
         
     | 
| 31 | 
         
            +
            }) => {
         
     | 
| 32 | 
         
            +
              const { functionCode } = extractFunctionAndRenderer(tool.code);
         
     | 
| 33 | 
         
            +
              const schema = useMemo(
         
     | 
| 34 | 
         
            +
                () => generateSchemaFromCode(functionCode),
         
     | 
| 35 | 
         
            +
                [functionCode],
         
     | 
| 36 | 
         
            +
              );
         
     | 
| 37 | 
         
            +
             
     | 
| 38 | 
         
            +
              return (
         
     | 
| 39 | 
         
            +
                <div
         
     | 
| 40 | 
         
            +
                  className={`rounded-2xl p-5 border border-white/10 bg-white/5 backdrop-blur-sm transition-all shadow-[0_18px_55px_rgba(3,27,78,0.45)] ${!tool.enabled ? "opacity-40" : ""}`}
         
     | 
| 41 | 
         
            +
                >
         
     | 
| 42 | 
         
            +
                  <div
         
     | 
| 43 | 
         
            +
                    className="flex justify-between items-center cursor-pointer"
         
     | 
| 44 | 
         
            +
                    onClick={onToggleCollapsed}
         
     | 
| 45 | 
         
            +
                  >
         
     | 
| 46 | 
         
            +
                    <div>
         
     | 
| 47 | 
         
            +
                      <h3 className="text-lg font-semibold text-[#78a9ff] font-mono">
         
     | 
| 48 | 
         
            +
                        {schema.name}
         
     | 
| 49 | 
         
            +
                      </h3>
         
     | 
| 50 | 
         
            +
                      <div className="text-xs text-[#a6c8ff]/80 mt-1">
         
     | 
| 51 | 
         
            +
                        {schema.description}
         
     | 
| 52 | 
         
            +
                      </div>
         
     | 
| 53 | 
         
            +
                    </div>
         
     | 
| 54 | 
         
            +
                    <div className="flex items-center space-x-3">
         
     | 
| 55 | 
         
            +
                      <button
         
     | 
| 56 | 
         
            +
                        onClick={(e) => {
         
     | 
| 57 | 
         
            +
                          e.stopPropagation();
         
     | 
| 58 | 
         
            +
                          onToggleEnabled();
         
     | 
| 59 | 
         
            +
                        }}
         
     | 
| 60 | 
         
            +
                        className={`p-1.5 rounded-full border transition-all ${tool.enabled ? "border-[#0f62fe]/50 bg-[#0f62fe]/20 text-[#78a9ff]" : "border-white/15 bg-white/5 text-white/60"}`}
         
     | 
| 61 | 
         
            +
                      >
         
     | 
| 62 | 
         
            +
                        <Power size={18} />
         
     | 
| 63 | 
         
            +
                      </button>
         
     | 
| 64 | 
         
            +
                      <button
         
     | 
| 65 | 
         
            +
                        onClick={(e) => {
         
     | 
| 66 | 
         
            +
                          e.stopPropagation();
         
     | 
| 67 | 
         
            +
                          onDelete();
         
     | 
| 68 | 
         
            +
                        }}
         
     | 
| 69 | 
         
            +
                        className="p-2 text-[#ff8389] hover:text-white border border-transparent hover:border-[#ff8389]/40 hover:bg-[#ff8389]/10 rounded-full transition-all"
         
     | 
| 70 | 
         
            +
                      >
         
     | 
| 71 | 
         
            +
                        <Trash2 size={18} />
         
     | 
| 72 | 
         
            +
                      </button>
         
     | 
| 73 | 
         
            +
                      <button
         
     | 
| 74 | 
         
            +
                        onClick={(e) => {
         
     | 
| 75 | 
         
            +
                          e.stopPropagation();
         
     | 
| 76 | 
         
            +
                          onToggleCollapsed();
         
     | 
| 77 | 
         
            +
                        }}
         
     | 
| 78 | 
         
            +
                        className="p-2 text-white/70 hover:text-white border border-white/10 hover:border-white/30 hover:bg-white/10 rounded-full transition-all"
         
     | 
| 79 | 
         
            +
                      >
         
     | 
| 80 | 
         
            +
                        {tool.isCollapsed ? (
         
     | 
| 81 | 
         
            +
                          <ChevronDown size={20} />
         
     | 
| 82 | 
         
            +
                        ) : (
         
     | 
| 83 | 
         
            +
                          <ChevronUp size={20} />
         
     | 
| 84 | 
         
            +
                        )}
         
     | 
| 85 | 
         
            +
                      </button>
         
     | 
| 86 | 
         
            +
                    </div>
         
     | 
| 87 | 
         
            +
                  </div>
         
     | 
| 88 | 
         
            +
                  {!tool.isCollapsed && (
         
     | 
| 89 | 
         
            +
                    <div className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
         
     | 
| 90 | 
         
            +
                      <div className="md:col-span-2">
         
     | 
| 91 | 
         
            +
                        <label className="text-xs font-semibold uppercase tracking-[0.2em] text-[#a6c8ff]/80">
         
     | 
| 92 | 
         
            +
                          Implementation & Renderer
         
     | 
| 93 | 
         
            +
                        </label>
         
     | 
| 94 | 
         
            +
                        <div
         
     | 
| 95 | 
         
            +
                          className="mt-1 rounded-2xl overflow-visible border border-white/12 bg-[#031b4e]/60"
         
     | 
| 96 | 
         
            +
                          style={{ overflow: "visible" }}
         
     | 
| 97 | 
         
            +
                        >
         
     | 
| 98 | 
         
            +
                          <Editor
         
     | 
| 99 | 
         
            +
                            height="300px"
         
     | 
| 100 | 
         
            +
                            language="javascript"
         
     | 
| 101 | 
         
            +
                            theme="vs-dark"
         
     | 
| 102 | 
         
            +
                            value={tool.code}
         
     | 
| 103 | 
         
            +
                            onChange={(value) => onCodeChange(value || "")}
         
     | 
| 104 | 
         
            +
                            options={{
         
     | 
| 105 | 
         
            +
                              minimap: { enabled: false },
         
     | 
| 106 | 
         
            +
                              scrollbar: { verticalScrollbarSize: 10 },
         
     | 
| 107 | 
         
            +
                              fontSize: 14,
         
     | 
| 108 | 
         
            +
                              lineDecorationsWidth: 0,
         
     | 
| 109 | 
         
            +
                              lineNumbersMinChars: 3,
         
     | 
| 110 | 
         
            +
                              scrollBeyondLastLine: false,
         
     | 
| 111 | 
         
            +
                            }}
         
     | 
| 112 | 
         
            +
                          />
         
     | 
| 113 | 
         
            +
                        </div>
         
     | 
| 114 | 
         
            +
                      </div>
         
     | 
| 115 | 
         
            +
                      <div className="flex flex-col">
         
     | 
| 116 | 
         
            +
                        <label className="text-xs font-semibold uppercase tracking-[0.2em] text-[#a6c8ff]/80">
         
     | 
| 117 | 
         
            +
                          Generated Schema
         
     | 
| 118 | 
         
            +
                        </label>
         
     | 
| 119 | 
         
            +
                        <div className="mt-1 rounded-2xl flex-grow overflow-visible border border-white/12 bg-[#031b4e]/60">
         
     | 
| 120 | 
         
            +
                          <Editor
         
     | 
| 121 | 
         
            +
                            height="300px"
         
     | 
| 122 | 
         
            +
                            language="json"
         
     | 
| 123 | 
         
            +
                            theme="vs-dark"
         
     | 
| 124 | 
         
            +
                            value={JSON.stringify(schema, null, 2)}
         
     | 
| 125 | 
         
            +
                            options={{
         
     | 
| 126 | 
         
            +
                              readOnly: true,
         
     | 
| 127 | 
         
            +
                              minimap: { enabled: false },
         
     | 
| 128 | 
         
            +
                              scrollbar: { verticalScrollbarSize: 10 },
         
     | 
| 129 | 
         
            +
                              lineNumbers: "off",
         
     | 
| 130 | 
         
            +
                              glyphMargin: false,
         
     | 
| 131 | 
         
            +
                              folding: false,
         
     | 
| 132 | 
         
            +
                              lineDecorationsWidth: 0,
         
     | 
| 133 | 
         
            +
                              lineNumbersMinChars: 0,
         
     | 
| 134 | 
         
            +
                              scrollBeyondLastLine: false,
         
     | 
| 135 | 
         
            +
                              fontSize: 12,
         
     | 
| 136 | 
         
            +
                            }}
         
     | 
| 137 | 
         
            +
                          />
         
     | 
| 138 | 
         
            +
                        </div>
         
     | 
| 139 | 
         
            +
                      </div>
         
     | 
| 140 | 
         
            +
                    </div>
         
     | 
| 141 | 
         
            +
                  )}
         
     | 
| 142 | 
         
            +
                </div>
         
     | 
| 143 | 
         
            +
              );
         
     | 
| 144 | 
         
            +
            };
         
     | 
| 145 | 
         
            +
             
     | 
| 146 | 
         
            +
            export default ToolItem;
         
     | 
    	
        src/components/ToolResultRenderer.tsx
    ADDED
    
    | 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import React from "react";
         
     | 
| 2 | 
         
            +
            import ResultBlock from "./ResultBlock";
         
     | 
| 3 | 
         
            +
             
     | 
| 4 | 
         
            +
            const ToolResultRenderer: React.FC<{
         
     | 
| 5 | 
         
            +
              result: any;
         
     | 
| 6 | 
         
            +
              rendererCode?: string;
         
     | 
| 7 | 
         
            +
              input?: any;
         
     | 
| 8 | 
         
            +
            }> = ({ result, rendererCode, input }) => {
         
     | 
| 9 | 
         
            +
              if (!rendererCode) {
         
     | 
| 10 | 
         
            +
                return <ResultBlock result={result} />;
         
     | 
| 11 | 
         
            +
              }
         
     | 
| 12 | 
         
            +
             
     | 
| 13 | 
         
            +
              try {
         
     | 
| 14 | 
         
            +
                const exportMatch = rendererCode.match(/export\s+default\s+(.*)/s);
         
     | 
| 15 | 
         
            +
                if (!exportMatch) {
         
     | 
| 16 | 
         
            +
                  throw new Error("Invalid renderer format - no export default found");
         
     | 
| 17 | 
         
            +
                }
         
     | 
| 18 | 
         
            +
             
     | 
| 19 | 
         
            +
                const componentCode = exportMatch[1].trim();
         
     | 
| 20 | 
         
            +
                const componentFunction = new Function(
         
     | 
| 21 | 
         
            +
                  "React",
         
     | 
| 22 | 
         
            +
                  "input",
         
     | 
| 23 | 
         
            +
                  "output",
         
     | 
| 24 | 
         
            +
                  `
         
     | 
| 25 | 
         
            +
                  const { createElement: h, Fragment } = React;
         
     | 
| 26 | 
         
            +
                  const JSXComponent = ${componentCode};
         
     | 
| 27 | 
         
            +
                  return JSXComponent(input, output);
         
     | 
| 28 | 
         
            +
                  `,
         
     | 
| 29 | 
         
            +
                );
         
     | 
| 30 | 
         
            +
             
     | 
| 31 | 
         
            +
                const element = componentFunction(React, input || {}, result);
         
     | 
| 32 | 
         
            +
                return element;
         
     | 
| 33 | 
         
            +
              } catch (error) {
         
     | 
| 34 | 
         
            +
                return (
         
     | 
| 35 | 
         
            +
                  <ResultBlock
         
     | 
| 36 | 
         
            +
                    error={error instanceof Error ? error.message : "Unknown error"}
         
     | 
| 37 | 
         
            +
                    result={result}
         
     | 
| 38 | 
         
            +
                  />
         
     | 
| 39 | 
         
            +
                );
         
     | 
| 40 | 
         
            +
              }
         
     | 
| 41 | 
         
            +
            };
         
     | 
| 42 | 
         
            +
            export default ToolResultRenderer;
         
     | 
    	
        src/components/icons/HfLogo.tsx
    ADDED
    
    | 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import type React from "react";
         
     | 
| 2 | 
         
            +
             
     | 
| 3 | 
         
            +
            export default (props: React.SVGProps<SVGSVGElement>) => (
         
     | 
| 4 | 
         
            +
              <svg
         
     | 
| 5 | 
         
            +
                {...props}
         
     | 
| 6 | 
         
            +
                xmlns="http://www.w3.org/2000/svg"
         
     | 
| 7 | 
         
            +
                viewBox="0 0 24 24"
         
     | 
| 8 | 
         
            +
                fill="currentColor"
         
     | 
| 9 | 
         
            +
              >
         
     | 
| 10 | 
         
            +
                <path
         
     | 
| 11 | 
         
            +
                  d="M2.25 11.535c0-3.407 1.847-6.554 4.844-8.258a9.822 9.822 0 019.687 0c2.997 1.704 4.844 4.851 4.844 8.258 0 5.266-4.337 9.535-9.687 9.535S2.25 16.8 2.25 11.535z"
         
     | 
| 12 | 
         
            +
                  fill="#FF9D0B"
         
     | 
| 13 | 
         
            +
                ></path>
         
     | 
| 14 | 
         
            +
                <path
         
     | 
| 15 | 
         
            +
                  d="M11.938 20.086c4.797 0 8.687-3.829 8.687-8.551 0-4.722-3.89-8.55-8.687-8.55-4.798 0-8.688 3.828-8.688 8.55 0 4.722 3.89 8.55 8.688 8.55z"
         
     | 
| 16 | 
         
            +
                  fill="#FFD21E"
         
     | 
| 17 | 
         
            +
                ></path>
         
     | 
| 18 | 
         
            +
                <path
         
     | 
| 19 | 
         
            +
                  d="M11.875 15.113c2.457 0 3.25-2.156 3.25-3.263 0-.576-.393-.394-1.023-.089-.582.283-1.365.675-2.224.675-1.798 0-3.25-1.693-3.25-.586 0 1.107.79 3.263 3.25 3.263h-.003z"
         
     | 
| 20 | 
         
            +
                  fill="#FF323D"
         
     | 
| 21 | 
         
            +
                ></path>
         
     | 
| 22 | 
         
            +
                <path
         
     | 
| 23 | 
         
            +
                  d="M14.76 9.21c.32.108.445.753.767.585.447-.233.707-.708.659-1.204a1.235 1.235 0 00-.879-1.059 1.262 1.262 0 00-1.33.394c-.322.384-.377.92-.14 1.36.153.283.638-.177.925-.079l-.002.003zm-5.887 0c-.32.108-.448.753-.768.585a1.226 1.226 0 01-.658-1.204c.048-.495.395-.913.878-1.059a1.262 1.262 0 011.33.394c.322.384.377.92.14 1.36-.152.283-.64-.177-.925-.079l.003.003zm1.12 5.34a2.166 2.166 0 011.325-1.106c.07-.02.144.06.219.171l.192.306c.069.1.139.175.209.175.074 0 .15-.074.223-.172l.205-.302c.08-.11.157-.188.234-.165.537.168.986.536 1.25 1.026.932-.724 1.275-1.905 1.275-2.633 0-.508-.306-.426-.81-.19l-.616.296c-.52.24-1.148.48-1.824.48-.676 0-1.302-.24-1.823-.48l-.589-.283c-.52-.248-.838-.342-.838.177 0 .703.32 1.831 1.187 2.56l.18.14z"
         
     | 
| 24 | 
         
            +
                  fill="#3A3B45"
         
     | 
| 25 | 
         
            +
                ></path>
         
     | 
| 26 | 
         
            +
                <path
         
     | 
| 27 | 
         
            +
                  d="M17.812 10.366a.806.806 0 00.813-.8c0-.441-.364-.8-.813-.8a.806.806 0 00-.812.8c0 .442.364.8.812.8zm-11.624 0a.806.806 0 00.812-.8c0-.441-.364-.8-.812-.8a.806.806 0 00-.813.8c0 .442.364.8.813.8zM4.515 13.073c-.405 0-.765.162-1.017.46a1.455 1.455 0 00-.333.925 1.801 1.801 0 00-.485-.074c-.387 0-.737.146-.985.409a1.41 1.41 0 00-.2 1.722 1.302 1.302 0 00-.447.694c-.06.222-.12.69.2 1.166a1.267 1.267 0 00-.093 1.236c.238.533.81.958 1.89 1.405l.24.096c.768.3 1.473.492 1.478.494.89.243 1.808.375 2.732.394 1.465 0 2.513-.443 3.115-1.314.93-1.342.842-2.575-.274-3.763l-.151-.154c-.692-.684-1.155-1.69-1.25-1.912-.195-.655-.71-1.383-1.562-1.383-.46.007-.889.233-1.15.605-.25-.31-.495-.553-.715-.694a1.87 1.87 0 00-.993-.312zm14.97 0c.405 0 .767.162 1.017.46.216.262.333.588.333.925.158-.047.322-.071.487-.074.388 0 .738.146.985.409a1.41 1.41 0 01.2 1.722c.22.178.377.422.445.694.06.222.12.69-.2 1.166.244.37.279.836.093 1.236-.238.533-.81.958-1.889 1.405l-.239.096c-.77.3-1.475.492-1.48.494-.89.243-1.808.375-2.732.394-1.465 0-2.513-.443-3.115-1.314-.93-1.342-.842-2.575.274-3.763l.151-.154c.695-.684 1.157-1.69 1.252-1.912.195-.655.708-1.383 1.56-1.383.46.007.889.233 1.15.605.25-.31.495-.553.718-.694.244-.162.523-.265.814-.3l.176-.012z"
         
     | 
| 28 | 
         
            +
                  fill="#FF9D0B"
         
     | 
| 29 | 
         
            +
                ></path>
         
     | 
| 30 | 
         
            +
                <path
         
     | 
| 31 | 
         
            +
                  d="M9.785 20.132c.688-.994.638-1.74-.305-2.667-.945-.928-1.495-2.288-1.495-2.288s-.205-.788-.672-.714c-.468.074-.81 1.25.17 1.971.977.721-.195 1.21-.573.534-.375-.677-1.405-2.416-1.94-2.751-.532-.332-.907-.148-.782.541.125.687 2.357 2.35 2.14 2.707-.218.362-.983-.42-.983-.42S2.953 14.9 2.43 15.46c-.52.558.398 1.026 1.7 1.803 1.308.778 1.41.985 1.225 1.28-.187.295-3.07-2.1-3.34-1.083-.27 1.011 2.943 1.304 2.745 2.006-.2.7-2.265-1.324-2.685-.537-.425.79 2.913 1.718 2.94 1.725 1.075.276 3.813.859 4.77-.522zm4.432 0c-.687-.994-.64-1.74.305-2.667.943-.928 1.493-2.288 1.493-2.288s.205-.788.675-.714c.465.074.807 1.25-.17 1.971-.98.721.195 1.21.57.534.377-.677 1.407-2.416 1.94-2.751.532-.332.91-.148.782.541-.125.687-2.355 2.35-2.137 2.707.215.362.98-.42.98-.42S21.05 14.9 21.57 15.46c.52.558-.395 1.026-1.7 1.803-1.308.778-1.408.985-1.225 1.28.187.295 3.07-2.1 3.34-1.083.27 1.011-2.94 1.304-2.743 2.006.2.7 2.263-1.324 2.685-.537.423.79-2.912 1.718-2.94 1.725-1.077.276-3.815.859-4.77-.522z"
         
     | 
| 32 | 
         
            +
                  fill="#FFD21E"
         
     | 
| 33 | 
         
            +
                ></path>
         
     | 
| 34 | 
         
            +
              </svg>
         
     | 
| 35 | 
         
            +
            );
         
     | 
    	
        src/components/icons/IBMLogo.tsx
    ADDED
    
    | 
         @@ -0,0 +1,12 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import type React from "react";
         
     | 
| 2 | 
         
            +
             
     | 
| 3 | 
         
            +
            export default (props: React.SVGProps<SVGSVGElement>) => (
         
     | 
| 4 | 
         
            +
              <svg
         
     | 
| 5 | 
         
            +
                {...props}
         
     | 
| 6 | 
         
            +
                xmlns="http://www.w3.org/2000/svg"
         
     | 
| 7 | 
         
            +
                viewBox="0 0 1000 400"
         
     | 
| 8 | 
         
            +
                fill="currentColor"
         
     | 
| 9 | 
         
            +
              >
         
     | 
| 10 | 
         
            +
                <path d="M0 0v27.367h194.648V0H0zm222.226 0v27.367h277.383S471.276 0 433.75 0H222.226zm331.797 0v27.367h167.812L711.875 0H554.023zm288.125 0l-9.961 27.367h166.289V0H842.148zM0 53.222v27.367h194.648V53.222H0zm222.226.039V80.59h309.57s-3.615-21.063-9.922-27.329H222.226zm331.797 0V80.59h186.211l-9.219-27.329H554.023zm268.203 0l-9.219 27.329h185.469V53.261h-176.25zM55.937 106.444v27.406h84.297v-27.406H55.937zm222.227 0v27.406h84.297v-27.406h-84.297zm166.289 0v27.406h84.297s5.352-14.473 5.352-27.406h-89.649zm165.508 0v27.406h149.453l-9.961-27.406H609.961zm193.906 0l-10 27.406h150.195v-27.406H803.867zm-747.93 53.262v27.367h84.297v-27.367H55.937zm222.227 0v27.367h215.312s18.012-14.042 23.75-27.367H278.164zm331.797 0v27.367h84.297v-15.234l5.352 15.234h154.414l5.742-15.234v15.234h84.297v-27.367H785.82l-8.398 23.18-8.438-23.18H609.961zM55.937 212.928v27.367h84.297v-27.367H55.937zm222.227 0v27.367h239.062c-5.739-13.281-23.75-27.367-23.75-27.367H278.164zm331.797 0v27.367h84.297v-27.367h-84.297zm99.609 0l10.195 27.367h115.781l9.688-27.367H709.57zm150.195 0v27.367h84.297v-27.367h-84.297zM55.937 266.15v27.366h84.297V266.15H55.937zm222.227 0v27.366h84.297V266.15h-84.297zm166.289 0v27.366h89.648c0-12.915-5.352-27.366-5.352-27.366h-84.296zm165.508 0v27.366h84.297V266.15h-84.297zm118.75 0l9.883 27.366h77.617l9.961-27.366h-97.461zm131.054 0v27.366h84.297V266.15h-84.297zM1.523 319.372v27.406h194.648v-27.406H1.523zm220.703 0v27.406h299.648c6.307-6.275 9.922-27.406 9.922-27.406h-309.57zm333.321 0v27.406h138.711v-27.406H555.547zm192.343 0l10.156 27.406h39.492l9.531-27.406H747.89zm111.875 0v27.406H1000v-27.406H859.765zM1.523 372.633V400h194.648v-27.367H1.523zm220.703 0v27.328H433.75c37.526 0 65.859-27.328 65.859-27.328H222.226zm333.321 0V400h138.711v-27.367H555.547zm211.601 0l9.766 27.29 1.68.038 9.922-27.328h-21.368zm92.617 0V400H1000v-27.367H859.765z" />
         
     | 
| 11 | 
         
            +
              </svg>
         
     | 
| 12 | 
         
            +
            );
         
     | 
    	
        src/constants/db.ts
    ADDED
    
    | 
         @@ -0,0 +1,3 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            export const DB_NAME = "tool-caller-db";
         
     | 
| 2 | 
         
            +
            export const STORE_NAME = "tools";
         
     | 
| 3 | 
         
            +
            export const SETTINGS_STORE_NAME = "settings";
         
     | 
    	
        src/constants/examples.ts
    ADDED
    
    | 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            export interface Example {
         
     | 
| 2 | 
         
            +
              icon: string;
         
     | 
| 3 | 
         
            +
              displayText: string;
         
     | 
| 4 | 
         
            +
              messageText: string;
         
     | 
| 5 | 
         
            +
            }
         
     | 
| 6 | 
         
            +
             
     | 
| 7 | 
         
            +
            export const DEFAULT_EXAMPLES: Example[] = [
         
     | 
| 8 | 
         
            +
              {
         
     | 
| 9 | 
         
            +
                icon: "🌍",
         
     | 
| 10 | 
         
            +
                displayText: "Where am I and what time is it?",
         
     | 
| 11 | 
         
            +
                messageText: "Where am I and what time is it?",
         
     | 
| 12 | 
         
            +
              },
         
     | 
| 13 | 
         
            +
              {
         
     | 
| 14 | 
         
            +
                icon: "😂",
         
     | 
| 15 | 
         
            +
                displayText: "Tell me a joke.",
         
     | 
| 16 | 
         
            +
                messageText: "Tell me a joke.",
         
     | 
| 17 | 
         
            +
              },
         
     | 
| 18 | 
         
            +
              {
         
     | 
| 19 | 
         
            +
                icon: "🔢",
         
     | 
| 20 | 
         
            +
                displayText: "Solve a math problem",
         
     | 
| 21 | 
         
            +
                messageText: "What is 123 times 456 divided by 789?",
         
     | 
| 22 | 
         
            +
              },
         
     | 
| 23 | 
         
            +
              {
         
     | 
| 24 | 
         
            +
                icon: "😴",
         
     | 
| 25 | 
         
            +
                displayText: "Sleep for 3 seconds",
         
     | 
| 26 | 
         
            +
                messageText: "Sleep for 3 seconds",
         
     | 
| 27 | 
         
            +
              },
         
     | 
| 28 | 
         
            +
              {
         
     | 
| 29 | 
         
            +
                icon: "🎲",
         
     | 
| 30 | 
         
            +
                displayText: "Generate a random number",
         
     | 
| 31 | 
         
            +
                messageText: "Generate a random number between 1 and 100.",
         
     | 
| 32 | 
         
            +
              },
         
     | 
| 33 | 
         
            +
              {
         
     | 
| 34 | 
         
            +
                icon: "📹",
         
     | 
| 35 | 
         
            +
                displayText: "Play a video",
         
     | 
| 36 | 
         
            +
                messageText:
         
     | 
| 37 | 
         
            +
                  'Open this website: "https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1" and do nothing else.',
         
     | 
| 38 | 
         
            +
              },
         
     | 
| 39 | 
         
            +
            ];
         
     | 
    	
        src/constants/models.ts
    ADDED
    
    | 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            export const MODEL_OPTIONS = [
         
     | 
| 2 | 
         
            +
              {
         
     | 
| 3 | 
         
            +
                id: "350M",
         
     | 
| 4 | 
         
            +
                modelId: "onnx-community/granite-4.0-350m-ONNX-web",
         
     | 
| 5 | 
         
            +
                dtype: "fp16",
         
     | 
| 6 | 
         
            +
                label: "Granite-4.0 350M (fp16)",
         
     | 
| 7 | 
         
            +
                size: "A lightweight 350M instruct model (~709 MB in size).",
         
     | 
| 8 | 
         
            +
              },
         
     | 
| 9 | 
         
            +
              {
         
     | 
| 10 | 
         
            +
                id: "1B",
         
     | 
| 11 | 
         
            +
                modelId: "onnx-community/granite-4.0-1b-ONNX-web",
         
     | 
| 12 | 
         
            +
                dtype: "q4",
         
     | 
| 13 | 
         
            +
                label: "Granite-4.0 1B (q4)",
         
     | 
| 14 | 
         
            +
                size: "A medium-sized instruct model (~1.78 GB in size).",
         
     | 
| 15 | 
         
            +
              },
         
     | 
| 16 | 
         
            +
              {
         
     | 
| 17 | 
         
            +
                id: "3B",
         
     | 
| 18 | 
         
            +
                modelId: "onnx-community/granite-4.0-micro-ONNX-web",
         
     | 
| 19 | 
         
            +
                dtype: "q4f16",
         
     | 
| 20 | 
         
            +
                label: "Granite-4.0 3B (q4f16)",
         
     | 
| 21 | 
         
            +
                size: "A large 3B parameter instruct model (~2.3 GB in size).",
         
     | 
| 22 | 
         
            +
              },
         
     | 
| 23 | 
         
            +
            ] as const;
         
     | 
    	
        src/hooks/useLLM.ts
    ADDED
    
    | 
         @@ -0,0 +1,234 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import { useState, useEffect, useRef, useCallback } from "react";
         
     | 
| 2 | 
         
            +
            import {
         
     | 
| 3 | 
         
            +
              AutoModelForCausalLM,
         
     | 
| 4 | 
         
            +
              AutoTokenizer,
         
     | 
| 5 | 
         
            +
              TextStreamer,
         
     | 
| 6 | 
         
            +
            } from "@huggingface/transformers";
         
     | 
| 7 | 
         
            +
            import { MODEL_OPTIONS } from "../constants/models";
         
     | 
| 8 | 
         
            +
             
     | 
| 9 | 
         
            +
            interface LLMState {
         
     | 
| 10 | 
         
            +
              isLoading: boolean;
         
     | 
| 11 | 
         
            +
              isReady: boolean;
         
     | 
| 12 | 
         
            +
              error: string | null;
         
     | 
| 13 | 
         
            +
              progress: number;
         
     | 
| 14 | 
         
            +
            }
         
     | 
| 15 | 
         
            +
             
     | 
| 16 | 
         
            +
            interface LLMInstance {
         
     | 
| 17 | 
         
            +
              model: any;
         
     | 
| 18 | 
         
            +
              tokenizer: any;
         
     | 
| 19 | 
         
            +
            }
         
     | 
| 20 | 
         
            +
             
     | 
| 21 | 
         
            +
            let moduleCache: {
         
     | 
| 22 | 
         
            +
              [modelId: string]: {
         
     | 
| 23 | 
         
            +
                instance: LLMInstance | null;
         
     | 
| 24 | 
         
            +
                loadingPromise: Promise<LLMInstance> | null;
         
     | 
| 25 | 
         
            +
              };
         
     | 
| 26 | 
         
            +
            } = {};
         
     | 
| 27 | 
         
            +
             
     | 
| 28 | 
         
            +
            export const useLLM = (modelName?: string) => {
         
     | 
| 29 | 
         
            +
              const [state, setState] = useState<LLMState>({
         
     | 
| 30 | 
         
            +
                isLoading: false,
         
     | 
| 31 | 
         
            +
                isReady: false,
         
     | 
| 32 | 
         
            +
                error: null,
         
     | 
| 33 | 
         
            +
                progress: 0,
         
     | 
| 34 | 
         
            +
              });
         
     | 
| 35 | 
         
            +
             
     | 
| 36 | 
         
            +
              const instanceRef = useRef<LLMInstance | null>(null);
         
     | 
| 37 | 
         
            +
              const loadingPromiseRef = useRef<Promise<LLMInstance> | null>(null);
         
     | 
| 38 | 
         
            +
             
     | 
| 39 | 
         
            +
              const abortControllerRef = useRef<AbortController | null>(null);
         
     | 
| 40 | 
         
            +
              const pastKeyValuesRef = useRef<any>(null);
         
     | 
| 41 | 
         
            +
             
     | 
| 42 | 
         
            +
              const { modelId, dtype } = MODEL_OPTIONS.find((opt) => opt.id === modelName)!;
         
     | 
| 43 | 
         
            +
              const loadModel = useCallback(async () => {
         
     | 
| 44 | 
         
            +
                if (!modelId) {
         
     | 
| 45 | 
         
            +
                  throw new Error("Model ID is required");
         
     | 
| 46 | 
         
            +
                }
         
     | 
| 47 | 
         
            +
             
     | 
| 48 | 
         
            +
                if (!moduleCache[modelId]) {
         
     | 
| 49 | 
         
            +
                  moduleCache[modelId] = {
         
     | 
| 50 | 
         
            +
                    instance: null,
         
     | 
| 51 | 
         
            +
                    loadingPromise: null,
         
     | 
| 52 | 
         
            +
                  };
         
     | 
| 53 | 
         
            +
                }
         
     | 
| 54 | 
         
            +
             
     | 
| 55 | 
         
            +
                const cache = moduleCache[modelId];
         
     | 
| 56 | 
         
            +
             
     | 
| 57 | 
         
            +
                const existingInstance = instanceRef.current || cache.instance;
         
     | 
| 58 | 
         
            +
                if (existingInstance) {
         
     | 
| 59 | 
         
            +
                  instanceRef.current = existingInstance;
         
     | 
| 60 | 
         
            +
                  cache.instance = existingInstance;
         
     | 
| 61 | 
         
            +
                  setState((prev) => ({ ...prev, isReady: true, isLoading: false }));
         
     | 
| 62 | 
         
            +
                  return existingInstance;
         
     | 
| 63 | 
         
            +
                }
         
     | 
| 64 | 
         
            +
             
     | 
| 65 | 
         
            +
                const existingPromise = loadingPromiseRef.current || cache.loadingPromise;
         
     | 
| 66 | 
         
            +
                if (existingPromise) {
         
     | 
| 67 | 
         
            +
                  try {
         
     | 
| 68 | 
         
            +
                    const instance = await existingPromise;
         
     | 
| 69 | 
         
            +
                    instanceRef.current = instance;
         
     | 
| 70 | 
         
            +
                    cache.instance = instance;
         
     | 
| 71 | 
         
            +
                    setState((prev) => ({ ...prev, isReady: true, isLoading: false }));
         
     | 
| 72 | 
         
            +
                    return instance;
         
     | 
| 73 | 
         
            +
                  } catch (error) {
         
     | 
| 74 | 
         
            +
                    setState((prev) => ({
         
     | 
| 75 | 
         
            +
                      ...prev,
         
     | 
| 76 | 
         
            +
                      isLoading: false,
         
     | 
| 77 | 
         
            +
                      error:
         
     | 
| 78 | 
         
            +
                        error instanceof Error ? error.message : "Failed to load model",
         
     | 
| 79 | 
         
            +
                    }));
         
     | 
| 80 | 
         
            +
                    throw error;
         
     | 
| 81 | 
         
            +
                  }
         
     | 
| 82 | 
         
            +
                }
         
     | 
| 83 | 
         
            +
             
     | 
| 84 | 
         
            +
                setState((prev) => ({
         
     | 
| 85 | 
         
            +
                  ...prev,
         
     | 
| 86 | 
         
            +
                  isLoading: true,
         
     | 
| 87 | 
         
            +
                  error: null,
         
     | 
| 88 | 
         
            +
                  progress: 0,
         
     | 
| 89 | 
         
            +
                }));
         
     | 
| 90 | 
         
            +
             
     | 
| 91 | 
         
            +
                abortControllerRef.current = new AbortController();
         
     | 
| 92 | 
         
            +
             
     | 
| 93 | 
         
            +
                const loadingPromise = (async () => {
         
     | 
| 94 | 
         
            +
                  try {
         
     | 
| 95 | 
         
            +
                    const progress_callback = (progress: any) => {
         
     | 
| 96 | 
         
            +
                      // Only update progress for weights
         
     | 
| 97 | 
         
            +
                      if (
         
     | 
| 98 | 
         
            +
                        progress.status === "progress" &&
         
     | 
| 99 | 
         
            +
                        progress.file.endsWith(".onnx_data")
         
     | 
| 100 | 
         
            +
                      ) {
         
     | 
| 101 | 
         
            +
                        const percentage = Math.round(
         
     | 
| 102 | 
         
            +
                          (progress.loaded / progress.total) * 100,
         
     | 
| 103 | 
         
            +
                        );
         
     | 
| 104 | 
         
            +
                        setState((prev) => ({ ...prev, progress: percentage }));
         
     | 
| 105 | 
         
            +
                      }
         
     | 
| 106 | 
         
            +
                    };
         
     | 
| 107 | 
         
            +
             
     | 
| 108 | 
         
            +
                    const tokenizer = await AutoTokenizer.from_pretrained(modelId, {
         
     | 
| 109 | 
         
            +
                      progress_callback,
         
     | 
| 110 | 
         
            +
                    });
         
     | 
| 111 | 
         
            +
             
     | 
| 112 | 
         
            +
                    const model = await AutoModelForCausalLM.from_pretrained(modelId, {
         
     | 
| 113 | 
         
            +
                      dtype,
         
     | 
| 114 | 
         
            +
                      device: "webgpu",
         
     | 
| 115 | 
         
            +
                      progress_callback,
         
     | 
| 116 | 
         
            +
                    });
         
     | 
| 117 | 
         
            +
             
     | 
| 118 | 
         
            +
                    const instance = { model, tokenizer };
         
     | 
| 119 | 
         
            +
                    instanceRef.current = instance;
         
     | 
| 120 | 
         
            +
                    cache.instance = instance;
         
     | 
| 121 | 
         
            +
                    loadingPromiseRef.current = null;
         
     | 
| 122 | 
         
            +
                    cache.loadingPromise = null;
         
     | 
| 123 | 
         
            +
             
     | 
| 124 | 
         
            +
                    setState((prev) => ({
         
     | 
| 125 | 
         
            +
                      ...prev,
         
     | 
| 126 | 
         
            +
                      isLoading: false,
         
     | 
| 127 | 
         
            +
                      isReady: true,
         
     | 
| 128 | 
         
            +
                      progress: 100,
         
     | 
| 129 | 
         
            +
                    }));
         
     | 
| 130 | 
         
            +
                    return instance;
         
     | 
| 131 | 
         
            +
                  } catch (error) {
         
     | 
| 132 | 
         
            +
                    loadingPromiseRef.current = null;
         
     | 
| 133 | 
         
            +
                    cache.loadingPromise = null;
         
     | 
| 134 | 
         
            +
                    setState((prev) => ({
         
     | 
| 135 | 
         
            +
                      ...prev,
         
     | 
| 136 | 
         
            +
                      isLoading: false,
         
     | 
| 137 | 
         
            +
                      error:
         
     | 
| 138 | 
         
            +
                        error instanceof Error ? error.message : "Failed to load model",
         
     | 
| 139 | 
         
            +
                    }));
         
     | 
| 140 | 
         
            +
                    throw error;
         
     | 
| 141 | 
         
            +
                  }
         
     | 
| 142 | 
         
            +
                })();
         
     | 
| 143 | 
         
            +
             
     | 
| 144 | 
         
            +
                loadingPromiseRef.current = loadingPromise;
         
     | 
| 145 | 
         
            +
                cache.loadingPromise = loadingPromise;
         
     | 
| 146 | 
         
            +
                return loadingPromise;
         
     | 
| 147 | 
         
            +
              }, [modelId]);
         
     | 
| 148 | 
         
            +
             
     | 
| 149 | 
         
            +
              const generateResponse = useCallback(
         
     | 
| 150 | 
         
            +
                async (
         
     | 
| 151 | 
         
            +
                  messages: Array<{ role: string; content: string }>,
         
     | 
| 152 | 
         
            +
                  tools: Array<any>,
         
     | 
| 153 | 
         
            +
                  onToken?: (token: string) => void,
         
     | 
| 154 | 
         
            +
                ): Promise<string> => {
         
     | 
| 155 | 
         
            +
                  const instance = instanceRef.current;
         
     | 
| 156 | 
         
            +
                  if (!instance) {
         
     | 
| 157 | 
         
            +
                    throw new Error("Model not loaded. Call loadModel() first.");
         
     | 
| 158 | 
         
            +
                  }
         
     | 
| 159 | 
         
            +
             
     | 
| 160 | 
         
            +
                  const { model, tokenizer } = instance;
         
     | 
| 161 | 
         
            +
             
     | 
| 162 | 
         
            +
                  // Apply chat template with tools
         
     | 
| 163 | 
         
            +
                  const input = tokenizer.apply_chat_template(messages, {
         
     | 
| 164 | 
         
            +
                    tools,
         
     | 
| 165 | 
         
            +
                    add_generation_prompt: true,
         
     | 
| 166 | 
         
            +
                    return_dict: true,
         
     | 
| 167 | 
         
            +
                  });
         
     | 
| 168 | 
         
            +
             
     | 
| 169 | 
         
            +
                  const streamer = onToken
         
     | 
| 170 | 
         
            +
                    ? new TextStreamer(tokenizer, {
         
     | 
| 171 | 
         
            +
                        skip_prompt: true,
         
     | 
| 172 | 
         
            +
                        skip_special_tokens: false,
         
     | 
| 173 | 
         
            +
                        callback_function: (token: string) => {
         
     | 
| 174 | 
         
            +
                          onToken(token);
         
     | 
| 175 | 
         
            +
                        },
         
     | 
| 176 | 
         
            +
                      })
         
     | 
| 177 | 
         
            +
                    : undefined;
         
     | 
| 178 | 
         
            +
             
     | 
| 179 | 
         
            +
                  // Generate the response
         
     | 
| 180 | 
         
            +
                  const { sequences, past_key_values } = await model.generate({
         
     | 
| 181 | 
         
            +
                    ...input,
         
     | 
| 182 | 
         
            +
                    past_key_values: pastKeyValuesRef.current,
         
     | 
| 183 | 
         
            +
                    max_new_tokens: 512,
         
     | 
| 184 | 
         
            +
                    do_sample: false,
         
     | 
| 185 | 
         
            +
                    streamer,
         
     | 
| 186 | 
         
            +
                    return_dict_in_generate: true,
         
     | 
| 187 | 
         
            +
                  });
         
     | 
| 188 | 
         
            +
                  pastKeyValuesRef.current = past_key_values;
         
     | 
| 189 | 
         
            +
             
     | 
| 190 | 
         
            +
                  // Decode the generated text with special tokens preserved (except final <|end_of_text|>) for tool call detection
         
     | 
| 191 | 
         
            +
                  const response = tokenizer
         
     | 
| 192 | 
         
            +
                    .batch_decode(sequences.slice(null, [input.input_ids.dims[1], null]), {
         
     | 
| 193 | 
         
            +
                      skip_special_tokens: false,
         
     | 
| 194 | 
         
            +
                    })[0]
         
     | 
| 195 | 
         
            +
                    .replace(/<\|end_of_text\|>$/, "");
         
     | 
| 196 | 
         
            +
             
     | 
| 197 | 
         
            +
                  return response;
         
     | 
| 198 | 
         
            +
                },
         
     | 
| 199 | 
         
            +
                [],
         
     | 
| 200 | 
         
            +
              );
         
     | 
| 201 | 
         
            +
             
     | 
| 202 | 
         
            +
              const clearPastKeyValues = useCallback(() => {
         
     | 
| 203 | 
         
            +
                pastKeyValuesRef.current = null;
         
     | 
| 204 | 
         
            +
              }, []);
         
     | 
| 205 | 
         
            +
             
     | 
| 206 | 
         
            +
              const cleanup = useCallback(() => {
         
     | 
| 207 | 
         
            +
                if (abortControllerRef.current) {
         
     | 
| 208 | 
         
            +
                  abortControllerRef.current.abort();
         
     | 
| 209 | 
         
            +
                }
         
     | 
| 210 | 
         
            +
              }, []);
         
     | 
| 211 | 
         
            +
             
     | 
| 212 | 
         
            +
              useEffect(() => {
         
     | 
| 213 | 
         
            +
                return cleanup;
         
     | 
| 214 | 
         
            +
              }, [cleanup]);
         
     | 
| 215 | 
         
            +
             
     | 
| 216 | 
         
            +
              useEffect(() => {
         
     | 
| 217 | 
         
            +
                if (modelId && moduleCache[modelId]) {
         
     | 
| 218 | 
         
            +
                  const existingInstance =
         
     | 
| 219 | 
         
            +
                    instanceRef.current || moduleCache[modelId].instance;
         
     | 
| 220 | 
         
            +
                  if (existingInstance) {
         
     | 
| 221 | 
         
            +
                    instanceRef.current = existingInstance;
         
     | 
| 222 | 
         
            +
                    setState((prev) => ({ ...prev, isReady: true }));
         
     | 
| 223 | 
         
            +
                  }
         
     | 
| 224 | 
         
            +
                }
         
     | 
| 225 | 
         
            +
              }, [modelId]);
         
     | 
| 226 | 
         
            +
             
     | 
| 227 | 
         
            +
              return {
         
     | 
| 228 | 
         
            +
                ...state,
         
     | 
| 229 | 
         
            +
                loadModel,
         
     | 
| 230 | 
         
            +
                generateResponse,
         
     | 
| 231 | 
         
            +
                clearPastKeyValues,
         
     | 
| 232 | 
         
            +
                cleanup,
         
     | 
| 233 | 
         
            +
              };
         
     | 
| 234 | 
         
            +
            };
         
     | 
    	
        src/index.css
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            @import "tailwindcss";
         
     | 
    	
        src/main.tsx
    ADDED
    
    | 
         @@ -0,0 +1,10 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import { StrictMode } from "react";
         
     | 
| 2 | 
         
            +
            import { createRoot } from "react-dom/client";
         
     | 
| 3 | 
         
            +
            import "./index.css";
         
     | 
| 4 | 
         
            +
            import App from "./App.tsx";
         
     | 
| 5 | 
         
            +
             
     | 
| 6 | 
         
            +
            createRoot(document.getElementById("root")!).render(
         
     | 
| 7 | 
         
            +
              <StrictMode>
         
     | 
| 8 | 
         
            +
                <App />
         
     | 
| 9 | 
         
            +
              </StrictMode>,
         
     | 
| 10 | 
         
            +
            );
         
     | 
    	
        src/tools/get_location.js
    ADDED
    
    | 
         @@ -0,0 +1,84 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /**
         
     | 
| 2 | 
         
            +
             * Get the user's current location using the browser's geolocation API.
         
     | 
| 3 | 
         
            +
             * @returns {Promise<{ latitude: number, longitude: number }>} The current position { latitude, longitude }.
         
     | 
| 4 | 
         
            +
             */
         
     | 
| 5 | 
         
            +
            export async function get_location() {
         
     | 
| 6 | 
         
            +
              return new Promise((resolve, reject) => {
         
     | 
| 7 | 
         
            +
                if (!navigator.geolocation) {
         
     | 
| 8 | 
         
            +
                  reject("Geolocation not supported.");
         
     | 
| 9 | 
         
            +
                  return;
         
     | 
| 10 | 
         
            +
                }
         
     | 
| 11 | 
         
            +
                navigator.geolocation.getCurrentPosition(
         
     | 
| 12 | 
         
            +
                  (pos) =>
         
     | 
| 13 | 
         
            +
                    resolve({
         
     | 
| 14 | 
         
            +
                      latitude: pos.coords.latitude,
         
     | 
| 15 | 
         
            +
                      longitude: pos.coords.longitude,
         
     | 
| 16 | 
         
            +
                    }),
         
     | 
| 17 | 
         
            +
                  (err) => reject(err.message || "Geolocation error"),
         
     | 
| 18 | 
         
            +
                );
         
     | 
| 19 | 
         
            +
              });
         
     | 
| 20 | 
         
            +
            }
         
     | 
| 21 | 
         
            +
             
     | 
| 22 | 
         
            +
            export default (input, output) =>
         
     | 
| 23 | 
         
            +
              React.createElement(
         
     | 
| 24 | 
         
            +
                "div",
         
     | 
| 25 | 
         
            +
                { className: "bg-green-50 border border-green-200 rounded-lg p-4" },
         
     | 
| 26 | 
         
            +
                React.createElement(
         
     | 
| 27 | 
         
            +
                  "div",
         
     | 
| 28 | 
         
            +
                  { className: "flex items-center mb-2" },
         
     | 
| 29 | 
         
            +
                  React.createElement(
         
     | 
| 30 | 
         
            +
                    "div",
         
     | 
| 31 | 
         
            +
                    {
         
     | 
| 32 | 
         
            +
                      className:
         
     | 
| 33 | 
         
            +
                        "w-8 h-8 bg-green-100 rounded-full flex items-center justify-center mr-3",
         
     | 
| 34 | 
         
            +
                    },
         
     | 
| 35 | 
         
            +
                    "📍",
         
     | 
| 36 | 
         
            +
                  ),
         
     | 
| 37 | 
         
            +
                  React.createElement(
         
     | 
| 38 | 
         
            +
                    "h3",
         
     | 
| 39 | 
         
            +
                    { className: "text-green-900 font-semibold" },
         
     | 
| 40 | 
         
            +
                    "Location",
         
     | 
| 41 | 
         
            +
                  ),
         
     | 
| 42 | 
         
            +
                ),
         
     | 
| 43 | 
         
            +
                output?.latitude && output?.longitude
         
     | 
| 44 | 
         
            +
                  ? React.createElement(
         
     | 
| 45 | 
         
            +
                      "div",
         
     | 
| 46 | 
         
            +
                      { className: "space-y-1 text-sm" },
         
     | 
| 47 | 
         
            +
                      React.createElement(
         
     | 
| 48 | 
         
            +
                        "p",
         
     | 
| 49 | 
         
            +
                        { className: "text-green-700" },
         
     | 
| 50 | 
         
            +
                        React.createElement(
         
     | 
| 51 | 
         
            +
                          "span",
         
     | 
| 52 | 
         
            +
                          { className: "font-medium" },
         
     | 
| 53 | 
         
            +
                          "Latitude: ",
         
     | 
| 54 | 
         
            +
                        ),
         
     | 
| 55 | 
         
            +
                        output.latitude.toFixed(6),
         
     | 
| 56 | 
         
            +
                      ),
         
     | 
| 57 | 
         
            +
                      React.createElement(
         
     | 
| 58 | 
         
            +
                        "p",
         
     | 
| 59 | 
         
            +
                        { className: "text-green-700" },
         
     | 
| 60 | 
         
            +
                        React.createElement(
         
     | 
| 61 | 
         
            +
                          "span",
         
     | 
| 62 | 
         
            +
                          { className: "font-medium" },
         
     | 
| 63 | 
         
            +
                          "Longitude: ",
         
     | 
| 64 | 
         
            +
                        ),
         
     | 
| 65 | 
         
            +
                        output.longitude.toFixed(6),
         
     | 
| 66 | 
         
            +
                      ),
         
     | 
| 67 | 
         
            +
                      React.createElement(
         
     | 
| 68 | 
         
            +
                        "a",
         
     | 
| 69 | 
         
            +
                        {
         
     | 
| 70 | 
         
            +
                          href: `https://maps.google.com?q=${output.latitude},${output.longitude}`,
         
     | 
| 71 | 
         
            +
                          target: "_blank",
         
     | 
| 72 | 
         
            +
                          rel: "noopener noreferrer",
         
     | 
| 73 | 
         
            +
                          className:
         
     | 
| 74 | 
         
            +
                            "inline-block mt-2 text-green-600 hover:text-green-800 underline text-xs",
         
     | 
| 75 | 
         
            +
                        },
         
     | 
| 76 | 
         
            +
                        "View on Google Maps",
         
     | 
| 77 | 
         
            +
                      ),
         
     | 
| 78 | 
         
            +
                    )
         
     | 
| 79 | 
         
            +
                  : React.createElement(
         
     | 
| 80 | 
         
            +
                      "p",
         
     | 
| 81 | 
         
            +
                      { className: "text-green-700 text-sm" },
         
     | 
| 82 | 
         
            +
                      JSON.stringify(output),
         
     | 
| 83 | 
         
            +
                    ),
         
     | 
| 84 | 
         
            +
              );
         
     | 
    	
        src/tools/get_time.js
    ADDED
    
    | 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /**
         
     | 
| 2 | 
         
            +
             * Get the current date and time.
         
     | 
| 3 | 
         
            +
             * @returns {{ iso: string, local: string }} The current date and time as ISO and local time strings.
         
     | 
| 4 | 
         
            +
             */
         
     | 
| 5 | 
         
            +
            export function get_time() {
         
     | 
| 6 | 
         
            +
              const now = new Date();
         
     | 
| 7 | 
         
            +
              return {
         
     | 
| 8 | 
         
            +
                iso: now.toISOString(),
         
     | 
| 9 | 
         
            +
                local: now.toLocaleString(undefined, {
         
     | 
| 10 | 
         
            +
                  dateStyle: "full",
         
     | 
| 11 | 
         
            +
                  timeStyle: "long",
         
     | 
| 12 | 
         
            +
                }),
         
     | 
| 13 | 
         
            +
              };
         
     | 
| 14 | 
         
            +
            }
         
     | 
| 15 | 
         
            +
             
     | 
| 16 | 
         
            +
            export default (input, output) =>
         
     | 
| 17 | 
         
            +
              React.createElement(
         
     | 
| 18 | 
         
            +
                "div",
         
     | 
| 19 | 
         
            +
                { className: "bg-amber-50 border border-amber-200 rounded-lg p-4" },
         
     | 
| 20 | 
         
            +
                React.createElement(
         
     | 
| 21 | 
         
            +
                  "div",
         
     | 
| 22 | 
         
            +
                  { className: "flex items-center mb-2" },
         
     | 
| 23 | 
         
            +
                  React.createElement(
         
     | 
| 24 | 
         
            +
                    "div",
         
     | 
| 25 | 
         
            +
                    {
         
     | 
| 26 | 
         
            +
                      className:
         
     | 
| 27 | 
         
            +
                        "w-8 h-8 bg-amber-100 rounded-full flex items-center justify-center mr-3",
         
     | 
| 28 | 
         
            +
                    },
         
     | 
| 29 | 
         
            +
                    "🕐",
         
     | 
| 30 | 
         
            +
                  ),
         
     | 
| 31 | 
         
            +
                  React.createElement(
         
     | 
| 32 | 
         
            +
                    "h3",
         
     | 
| 33 | 
         
            +
                    { className: "text-amber-900 font-semibold" },
         
     | 
| 34 | 
         
            +
                    "Current Time",
         
     | 
| 35 | 
         
            +
                  ),
         
     | 
| 36 | 
         
            +
                ),
         
     | 
| 37 | 
         
            +
                React.createElement(
         
     | 
| 38 | 
         
            +
                  "div",
         
     | 
| 39 | 
         
            +
                  { className: "text-sm space-y-1" },
         
     | 
| 40 | 
         
            +
                  React.createElement(
         
     | 
| 41 | 
         
            +
                    "p",
         
     | 
| 42 | 
         
            +
                    { className: "text-amber-700 font-mono" },
         
     | 
| 43 | 
         
            +
                    output.local,
         
     | 
| 44 | 
         
            +
                  ),
         
     | 
| 45 | 
         
            +
                  React.createElement(
         
     | 
| 46 | 
         
            +
                    "p",
         
     | 
| 47 | 
         
            +
                    { className: "text-amber-600 text-xs" },
         
     | 
| 48 | 
         
            +
                    new Date(output.iso).toLocaleString(),
         
     | 
| 49 | 
         
            +
                  ),
         
     | 
| 50 | 
         
            +
                ),
         
     | 
| 51 | 
         
            +
              );
         
     | 
    	
        src/tools/index.ts
    ADDED
    
    | 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import SPEAK_TOOL from "./speak.js?raw";
         
     | 
| 2 | 
         
            +
            import GET_LOCATION_TOOL from "./get_location.js?raw";
         
     | 
| 3 | 
         
            +
            import SLEEP_TOOL from "./sleep.js?raw";
         
     | 
| 4 | 
         
            +
            import GET_TIME_TOOL from "./get_time.js?raw";
         
     | 
| 5 | 
         
            +
            import RANDOM_NUMBER_TOOL from "./random_number.js?raw";
         
     | 
| 6 | 
         
            +
            import MATH_EVAL_TOOL from "./math_eval.js?raw";
         
     | 
| 7 | 
         
            +
            import TEMPLATE_TOOL from "./template.js?raw";
         
     | 
| 8 | 
         
            +
            import OPEN_WEBPAGE_TOOL from "./open_webpage.js?raw";
         
     | 
| 9 | 
         
            +
             
     | 
| 10 | 
         
            +
            export const DEFAULT_TOOLS = {
         
     | 
| 11 | 
         
            +
              speak: SPEAK_TOOL,
         
     | 
| 12 | 
         
            +
              get_location: GET_LOCATION_TOOL,
         
     | 
| 13 | 
         
            +
              sleep: SLEEP_TOOL,
         
     | 
| 14 | 
         
            +
              get_time: GET_TIME_TOOL,
         
     | 
| 15 | 
         
            +
              random_number: RANDOM_NUMBER_TOOL,
         
     | 
| 16 | 
         
            +
              math_eval: MATH_EVAL_TOOL,
         
     | 
| 17 | 
         
            +
              open_webpage: OPEN_WEBPAGE_TOOL,
         
     | 
| 18 | 
         
            +
            };
         
     | 
| 19 | 
         
            +
            export const TEMPLATE = TEMPLATE_TOOL;
         
     | 
    	
        src/tools/math_eval.js
    ADDED
    
    | 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /**
         
     | 
| 2 | 
         
            +
             * Evaluate a math expression.
         
     | 
| 3 | 
         
            +
             * @param {string} expression - The math expression (e.g., "2 + 2 * (3 - 1)").
         
     | 
| 4 | 
         
            +
             * @returns {number} The result of the expression.
         
     | 
| 5 | 
         
            +
             */
         
     | 
| 6 | 
         
            +
            export function math_eval(expression) {
         
     | 
| 7 | 
         
            +
              // Only allow numbers, spaces, and math symbols: + - * / % ( ) .
         
     | 
| 8 | 
         
            +
              if (!/^[\d\s+\-*/%.()]+$/.test(expression)) {
         
     | 
| 9 | 
         
            +
                throw new Error("Invalid characters in expression.");
         
     | 
| 10 | 
         
            +
              }
         
     | 
| 11 | 
         
            +
              return Function('"use strict";return (' + expression + ")")();
         
     | 
| 12 | 
         
            +
            }
         
     | 
| 13 | 
         
            +
             
     | 
| 14 | 
         
            +
            export default (input, output) =>
         
     | 
| 15 | 
         
            +
              React.createElement(
         
     | 
| 16 | 
         
            +
                "div",
         
     | 
| 17 | 
         
            +
                { className: "bg-emerald-50 border border-emerald-200 rounded-lg p-4" },
         
     | 
| 18 | 
         
            +
                React.createElement(
         
     | 
| 19 | 
         
            +
                  "div",
         
     | 
| 20 | 
         
            +
                  { className: "flex items-center mb-2" },
         
     | 
| 21 | 
         
            +
                  React.createElement(
         
     | 
| 22 | 
         
            +
                    "div",
         
     | 
| 23 | 
         
            +
                    {
         
     | 
| 24 | 
         
            +
                      className:
         
     | 
| 25 | 
         
            +
                        "w-8 h-8 bg-emerald-100 rounded-full flex items-center justify-center mr-3",
         
     | 
| 26 | 
         
            +
                    },
         
     | 
| 27 | 
         
            +
                    "🧮",
         
     | 
| 28 | 
         
            +
                  ),
         
     | 
| 29 | 
         
            +
                  React.createElement(
         
     | 
| 30 | 
         
            +
                    "h3",
         
     | 
| 31 | 
         
            +
                    { className: "text-emerald-900 font-semibold" },
         
     | 
| 32 | 
         
            +
                    "Math Evaluation",
         
     | 
| 33 | 
         
            +
                  ),
         
     | 
| 34 | 
         
            +
                ),
         
     | 
| 35 | 
         
            +
                React.createElement(
         
     | 
| 36 | 
         
            +
                  "div",
         
     | 
| 37 | 
         
            +
                  { className: "text-center" },
         
     | 
| 38 | 
         
            +
                  React.createElement(
         
     | 
| 39 | 
         
            +
                    "div",
         
     | 
| 40 | 
         
            +
                    { className: "text-lg font-mono text-emerald-700 mb-1" },
         
     | 
| 41 | 
         
            +
                    input.expression || "Unknown expression",
         
     | 
| 42 | 
         
            +
                  ),
         
     | 
| 43 | 
         
            +
                  React.createElement(
         
     | 
| 44 | 
         
            +
                    "div",
         
     | 
| 45 | 
         
            +
                    { className: "text-2xl font-bold text-emerald-600 mb-1" },
         
     | 
| 46 | 
         
            +
                    `= ${output}`,
         
     | 
| 47 | 
         
            +
                  ),
         
     | 
| 48 | 
         
            +
                  React.createElement(
         
     | 
| 49 | 
         
            +
                    "p",
         
     | 
| 50 | 
         
            +
                    { className: "text-emerald-500 text-xs" },
         
     | 
| 51 | 
         
            +
                    "Calculation result",
         
     | 
| 52 | 
         
            +
                  ),
         
     | 
| 53 | 
         
            +
                ),
         
     | 
| 54 | 
         
            +
              );
         
     | 
    	
        src/tools/open_webpage.js
    ADDED
    
    | 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /**
         
     | 
| 2 | 
         
            +
             * Open a webpage
         
     | 
| 3 | 
         
            +
             * @param {string} src - The URL of the webpage.
         
     | 
| 4 | 
         
            +
             * @returns {string} The validated URL.
         
     | 
| 5 | 
         
            +
             */
         
     | 
| 6 | 
         
            +
            export function open_webpage(src) {
         
     | 
| 7 | 
         
            +
              try {
         
     | 
| 8 | 
         
            +
                const urlObj = new URL(src);
         
     | 
| 9 | 
         
            +
                if (!["http:", "https:"].includes(urlObj.protocol)) {
         
     | 
| 10 | 
         
            +
                  throw new Error("Only HTTP and HTTPS URLs are allowed.");
         
     | 
| 11 | 
         
            +
                }
         
     | 
| 12 | 
         
            +
                return urlObj.href;
         
     | 
| 13 | 
         
            +
              } catch (error) {
         
     | 
| 14 | 
         
            +
                throw new Error("Invalid URL provided.");
         
     | 
| 15 | 
         
            +
              }
         
     | 
| 16 | 
         
            +
            }
         
     | 
| 17 | 
         
            +
             
     | 
| 18 | 
         
            +
            export default (input, output) => {
         
     | 
| 19 | 
         
            +
              return React.createElement(
         
     | 
| 20 | 
         
            +
                "div",
         
     | 
| 21 | 
         
            +
                { className: "bg-blue-50 border border-blue-200 rounded-lg p-4" },
         
     | 
| 22 | 
         
            +
                React.createElement(
         
     | 
| 23 | 
         
            +
                  "div",
         
     | 
| 24 | 
         
            +
                  { className: "flex items-center mb-2" },
         
     | 
| 25 | 
         
            +
                  React.createElement(
         
     | 
| 26 | 
         
            +
                    "div",
         
     | 
| 27 | 
         
            +
                    {
         
     | 
| 28 | 
         
            +
                      className:
         
     | 
| 29 | 
         
            +
                        "w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3",
         
     | 
| 30 | 
         
            +
                    },
         
     | 
| 31 | 
         
            +
                    "🌐",
         
     | 
| 32 | 
         
            +
                  ),
         
     | 
| 33 | 
         
            +
                  React.createElement(
         
     | 
| 34 | 
         
            +
                    "h3",
         
     | 
| 35 | 
         
            +
                    { className: "text-blue-900 font-semibold" },
         
     | 
| 36 | 
         
            +
                    "Web Page",
         
     | 
| 37 | 
         
            +
                  ),
         
     | 
| 38 | 
         
            +
                ),
         
     | 
| 39 | 
         
            +
                React.createElement("iframe", {
         
     | 
| 40 | 
         
            +
                  src: output,
         
     | 
| 41 | 
         
            +
                  className: "w-full border border-blue-300 rounded",
         
     | 
| 42 | 
         
            +
                  width: 480,
         
     | 
| 43 | 
         
            +
                  height: 360,
         
     | 
| 44 | 
         
            +
                  title: "Embedded content",
         
     | 
| 45 | 
         
            +
                  allow: "autoplay",
         
     | 
| 46 | 
         
            +
                  frameBorder: "0",
         
     | 
| 47 | 
         
            +
                }),
         
     | 
| 48 | 
         
            +
              );
         
     | 
| 49 | 
         
            +
            };
         
     | 
    	
        src/tools/random_number.js
    ADDED
    
    | 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /**
         
     | 
| 2 | 
         
            +
             * Generate a random integer between min and max (inclusive).
         
     | 
| 3 | 
         
            +
             * @param {number} min - Minimum value (inclusive).
         
     | 
| 4 | 
         
            +
             * @param {number} max - Maximum value (inclusive).
         
     | 
| 5 | 
         
            +
             * @returns {number} A random integer.
         
     | 
| 6 | 
         
            +
             */
         
     | 
| 7 | 
         
            +
            export function random_number(min, max) {
         
     | 
| 8 | 
         
            +
              min = Math.ceil(Number(min));
         
     | 
| 9 | 
         
            +
              max = Math.floor(Number(max));
         
     | 
| 10 | 
         
            +
              if (isNaN(min) || isNaN(max) || min > max) {
         
     | 
| 11 | 
         
            +
                throw new Error("Invalid min or max value.");
         
     | 
| 12 | 
         
            +
              }
         
     | 
| 13 | 
         
            +
              return Math.floor(Math.random() * (max - min + 1)) + min;
         
     | 
| 14 | 
         
            +
            }
         
     | 
| 15 | 
         
            +
             
     | 
| 16 | 
         
            +
            export default (input, output) =>
         
     | 
| 17 | 
         
            +
              React.createElement(
         
     | 
| 18 | 
         
            +
                "div",
         
     | 
| 19 | 
         
            +
                { className: "bg-indigo-50 border border-indigo-200 rounded-lg p-4" },
         
     | 
| 20 | 
         
            +
                React.createElement(
         
     | 
| 21 | 
         
            +
                  "div",
         
     | 
| 22 | 
         
            +
                  { className: "flex items-center mb-2" },
         
     | 
| 23 | 
         
            +
                  React.createElement(
         
     | 
| 24 | 
         
            +
                    "div",
         
     | 
| 25 | 
         
            +
                    {
         
     | 
| 26 | 
         
            +
                      className:
         
     | 
| 27 | 
         
            +
                        "w-8 h-8 bg-indigo-100 rounded-full flex items-center justify-center mr-3",
         
     | 
| 28 | 
         
            +
                    },
         
     | 
| 29 | 
         
            +
                    "🎲",
         
     | 
| 30 | 
         
            +
                  ),
         
     | 
| 31 | 
         
            +
                  React.createElement(
         
     | 
| 32 | 
         
            +
                    "h3",
         
     | 
| 33 | 
         
            +
                    { className: "text-indigo-900 font-semibold" },
         
     | 
| 34 | 
         
            +
                    "Random Number",
         
     | 
| 35 | 
         
            +
                  ),
         
     | 
| 36 | 
         
            +
                ),
         
     | 
| 37 | 
         
            +
                React.createElement(
         
     | 
| 38 | 
         
            +
                  "div",
         
     | 
| 39 | 
         
            +
                  { className: "text-center" },
         
     | 
| 40 | 
         
            +
                  React.createElement(
         
     | 
| 41 | 
         
            +
                    "div",
         
     | 
| 42 | 
         
            +
                    { className: "text-3xl font-bold text-indigo-600 mb-1" },
         
     | 
| 43 | 
         
            +
                    output,
         
     | 
| 44 | 
         
            +
                  ),
         
     | 
| 45 | 
         
            +
                  React.createElement(
         
     | 
| 46 | 
         
            +
                    "p",
         
     | 
| 47 | 
         
            +
                    { className: "text-indigo-500 text-xs" },
         
     | 
| 48 | 
         
            +
                    `Range: ${input.min || "?"} - ${input.max || "?"}`,
         
     | 
| 49 | 
         
            +
                  ),
         
     | 
| 50 | 
         
            +
                ),
         
     | 
| 51 | 
         
            +
              );
         
     | 
    	
        src/tools/sleep.js
    ADDED
    
    | 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /**
         
     | 
| 2 | 
         
            +
             * Sleep for a given number of seconds.
         
     | 
| 3 | 
         
            +
             * @param {number} seconds - The number of seconds to sleep.
         
     | 
| 4 | 
         
            +
             * @return {void}
         
     | 
| 5 | 
         
            +
             */
         
     | 
| 6 | 
         
            +
            export async function sleep(seconds) {
         
     | 
| 7 | 
         
            +
              return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
         
     | 
| 8 | 
         
            +
            }
         
     | 
| 9 | 
         
            +
             
     | 
| 10 | 
         
            +
            export default (input, output) =>
         
     | 
| 11 | 
         
            +
              React.createElement(
         
     | 
| 12 | 
         
            +
                "div",
         
     | 
| 13 | 
         
            +
                { className: "bg-purple-50 border border-purple-200 rounded-lg p-4" },
         
     | 
| 14 | 
         
            +
                React.createElement(
         
     | 
| 15 | 
         
            +
                  "div",
         
     | 
| 16 | 
         
            +
                  { className: "flex items-center mb-2" },
         
     | 
| 17 | 
         
            +
                  React.createElement(
         
     | 
| 18 | 
         
            +
                    "div",
         
     | 
| 19 | 
         
            +
                    {
         
     | 
| 20 | 
         
            +
                      className:
         
     | 
| 21 | 
         
            +
                        "w-8 h-8 bg-purple-100 rounded-full flex items-center justify-center mr-3",
         
     | 
| 22 | 
         
            +
                    },
         
     | 
| 23 | 
         
            +
                    "😴",
         
     | 
| 24 | 
         
            +
                  ),
         
     | 
| 25 | 
         
            +
                  React.createElement(
         
     | 
| 26 | 
         
            +
                    "h3",
         
     | 
| 27 | 
         
            +
                    { className: "text-purple-900 font-semibold" },
         
     | 
| 28 | 
         
            +
                    "Sleep",
         
     | 
| 29 | 
         
            +
                  ),
         
     | 
| 30 | 
         
            +
                ),
         
     | 
| 31 | 
         
            +
                React.createElement(
         
     | 
| 32 | 
         
            +
                  "div",
         
     | 
| 33 | 
         
            +
                  { className: "text-sm space-y-1" },
         
     | 
| 34 | 
         
            +
                  React.createElement(
         
     | 
| 35 | 
         
            +
                    "p",
         
     | 
| 36 | 
         
            +
                    { className: "text-purple-700 font-medium" },
         
     | 
| 37 | 
         
            +
                    `Slept for ${input.seconds || "unknown"} seconds`,
         
     | 
| 38 | 
         
            +
                  ),
         
     | 
| 39 | 
         
            +
                  React.createElement(
         
     | 
| 40 | 
         
            +
                    "p",
         
     | 
| 41 | 
         
            +
                    { className: "text-purple-600 text-xs" },
         
     | 
| 42 | 
         
            +
                    output,
         
     | 
| 43 | 
         
            +
                  ),
         
     | 
| 44 | 
         
            +
                ),
         
     | 
| 45 | 
         
            +
              );
         
     | 
    	
        src/tools/speak.js
    ADDED
    
    | 
         @@ -0,0 +1,59 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /**
         
     | 
| 2 | 
         
            +
             * Speak text using the browser's speech synthesis API.
         
     | 
| 3 | 
         
            +
             * @param {string} text - The text to speak.
         
     | 
| 4 | 
         
            +
             * @param {string} [voice] - The name of the voice to use (optional).
         
     | 
| 5 | 
         
            +
             * @return {boolean} - Whether the tool was executed successfully.
         
     | 
| 6 | 
         
            +
             */
         
     | 
| 7 | 
         
            +
            export function speak(text, voice = undefined) {
         
     | 
| 8 | 
         
            +
              const utter = new window.SpeechSynthesisUtterance(text);
         
     | 
| 9 | 
         
            +
              if (voice) {
         
     | 
| 10 | 
         
            +
                const voices = window.speechSynthesis.getVoices();
         
     | 
| 11 | 
         
            +
                const match = voices.find((v) => v.name === voice);
         
     | 
| 12 | 
         
            +
                if (match) utter.voice = match;
         
     | 
| 13 | 
         
            +
              }
         
     | 
| 14 | 
         
            +
              window.speechSynthesis.speak(utter);
         
     | 
| 15 | 
         
            +
              return true;
         
     | 
| 16 | 
         
            +
            }
         
     | 
| 17 | 
         
            +
             
     | 
| 18 | 
         
            +
            export default (input, output) =>
         
     | 
| 19 | 
         
            +
              React.createElement(
         
     | 
| 20 | 
         
            +
                "div",
         
     | 
| 21 | 
         
            +
                { className: "bg-blue-50 border border-blue-200 rounded-lg p-4" },
         
     | 
| 22 | 
         
            +
                React.createElement(
         
     | 
| 23 | 
         
            +
                  "div",
         
     | 
| 24 | 
         
            +
                  { className: "flex items-center mb-2" },
         
     | 
| 25 | 
         
            +
                  React.createElement(
         
     | 
| 26 | 
         
            +
                    "div",
         
     | 
| 27 | 
         
            +
                    {
         
     | 
| 28 | 
         
            +
                      className:
         
     | 
| 29 | 
         
            +
                        "w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3",
         
     | 
| 30 | 
         
            +
                    },
         
     | 
| 31 | 
         
            +
                    "🔊",
         
     | 
| 32 | 
         
            +
                  ),
         
     | 
| 33 | 
         
            +
                  React.createElement(
         
     | 
| 34 | 
         
            +
                    "h3",
         
     | 
| 35 | 
         
            +
                    { className: "text-blue-900 font-semibold" },
         
     | 
| 36 | 
         
            +
                    "Speech Synthesis",
         
     | 
| 37 | 
         
            +
                  ),
         
     | 
| 38 | 
         
            +
                ),
         
     | 
| 39 | 
         
            +
                React.createElement(
         
     | 
| 40 | 
         
            +
                  "div",
         
     | 
| 41 | 
         
            +
                  { className: "text-sm space-y-1" },
         
     | 
| 42 | 
         
            +
                  React.createElement(
         
     | 
| 43 | 
         
            +
                    "p",
         
     | 
| 44 | 
         
            +
                    { className: "text-blue-700 font-medium" },
         
     | 
| 45 | 
         
            +
                    `Speaking: "${input.text || "Unknown text"}"`,
         
     | 
| 46 | 
         
            +
                  ),
         
     | 
| 47 | 
         
            +
                  input.voice &&
         
     | 
| 48 | 
         
            +
                    React.createElement(
         
     | 
| 49 | 
         
            +
                      "p",
         
     | 
| 50 | 
         
            +
                      { className: "text-blue-600 text-xs" },
         
     | 
| 51 | 
         
            +
                      `Voice: ${input.voice}`,
         
     | 
| 52 | 
         
            +
                    ),
         
     | 
| 53 | 
         
            +
                  React.createElement(
         
     | 
| 54 | 
         
            +
                    "p",
         
     | 
| 55 | 
         
            +
                    { className: "text-blue-600 text-xs" },
         
     | 
| 56 | 
         
            +
                    typeof output === "string" ? output : "Speech completed successfully",
         
     | 
| 57 | 
         
            +
                  ),
         
     | 
| 58 | 
         
            +
                ),
         
     | 
| 59 | 
         
            +
              );
         
     | 
    	
        src/tools/template.js
    ADDED
    
    | 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /**
         
     | 
| 2 | 
         
            +
             * Description of the tool.
         
     | 
| 3 | 
         
            +
             * @param {any} parameter1 - Description of the first parameter.
         
     | 
| 4 | 
         
            +
             * @param {any} parameter2 - Description of the second parameter.
         
     | 
| 5 | 
         
            +
             * @returns {any} Description of the return value.
         
     | 
| 6 | 
         
            +
             */
         
     | 
| 7 | 
         
            +
            export function new_tool(parameter1, parameter2) {
         
     | 
| 8 | 
         
            +
              // TODO: Implement the tool logic here
         
     | 
| 9 | 
         
            +
              return true; // Placeholder return value
         
     | 
| 10 | 
         
            +
            }
         
     | 
| 11 | 
         
            +
             
     | 
| 12 | 
         
            +
            export default (input, output) =>
         
     | 
| 13 | 
         
            +
              React.createElement(
         
     | 
| 14 | 
         
            +
                "div",
         
     | 
| 15 | 
         
            +
                { className: "bg-amber-50 border border-amber-200 rounded-lg p-4" },
         
     | 
| 16 | 
         
            +
                React.createElement(
         
     | 
| 17 | 
         
            +
                  "div",
         
     | 
| 18 | 
         
            +
                  { className: "flex items-center mb-2" },
         
     | 
| 19 | 
         
            +
                  React.createElement(
         
     | 
| 20 | 
         
            +
                    "div",
         
     | 
| 21 | 
         
            +
                    {
         
     | 
| 22 | 
         
            +
                      className:
         
     | 
| 23 | 
         
            +
                        "w-8 h-8 bg-amber-100 rounded-full flex items-center justify-center mr-3",
         
     | 
| 24 | 
         
            +
                    },
         
     | 
| 25 | 
         
            +
                    "🛠️",
         
     | 
| 26 | 
         
            +
                  ),
         
     | 
| 27 | 
         
            +
                  React.createElement(
         
     | 
| 28 | 
         
            +
                    "h3",
         
     | 
| 29 | 
         
            +
                    { className: "text-amber-900 font-semibold" },
         
     | 
| 30 | 
         
            +
                    "Tool Name",
         
     | 
| 31 | 
         
            +
                  ),
         
     | 
| 32 | 
         
            +
                ),
         
     | 
| 33 | 
         
            +
                React.createElement(
         
     | 
| 34 | 
         
            +
                  "div",
         
     | 
| 35 | 
         
            +
                  { className: "text-sm space-y-1" },
         
     | 
| 36 | 
         
            +
                  React.createElement(
         
     | 
| 37 | 
         
            +
                    "p",
         
     | 
| 38 | 
         
            +
                    { className: "text-amber-700 font-medium" },
         
     | 
| 39 | 
         
            +
                    `Input: ${JSON.stringify(input)}`,
         
     | 
| 40 | 
         
            +
                  ),
         
     | 
| 41 | 
         
            +
                  React.createElement(
         
     | 
| 42 | 
         
            +
                    "p",
         
     | 
| 43 | 
         
            +
                    { className: "text-amber-600 text-xs" },
         
     | 
| 44 | 
         
            +
                    `Output: ${output}`,
         
     | 
| 45 | 
         
            +
                  ),
         
     | 
| 46 | 
         
            +
                ),
         
     | 
| 47 | 
         
            +
              );
         
     | 
    	
        src/utils.ts
    ADDED
    
    | 
         @@ -0,0 +1,249 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            interface Schema {
         
     | 
| 2 | 
         
            +
              name: string;
         
     | 
| 3 | 
         
            +
              description: string;
         
     | 
| 4 | 
         
            +
              parameters: {
         
     | 
| 5 | 
         
            +
                type: string;
         
     | 
| 6 | 
         
            +
                properties: Record<
         
     | 
| 7 | 
         
            +
                  string,
         
     | 
| 8 | 
         
            +
                  {
         
     | 
| 9 | 
         
            +
                    type: string;
         
     | 
| 10 | 
         
            +
                    description: string;
         
     | 
| 11 | 
         
            +
                    default?: any;
         
     | 
| 12 | 
         
            +
                  }
         
     | 
| 13 | 
         
            +
                >;
         
     | 
| 14 | 
         
            +
                required: string[];
         
     | 
| 15 | 
         
            +
              };
         
     | 
| 16 | 
         
            +
            }
         
     | 
| 17 | 
         
            +
             
     | 
| 18 | 
         
            +
            interface JSDocParam {
         
     | 
| 19 | 
         
            +
              type: string;
         
     | 
| 20 | 
         
            +
              description: string;
         
     | 
| 21 | 
         
            +
              isOptional: boolean;
         
     | 
| 22 | 
         
            +
              defaultValue?: string;
         
     | 
| 23 | 
         
            +
            }
         
     | 
| 24 | 
         
            +
             
     | 
| 25 | 
         
            +
            export const extractFunctionAndRenderer = (
         
     | 
| 26 | 
         
            +
              code: string,
         
     | 
| 27 | 
         
            +
            ): { functionCode: string; rendererCode?: string } => {
         
     | 
| 28 | 
         
            +
              if (typeof code !== "string") {
         
     | 
| 29 | 
         
            +
                return { functionCode: code };
         
     | 
| 30 | 
         
            +
              }
         
     | 
| 31 | 
         
            +
             
     | 
| 32 | 
         
            +
              const exportMatch = code.match(/export\s+default\s+/);
         
     | 
| 33 | 
         
            +
              if (!exportMatch) {
         
     | 
| 34 | 
         
            +
                return { functionCode: code };
         
     | 
| 35 | 
         
            +
              }
         
     | 
| 36 | 
         
            +
             
     | 
| 37 | 
         
            +
              const exportIndex = exportMatch.index!;
         
     | 
| 38 | 
         
            +
              const functionCode = code.substring(0, exportIndex).trim();
         
     | 
| 39 | 
         
            +
              const rendererCode = code.substring(exportIndex).trim();
         
     | 
| 40 | 
         
            +
             
     | 
| 41 | 
         
            +
              return { functionCode, rendererCode };
         
     | 
| 42 | 
         
            +
            };
         
     | 
| 43 | 
         
            +
             
     | 
| 44 | 
         
            +
            /**
         
     | 
| 45 | 
         
            +
             * Helper function to extract JSDoc parameters from JSDoc comments.
         
     | 
| 46 | 
         
            +
             */
         
     | 
| 47 | 
         
            +
            const extractJSDocParams = (
         
     | 
| 48 | 
         
            +
              jsdoc: string,
         
     | 
| 49 | 
         
            +
            ): Record<string, JSDocParam & { jsdocDefault?: string }> => {
         
     | 
| 50 | 
         
            +
              const jsdocParams: Record<string, JSDocParam & { jsdocDefault?: string }> =
         
     | 
| 51 | 
         
            +
                {};
         
     | 
| 52 | 
         
            +
              const lines = jsdoc
         
     | 
| 53 | 
         
            +
                .split("\n")
         
     | 
| 54 | 
         
            +
                .map((line) => line.trim().replace(/^\*\s?/, ""));
         
     | 
| 55 | 
         
            +
              const paramRegex =
         
     | 
| 56 | 
         
            +
                /@param\s+\{([^}]+)\}\s+(\[?[a-zA-Z0-9_]+(?:=[^\]]+)?\]?|\S+)\s*-?\s*(.*)?/;
         
     | 
| 57 | 
         
            +
             
     | 
| 58 | 
         
            +
              for (const line of lines) {
         
     | 
| 59 | 
         
            +
                const paramMatch = line.match(paramRegex);
         
     | 
| 60 | 
         
            +
                if (paramMatch) {
         
     | 
| 61 | 
         
            +
                  let [, type, namePart, description] = paramMatch;
         
     | 
| 62 | 
         
            +
                  description = description || "";
         
     | 
| 63 | 
         
            +
                  let isOptional = false;
         
     | 
| 64 | 
         
            +
                  let name = namePart;
         
     | 
| 65 | 
         
            +
                  let jsdocDefault: string | undefined = undefined;
         
     | 
| 66 | 
         
            +
             
     | 
| 67 | 
         
            +
                  if (name.startsWith("[") && name.endsWith("]")) {
         
     | 
| 68 | 
         
            +
                    isOptional = true;
         
     | 
| 69 | 
         
            +
                    name = name.slice(1, -1);
         
     | 
| 70 | 
         
            +
                  }
         
     | 
| 71 | 
         
            +
                  if (name.includes("=")) {
         
     | 
| 72 | 
         
            +
                    const [n, def] = name.split("=");
         
     | 
| 73 | 
         
            +
                    name = n.trim();
         
     | 
| 74 | 
         
            +
                    jsdocDefault = def.trim().replace(/['"]/g, "");
         
     | 
| 75 | 
         
            +
                  }
         
     | 
| 76 | 
         
            +
             
     | 
| 77 | 
         
            +
                  jsdocParams[name] = {
         
     | 
| 78 | 
         
            +
                    type: type.toLowerCase(),
         
     | 
| 79 | 
         
            +
                    description: description.trim(),
         
     | 
| 80 | 
         
            +
                    isOptional,
         
     | 
| 81 | 
         
            +
                    defaultValue: undefined,
         
     | 
| 82 | 
         
            +
                    jsdocDefault,
         
     | 
| 83 | 
         
            +
                  };
         
     | 
| 84 | 
         
            +
                }
         
     | 
| 85 | 
         
            +
              }
         
     | 
| 86 | 
         
            +
              return jsdocParams;
         
     | 
| 87 | 
         
            +
            };
         
     | 
| 88 | 
         
            +
             
     | 
| 89 | 
         
            +
            /**
         
     | 
| 90 | 
         
            +
             * Helper function to extract function signature information.
         
     | 
| 91 | 
         
            +
             */
         
     | 
| 92 | 
         
            +
            const extractFunctionSignature = (
         
     | 
| 93 | 
         
            +
              functionCode: string,
         
     | 
| 94 | 
         
            +
            ): {
         
     | 
| 95 | 
         
            +
              name: string;
         
     | 
| 96 | 
         
            +
              params: { name: string; defaultValue?: string }[];
         
     | 
| 97 | 
         
            +
            } | null => {
         
     | 
| 98 | 
         
            +
              const functionSignatureMatch = functionCode.match(
         
     | 
| 99 | 
         
            +
                /function\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)/,
         
     | 
| 100 | 
         
            +
              );
         
     | 
| 101 | 
         
            +
              if (!functionSignatureMatch) {
         
     | 
| 102 | 
         
            +
                return null;
         
     | 
| 103 | 
         
            +
              }
         
     | 
| 104 | 
         
            +
             
     | 
| 105 | 
         
            +
              const functionName = functionSignatureMatch[1];
         
     | 
| 106 | 
         
            +
              const params = functionSignatureMatch[2]
         
     | 
| 107 | 
         
            +
                .split(",")
         
     | 
| 108 | 
         
            +
                .map((p) => p.trim())
         
     | 
| 109 | 
         
            +
                .filter(Boolean)
         
     | 
| 110 | 
         
            +
                .map((p) => {
         
     | 
| 111 | 
         
            +
                  const [name, defaultValue] = p.split("=").map((s) => s.trim());
         
     | 
| 112 | 
         
            +
                  return { name, defaultValue };
         
     | 
| 113 | 
         
            +
                });
         
     | 
| 114 | 
         
            +
             
     | 
| 115 | 
         
            +
              return { name: functionName, params };
         
     | 
| 116 | 
         
            +
            };
         
     | 
| 117 | 
         
            +
             
     | 
| 118 | 
         
            +
            export const generateSchemaFromCode = (code: string): Schema => {
         
     | 
| 119 | 
         
            +
              const { functionCode } = extractFunctionAndRenderer(code);
         
     | 
| 120 | 
         
            +
             
     | 
| 121 | 
         
            +
              if (typeof functionCode !== "string") {
         
     | 
| 122 | 
         
            +
                return {
         
     | 
| 123 | 
         
            +
                  name: "invalid_code",
         
     | 
| 124 | 
         
            +
                  description: "Code is not a valid string.",
         
     | 
| 125 | 
         
            +
                  parameters: { type: "object", properties: {}, required: [] },
         
     | 
| 126 | 
         
            +
                };
         
     | 
| 127 | 
         
            +
              }
         
     | 
| 128 | 
         
            +
             
     | 
| 129 | 
         
            +
              // 1. Extract function signature, name, and parameter names directly from the code
         
     | 
| 130 | 
         
            +
              const signatureInfo = extractFunctionSignature(functionCode);
         
     | 
| 131 | 
         
            +
              if (!signatureInfo) {
         
     | 
| 132 | 
         
            +
                return {
         
     | 
| 133 | 
         
            +
                  name: "invalid_function",
         
     | 
| 134 | 
         
            +
                  description: "Could not parse function signature.",
         
     | 
| 135 | 
         
            +
                  parameters: { type: "object", properties: {}, required: [] },
         
     | 
| 136 | 
         
            +
                };
         
     | 
| 137 | 
         
            +
              }
         
     | 
| 138 | 
         
            +
             
     | 
| 139 | 
         
            +
              const { name: functionName, params: paramsFromSignature } = signatureInfo;
         
     | 
| 140 | 
         
            +
             
     | 
| 141 | 
         
            +
              const schema: Schema = {
         
     | 
| 142 | 
         
            +
                name: functionName,
         
     | 
| 143 | 
         
            +
                description: "",
         
     | 
| 144 | 
         
            +
                parameters: {
         
     | 
| 145 | 
         
            +
                  type: "object",
         
     | 
| 146 | 
         
            +
                  properties: {},
         
     | 
| 147 | 
         
            +
                  required: [],
         
     | 
| 148 | 
         
            +
                },
         
     | 
| 149 | 
         
            +
              };
         
     | 
| 150 | 
         
            +
             
     | 
| 151 | 
         
            +
              // 2. Parse JSDoc comments to get descriptions and types
         
     | 
| 152 | 
         
            +
              const jsdocMatch = functionCode.match(/\/\*\*([\s\S]*?)\*\//);
         
     | 
| 153 | 
         
            +
              let jsdocParams: Record<string, JSDocParam & { jsdocDefault?: string }> = {};
         
     | 
| 154 | 
         
            +
              if (jsdocMatch) {
         
     | 
| 155 | 
         
            +
                const jsdoc = jsdocMatch[1];
         
     | 
| 156 | 
         
            +
                jsdocParams = extractJSDocParams(jsdoc);
         
     | 
| 157 | 
         
            +
             
     | 
| 158 | 
         
            +
                const descriptionLines = jsdoc
         
     | 
| 159 | 
         
            +
                  .split("\n")
         
     | 
| 160 | 
         
            +
                  .map((line) => line.trim().replace(/^\*\s?/, ""))
         
     | 
| 161 | 
         
            +
                  .filter((line) => !line.startsWith("@") && line);
         
     | 
| 162 | 
         
            +
             
     | 
| 163 | 
         
            +
                schema.description = descriptionLines.join(" ").trim();
         
     | 
| 164 | 
         
            +
              }
         
     | 
| 165 | 
         
            +
             
     | 
| 166 | 
         
            +
              // 3. Combine signature parameters with JSDoc info
         
     | 
| 167 | 
         
            +
              for (const param of paramsFromSignature) {
         
     | 
| 168 | 
         
            +
                const paramName = param.name;
         
     | 
| 169 | 
         
            +
                const jsdocInfo = jsdocParams[paramName];
         
     | 
| 170 | 
         
            +
                schema.parameters.properties[paramName] = {
         
     | 
| 171 | 
         
            +
                  type: jsdocInfo ? jsdocInfo.type : "any",
         
     | 
| 172 | 
         
            +
                  description: jsdocInfo ? jsdocInfo.description : "",
         
     | 
| 173 | 
         
            +
                };
         
     | 
| 174 | 
         
            +
             
     | 
| 175 | 
         
            +
                // Prefer default from signature, then from JSDoc
         
     | 
| 176 | 
         
            +
                if (param.defaultValue !== undefined) {
         
     | 
| 177 | 
         
            +
                  // Try to parse as JSON, fallback to string
         
     | 
| 178 | 
         
            +
                  try {
         
     | 
| 179 | 
         
            +
                    schema.parameters.properties[paramName].default = JSON.parse(
         
     | 
| 180 | 
         
            +
                      param.defaultValue.replace(/'/g, '"'),
         
     | 
| 181 | 
         
            +
                    );
         
     | 
| 182 | 
         
            +
                  } catch {
         
     | 
| 183 | 
         
            +
                    schema.parameters.properties[paramName].default = param.defaultValue;
         
     | 
| 184 | 
         
            +
                  }
         
     | 
| 185 | 
         
            +
                } else if (jsdocInfo && jsdocInfo.jsdocDefault !== undefined) {
         
     | 
| 186 | 
         
            +
                  schema.parameters.properties[paramName].default = jsdocInfo.jsdocDefault;
         
     | 
| 187 | 
         
            +
                }
         
     | 
| 188 | 
         
            +
             
     | 
| 189 | 
         
            +
                // A parameter is required if:
         
     | 
| 190 | 
         
            +
                // - Not optional in JSDoc
         
     | 
| 191 | 
         
            +
                // - No default in signature
         
     | 
| 192 | 
         
            +
                // - No default in JSDoc
         
     | 
| 193 | 
         
            +
                const hasDefault =
         
     | 
| 194 | 
         
            +
                  param.defaultValue !== undefined ||
         
     | 
| 195 | 
         
            +
                  (jsdocInfo && jsdocInfo.jsdocDefault !== undefined);
         
     | 
| 196 | 
         
            +
                if (!jsdocInfo || (!jsdocInfo.isOptional && !hasDefault)) {
         
     | 
| 197 | 
         
            +
                  schema.parameters.required.push(paramName);
         
     | 
| 198 | 
         
            +
                }
         
     | 
| 199 | 
         
            +
              }
         
     | 
| 200 | 
         
            +
             
     | 
| 201 | 
         
            +
              return schema;
         
     | 
| 202 | 
         
            +
            };
         
     | 
| 203 | 
         
            +
             
     | 
| 204 | 
         
            +
            /**
         
     | 
| 205 | 
         
            +
             * Extracts tool call content from a string using the tool call markers.
         
     | 
| 206 | 
         
            +
             */
         
     | 
| 207 | 
         
            +
            export const extractToolCallContent = (content: string): string[] | null => {
         
     | 
| 208 | 
         
            +
              if (typeof content !== "string") return null;
         
     | 
| 209 | 
         
            +
              const matches = [...content.matchAll(/<tool_call>([\s\S]*?)<\/tool_call>/g)];
         
     | 
| 210 | 
         
            +
              return matches.length ? matches.map(([, inner]) => inner.trim()) : null;
         
     | 
| 211 | 
         
            +
            };
         
     | 
| 212 | 
         
            +
             
     | 
| 213 | 
         
            +
            export const getErrorMessage = (error: unknown): string => {
         
     | 
| 214 | 
         
            +
              if (error instanceof Error) {
         
     | 
| 215 | 
         
            +
                return error.message;
         
     | 
| 216 | 
         
            +
              }
         
     | 
| 217 | 
         
            +
              if (typeof error === "string") {
         
     | 
| 218 | 
         
            +
                return error;
         
     | 
| 219 | 
         
            +
              }
         
     | 
| 220 | 
         
            +
              if (error && typeof error === "object") {
         
     | 
| 221 | 
         
            +
                return JSON.stringify(error);
         
     | 
| 222 | 
         
            +
              }
         
     | 
| 223 | 
         
            +
              return String(error);
         
     | 
| 224 | 
         
            +
            };
         
     | 
| 225 | 
         
            +
             
     | 
| 226 | 
         
            +
            /**
         
     | 
| 227 | 
         
            +
             * Adapted from https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser.
         
     | 
| 228 | 
         
            +
             */
         
     | 
| 229 | 
         
            +
            export function isMobileOrTablet() {
         
     | 
| 230 | 
         
            +
              let check = false;
         
     | 
| 231 | 
         
            +
              (function (a: string) {
         
     | 
| 232 | 
         
            +
                if (
         
     | 
| 233 | 
         
            +
                  /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(
         
     | 
| 234 | 
         
            +
                    a,
         
     | 
| 235 | 
         
            +
                  ) ||
         
     | 
| 236 | 
         
            +
                  /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
         
     | 
| 237 | 
         
            +
                    a.slice(0, 4),
         
     | 
| 238 | 
         
            +
                  )
         
     | 
| 239 | 
         
            +
                )
         
     | 
| 240 | 
         
            +
                  check = true;
         
     | 
| 241 | 
         
            +
              })(
         
     | 
| 242 | 
         
            +
                navigator.userAgent ||
         
     | 
| 243 | 
         
            +
                  navigator.vendor ||
         
     | 
| 244 | 
         
            +
                  ("opera" in window && typeof window.opera === "string"
         
     | 
| 245 | 
         
            +
                    ? window.opera
         
     | 
| 246 | 
         
            +
                    : ""),
         
     | 
| 247 | 
         
            +
              );
         
     | 
| 248 | 
         
            +
              return check;
         
     | 
| 249 | 
         
            +
            }
         
     | 
    	
        src/vite-env.d.ts
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            /// <reference types="vite/client" />
         
     | 
    	
        style.css
    DELETED
    
    | 
         @@ -1,76 +0,0 @@ 
     | 
|
| 1 | 
         
            -
            * {
         
     | 
| 2 | 
         
            -
                box-sizing: border-box;
         
     | 
| 3 | 
         
            -
                padding: 0;
         
     | 
| 4 | 
         
            -
                margin: 0;
         
     | 
| 5 | 
         
            -
                font-family: sans-serif;
         
     | 
| 6 | 
         
            -
            }
         
     | 
| 7 | 
         
            -
             
     | 
| 8 | 
         
            -
            html,
         
     | 
| 9 | 
         
            -
            body {
         
     | 
| 10 | 
         
            -
                height: 100%;
         
     | 
| 11 | 
         
            -
            }
         
     | 
| 12 | 
         
            -
             
     | 
| 13 | 
         
            -
            body {
         
     | 
| 14 | 
         
            -
                padding: 32px;
         
     | 
| 15 | 
         
            -
            }
         
     | 
| 16 | 
         
            -
             
     | 
| 17 | 
         
            -
            body,
         
     | 
| 18 | 
         
            -
            #container {
         
     | 
| 19 | 
         
            -
                display: flex;
         
     | 
| 20 | 
         
            -
                flex-direction: column;
         
     | 
| 21 | 
         
            -
                justify-content: center;
         
     | 
| 22 | 
         
            -
                align-items: center;
         
     | 
| 23 | 
         
            -
            }
         
     | 
| 24 | 
         
            -
             
     | 
| 25 | 
         
            -
            #container {
         
     | 
| 26 | 
         
            -
                position: relative;
         
     | 
| 27 | 
         
            -
                gap: 0.4rem;
         
     | 
| 28 | 
         
            -
             
     | 
| 29 | 
         
            -
                width: 640px;
         
     | 
| 30 | 
         
            -
                height: 640px;
         
     | 
| 31 | 
         
            -
                max-width: 100%;
         
     | 
| 32 | 
         
            -
                max-height: 100%;
         
     | 
| 33 | 
         
            -
             
     | 
| 34 | 
         
            -
                border: 2px dashed #D1D5DB;
         
     | 
| 35 | 
         
            -
                border-radius: 0.75rem;
         
     | 
| 36 | 
         
            -
                overflow: hidden;
         
     | 
| 37 | 
         
            -
                cursor: pointer;
         
     | 
| 38 | 
         
            -
                margin: 1rem;
         
     | 
| 39 | 
         
            -
             
     | 
| 40 | 
         
            -
                background-size: 100% 100%;
         
     | 
| 41 | 
         
            -
                background-position: center;
         
     | 
| 42 | 
         
            -
                background-repeat: no-repeat;
         
     | 
| 43 | 
         
            -
                font-size: 18px;
         
     | 
| 44 | 
         
            -
            }
         
     | 
| 45 | 
         
            -
             
     | 
| 46 | 
         
            -
            #upload {
         
     | 
| 47 | 
         
            -
                display: none;
         
     | 
| 48 | 
         
            -
            }
         
     | 
| 49 | 
         
            -
             
     | 
| 50 | 
         
            -
            svg {
         
     | 
| 51 | 
         
            -
                pointer-events: none;
         
     | 
| 52 | 
         
            -
            }
         
     | 
| 53 | 
         
            -
             
     | 
| 54 | 
         
            -
            #example {
         
     | 
| 55 | 
         
            -
                font-size: 14px;
         
     | 
| 56 | 
         
            -
                text-decoration: underline;
         
     | 
| 57 | 
         
            -
                cursor: pointer;
         
     | 
| 58 | 
         
            -
            }
         
     | 
| 59 | 
         
            -
             
     | 
| 60 | 
         
            -
            #example:hover {
         
     | 
| 61 | 
         
            -
                color: #2563EB;
         
     | 
| 62 | 
         
            -
            }
         
     | 
| 63 | 
         
            -
             
     | 
| 64 | 
         
            -
            .bounding-box {
         
     | 
| 65 | 
         
            -
                position: absolute;
         
     | 
| 66 | 
         
            -
                box-sizing: border-box;
         
     | 
| 67 | 
         
            -
                border: solid 2px;
         
     | 
| 68 | 
         
            -
            }
         
     | 
| 69 | 
         
            -
             
     | 
| 70 | 
         
            -
            .bounding-box-label {
         
     | 
| 71 | 
         
            -
                color: white;
         
     | 
| 72 | 
         
            -
                position: absolute;
         
     | 
| 73 | 
         
            -
                font-size: 12px;
         
     | 
| 74 | 
         
            -
                margin: -16px 0 0 -2px;
         
     | 
| 75 | 
         
            -
                padding: 1px;
         
     | 
| 76 | 
         
            -
            }
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
    	
        tsconfig.app.json
    ADDED
    
    | 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            {
         
     | 
| 2 | 
         
            +
              "compilerOptions": {
         
     | 
| 3 | 
         
            +
                "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
         
     | 
| 4 | 
         
            +
                "target": "ES2022",
         
     | 
| 5 | 
         
            +
                "useDefineForClassFields": true,
         
     | 
| 6 | 
         
            +
                "lib": ["ES2022", "DOM", "DOM.Iterable"],
         
     | 
| 7 | 
         
            +
                "module": "ESNext",
         
     | 
| 8 | 
         
            +
                "skipLibCheck": true,
         
     | 
| 9 | 
         
            +
             
     | 
| 10 | 
         
            +
                /* Bundler mode */
         
     | 
| 11 | 
         
            +
                "moduleResolution": "bundler",
         
     | 
| 12 | 
         
            +
                "allowImportingTsExtensions": true,
         
     | 
| 13 | 
         
            +
                "verbatimModuleSyntax": true,
         
     | 
| 14 | 
         
            +
                "moduleDetection": "force",
         
     | 
| 15 | 
         
            +
                "noEmit": true,
         
     | 
| 16 | 
         
            +
                "jsx": "react-jsx",
         
     | 
| 17 | 
         
            +
             
     | 
| 18 | 
         
            +
                /* Linting */
         
     | 
| 19 | 
         
            +
                "strict": true,
         
     | 
| 20 | 
         
            +
                "noUnusedLocals": true,
         
     | 
| 21 | 
         
            +
                "noUnusedParameters": true,
         
     | 
| 22 | 
         
            +
                "erasableSyntaxOnly": true,
         
     | 
| 23 | 
         
            +
                "noFallthroughCasesInSwitch": true,
         
     | 
| 24 | 
         
            +
                "noUncheckedSideEffectImports": true
         
     | 
| 25 | 
         
            +
              },
         
     | 
| 26 | 
         
            +
              "include": ["src"]
         
     | 
| 27 | 
         
            +
            }
         
     | 
    	
        tsconfig.json
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            {
         
     | 
| 2 | 
         
            +
              "files": [],
         
     | 
| 3 | 
         
            +
              "references": [
         
     | 
| 4 | 
         
            +
                { "path": "./tsconfig.app.json" },
         
     | 
| 5 | 
         
            +
                { "path": "./tsconfig.node.json" }
         
     | 
| 6 | 
         
            +
              ]
         
     | 
| 7 | 
         
            +
            }
         
     | 
    	
        tsconfig.node.json
    ADDED
    
    | 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            {
         
     | 
| 2 | 
         
            +
              "compilerOptions": {
         
     | 
| 3 | 
         
            +
                "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
         
     | 
| 4 | 
         
            +
                "target": "ES2023",
         
     | 
| 5 | 
         
            +
                "lib": ["ES2023"],
         
     | 
| 6 | 
         
            +
                "module": "ESNext",
         
     | 
| 7 | 
         
            +
                "skipLibCheck": true,
         
     | 
| 8 | 
         
            +
             
     | 
| 9 | 
         
            +
                /* Bundler mode */
         
     | 
| 10 | 
         
            +
                "moduleResolution": "bundler",
         
     | 
| 11 | 
         
            +
                "allowImportingTsExtensions": true,
         
     | 
| 12 | 
         
            +
                "verbatimModuleSyntax": true,
         
     | 
| 13 | 
         
            +
                "moduleDetection": "force",
         
     | 
| 14 | 
         
            +
                "noEmit": true,
         
     | 
| 15 | 
         
            +
             
     | 
| 16 | 
         
            +
                /* Linting */
         
     | 
| 17 | 
         
            +
                "strict": true,
         
     | 
| 18 | 
         
            +
                "noUnusedLocals": true,
         
     | 
| 19 | 
         
            +
                "noUnusedParameters": true,
         
     | 
| 20 | 
         
            +
                "erasableSyntaxOnly": true,
         
     | 
| 21 | 
         
            +
                "noFallthroughCasesInSwitch": true,
         
     | 
| 22 | 
         
            +
                "noUncheckedSideEffectImports": true
         
     | 
| 23 | 
         
            +
              },
         
     | 
| 24 | 
         
            +
              "include": ["vite.config.ts"]
         
     | 
| 25 | 
         
            +
            }
         
     | 
    	
        vite.config.ts
    ADDED
    
    | 
         @@ -0,0 +1,8 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import { defineConfig } from "vite";
         
     | 
| 2 | 
         
            +
            import react from "@vitejs/plugin-react";
         
     | 
| 3 | 
         
            +
            import tailwindcss from "@tailwindcss/vite";
         
     | 
| 4 | 
         
            +
             
     | 
| 5 | 
         
            +
            // https://vite.dev/config/
         
     | 
| 6 | 
         
            +
            export default defineConfig({
         
     | 
| 7 | 
         
            +
              plugins: [react(), tailwindcss()],
         
     | 
| 8 | 
         
            +
            });
         
     |