Remove Charts tab and related Vega components from Sketch
Co-Authored-By: sketch <hello@sketch.dev>
- Removed Charts tab from navigation
- Removed sketch-charts component and vega-embed component
- Removed vega, vega-lite, and vega-embed dependencies from package.json
- Removed all associated code and UI elements
diff --git a/webui/package.json b/webui/package.json
index a330776..0248c77 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -30,10 +30,7 @@
"lit": "^3.2.1",
"marked": "^15.0.7",
"mermaid": "^11.6.0",
- "sanitize-html": "^2.15.0",
- "vega": "^5.33.0",
- "vega-embed": "^6.29.0",
- "vega-lite": "^5.23.0"
+ "sanitize-html": "^2.15.0"
},
"devDependencies": {
"@sand4rt/experimental-ct-web": "^1.51.1",
diff --git a/webui/src/web-components/demo/sketch-charts.demo.html b/webui/src/web-components/demo/sketch-charts.demo.html
deleted file mode 100644
index 64a9bd2..0000000
--- a/webui/src/web-components/demo/sketch-charts.demo.html
+++ /dev/null
@@ -1,134 +0,0 @@
-<!doctype html>
-<html>
- <head>
- <meta charset="utf-8" />
- <title>Sketch Charts Demo</title>
- <script type="module" src="../sketch-charts.ts"></script>
- <link rel="stylesheet" href="demo.css" />
- <style>
- sketch-charts {
- margin: 20px;
- max-width: 1000px;
- }
-
- body {
- font-family:
- system-ui,
- -apple-system,
- BlinkMacSystemFont,
- "Segoe UI",
- Roboto,
- sans-serif;
- }
- </style>
- </head>
- <body>
- <h1>Sketch Charts Demo</h1>
- <sketch-charts id="charts"></sketch-charts>
-
- <script>
- // Sample data for testing
- const sampleMessages = [
- {
- idx: 1,
- type: "human",
- content: "Hello, can you help me with a coding task?",
- timestamp: new Date(Date.now() - 3600000).toISOString(),
- usage: { cost_usd: 0.0001 },
- },
- {
- idx: 2,
- type: "assistant",
- content:
- "I'd be happy to help! What kind of coding task are you working on?",
- timestamp: new Date(Date.now() - 3500000).toISOString(),
- usage: { cost_usd: 0.0005 },
- },
- {
- idx: 3,
- type: "human",
- content: "I need to create a web component using lit-element",
- timestamp: new Date(Date.now() - 3400000).toISOString(),
- usage: { cost_usd: 0.0001 },
- },
- {
- idx: 4,
- type: "assistant",
- content:
- "I can definitely help with that. Lit Element is a great library for building web components.",
- timestamp: new Date(Date.now() - 3300000).toISOString(),
- usage: { cost_usd: 0.0008 },
- },
- {
- idx: 5,
- type: "assistant",
- tool_name: "bash",
- input: "ls -la",
- tool_result:
- "total 16\ndrwxr-xr-x 4 user staff 128 Jan 10 12:34 .\ndrwxr-xr-x 10 user staff 320 Jan 10 12:34 ..\n-rw-r--r-- 1 user staff 123 Jan 10 12:34 file1.txt\n-rw-r--r-- 1 user staff 456 Jan 10 12:34 file2.txt",
- start_time: new Date(Date.now() - 3200000).toISOString(),
- end_time: new Date(Date.now() - 3190000).toISOString(),
- timestamp: new Date(Date.now() - 3190000).toISOString(),
- usage: { cost_usd: 0.0002 },
- },
- {
- idx: 6,
- type: "assistant",
- content: "Let me create a basic web component for you.",
- timestamp: new Date(Date.now() - 3100000).toISOString(),
- usage: { cost_usd: 0.0015 },
- },
- {
- idx: 7,
- type: "human",
- content: "Can you show me how to handle events in the web component?",
- timestamp: new Date(Date.now() - 3000000).toISOString(),
- usage: { cost_usd: 0.0001 },
- },
- {
- idx: 8,
- type: "assistant",
- tool_name: "bash",
- input: "cat example.ts",
- tool_result:
- "import { LitElement, html } from 'lit';\nimport { customElement } from 'lit/decorators.js';\n\n@customElement('my-element')\nexport class MyElement extends LitElement {\n render() {\n return html`<div>Hello World</div>`;\n }\n}",
- start_time: new Date(Date.now() - 2900000).toISOString(),
- end_time: new Date(Date.now() - 2800000).toISOString(),
- timestamp: new Date(Date.now() - 2800000).toISOString(),
- usage: { cost_usd: 0.0003 },
- },
- {
- idx: 9,
- type: "assistant",
- content:
- "Here's how you can handle events in a web component using Lit.",
- timestamp: new Date(Date.now() - 2700000).toISOString(),
- usage: { cost_usd: 0.002 },
- },
- {
- idx: 10,
- type: "human",
- content: "Thank you! How about adding properties and attributes?",
- timestamp: new Date(Date.now() - 2600000).toISOString(),
- usage: { cost_usd: 0.0001 },
- },
- {
- idx: 11,
- type: "assistant",
- content:
- "You can use the @property decorator to define properties in your Lit Element component.",
- timestamp: new Date(Date.now() - 2500000).toISOString(),
- usage: { cost_usd: 0.0025 },
- },
- ];
-
- // Set sample data as soon as the component is defined
- document.addEventListener("DOMContentLoaded", () => {
- console.time("chart-demo-load");
- const chartsComponent = document.getElementById("charts");
- chartsComponent.messages = sampleMessages;
- console.timeEnd("chart-demo-load");
- });
- </script>
- </body>
-</html>
diff --git a/webui/src/web-components/sketch-app-shell.ts b/webui/src/web-components/sketch-app-shell.ts
index c2d5e6a..d351339 100644
--- a/webui/src/web-components/sketch-app-shell.ts
+++ b/webui/src/web-components/sketch-app-shell.ts
@@ -3,7 +3,7 @@
import { ConnectionStatus, DataManager } from "../data";
import { AgentMessage, GitCommit, State } from "../types";
import { aggregateAgentMessages } from "./aggregateAgentMessages";
-import "./sketch-charts";
+
import "./sketch-chat-input";
import "./sketch-container-status";
import "./sketch-diff-view";
@@ -18,13 +18,13 @@
import { createRef, ref } from "lit/directives/ref.js";
import { SketchChatInput } from "./sketch-chat-input";
-type ViewMode = "chat" | "diff" | "charts" | "terminal";
+type ViewMode = "chat" | "diff" | "terminal";
@customElement("sketch-app-shell")
export class SketchAppShell extends LitElement {
- // Current view mode (chat, diff, charts, terminal)
+ // Current view mode (chat, diff, terminal)
@state()
- viewMode: "chat" | "diff" | "charts" | "terminal" = "chat";
+ viewMode: "chat" | "diff" | "terminal" = "chat";
// Current commit hash for diff view
@state()
@@ -197,7 +197,6 @@
/* Individual view styles */
.chat-view,
.diff-view,
- .chart-view,
.terminal-view {
display: none; /* Hidden by default */
width: 100%;
@@ -488,7 +487,7 @@
}
}
- updateUrlForViewMode(mode: "chat" | "diff" | "charts" | "terminal"): void {
+ updateUrlForViewMode(mode: "chat" | "diff" | "terminal"): void {
// Get the current URL without search parameters
const url = new URL(window.location.href);
@@ -524,7 +523,7 @@
* Handle view mode selection event
*/
private _handleViewModeSelect(event: CustomEvent) {
- const mode = event.detail.mode as "chat" | "diff" | "charts" | "terminal";
+ const mode = event.detail.mode as "chat" | "diff" | "terminal";
this.toggleViewMode(mode, true);
}
@@ -565,7 +564,7 @@
}
/**
- * Toggle between different view modes: chat, diff, charts, terminal
+ * Toggle between different view modes: chat, diff, terminal
*/
private toggleViewMode(mode: ViewMode, updateHistory: boolean): void {
// Don't do anything if the mode is already active
@@ -587,13 +586,12 @@
);
const chatView = this.shadowRoot?.querySelector(".chat-view");
const diffView = this.shadowRoot?.querySelector(".diff-view");
- const chartView = this.shadowRoot?.querySelector(".chart-view");
+
const terminalView = this.shadowRoot?.querySelector(".terminal-view");
// Remove active class from all views
chatView?.classList.remove("view-active");
diffView?.classList.remove("view-active");
- chartView?.classList.remove("view-active");
terminalView?.classList.remove("view-active");
// Add/remove diff-active class on view container
@@ -619,9 +617,7 @@
(diffViewComp as any).loadDiffContent();
}
break;
- case "charts":
- chartView?.classList.add("view-active");
- break;
+
case "terminal":
terminalView?.classList.add("view-active");
break;
@@ -639,12 +635,6 @@
});
viewModeSelect.dispatchEvent(event);
}
-
- // FIXME: This is a hack to get vega chart in sketch-charts.ts to work properly
- // When the chart is in the background, its container has a width of 0, so vega
- // renders width 0 and only changes that width on a resize event.
- // See https://github.com/vega/react-vega/issues/85#issuecomment-1826421132
- window.dispatchEvent(new Event("resize"));
});
}
@@ -1073,13 +1063,7 @@
.commitHash=${this.currentCommitHash}
></sketch-diff-view>
</div>
- <div
- class="chart-view ${this.viewMode === "charts"
- ? "view-active"
- : ""}"
- >
- <sketch-charts .messages=${this.messages}></sketch-charts>
- </div>
+
<div
class="terminal-view ${this.viewMode === "terminal"
? "view-active"
diff --git a/webui/src/web-components/sketch-charts.ts b/webui/src/web-components/sketch-charts.ts
deleted file mode 100644
index 44779b2..0000000
--- a/webui/src/web-components/sketch-charts.ts
+++ /dev/null
@@ -1,524 +0,0 @@
-import "./vega-embed";
-import { css, html, LitElement, PropertyValues } from "lit";
-import { customElement, property, state } from "lit/decorators.js";
-import { TopLevelSpec } from "vega-lite";
-import type { AgentMessage } from "../types";
-import "vega-embed";
-import { VisualizationSpec } from "vega-embed";
-
-/**
- * Web component for rendering charts related to the timeline data
- * Displays cumulative cost over time and message timing visualization
- */
-@customElement("sketch-charts")
-export class SketchCharts extends LitElement {
- @property({ type: Array })
- messages: AgentMessage[] = [];
-
- @state()
- private chartData: { timestamp: Date; cost: number }[] = [];
-
- // We need to make the styles available to Vega-Embed when it's rendered
- static styles = css`
- :host {
- display: block;
- width: 100%;
- }
-
- .chart-container {
- padding: 20px;
- background-color: #fff;
- border-radius: 8px;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- margin-bottom: 20px;
- }
-
- .chart-section {
- margin-bottom: 30px;
- }
-
- .chart-section h3 {
- margin-top: 0;
- margin-bottom: 15px;
- font-size: 18px;
- color: #333;
- border-bottom: 1px solid #eee;
- padding-bottom: 8px;
- }
-
- .chart-content {
- width: 100%;
- min-height: 300px;
- }
-
- .loader {
- border: 4px solid #f3f3f3;
- border-radius: 50%;
- border-top: 4px solid #3498db;
- width: 40px;
- height: 40px;
- margin: 20px auto;
- animation: spin 2s linear infinite;
- }
-
- @keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
- }
- `;
-
- constructor() {
- super();
- this.chartData = [];
- }
-
- @state()
- private dollarsPerHour: number | null = null;
-
- private calculateCumulativeCostData(
- messages: AgentMessage[],
- ): { timestamp: Date; cost: number }[] {
- if (!messages || messages.length === 0) {
- return [];
- }
-
- let cumulativeCost = 0;
- const data: { timestamp: Date; cost: number }[] = [];
-
- for (const message of messages) {
- if (message.timestamp && message.usage && message.usage.cost_usd) {
- const timestamp = new Date(message.timestamp);
- cumulativeCost += message.usage.cost_usd;
-
- data.push({
- timestamp,
- cost: cumulativeCost,
- });
- }
- }
-
- // Calculate dollars per hour if we have at least two data points
- if (data.length >= 2) {
- const startTime = data[0].timestamp;
- const endTime = data[data.length - 1].timestamp;
- const totalHours =
- (endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60);
- const totalCost = data[data.length - 1].cost;
-
- // Only calculate if we have a valid time span
- if (totalHours > 0) {
- this.dollarsPerHour = totalCost / totalHours;
- } else {
- this.dollarsPerHour = null;
- }
- } else {
- this.dollarsPerHour = null;
- }
-
- return data;
- }
-
- protected willUpdate(changedProperties: PropertyValues): void {
- if (changedProperties.has("messages")) {
- this.chartData = this.calculateCumulativeCostData(this.messages);
- }
- }
-
- private getMessagesChartSpec(): VisualizationSpec {
- try {
- const allMessages = this.messages;
- if (!Array.isArray(allMessages) || allMessages.length === 0) {
- return null;
- }
-
- // Sort messages chronologically
- allMessages.sort((a, b) => {
- const dateA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
- const dateB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
- return dateA - dateB;
- });
-
- // Create unique indexes for all messages
- const messageIndexMap = new Map<string, number>();
- let messageIdx = 0;
-
- // First pass: Process parent messages
- allMessages.forEach((msg, index) => {
- // Create a unique ID for each message to track its position
- const msgId = msg.timestamp ? msg.timestamp.toString() : `msg-${index}`;
- messageIndexMap.set(msgId, messageIdx++);
- });
-
- // Process tool calls from messages to account for filtered out tool messages
- const toolCallData: any[] = [];
- allMessages.forEach((msg) => {
- if (msg.tool_calls && msg.tool_calls.length > 0) {
- msg.tool_calls.forEach((toolCall) => {
- if (toolCall.result_message) {
- // Add this tool result message to our data
- const resultMsg = toolCall.result_message;
-
- // Important: use the original message's idx to maintain the correct order
- // The original message idx value is what we want to show in the chart
- if (resultMsg.idx !== undefined) {
- // If the tool call has start/end times, add it to bar data, otherwise to point data
- if (resultMsg.start_time && resultMsg.end_time) {
- toolCallData.push({
- type: "bar",
- index: resultMsg.idx, // Use actual idx from message
- message_type: "tool",
- content: resultMsg.content || "",
- tool_name: resultMsg.tool_name || toolCall.name || "",
- tool_input: toolCall.input || "",
- tool_result: resultMsg.tool_result || "",
- start_time: new Date(resultMsg.start_time).toISOString(),
- end_time: new Date(resultMsg.end_time).toISOString(),
- message: JSON.stringify(resultMsg, null, 2),
- });
- } else if (resultMsg.timestamp) {
- toolCallData.push({
- type: "point",
- index: resultMsg.idx, // Use actual idx from message
- message_type: "tool",
- content: resultMsg.content || "",
- tool_name: resultMsg.tool_name || toolCall.name || "",
- tool_input: toolCall.input || "",
- tool_result: resultMsg.tool_result || "",
- time: new Date(resultMsg.timestamp).toISOString(),
- message: JSON.stringify(resultMsg, null, 2),
- });
- }
- }
- }
- });
- }
- });
-
- // Prepare data for messages with start_time and end_time (bar marks)
- const barData = allMessages
- .filter((msg) => msg.start_time && msg.end_time) // Only include messages with explicit start and end times
- .map((msg) => {
- // Parse start and end times
- const startTime = new Date(msg.start_time!);
- const endTime = new Date(msg.end_time!);
-
- // Use the message idx directly for consistent ordering
- const index = msg.idx;
-
- // Truncate content for tooltip readability
- const displayContent = msg.content
- ? msg.content.length > 100
- ? msg.content.substring(0, 100) + "..."
- : msg.content
- : "No content";
-
- // Prepare tool input and output for tooltip if applicable
- const toolInput = msg.input
- ? msg.input.length > 100
- ? msg.input.substring(0, 100) + "..."
- : msg.input
- : "";
-
- const toolResult = msg.tool_result
- ? msg.tool_result.length > 100
- ? msg.tool_result.substring(0, 100) + "..."
- : msg.tool_result
- : "";
-
- return {
- index: index,
- message_type: msg.type,
- content: displayContent,
- tool_name: msg.tool_name || "",
- tool_input: toolInput,
- tool_result: toolResult,
- start_time: startTime.toISOString(),
- end_time: endTime.toISOString(),
- message: JSON.stringify(msg, null, 2), // Full message for detailed inspection
- };
- });
-
- // Prepare data for messages with timestamps only (point marks)
- const pointData = allMessages
- .filter((msg) => msg.timestamp && !(msg.start_time && msg.end_time)) // Only messages with timestamp but without start/end times
- .map((msg) => {
- // Get the timestamp
- const timestamp = new Date(msg.timestamp!);
-
- // Use the message idx directly for consistent ordering
- const index = msg.idx;
-
- // Truncate content for tooltip readability
- const displayContent = msg.content
- ? msg.content.length > 100
- ? msg.content.substring(0, 100) + "..."
- : msg.content
- : "No content";
-
- // Prepare tool input and output for tooltip if applicable
- const toolInput = msg.input
- ? msg.input.length > 100
- ? msg.input.substring(0, 100) + "..."
- : msg.input
- : "";
-
- const toolResult = msg.tool_result
- ? msg.tool_result.length > 100
- ? msg.tool_result.substring(0, 100) + "..."
- : msg.tool_result
- : "";
-
- return {
- index: index,
- message_type: msg.type,
- content: displayContent,
- tool_name: msg.tool_name || "",
- tool_input: toolInput,
- tool_result: toolResult,
- time: timestamp.toISOString(),
- message: JSON.stringify(msg, null, 2), // Full message for detailed inspection
- };
- });
-
- // Add tool call data to the appropriate arrays
- const toolBarData = toolCallData
- .filter((d) => d.type === "bar")
- .map((d) => {
- delete d.type;
- return d;
- });
-
- const toolPointData = toolCallData
- .filter((d) => d.type === "point")
- .map((d) => {
- delete d.type;
- return d;
- });
-
- // Check if we have any data to display
- if (
- barData.length === 0 &&
- pointData.length === 0 &&
- toolBarData.length === 0 &&
- toolPointData.length === 0
- ) {
- return null;
- }
-
- // Calculate height based on number of unique messages
- const chartHeight = 20 * Math.min(allMessages.length, 25); // Max 25 visible at once
-
- // Create a layered Vega-Lite spec combining bars and points
- const messagesSpec: TopLevelSpec = {
- $schema: "https://vega.github.io/schema/vega-lite/v5.json",
- description: "Message Timeline",
- width: "container",
- height: chartHeight,
- layer: [],
- };
-
- // Add bar layer if we have bar data
- if (barData.length > 0 || toolBarData.length > 0) {
- const combinedBarData = [...barData, ...toolBarData];
- messagesSpec.layer.push({
- data: { values: combinedBarData },
- mark: {
- type: "bar",
- height: 16,
- },
- encoding: {
- x: {
- field: "start_time",
- type: "temporal",
- title: "Time",
- axis: {
- format: "%H:%M:%S",
- title: "Time",
- labelAngle: -45,
- },
- },
- x2: { field: "end_time" },
- y: {
- field: "index",
- type: "ordinal",
- title: "Message Index",
- axis: {
- grid: true,
- },
- },
- color: {
- field: "message_type",
- type: "nominal",
- title: "Message Type",
- legend: {},
- },
- tooltip: [
- { field: "message_type", type: "nominal", title: "Type" },
- { field: "tool_name", type: "nominal", title: "Tool" },
- {
- field: "start_time",
- type: "temporal",
- title: "Start Time",
- format: "%H:%M:%S.%L",
- },
- {
- field: "end_time",
- type: "temporal",
- title: "End Time",
- format: "%H:%M:%S.%L",
- },
- { field: "content", type: "nominal", title: "Content" },
- { field: "tool_input", type: "nominal", title: "Tool Input" },
- { field: "tool_result", type: "nominal", title: "Tool Result" },
- ],
- },
- });
- }
-
- // Add point layer if we have point data
- if (pointData.length > 0 || toolPointData.length > 0) {
- const combinedPointData = [...pointData, ...toolPointData];
- messagesSpec.layer.push({
- data: { values: combinedPointData },
- mark: {
- type: "point",
- size: 100,
- filled: true,
- },
- encoding: {
- x: {
- field: "time",
- type: "temporal",
- title: "Time",
- axis: {
- format: "%H:%M:%S",
- title: "Time",
- labelAngle: -45,
- },
- },
- y: {
- field: "index",
- type: "ordinal",
- title: "Message Index",
- },
- color: {
- field: "message_type",
- type: "nominal",
- title: "Message Type",
- },
- tooltip: [
- { field: "message_type", type: "nominal", title: "Type" },
- { field: "tool_name", type: "nominal", title: "Tool" },
- {
- field: "time",
- type: "temporal",
- title: "Timestamp",
- format: "%H:%M:%S.%L",
- },
- { field: "content", type: "nominal", title: "Content" },
- { field: "tool_input", type: "nominal", title: "Tool Input" },
- { field: "tool_result", type: "nominal", title: "Tool Result" },
- ],
- },
- });
- }
- return messagesSpec;
- } catch (error) {
- console.error("Error rendering messages chart:", error);
- }
- }
-
- render() {
- const costSpec = this.createCostChartSpec();
- const messagesSpec = this.getMessagesChartSpec();
-
- return html`
- <div class="chart-container" id="chartContainer">
- <div class="chart-section">
- <h3>
- Dollar Usage Over
- Time${this.dollarsPerHour !== null
- ? html` (Avg: $${this.dollarsPerHour.toFixed(2)}/hr)`
- : ""}
- </h3>
- <div class="chart-content">
- ${this.chartData.length > 0
- ? html`<vega-embed .spec=${costSpec}></vega-embed>`
- : html`<p>No cost data available to display.</p>`}
- </div>
- </div>
- <div class="chart-section">
- <h3>Message Timeline</h3>
- <div class="chart-content">
- ${messagesSpec?.data
- ? html`<vega-embed .spec=${messagesSpec}></vega-embed>`
- : html`<p>No messages available to display.</p>`}
- </div>
- </div>
- </div>
- `;
- }
-
- private createCostChartSpec(): VisualizationSpec {
- return {
- $schema: "https://vega.github.io/schema/vega-lite/v5.json",
- description: "Cumulative cost over time",
- width: "container",
- height: 300,
- data: {
- values: this.chartData.map((d) => ({
- timestamp: d.timestamp.toISOString(),
- cost: d.cost,
- })),
- },
- mark: {
- type: "line",
- point: true,
- },
- encoding: {
- x: {
- field: "timestamp",
- type: "temporal",
- title: "Time",
- axis: {
- format: "%H:%M:%S",
- title: "Time",
- labelAngle: -45,
- },
- },
- y: {
- field: "cost",
- type: "quantitative",
- title: "Cumulative Cost (USD)",
- axis: {
- format: "$,.4f",
- },
- },
- tooltip: [
- {
- field: "timestamp",
- type: "temporal",
- title: "Time",
- format: "%Y-%m-%d %H:%M:%S",
- },
- {
- field: "cost",
- type: "quantitative",
- title: "Cumulative Cost",
- format: "$,.4f",
- },
- ],
- },
- };
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "sketch-charts": SketchCharts;
- }
-}
diff --git a/webui/src/web-components/sketch-view-mode-select.ts b/webui/src/web-components/sketch-view-mode-select.ts
index 3b94303..abfe551 100644
--- a/webui/src/web-components/sketch-view-mode-select.ts
+++ b/webui/src/web-components/sketch-view-mode-select.ts
@@ -6,7 +6,7 @@
export class SketchViewModeSelect extends LitElement {
// Current active mode
@property()
- activeMode: "chat" | "diff" | "charts" | "terminal" = "chat";
+ activeMode: "chat" | "diff" | "terminal" = "chat";
// Header bar: view mode buttons
static styles = css`
@@ -87,7 +87,7 @@
/**
* Handle view mode button clicks
*/
- private _handleViewModeClick(mode: "chat" | "diff" | "charts" | "terminal") {
+ private _handleViewModeClick(mode: "chat" | "diff" | "terminal") {
// Dispatch a custom event to notify the app shell to change the view
const event = new CustomEvent("view-mode-select", {
detail: { mode },
@@ -139,15 +139,7 @@
<span class="tab-icon">±</span>
<span>Diff</span>
</button>
- <button
- id="showChartsButton"
- class="tab-btn ${this.activeMode === "charts" ? "active" : ""}"
- title="Charts View"
- @click=${() => this._handleViewModeClick("charts")}
- >
- <span class="tab-icon">📈</span>
- <span>Charts</span>
- </button>
+
<button
id="showTerminalButton"
class="tab-btn ${this.activeMode === "terminal" ? "active" : ""}"
diff --git a/webui/src/web-components/vega-embed.ts b/webui/src/web-components/vega-embed.ts
deleted file mode 100644
index 04f0087..0000000
--- a/webui/src/web-components/vega-embed.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { customElement, property, query } from "lit/decorators.js";
-import vegaEmbed from "vega-embed";
-import { VisualizationSpec } from "vega-embed";
-
-/**
- * A web component wrapper for vega-embed.
- * Renders Vega and Vega-Lite visualizations.
- *
- * Usage:
- * <vega-embed .spec="${yourVegaLiteSpec}"></vega-embed>
- */
-@customElement("vega-embed")
-export class VegaEmbed extends LitElement {
- /**
- * The Vega or Vega-Lite specification to render
- */
- @property({ type: Object })
- spec?: VisualizationSpec;
-
- static styles = css`
- :host {
- display: block;
- width: 100%;
- height: 100%;
- }
-
- #vega-container {
- width: 100%;
- height: 100%;
- min-height: 200px;
- }
- `;
-
- @query("#vega-container")
- protected container?: HTMLElement;
-
- protected firstUpdated() {
- this.renderVegaVisualization();
- }
-
- protected updated() {
- this.renderVegaVisualization();
- }
-
- /**
- * Renders the Vega/Vega-Lite visualization using vega-embed
- */
- private async renderVegaVisualization() {
- if (!this.spec) {
- return;
- }
-
- if (!this.container) {
- return;
- }
-
- try {
- // Clear previous visualization if any
- this.container.innerHTML = "";
-
- // Render new visualization
- await vegaEmbed(this.container, this.spec, {
- actions: true,
- renderer: "svg",
- });
- } catch (error) {
- console.error("Error rendering Vega visualization:", error);
- this.container.innerHTML = `<div style="color: red; padding: 10px;">
- Error rendering visualization: ${
- error instanceof Error ? error.message : String(error)
- }
- </div>`;
- }
- }
-
- render() {
- return html`<div id="vega-container"></div> `;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "vega-embed": VegaEmbed;
- }
-}