From 0922021d28cc1cfebff203f005e9b600be787dfc Mon Sep 17 00:00:00 2001 From: mik-tf Date: Fri, 19 Dec 2025 18:45:22 -0500 Subject: [PATCH 1/4] feat: Add conversation clear functionality and user message editing --- .../src/components/ChatInterface.svelte | 99 +++++++++- .../src/components/ChatMessage.svelte | 183 +++++++++++++++++- 2 files changed, 277 insertions(+), 5 deletions(-) diff --git a/grid-agent-gui/frontend/src/components/ChatInterface.svelte b/grid-agent-gui/frontend/src/components/ChatInterface.svelte index 5f279fe..e21c6e4 100644 --- a/grid-agent-gui/frontend/src/components/ChatInterface.svelte +++ b/grid-agent-gui/frontend/src/components/ChatInterface.svelte @@ -32,6 +32,7 @@ let showErrorModal = false; let showSettings = false; let showDocs = false; + let showClearModal = false; let errorMessage = ""; let isExporting = false; let isAborting = false; @@ -235,6 +236,57 @@ errorMessage = ""; } + function handleClear() { + showClearModal = true; + } + + function clearConversation() { + showClearModal = false; + messagesStore.set([]); + } + + async function saveAndClear() { + showClearModal = false; + await handleExport(); + messagesStore.set([]); + } + + function editMessage(index: number, newContent: string) { + // First update the message content + messagesStore.update((msgs) => { + const newMsgs = [...msgs]; + if (newMsgs[index] && newMsgs[index].role === "user") { + newMsgs[index] = { ...newMsgs[index], content: newContent }; + } + return newMsgs; + }); + + // If this is the last user message, retrigger AI response + const messages = $messagesStore; + if (index === messages.length - 1 && messages[index].role === "user") { + // Remove only the immediate next agent message if it exists (the response to this message) + messagesStore.update((msgs) => { + const nextIndex = index + 1; + if (nextIndex < msgs.length && msgs[nextIndex].role === "agent") { + // Remove just this agent response + return msgs.filter((_, i) => i !== nextIndex); + } + return msgs; + }); + + // Trigger new AI response by simulating user input + setTimeout(() => { + // Temporarily set input to trigger handleSubmit + const originalInput = input; + input = newContent; + handleSubmit().finally(() => { + // Restore original input if needed + input = originalInput; + }); + }, 100); + } + } + function toggleSettings() { showSettings = !showSettings; } @@ -612,6 +664,25 @@ > {/if} + + + + + + +{/if} + (showSettings = false)} /> (showDocs = false)} /> @@ -970,6 +1056,15 @@ background: rgba(239, 68, 68, 0.1); } + .clear-btn { + color: var(--text-secondary); + } + + .clear-btn:hover { + color: var(--error); + background: rgba(239, 68, 68, 0.1); + } + /* Floating Input Area */ .input-area { padding: 1.5rem; diff --git a/grid-agent-gui/frontend/src/components/ChatMessage.svelte b/grid-agent-gui/frontend/src/components/ChatMessage.svelte index ed5cbe5..79c77d2 100644 --- a/grid-agent-gui/frontend/src/components/ChatMessage.svelte +++ b/grid-agent-gui/frontend/src/components/ChatMessage.svelte @@ -6,6 +6,8 @@ import { marked } from "marked"; import DOMPurify from "dompurify"; import { BrowserOpenURL } from "../../wailsjs/runtime/runtime.js"; + import { createEventDispatcher } from "svelte"; + const dispatch = createEventDispatcher(); // Step types const STEP_TYPE_TOOL = "tool"; @@ -32,6 +34,9 @@ error?: string; }; + let isEditing = false; + let editContent = message.content; + const isUser = message.role === "user"; let showSteps = false; @@ -104,6 +109,34 @@ BrowserOpenURL(link.href); } } + + function startEditing() { + if (isUser) { + isEditing = true; + editContent = message.content; + } + } + + function saveEdit() { + if (isUser && editContent.trim()) { + dispatch('edit', { content: editContent.trim() }); + isEditing = false; + } + } + + function cancelEdit() { + isEditing = false; + editContent = message.content; + } + + function handleKeydown(event: KeyboardEvent) { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + saveEdit(); + } else if (event.key === 'Escape') { + cancelEdit(); + } + }
@@ -118,9 +151,44 @@
{#if message.content} -
- {@html renderMarkdown(message.content)} -
+ {#if isEditing} +
+ +
+ + +
+
+ {:else} +
+
+ {@html renderMarkdown(message.content)} +
+ {#if isUser} + + {/if} +
+ {/if} {:else if message.steps && message.steps.length > 0}
@@ -543,4 +611,113 @@ font-weight: 700; color: inherit; /* inherit ensures it looks good in user bubbles too */ } + + /* Edit functionality styling */ + .message-content { + position: relative; + } + + .edit-trigger { + position: absolute; + bottom: 0.5rem; + right: 0.5rem; + background: transparent; + border: none; + cursor: pointer; + font-size: 1rem; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s; + padding: 0.25rem; + border-radius: 0.25rem; + color: var(--text-secondary); + } + + .message-content:hover .edit-trigger { + opacity: 1; + pointer-events: auto; + background: var(--bg-tertiary); + color: var(--text-primary); + } + + .edit-container { + display: flex; + flex-direction: column; + gap: 0.75rem; + } + + .edit-textarea { + width: 100%; + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: 0.5rem; + background: var(--bg-primary); + color: var(--text-primary); + font-family: inherit; + font-size: 0.95rem; + resize: vertical; + min-height: 3rem; + outline: none; + transition: border-color 0.2s; + } + + .edit-textarea:focus { + border-color: var(--accent); + } + + .user .edit-textarea { + background: rgba(255, 255, 255, 0.1); + color: white; + } + + .agent .edit-textarea { + background: var(--bg-primary); + color: var(--text-primary); + } + + .edit-actions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; + } + + .edit-btn { + padding: 0.5rem 1rem; + border-radius: 0.375rem; + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + border: none; + } + + .save-btn { + background: var(--accent); + color: white; + } + + .save-btn:hover { + background: var(--accent-hover); + } + + .cancel-btn { + background: transparent; + color: var(--text-secondary); + border: 1px solid var(--border); + } + + .cancel-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); + } + + .user .cancel-btn { + color: rgba(255, 255, 255, 0.8); + border-color: rgba(255, 255, 255, 0.3); + } + + .user .cancel-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: white; + } From e4598558a3c42fddbd8246fcc4696ed78b3a938f Mon Sep 17 00:00:00 2001 From: mik-tf Date: Fri, 19 Dec 2025 18:52:50 -0500 Subject: [PATCH 2/4] feat: Add chat export functionality and improve message editing logic --- grid-agent-chat-1766188355016.md | 121 ++++++++++++++++++ .../src/components/ChatInterface.svelte | 14 +- 2 files changed, 126 insertions(+), 9 deletions(-) create mode 100644 grid-agent-chat-1766188355016.md diff --git a/grid-agent-chat-1766188355016.md b/grid-agent-chat-1766188355016.md new file mode 100644 index 0000000..26762fd --- /dev/null +++ b/grid-agent-chat-1766188355016.md @@ -0,0 +1,121 @@ +# Chat Export + +**App Version:** v0.5.1 +**Active Persona:** freefarm + +--- + +# Grid Agent Conversation + +**Date:** 2025-12-19, 6:52:35 p.m. +**Network:** main + +## Full Conversation + +### 👤 USER (6:49:21 p.m.) + +hi show me contracts on my account + +--- + +### 👤 USER (6:49:53 p.m.) + +hi show me contracts on my account + +--- + +### 🤖 AGENT (6:50:23 p.m.) + +Here are the contracts associated with your account (Twin ID: 6905): + +**Node Contracts** +| ID | Node ID | Type | Name | Project Name | +|----|---------|------|------|--------------| +| 1776775 | 920 | network | net_tuxktwe2 | Network | +| 1776776 | 920 | vm | vm | vm/vm | +| 1772537 | 8 | network | net_iksrno2f | Network | +| 1772538 | 8 | vm | vm | vm/vm | + +**Name Contracts** +No name contracts found. + +#### 🛠️ Workflow Steps + +**1. 📊 Analysis** + +Executing 'tfcmd get contracts' to retrieve and list all contracts associated with the current user's twin ID. + +**2. Command Executed** + +Command: tfcmd get contracts + +```bash +6:50PM INF starting peer session=tf-81159 twin=6905 +Node contracts: +ID Node ID Type Name Project Name +1776775 920 network net_tuxktwe2 Network +1776776 920 vm vm vm/vm +1772537 8 network net_iksrno2f Network +1772538 8 vm vm vm/vm + +Name contracts: +ID Name +``` + +**3. 💡 Answer** + +Here are the contracts associated with your account (Twin ID: 6905): + +**Node Contracts** +| ID | Node ID | Type | Name | Project Name | +|----|---------|------|------|--------------| +| 1776775 | 920 | network | net_tuxktwe2 | Network | +| 1776776 | 920 | vm | vm | vm/vm | +| 1772537 | 8 | network | net_iksrno2f | Network | +| 1772538 | 8 | vm | vm | vm/vm | + +**Name Contracts** +No name contracts found. + +--- + +### 👤 USER (6:50:29 p.m.) + +what is the newest contract + +--- + +### 👤 USER (6:52:06 p.m.) + +what is the newest contract + +--- + +### 🤖 AGENT (6:52:11 p.m.) + +The newest contract is **ID 1776776**. + +Details: +- **Type:** vm +- **Node ID:** 920 +- **Name:** vm +- **Project Name:** vm/vm + +(Contract IDs are sequential, so the highest number represents the most recently created contract.) + +#### 🛠️ Workflow Steps + +**1. 💡 Answer** + +The newest contract is **ID 1776776**. + +Details: +- **Type:** vm +- **Node ID:** 920 +- **Name:** vm +- **Project Name:** vm/vm + +(Contract IDs are sequential, so the highest number represents the most recently created contract.) + +--- + diff --git a/grid-agent-gui/frontend/src/components/ChatInterface.svelte b/grid-agent-gui/frontend/src/components/ChatInterface.svelte index e21c6e4..8d9d711 100644 --- a/grid-agent-gui/frontend/src/components/ChatInterface.svelte +++ b/grid-agent-gui/frontend/src/components/ChatInterface.svelte @@ -261,17 +261,13 @@ return newMsgs; }); - // If this is the last user message, retrigger AI response + // If this user message has an agent response immediately following, retrigger AI const messages = $messagesStore; - if (index === messages.length - 1 && messages[index].role === "user") { - // Remove only the immediate next agent message if it exists (the response to this message) + const nextIndex = index + 1; + if (messages[index].role === "user" && nextIndex < messages.length && messages[nextIndex].role === "agent") { + // Remove the agent response messagesStore.update((msgs) => { - const nextIndex = index + 1; - if (nextIndex < msgs.length && msgs[nextIndex].role === "agent") { - // Remove just this agent response - return msgs.filter((_, i) => i !== nextIndex); - } - return msgs; + return msgs.filter((_, i) => i !== nextIndex); }); // Trigger new AI response by simulating user input From c82756a94e979856a1a071cf4e6d287d6241ff47 Mon Sep 17 00:00:00 2001 From: mik-tf Date: Fri, 19 Dec 2025 19:14:51 -0500 Subject: [PATCH 3/4] feat: Improve message editing with direct API call and placeholder response --- .../src/components/ChatInterface.svelte | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/grid-agent-gui/frontend/src/components/ChatInterface.svelte b/grid-agent-gui/frontend/src/components/ChatInterface.svelte index 8d9d711..678cc4d 100644 --- a/grid-agent-gui/frontend/src/components/ChatInterface.svelte +++ b/grid-agent-gui/frontend/src/components/ChatInterface.svelte @@ -270,16 +270,65 @@ return msgs.filter((_, i) => i !== nextIndex); }); - // Trigger new AI response by simulating user input - setTimeout(() => { - // Temporarily set input to trigger handleSubmit - const originalInput = input; - input = newContent; - handleSubmit().finally(() => { - // Restore original input if needed - input = originalInput; - }); - }, 100); + // Add placeholder for new response + const requestID = `req_${Date.now()}_${Math.random()}`; + const placeholderMsg = { + role: "agent", + content: "", + timestamp: new Date().toISOString(), + requestID: requestID, + steps: [ + { + progressText: "🤔 Processing your request...", + exportPrefix: "", + content: "Processing your request...", + output: "", + error: "", + }, + ], + }; + messagesStore.update((msgs) => [...msgs, placeholderMsg]); + + // Send the edited message directly + (async () => { + isSending = true; + currentRequestID = requestID; + try { + const response = await SendMessage(newContent, requestID); + messagesStore.update((msgs) => { + const idx = msgs.findIndex((m) => m.requestID === response.requestID); + if (idx !== -1) { + const newMsgs = [...msgs]; + newMsgs[idx] = response; + return newMsgs; + } + return [...msgs, response]; // Fallback + }); + } catch (error) { + console.error("Failed to send message:", error); + messagesStore.update((msgs) => { + const idx = msgs.findIndex((m) => m.requestID === requestID); + if (idx !== -1) { + const existingMsg = msgs[idx]; + const steps = (existingMsg.steps || []).filter( + (s) => !s.progressText?.includes("🤔 Processing"), + ); + steps.push({ + progressText: "❌ Error", + exportPrefix: "", + content: "", + output: "", + error: String(error), + }); + msgs[idx] = { ...existingMsg, content: error, steps }; + } + return [...msgs]; + }); + } finally { + isSending = false; + currentRequestID = ""; + } + })(); } } From d54ae679092f72725c1f689b976454aebc82a5c5 Mon Sep 17 00:00:00 2001 From: mik-tf Date: Sat, 20 Dec 2025 08:12:43 -0500 Subject: [PATCH 4/4] chore: Remove old chat export file and update message timestamp on edit --- grid-agent-chat-1766188355016.md | 121 ------------------ .../src/components/ChatInterface.svelte | 4 +- 2 files changed, 2 insertions(+), 123 deletions(-) delete mode 100644 grid-agent-chat-1766188355016.md diff --git a/grid-agent-chat-1766188355016.md b/grid-agent-chat-1766188355016.md deleted file mode 100644 index 26762fd..0000000 --- a/grid-agent-chat-1766188355016.md +++ /dev/null @@ -1,121 +0,0 @@ -# Chat Export - -**App Version:** v0.5.1 -**Active Persona:** freefarm - ---- - -# Grid Agent Conversation - -**Date:** 2025-12-19, 6:52:35 p.m. -**Network:** main - -## Full Conversation - -### 👤 USER (6:49:21 p.m.) - -hi show me contracts on my account - ---- - -### 👤 USER (6:49:53 p.m.) - -hi show me contracts on my account - ---- - -### 🤖 AGENT (6:50:23 p.m.) - -Here are the contracts associated with your account (Twin ID: 6905): - -**Node Contracts** -| ID | Node ID | Type | Name | Project Name | -|----|---------|------|------|--------------| -| 1776775 | 920 | network | net_tuxktwe2 | Network | -| 1776776 | 920 | vm | vm | vm/vm | -| 1772537 | 8 | network | net_iksrno2f | Network | -| 1772538 | 8 | vm | vm | vm/vm | - -**Name Contracts** -No name contracts found. - -#### 🛠️ Workflow Steps - -**1. 📊 Analysis** - -Executing 'tfcmd get contracts' to retrieve and list all contracts associated with the current user's twin ID. - -**2. Command Executed** - -Command: tfcmd get contracts - -```bash -6:50PM INF starting peer session=tf-81159 twin=6905 -Node contracts: -ID Node ID Type Name Project Name -1776775 920 network net_tuxktwe2 Network -1776776 920 vm vm vm/vm -1772537 8 network net_iksrno2f Network -1772538 8 vm vm vm/vm - -Name contracts: -ID Name -``` - -**3. 💡 Answer** - -Here are the contracts associated with your account (Twin ID: 6905): - -**Node Contracts** -| ID | Node ID | Type | Name | Project Name | -|----|---------|------|------|--------------| -| 1776775 | 920 | network | net_tuxktwe2 | Network | -| 1776776 | 920 | vm | vm | vm/vm | -| 1772537 | 8 | network | net_iksrno2f | Network | -| 1772538 | 8 | vm | vm | vm/vm | - -**Name Contracts** -No name contracts found. - ---- - -### 👤 USER (6:50:29 p.m.) - -what is the newest contract - ---- - -### 👤 USER (6:52:06 p.m.) - -what is the newest contract - ---- - -### 🤖 AGENT (6:52:11 p.m.) - -The newest contract is **ID 1776776**. - -Details: -- **Type:** vm -- **Node ID:** 920 -- **Name:** vm -- **Project Name:** vm/vm - -(Contract IDs are sequential, so the highest number represents the most recently created contract.) - -#### 🛠️ Workflow Steps - -**1. 💡 Answer** - -The newest contract is **ID 1776776**. - -Details: -- **Type:** vm -- **Node ID:** 920 -- **Name:** vm -- **Project Name:** vm/vm - -(Contract IDs are sequential, so the highest number represents the most recently created contract.) - ---- - diff --git a/grid-agent-gui/frontend/src/components/ChatInterface.svelte b/grid-agent-gui/frontend/src/components/ChatInterface.svelte index 678cc4d..f28e20f 100644 --- a/grid-agent-gui/frontend/src/components/ChatInterface.svelte +++ b/grid-agent-gui/frontend/src/components/ChatInterface.svelte @@ -252,11 +252,11 @@ } function editMessage(index: number, newContent: string) { - // First update the message content + // First update the message content and timestamp messagesStore.update((msgs) => { const newMsgs = [...msgs]; if (newMsgs[index] && newMsgs[index].role === "user") { - newMsgs[index] = { ...newMsgs[index], content: newContent }; + newMsgs[index] = { ...newMsgs[index], content: newContent, timestamp: new Date().toISOString() }; } return newMsgs; });