Spaces:
Running
Running
| <script lang="ts"> | |
| import { onMount } from "svelte"; | |
| import gsap from "gsap"; | |
| import type { ToolExecution } from "../../stores/agent"; | |
| export let toolExecutions: ToolExecution[] = []; | |
| let blockElement: HTMLDivElement; | |
| let expandedTools: Set<string> = new Set(); | |
| const toolIcons: Record<string, string> = { | |
| read_editor: "π", | |
| write_editor: "βοΈ", | |
| observe_console: "π", | |
| default: "π§", | |
| }; | |
| const statusIcons: Record<string, string> = { | |
| pending: "β³", | |
| running: "β‘", | |
| completed: "β ", | |
| error: "β", | |
| }; | |
| const statusMessages: Record<string, (name: string) => string> = { | |
| read_editor: (name) => ({ | |
| pending: "Preparing to read code...", | |
| running: "Reading editor content...", | |
| completed: "Code read successfully", | |
| error: "Failed to read code" | |
| }[name] || name), | |
| write_editor: (name) => ({ | |
| pending: "Preparing to write code...", | |
| running: "Writing code and reloading game...", | |
| completed: "Code updated successfully", | |
| error: "Failed to write code" | |
| }[name] || name), | |
| observe_console: (name) => ({ | |
| pending: "Preparing to read console...", | |
| running: "Reading console output...", | |
| completed: "Console read successfully", | |
| error: "Failed to read console" | |
| }[name] || name), | |
| }; | |
| function toggleTool(toolId: string) { | |
| if (expandedTools.has(toolId)) { | |
| expandedTools.delete(toolId); | |
| } else { | |
| expandedTools.add(toolId); | |
| } | |
| expandedTools = expandedTools; | |
| } | |
| function getStatusMessage(tool: ToolExecution): string { | |
| const messageFunc = statusMessages[tool.name] || statusMessages.default; | |
| return messageFunc ? messageFunc(tool.status) : `${tool.name}: ${tool.status}`; | |
| } | |
| function formatDuration(startTime: number, endTime?: number): string { | |
| const duration = (endTime || Date.now()) - startTime; | |
| if (duration < 1000) { | |
| return `${duration}ms`; | |
| } | |
| return `${(duration / 1000).toFixed(1)}s`; | |
| } | |
| onMount(() => { | |
| gsap.fromTo( | |
| blockElement, | |
| { opacity: 0, y: -10 }, | |
| { opacity: 1, y: 0, duration: 0.3, ease: "power2.out" } | |
| ); | |
| }); | |
| </script> | |
| <div class="tool-block" bind:this={blockElement}> | |
| {#each toolExecutions as tool (tool.id)} | |
| <div class="tool-item {tool.status}" class:expanded={expandedTools.has(tool.id)}> | |
| <button | |
| class="tool-item-header" | |
| on:click={() => toggleTool(tool.id)} | |
| aria-expanded={expandedTools.has(tool.id)} | |
| > | |
| <span class="tool-status-icon"> | |
| {#if tool.status === "running"} | |
| <span class="spinner-small">{statusIcons[tool.status]}</span> | |
| {:else} | |
| {statusIcons[tool.status]} | |
| {/if} | |
| </span> | |
| <span class="tool-icon">{toolIcons[tool.name] || toolIcons.default}</span> | |
| <span class="tool-name">{getStatusMessage(tool)}</span> | |
| <span class="tool-duration"> | |
| {formatDuration(tool.startTime, tool.endTime)} | |
| </span> | |
| <span class="expand-icon" class:rotated={expandedTools.has(tool.id)}> | |
| βΆ | |
| </span> | |
| </button> | |
| {#if expandedTools.has(tool.id)} | |
| <div class="tool-details"> | |
| {#if tool.args && Object.keys(tool.args).length > 0} | |
| <div class="tool-section"> | |
| <div class="section-title">Parameters:</div> | |
| <div class="params"> | |
| {#each Object.entries(tool.args) as [key, value]} | |
| <div class="param"> | |
| <span class="param-key">{key}:</span> | |
| <span class="param-value"> | |
| {#if typeof value === 'string' && value.length > 100} | |
| <pre>{value}</pre> | |
| {:else} | |
| {JSON.stringify(value)} | |
| {/if} | |
| </span> | |
| </div> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| {#if tool.output} | |
| <div class="tool-section"> | |
| <div class="section-title">Output:</div> | |
| <pre class="tool-output">{tool.output}</pre> | |
| </div> | |
| {/if} | |
| {#if tool.consoleOutput && tool.consoleOutput.length > 0} | |
| <div class="tool-section"> | |
| <div class="section-title">Console Output:</div> | |
| <div class="console-output"> | |
| {#each tool.consoleOutput as line} | |
| <div class="console-line">{line}</div> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| {#if tool.error} | |
| <div class="tool-section error"> | |
| <div class="section-title">Error:</div> | |
| <div class="error-message">{tool.error}</div> | |
| </div> | |
| {/if} | |
| {#if tool.status === "running" && !tool.output} | |
| <div class="tool-section"> | |
| <div class="loading-indicator"> | |
| <span class="loading-dots">Processing</span> | |
| </div> | |
| </div> | |
| {/if} | |
| </div> | |
| {/if} | |
| </div> | |
| {/each} | |
| </div> | |
| <style> | |
| .tool-block { | |
| margin: 0.25rem 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.25rem; | |
| } | |
| .tool-item { | |
| border-radius: 4px; | |
| overflow: hidden; | |
| transition: all 0.2s ease; | |
| border: 1px solid rgba(65, 105, 225, 0.2); | |
| background: rgba(65, 105, 225, 0.05); | |
| } | |
| .tool-item.running { | |
| background: rgba(255, 210, 30, 0.08); | |
| border: 1px solid rgba(255, 210, 30, 0.3); | |
| } | |
| .tool-item.completed { | |
| background: rgba(0, 255, 0, 0.05); | |
| border: 1px solid rgba(0, 255, 0, 0.2); | |
| } | |
| .tool-item.error { | |
| background: rgba(255, 0, 0, 0.08); | |
| border: 1px solid rgba(255, 0, 0, 0.3); | |
| } | |
| .tool-item-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| width: 100%; | |
| padding: 0.4rem 0.6rem; | |
| background: transparent; | |
| border: none; | |
| color: inherit; | |
| font: inherit; | |
| text-align: left; | |
| cursor: pointer; | |
| transition: background 0.2s ease; | |
| } | |
| .tool-item-header:hover { | |
| background: rgba(255, 255, 255, 0.02); | |
| } | |
| .tool-status-icon { | |
| font-size: 0.9rem; | |
| width: 1.2rem; | |
| text-align: center; | |
| } | |
| .tool-icon { | |
| font-size: 1rem; | |
| } | |
| .tool-name { | |
| flex: 1; | |
| color: rgba(255, 255, 255, 0.9); | |
| font-size: 0.825rem; | |
| } | |
| .tool-duration { | |
| color: rgba(255, 255, 255, 0.4); | |
| font-size: 0.75rem; | |
| font-family: "Monaco", "Menlo", monospace; | |
| } | |
| .expand-icon { | |
| font-size: 0.7rem; | |
| color: rgba(255, 255, 255, 0.4); | |
| transition: transform 0.2s ease; | |
| } | |
| .expand-icon.rotated { | |
| transform: rotate(90deg); | |
| } | |
| .tool-details { | |
| padding: 0.75rem; | |
| background: rgba(0, 0, 0, 0.2); | |
| border-top: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .tool-section { | |
| margin-bottom: 0.75rem; | |
| } | |
| .tool-section:last-child { | |
| margin-bottom: 0; | |
| } | |
| .section-title { | |
| color: rgba(255, 255, 255, 0.5); | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| margin-bottom: 0.25rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .params { | |
| font-family: "Monaco", "Menlo", monospace; | |
| font-size: 0.8rem; | |
| } | |
| .param { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin: 0.25rem 0; | |
| } | |
| .param-key { | |
| color: rgba(255, 255, 255, 0.5); | |
| } | |
| .param-value { | |
| color: rgba(255, 210, 30, 0.8); | |
| word-break: break-all; | |
| } | |
| .param-value pre { | |
| margin: 0; | |
| padding: 0.5rem; | |
| background: rgba(0, 0, 0, 0.3); | |
| border-radius: 4px; | |
| font-size: 0.75rem; | |
| overflow-x: auto; | |
| max-height: 200px; | |
| } | |
| .tool-output, .console-output { | |
| background: rgba(0, 0, 0, 0.3); | |
| border-radius: 4px; | |
| padding: 0.5rem; | |
| font-family: "Monaco", "Menlo", monospace; | |
| font-size: 0.75rem; | |
| color: rgba(255, 255, 255, 0.8); | |
| overflow-x: auto; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| .console-line { | |
| margin: 0.1rem 0; | |
| } | |
| .error-message { | |
| color: #ff6b6b; | |
| font-family: "Monaco", "Menlo", monospace; | |
| font-size: 0.8rem; | |
| padding: 0.5rem; | |
| background: rgba(255, 0, 0, 0.1); | |
| border-radius: 4px; | |
| } | |
| .loading-indicator { | |
| text-align: center; | |
| padding: 1rem; | |
| color: rgba(255, 255, 255, 0.5); | |
| font-size: 0.85rem; | |
| } | |
| .loading-dots::after { | |
| content: ""; | |
| animation: dots 1.5s steps(4, end) infinite; | |
| } | |
| @keyframes dots { | |
| 0%, 20% { content: ""; } | |
| 40% { content: "."; } | |
| 60% { content: ".."; } | |
| 80%, 100% { content: "..."; } | |
| } | |
| .spinner { | |
| display: inline-block; | |
| animation: spin 1s linear infinite; | |
| } | |
| .spinner-small { | |
| display: inline-block; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| height: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 3px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| } | |
| </style> |