jostlebot Claude Opus 4.5 commited on
Commit
df3f4f9
·
1 Parent(s): f6bfe81

Add 3-panel layout, verbosity toggle, and improved Receive Mode

Browse files

- Added right tool panel for tool conversations (separate from main chat)
- Tools now open in dedicated side panel instead of mixing with conversation
- Added "Detailed Mode" toggle for more/less explanation from tools
- Improved Receive Mode with clearer output format
- Cleaned up old workspace mode code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +42 -21
  2. static/index.html +324 -108
app.py CHANGED
@@ -153,36 +153,38 @@ Does this capture what you want them to understand? You can copy this to send wh
153
  Guide through ONE stage at a time. Never use first-person language.""",
154
 
155
  # TOOL 4: RECEIVE MODE (Enemy Image Transformation)
156
- "receive_mode": """You help people truly hear a message from their partner before reacting.
157
 
158
- When someone receives a triggering message, their nervous system goes into fight/flight/freeze.
159
- In that state, they can't hear the humanity in the other person.
160
- This tool slows things down to find feelings/needs underneath the partner's words.
161
 
162
  ABSOLUTE RULE - NO FIRST PERSON:
163
  - NEVER say "I notice", "I hear", "I see", "I sense"
164
  - ALWAYS use: "It sounds like...", "What's landing here is...", "There seems to be..."
165
 
