webui: Refactor tool calls
diff --git a/webui/src/web-components/sketch-tool-card-multiple-choice.ts b/webui/src/web-components/sketch-tool-card-multiple-choice.ts
new file mode 100644
index 0000000..d69ec6b
--- /dev/null
+++ b/webui/src/web-components/sketch-tool-card-multiple-choice.ts
@@ -0,0 +1,151 @@
+import { LitElement, css, html } from "lit";
+import { customElement, property } from "lit/decorators.js";
+import { ToolCall, MultipleChoiceOption, MultipleChoiceParams } from "../types";
+
+@customElement("sketch-tool-card-multiple-choice")
+export class SketchToolCardMultipleChoice extends LitElement {
+ @property() toolCall: ToolCall;
+ @property() open: boolean;
+ @property() selectedOption: MultipleChoiceOption = null;
+
+ static styles = css`
+ .options-container {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin: 10px 0;
+ }
+ .option {
+ display: inline-flex;
+ align-items: center;
+ padding: 8px 12px;
+ border-radius: 4px;
+ background-color: #f5f5f5;
+ cursor: pointer;
+ transition: all 0.2s;
+ border: 1px solid transparent;
+ user-select: none;
+ }
+ .option:hover {
+ background-color: #e0e0e0;
+ border-color: #ccc;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ }
+ .option:active {
+ transform: translateY(0);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+ background-color: #d5d5d5;
+ }
+ .option.selected {
+ background-color: #e3f2fd;
+ border-color: #2196f3;
+ border-width: 1px;
+ border-style: solid;
+ }
+ .option-checkmark {
+ margin-left: 6px;
+ color: #2196f3;
+ }
+ .summary-text {
+ font-style: italic;
+ padding: 0.5em;
+ }
+ .summary-text strong {
+ font-style: normal;
+ color: #2196f3;
+ font-weight: 600;
+ }
+ `;
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.updateSelectedOption();
+ }
+
+ updated(changedProps) {
+ if (changedProps.has("toolCall")) {
+ this.updateSelectedOption();
+ }
+ }
+
+ updateSelectedOption() {
+ if (this.toolCall?.result_message?.tool_result) {
+ try {
+ this.selectedOption = JSON.parse(
+ this.toolCall.result_message.tool_result,
+ ).selected;
+ } catch (e) {
+ console.error("Error parsing result:", e);
+ }
+ } else {
+ this.selectedOption = null;
+ }
+ }
+
+ async handleOptionClick(choice) {
+ this.selectedOption = this.selectedOption === choice ? null : choice;
+
+ const event = new CustomEvent("multiple-choice-selected", {
+ detail: {
+ responseText: this.selectedOption.responseText,
+ toolCall: this.toolCall,
+ },
+ bubbles: true,
+ composed: true,
+ });
+ this.dispatchEvent(event);
+ }
+
+ render() {
+ let choices = [];
+ let question = "";
+ try {
+ const inputData = JSON.parse(
+ this.toolCall?.input || "{}",
+ ) as MultipleChoiceParams;
+ choices = inputData.responseOptions || [];
+ question = inputData.question || "Please select an option:";
+ } catch (e) {
+ console.error("Error parsing multiple-choice input:", e);
+ }
+
+ const summaryContent =
+ this.selectedOption !== null
+ ? html`<span class="summary-text">
+ ${question}: <strong>${this.selectedOption.caption}</strong>
+ </span>`
+ : html`<span class="summary-text">${question}</span>`;
+
+ return html`
+ <div class="multiple-choice-card">
+ ${summaryContent}
+ <div class="options-container">
+ ${choices.map((choice) => {
+ const isSelected =
+ this.selectedOption !== null && this.selectedOption === choice;
+ return html`
+ <div
+ class="option ${isSelected ? "selected" : ""}"
+ @click=${() => this.handleOptionClick(choice)}
+ title="${choice.responseText}"
+ >
+ <span class="option-label">${choice.caption}</span>
+ ${isSelected
+ ? html`<span class="option-checkmark">✓</span>`
+ : ""}
+ </div>
+ `;
+ })}
+ </div>
+ </div>
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "sketch-tool-card-multiple-choice": SketchToolCardMultipleChoice;
+ }
+}