166
- THE RECEIVE MODE PROCESS:
167
- 1. First, acknowledge how the message landed for THEM
168
- 2. Let them express their reaction fully (don't skip this!)
169
- 3. Help them find their own feelings and needs about it
170
- 4. THEN shift to curiosity about the sender
171
- 5. Help them guess the partner's feelings and needs
172
 
173
- ENEMY IMAGE TRANSFORMATION:
174
- - Everyone is always trying to meet needs (even with tragic strategies)
175
- - Seeing humanity doesn't mean condoning behavior
176
- - The goal is understanding, not agreement
177
 
178
- QUESTIONS TO OFFER:
179
- - "How did that message land?"
180
- - "What's happening in the body right now?"
181
- - "Setting aside their words, what might they be feeling?"
182
- - "What need might they be trying to meet?"
183
 
184
- End by returning to the relationship:
185
- "What feels clearest now? What do you want them to understand about where you are?"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
  Never:
188
  - Use first-person language
@@ -397,6 +399,7 @@ class ToolRequest(BaseModel):
397
  user_draft: Optional[str] = ""
398
  user_input: str
399
  stage: Optional[int] = 1 # For guided NVC multi-stage process
 
400
 
401
  class ChatRequest(BaseModel):
402
  messages: List[Message]
@@ -419,6 +422,24 @@ async def use_tool(request: ToolRequest):
419
 
420
  system_prompt = ARI_PROMPTS[request.tool]
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  # Build context-aware user message
423
  user_message = ""
424
  if request.partner_message:
 
153
  Guide through ONE stage at a time. Never use first-person language.""",
154
 
155
  # TOOL 4: RECEIVE MODE (Enemy Image Transformation)
156
+ "receive_mode": """You help people truly hear their partner's message before reacting.
157
 
158
+ PURPOSE: When triggered, people can't hear the humanity in the other person. This tool slows things down to find feelings/needs underneath the partner's words - both their OWN reaction and their partner's possible experience.
 
 
159
 
160
  ABSOLUTE RULE - NO FIRST PERSON:
161
  - NEVER say "I notice", "I hear", "I see", "I sense"
162
  - ALWAYS use: "It sounds like...", "What's landing here is...", "There seems to be..."
163
 
164
+ OUTPUT FORMAT (use this structure):
 
 
 
 
 
165
 
166
+ **How this might be landing:**
167
+ [Acknowledge the recipient's likely reaction - the trigger, the sting, what hurts]
 
 
168
 
169
+ **What YOU might be feeling:**
170
+ [Name 2-3 possible feelings the recipient could have]
 
 
 
171
 
172
+ **What YOU might be needing:**
173
+ [Name 2-3 possible needs underneath those feelings]
174
+
175
+ **Now, getting curious about THEM...**
176
+ [Shift to the partner's perspective]
177
+
178
+ **What THEY might be feeling:**
179
+ [Guess 2-3 feelings underneath the partner's words - not what they're saying, but the feeling driving it]
180
+
181
+ **What THEY might be needing:**
182
+ [Guess 2-3 needs the partner could be trying to meet, even if their strategy is painful]
183
+
184
+ **A bridge question:**
185
+ [Offer one question that could open understanding - e.g., "What was happening for you when...?" or "Help me understand what you're needing..."]
186
+
187
+ KEY PRINCIPLE: Everyone is always trying to meet needs (even with tragic strategies). Seeing humanity doesn't mean condoning behavior - it opens space for connection.
188
 
189
  Never:
190
  - Use first-person language
 
399
  user_draft: Optional[str] = ""
400
  user_input: str
401
  stage: Optional[int] = 1 # For guided NVC multi-stage process
402
+ verbose: Optional[bool] = False # When True, provide more explanation/psychoeducation
403
 
404
  class ChatRequest(BaseModel):
405
  messages: List[Message]
 
422
 
423
  system_prompt = ARI_PROMPTS[request.tool]
424
 
425
+ # Add verbosity modifier
426
+ if request.verbose:
427
+ system_prompt += """
428
+
429
+ DETAILED MODE (user wants more explanation):
430
+ - Include brief psychoeducation about WHY this approach works
431
+ - Offer context about what's happening relationally
432
+ - Include gentle skill-building notes where relevant
433
+ - Still keep language warm and accessible, not clinical"""
434
+ else:
435
+ system_prompt += """
436
+
437
+ MINIMAL MODE (user wants just the essentials):
438
+ - Be concise and direct
439
+ - Skip lengthy explanations
440
+ - Focus only on the practical output
441
+ - Keep responses brief (3-5 sentences when possible)"""
442
+
443
  # Build context-aware user message
444
  user_message = ""
445
  if request.partner_message:
static/index.html CHANGED
@@ -39,6 +39,151 @@
39
  height: 100vh;
40
  }
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  /* ===== TOOLKIT SIDEBAR ===== */
43
  .toolkit-sidebar {
44
  width: 340px;
@@ -305,14 +450,6 @@
305
  text-decoration: none;
306
  }
307
 
308
- /* ===== MAIN CONTENT ===== */
309
- .main-content {
310
- flex: 1;
311
- display: flex;
312
- flex-direction: column;
313
- overflow: hidden;
314
- }
315
-
316
  .content-header {
317
  padding: 16px 24px;
318
  border-bottom: 1px solid var(--border);
@@ -362,35 +499,6 @@
362
  background: var(--tend-color);
363
  }
364
 
365
- .active-tool-badge.workspace {
366
- background: var(--warning);
367
- color: #000;
368
- }
369
-
370
- .workspace-notice {
371
- background: rgba(245, 158, 11, 0.15);
372
- border: 1px solid var(--warning);
373
- border-radius: 8px;
374
- padding: 10px 14px;
375
- margin-bottom: 12px;
376
- font-size: 0.85rem;
377
- color: var(--warning);
378
- }
379
-
380
- .input-area.workspace-mode {
381
- background: rgba(245, 158, 11, 0.05);
382
- border-top-color: var(--warning);
383
- }
384
-
385
- .send-btn.workspace-mode {
386
- background: var(--warning);
387
- color: #000;
388
- }
389
-
390
- .send-btn.workspace-mode:hover {
391
- background: #d97706;
392
- }
393
-
394
  /* Conversation */
395
  .conversation-area {
396
  flex: 1;
@@ -760,6 +868,13 @@
760
 
761
  <!-- Toggle Features -->
762
  <div class="toggle-section">
 
 
 
 
 
 
 
763
  <div class="toggle-row">
764
  <label>Auto Feelings/Needs Reflection</label>
765
  <label class="toggle-switch">
@@ -917,6 +1032,19 @@
917
  </div>
918
  </div>
919
  </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
920
  </div>
921
 
922
  <script>
@@ -1037,6 +1165,131 @@
1037
  document.getElementById('partner-avatar').textContent = style[0].toUpperCase();
1038
  }
1039
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1040
  // ============================================================================
1041
  // TOOLS
1042
  // ============================================================================
@@ -1049,63 +1302,21 @@
1049
  if (selectedCard) selectedCard.classList.add('active');
1050
 
1051
  activeTool = tool;
1052
-
1053
- // Check if this is a workspace tool
1054
  inWorkspaceMode = WORKSPACE_TOOLS.includes(tool);
1055
 
1056
- // Update UI to show workspace mode
1057
  const badge = document.getElementById('active-tool-display');
1058
- const sendBtn = document.getElementById('send-btn');
1059
- const inputArea = document.querySelector('.input-area');
1060
-
1061
- if (inWorkspaceMode) {
1062
- badge.innerHTML = `<span class="active-tool-badge workspace">${TOOL_NAMES[tool]} (Workspace)</span>`;
1063
- sendBtn.textContent = 'Submit to Tool';
1064
- sendBtn.classList.add('workspace-mode');
1065
- inputArea.classList.add('workspace-mode');
1066
- // Show workspace notice
1067
- if (!document.getElementById('workspace-notice')) {
1068
- const notice = document.createElement('div');
1069
- notice.id = 'workspace-notice';
1070
- notice.className = 'workspace-notice';
1071
- notice.innerHTML = '<strong>Workspace Mode:</strong> Your messages go to this tool, not your partner. Exit by clicking another tool or TEND.';
1072
- inputArea.insertBefore(notice, inputArea.firstChild);
1073
- }
1074
- } else {
1075
- badge.innerHTML = `<span class="active-tool-badge ${tool === 'tend' ? 'tend' : ''}">${TOOL_NAMES[tool]}</span>`;
1076
- sendBtn.textContent = 'Send';
1077
- sendBtn.classList.remove('workspace-mode');
1078
- inputArea.classList.remove('workspace-mode');
1079
- // Remove workspace notice
1080
- const notice = document.getElementById('workspace-notice');
1081
- if (notice) notice.remove();
1082
- }
1083
 
 
1084
  if (tool !== 'tend') {
 
1085
  useTool(tool);
 
 
1086
  }
1087
  }
1088
 
1089
- function exitWorkspaceMode() {
1090
- inWorkspaceMode = false;
1091
- activeTool = 'tend';
1092
- const sendBtn = document.getElementById('send-btn');
1093
- const inputArea = document.querySelector('.input-area');
1094
- const badge = document.getElementById('active-tool-display');
1095
-
1096
- sendBtn.textContent = 'Send';
1097
- sendBtn.classList.remove('workspace-mode');
1098
- inputArea.classList.remove('workspace-mode');
1099
- badge.innerHTML = '<span class="active-tool-badge tend">TEND Ready</span>';
1100
-
1101
- const notice = document.getElementById('workspace-notice');
1102
- if (notice) notice.remove();
1103
-
1104
- document.querySelectorAll('.tool-card, .tend-tool').forEach(card => {
1105
- card.classList.remove('active');
1106
- });
1107
- }
1108
-
1109
  async function useTendTransform() {
1110
  const userInput = document.getElementById('user-input').value.trim();
1111
  if (!userInput) {
@@ -1123,6 +1334,12 @@
1123
  async function useTool(tool) {
1124
  const userInput = document.getElementById('user-input').value.trim();
1125
  let inputText = '';
 
 
 
 
 
 
1126
 
1127
  switch(tool) {
1128
  case 'tend':
@@ -1131,21 +1348,21 @@
1131
  break;
1132
  case 'receive_mode':
1133
  if (!lastPartnerMessage) {
1134
- addMessage('ari', 'Start the conversation first to use Receive Mode on their message.', TOOL_NAMES[tool]);
1135
  return;
1136
  }
1137
  inputText = userInput || 'Help me receive and understand this message before I react.';
1138
  break;
1139
  case 'pre_send_pause':
1140
  if (!userInput) {
1141
- addMessage('ari', 'Write a draft first, then use Pre-Send Pause to check your intention.', TOOL_NAMES[tool]);
1142
  return;
1143
  }
1144
  inputText = 'Help me pause and check my intention before sending this.';
1145
  break;
1146
  case 'observation_spotter':
1147
  if (!userInput) {
1148
- addMessage('ari', 'Write something first to check for judgments vs observations.', TOOL_NAMES[tool]);
1149
  return;
1150
  }
1151
  inputText = 'Help me find observations underneath any judgments in this.';
@@ -1153,7 +1370,7 @@
1153
  case 'feelings_needs':
1154
  const textToAnalyze = userInput || lastPartnerMessage;
1155
  if (!textToAnalyze) {
1156
- addMessage('ari', 'Need text to analyze - either their message or your draft.', TOOL_NAMES[tool]);
1157
  return;
1158
  }
1159
  inputText = `Identify the feelings and needs in this: "${textToAnalyze}"`;
@@ -1161,7 +1378,7 @@
1161
  case 'intensity_check':
1162
  const textToCheck = userInput || lastPartnerMessage;
1163
  if (!textToCheck) {
1164
- addMessage('ari', 'Need text to check intensity.', TOOL_NAMES[tool]);
1165
  return;
1166
  }
1167
  inputText = `Check the emotional intensity: "${textToCheck}"`;
@@ -1180,7 +1397,8 @@
1180
  break;
1181
  }
1182
 
1183
- addLoading();
 
1184
  try {
1185
  const response = await fetch('/api/tool', {
1186
  method: 'POST',
@@ -1190,26 +1408,30 @@
1190
  partner_message: lastPartnerMessage,
1191
  user_draft: userInput,
1192
  user_input: inputText,
1193
- stage: nvcStage
 
1194
  })
1195
  });
1196
 
1197
  const data = await response.json();
1198
- removeLoading();
1199
 
1200
  if (data.response) {
1201
- addMessage('ari', data.response, TOOL_NAMES[tool], tool === 'tend');
1202
-
1203
- // Auto feelings/needs if enabled
1204
- if (tool === 'tend' && document.getElementById('toggle-feelings').checked) {
1205
- setTimeout(() => autoFeelingsNeeds(userInput), 1000);
 
 
 
1206
  }
1207
  } else {
1208
- addMessage('ari', 'Could not process. Check API configuration.', 'Error');
1209
  }
1210
  } catch (error) {
1211
- removeLoading();
1212
- addMessage('ari', 'Connection error. Please try again.', 'Error');
1213
  }
1214
  }
1215
 
@@ -1321,16 +1543,10 @@ Based on this context, send an opening message that fits your attachment style a
1321
  conversationHistory = [];
1322
  conversationStarted = false;
1323
  lastPartnerMessage = '';
1324
- exitWorkspaceMode();
1325
  }
1326
 
1327
  async function sendMessage() {
1328
- // If in workspace mode, route to the active tool instead
1329
- if (inWorkspaceMode) {
1330
- await useTool(activeTool);
1331
- return;
1332
- }
1333
-
1334
  if (!conversationStarted) {
1335
  startConversation();
1336
  return;
 
39
  height: 100vh;
40
  }
41
 
42
+ /* THREE PANEL LAYOUT */
43
+ .main-content {
44
+ flex: 1;
45
+ display: flex;
46
+ flex-direction: column;
47
+ min-width: 0;
48
+ overflow: hidden;
49
+ }
50
+
51
+ .tool-panel {
52
+ width: 380px;
53
+ background: var(--bg-card);
54
+ border-left: 1px solid var(--border);
55
+ display: flex;
56
+ flex-direction: column;
57
+ transition: width 0.3s ease;
58
+ }
59
+
60
+ .tool-panel.collapsed {
61
+ width: 0;
62
+ overflow: hidden;
63
+ border-left: none;
64
+ }
65
+
66
+ .tool-panel-header {
67
+ padding: 16px 20px;
68
+ border-bottom: 1px solid var(--border);
69
+ display: flex;
70
+ justify-content: space-between;
71
+ align-items: center;
72
+ background: rgba(99, 102, 241, 0.1);
73
+ }
74
+
75
+ .tool-panel-header h3 {
76
+ font-size: 1rem;
77
+ color: var(--accent-light);
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 8px;
81
+ }
82
+
83
+ .tool-panel-header .close-btn {
84
+ background: transparent;
85
+ border: none;
86
+ color: var(--text-muted);
87
+ font-size: 1.2rem;
88
+ cursor: pointer;
89
+ padding: 4px 8px;
90
+ border-radius: 4px;
91
+ }
92
+
93
+ .tool-panel-header .close-btn:hover {
94
+ background: var(--bg-input);
95
+ color: var(--text-primary);
96
+ }
97
+
98
+ .tool-panel-content {
99
+ flex: 1;
100
+ overflow-y: auto;
101
+ padding: 16px 20px;
102
+ }
103
+
104
+ .tool-message {
105
+ margin-bottom: 16px;
106
+ padding: 14px 16px;
107
+ background: var(--bg-input);
108
+ border-radius: 10px;
109
+ border-left: 3px solid var(--accent);
110
+ }
111
+
112
+ .tool-message.user-input {
113
+ border-left-color: var(--text-muted);
114
+ background: rgba(255,255,255,0.03);
115
+ }
116
+
117
+ .tool-message .tool-label {
118
+ font-size: 0.7rem;
119
+ text-transform: uppercase;
120
+ letter-spacing: 0.05em;
121
+ color: var(--accent-light);
122
+ margin-bottom: 8px;
123
+ }
124
+
125
+ .tool-message.user-input .tool-label {
126
+ color: var(--text-muted);
127
+ }
128
+
129
+ .tool-message p {
130
+ line-height: 1.6;
131
+ color: var(--text-primary);
132
+ }
133
+
134
+ .tool-input-area {
135
+ padding: 16px 20px;
136
+ border-top: 1px solid var(--border);
137
+ background: var(--bg-dark);
138
+ }
139
+
140
+ .tool-input-area textarea {
141
+ width: 100%;
142
+ padding: 12px 14px;
143
+ background: var(--bg-input);
144
+ border: 1px solid var(--border);
145
+ border-radius: 8px;
146
+ color: var(--text-primary);
147
+ font-size: 0.9rem;
148
+ font-family: inherit;
149
+ resize: none;
150
+ margin-bottom: 10px;
151
+ }
152
+
153
+ .tool-input-area textarea:focus {
154
+ outline: none;
155
+ border-color: var(--accent);
156
+ }
157
+
158
+ .tool-input-area .tool-submit-btn {
159
+ width: 100%;
160
+ padding: 10px 16px;
161
+ background: var(--accent);
162
+ color: white;
163
+ border: none;
164
+ border-radius: 8px;
165
+ font-size: 0.9rem;
166
+ cursor: pointer;
167
+ }
168
+
169
+ .tool-input-area .tool-submit-btn:hover {
170
+ background: var(--accent-light);
171
+ }
172
+
173
+ @media (max-width: 1100px) {
174
+ .tool-panel {
175
+ position: fixed;
176
+ right: 0;
177
+ top: 0;
178
+ height: 100vh;
179
+ z-index: 100;
180
+ box-shadow: -4px 0 20px rgba(0,0,0,0.3);
181
+ }
182
+ .tool-panel.collapsed {
183
+ right: -380px;
184
+ }
185
+ }
186
+
187
  /* ===== TOOLKIT SIDEBAR ===== */
188
  .toolkit-sidebar {
189
  width: 340px;
 
450
  text-decoration: none;
451
  }
452
 
 
 
 
 
 
 
 
 
453
  .content-header {
454
  padding: 16px 24px;
455
  border-bottom: 1px solid var(--border);
 
499
  background: var(--tend-color);
500
  }
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  /* Conversation */
503
  .conversation-area {
504
  flex: 1;
 
868
 
869
  <!-- Toggle Features -->
870
  <div class="toggle-section">
871
+ <div class="toggle-row">
872
+ <label>Detailed Mode (more explanation)</label>
873
+ <label class="toggle-switch">
874
+ <input type="checkbox" id="toggle-verbose">
875
+ <span class="toggle-slider"></span>
876
+ </label>
877
+ </div>
878
  <div class="toggle-row">
879
  <label>Auto Feelings/Needs Reflection</label>
880
  <label class="toggle-switch">
 
1032
  </div>
1033
  </div>
1034
  </main>
1035
+
1036
+ <!-- TOOL PANEL (Right side - for tool conversations) -->
1037
+ <aside class="tool-panel collapsed" id="tool-panel">
1038
+ <div class="tool-panel-header">
1039
+ <h3><span id="tool-panel-icon">🛠</span> <span id="tool-panel-title">Tool Workspace</span></h3>
1040
+ <button class="close-btn" onclick="closeToolPanel()">×</button>
1041
+ </div>
1042
+ <div class="tool-panel-content" id="tool-conversation"></div>
1043
+ <div class="tool-input-area">
1044
+ <textarea id="tool-input" rows="2" placeholder="Continue working with this tool..." onkeydown="handleToolKeyDown(event)"></textarea>
1045
+ <button class="tool-submit-btn" onclick="submitToTool()">Submit</button>
1046
+ </div>
1047
+ </aside>
1048
  </div>
1049
 
1050
  <script>
 
1165
  document.getElementById('partner-avatar').textContent = style[0].toUpperCase();
1166
  }
1167
 
1168
+ // ============================================================================
1169
+ // TOOL PANEL (Right side)
1170
+ // ============================================================================
1171
+ const TOOL_ICONS = {
1172
+ tend: '✨',
1173
+ feelings_needs: '💜',
1174
+ guided_nvc: '📝',
1175
+ receive_mode: '👂',
1176
+ pre_send_pause: '⏸',
1177
+ observation_spotter: '🔍',
1178
+ pure_questioning: '❓',
1179
+ somatic_checkin: '🫁',
1180
+ intensity_check: '📊',
1181
+ repair_support: '🙏'
1182
+ };
1183
+
1184
+ function openToolPanel(tool) {
1185
+ const panel = document.getElementById('tool-panel');
1186
+ const title = document.getElementById('tool-panel-title');
1187
+ const icon = document.getElementById('tool-panel-icon');
1188
+
1189
+ panel.classList.remove('collapsed');
1190
+ title.textContent = TOOL_NAMES[tool] || 'Tool Workspace';
1191
+ icon.textContent = TOOL_ICONS[tool] || '🛠';
1192
+
1193
+ // Clear previous conversation if switching tools
1194
+ document.getElementById('tool-conversation').innerHTML = '';
1195
+ document.getElementById('tool-input').value = '';
1196
+ document.getElementById('tool-input').focus();
1197
+ }
1198
+
1199
+ function closeToolPanel() {
1200
+ const panel = document.getElementById('tool-panel');
1201
+ panel.classList.add('collapsed');
1202
+ activeTool = 'tend';
1203
+ inWorkspaceMode = false;
1204
+
1205
+ // Deselect tool cards
1206
+ document.querySelectorAll('.tool-card').forEach(card => {
1207
+ card.classList.remove('active');
1208
+ });
1209
+
1210
+ // Update badge
1211
+ const badge = document.getElementById('active-tool-display');
1212
+ badge.innerHTML = '<span class="active-tool-badge tend">TEND Ready</span>';
1213
+ }
1214
+
1215
+ function addToolMessage(type, content, toolName = null) {
1216
+ const conversation = document.getElementById('tool-conversation');
1217
+ const div = document.createElement('div');
1218
+ div.className = `tool-message ${type === 'user' ? 'user-input' : ''}`;
1219
+
1220
+ const label = document.createElement('div');
1221
+ label.className = 'tool-label';
1222
+ label.textContent = type === 'user' ? 'You' : (toolName || activeTool);
1223
+ div.appendChild(label);
1224
+
1225
+ const text = document.createElement('p');
1226
+ text.innerHTML = formatMessage(content);
1227
+ div.appendChild(text);
1228
+
1229
+ conversation.appendChild(div);
1230
+ conversation.scrollTop = conversation.scrollHeight;
1231
+ }
1232
+
1233
+ function addToolLoading() {
1234
+ const conversation = document.getElementById('tool-conversation');
1235
+ const div = document.createElement('div');
1236
+ div.className = 'loading';
1237
+ div.id = 'tool-loading';
1238
+ div.innerHTML = '<div class="spinner"></div> Processing...';
1239
+ conversation.appendChild(div);
1240
+ conversation.scrollTop = conversation.scrollHeight;
1241
+ }
1242
+
1243
+ function removeToolLoading() {
1244
+ const el = document.getElementById('tool-loading');
1245
+ if (el) el.remove();
1246
+ }
1247
+
1248
+ async function submitToTool() {
1249
+ const input = document.getElementById('tool-input');
1250
+ const userInput = input.value.trim();
1251
+ if (!userInput) return;
1252
+
1253
+ input.value = '';
1254
+ addToolMessage('user', userInput);
1255
+
1256
+ addToolLoading();
1257
+ const isVerbose = document.getElementById('toggle-verbose')?.checked || false;
1258
+ try {
1259
+ const response = await fetch('/api/tool', {
1260
+ method: 'POST',
1261
+ headers: { 'Content-Type': 'application/json' },
1262
+ body: JSON.stringify({
1263
+ tool: activeTool,
1264
+ partner_message: lastPartnerMessage,
1265
+ user_draft: document.getElementById('user-input').value.trim(),
1266
+ user_input: userInput,
1267
+ stage: nvcStage,
1268
+ verbose: isVerbose
1269
+ })
1270
+ });
1271
+
1272
+ const data = await response.json();
1273
+ removeToolLoading();
1274
+
1275
+ if (data.response) {
1276
+ addToolMessage('tool', data.response, TOOL_NAMES[activeTool]);
1277
+ } else {
1278
+ addToolMessage('tool', 'Could not process. Check API configuration.', 'Error');
1279
+ }
1280
+ } catch (error) {
1281
+ removeToolLoading();
1282
+ addToolMessage('tool', 'Connection error. Please try again.', 'Error');
1283
+ }
1284
+ }
1285
+
1286
+ function handleToolKeyDown(event) {
1287
+ if (event.key === 'Enter' && !event.shiftKey) {
1288
+ event.preventDefault();
1289
+ submitToTool();
1290
+ }
1291
+ }
1292
+
1293
  // ============================================================================
1294
  // TOOLS
1295
  // ============================================================================
 
1302
  if (selectedCard) selectedCard.classList.add('active');
1303
 
1304
  activeTool = tool;
 
 
1305
  inWorkspaceMode = WORKSPACE_TOOLS.includes(tool);
1306
 
1307
+ // Update active tool badge
1308
  const badge = document.getElementById('active-tool-display');
1309
+ badge.innerHTML = `<span class="active-tool-badge ${tool === 'tend' ? 'tend' : ''}">${TOOL_NAMES[tool]}</span>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1310
 
1311
+ // For non-TEND tools, open the tool panel
1312
  if (tool !== 'tend') {
1313
+ openToolPanel(tool);
1314
  useTool(tool);
1315
+ } else {
1316
+ closeToolPanel();
1317
  }
1318
  }
1319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1320
  async function useTendTransform() {
1321
  const userInput = document.getElementById('user-input').value.trim();
1322
  if (!userInput) {
 
1334
  async function useTool(tool) {
1335
  const userInput = document.getElementById('user-input').value.trim();
1336
  let inputText = '';
1337
+ const isTend = (tool === 'tend');
1338
+
1339
+ // For non-TEND tools, show in tool panel
1340
+ const addMsg = isTend ? addMessage : addToolMessage;
1341
+ const addLoad = isTend ? addLoading : addToolLoading;
1342
+ const removeLoad = isTend ? removeLoading : removeToolLoading;
1343
 
1344
  switch(tool) {
1345
  case 'tend':
 
1348
  break;
1349
  case 'receive_mode':
1350
  if (!lastPartnerMessage) {
1351
+ addToolMessage('tool', 'Start the conversation first to use Receive Mode on their message.', TOOL_NAMES[tool]);
1352
  return;
1353
  }
1354
  inputText = userInput || 'Help me receive and understand this message before I react.';
1355
  break;
1356
  case 'pre_send_pause':
1357
  if (!userInput) {
1358
+ addToolMessage('tool', 'Write a draft in the main chat first, then use Pre-Send Pause to check your intention.', TOOL_NAMES[tool]);
1359
  return;
1360
  }
1361
  inputText = 'Help me pause and check my intention before sending this.';
1362
  break;
1363
  case 'observation_spotter':
1364
  if (!userInput) {
1365
+ addToolMessage('tool', 'Write something in the main chat first to check for judgments vs observations.', TOOL_NAMES[tool]);
1366
  return;
1367
  }
1368
  inputText = 'Help me find observations underneath any judgments in this.';
 
1370
  case 'feelings_needs':
1371
  const textToAnalyze = userInput || lastPartnerMessage;
1372
  if (!textToAnalyze) {
1373
+ addToolMessage('tool', 'Need text to analyze - either their message or your draft.', TOOL_NAMES[tool]);
1374
  return;
1375
  }
1376
  inputText = `Identify the feelings and needs in this: "${textToAnalyze}"`;
 
1378
  case 'intensity_check':
1379
  const textToCheck = userInput || lastPartnerMessage;
1380
  if (!textToCheck) {
1381
+ addToolMessage('tool', 'Need text to check intensity.', TOOL_NAMES[tool]);
1382
  return;
1383
  }
1384
  inputText = `Check the emotional intensity: "${textToCheck}"`;
 
1397
  break;
1398
  }
1399
 
1400
+ addLoad();
1401
+ const isVerbose = document.getElementById('toggle-verbose')?.checked || false;
1402
  try {
1403
  const response = await fetch('/api/tool', {
1404
  method: 'POST',
 
1408
  partner_message: lastPartnerMessage,
1409
  user_draft: userInput,
1410
  user_input: inputText,
1411
+ stage: nvcStage,
1412
+ verbose: isVerbose
1413
  })
1414
  });
1415
 
1416
  const data = await response.json();
1417
+ removeLoad();
1418
 
1419
  if (data.response) {
1420
+ if (isTend) {
1421
+ addMessage('ari', data.response, TOOL_NAMES[tool], true);
1422
+ // Auto feelings/needs if enabled
1423
+ if (document.getElementById('toggle-feelings').checked) {
1424
+ setTimeout(() => autoFeelingsNeeds(userInput), 1000);
1425
+ }
1426
+ } else {
1427
+ addToolMessage('tool', data.response, TOOL_NAMES[tool]);
1428
  }
1429
  } else {
1430
+ addMsg(isTend ? 'ari' : 'tool', 'Could not process. Check API configuration.', 'Error');
1431
  }
1432
  } catch (error) {
1433
+ removeLoad();
1434
+ addMsg(isTend ? 'ari' : 'tool', 'Connection error. Please try again.', 'Error');
1435
  }
1436
  }
1437
 
 
1543
  conversationHistory = [];
1544
  conversationStarted = false;
1545
  lastPartnerMessage = '';
1546
+ closeToolPanel();
1547
  }
1548
 
1549
  async function sendMessage() {
 
 
 
 
 
 
1550
  if (!conversationStarted) {
1551
  startConversation();
1552
  return;