tryassay 0.22.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo/css/style.css +495 -836
- package/demo/index.html +40 -184
- package/demo/js/chat.js +385 -142
- package/demo/js/preview.js +456 -0
- package/demo/js/sse-client.js +262 -135
- package/demo/js/state.js +11 -1
- package/demo/js/timeline.js +57 -371
- package/dist/api/server.d.ts +2 -0
- package/dist/api/server.js +63 -19
- package/dist/api/server.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/assess.d.ts +2 -0
- package/dist/commands/assess.js +132 -164
- package/dist/commands/assess.js.map +1 -1
- package/dist/commands/demo.js +259 -9
- package/dist/commands/demo.js.map +1 -1
- package/dist/lib/__tests__/arithmetic-quick-test.d.ts +6 -0
- package/dist/lib/__tests__/arithmetic-quick-test.js +197 -0
- package/dist/lib/__tests__/arithmetic-quick-test.js.map +1 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.d.ts +13 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.js +284 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.js.map +1 -0
- package/dist/lib/__tests__/arithmetic-value-demo.d.ts +10 -0
- package/dist/lib/__tests__/arithmetic-value-demo.js +193 -0
- package/dist/lib/__tests__/arithmetic-value-demo.js.map +1 -0
- package/dist/lib/__tests__/flow-to-claims.test.d.ts +1 -0
- package/dist/lib/__tests__/flow-to-claims.test.js +91 -0
- package/dist/lib/__tests__/flow-to-claims.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.d.ts +9 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.js +391 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.d.ts +7 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.js +318 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.js.map +1 -0
- package/dist/lib/__tests__/intent-extractor.test.d.ts +1 -0
- package/dist/lib/__tests__/intent-extractor.test.js +97 -0
- package/dist/lib/__tests__/intent-extractor.test.js.map +1 -0
- package/dist/lib/__tests__/intent-reviewer.test.d.ts +1 -0
- package/dist/lib/__tests__/intent-reviewer.test.js +55 -0
- package/dist/lib/__tests__/intent-reviewer.test.js.map +1 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.d.ts +11 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.js +224 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.js.map +1 -0
- package/dist/lib/anthropic.js +25 -33
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/assessment-reporter.js +9 -13
- package/dist/lib/assessment-reporter.js.map +1 -1
- package/dist/lib/claim-extractor.js +10 -19
- package/dist/lib/claim-extractor.js.map +1 -1
- package/dist/lib/code-verifier.js +16 -36
- package/dist/lib/code-verifier.js.map +1 -1
- package/dist/lib/constraint-engine.js +10 -19
- package/dist/lib/constraint-engine.js.map +1 -1
- package/dist/lib/formal-verifier.d.ts +1 -1
- package/dist/lib/formal-verifier.js +454 -0
- package/dist/lib/formal-verifier.js.map +1 -1
- package/dist/lib/guided-generator.js +19 -37
- package/dist/lib/guided-generator.js.map +1 -1
- package/dist/lib/intent-extractor.d.ts +47 -0
- package/dist/lib/intent-extractor.js +427 -0
- package/dist/lib/intent-extractor.js.map +1 -0
- package/dist/lib/intent-reviewer.d.ts +14 -0
- package/dist/lib/intent-reviewer.js +148 -0
- package/dist/lib/intent-reviewer.js.map +1 -0
- package/dist/lib/intent-types.d.ts +89 -0
- package/dist/lib/intent-types.js +5 -0
- package/dist/lib/intent-types.js.map +1 -0
- package/dist/lib/inventory-extractor.js +9 -22
- package/dist/lib/inventory-extractor.js.map +1 -1
- package/dist/lib/llm-provider.d.ts +23 -0
- package/dist/lib/llm-provider.js +130 -0
- package/dist/lib/llm-provider.js.map +1 -0
- package/dist/lib/remediator.js +20 -28
- package/dist/lib/remediator.js.map +1 -1
- package/dist/lib/requirements-generator.js +14 -19
- package/dist/lib/requirements-generator.js.map +1 -1
- package/dist/lib/spec-synthesizer.js +10 -19
- package/dist/lib/spec-synthesizer.js.map +1 -1
- package/dist/runtime/app-create-orchestrator.d.ts +5 -1
- package/dist/runtime/app-create-orchestrator.js +114 -39
- package/dist/runtime/app-create-orchestrator.js.map +1 -1
- package/dist/runtime/check-catalog.js +5 -3
- package/dist/runtime/check-catalog.js.map +1 -1
- package/dist/runtime/check-definitions.d.ts +10 -0
- package/dist/runtime/check-definitions.js +52 -2
- package/dist/runtime/check-definitions.js.map +1 -1
- package/dist/runtime/composition-verifier.js +8 -12
- package/dist/runtime/composition-verifier.js.map +1 -1
- package/dist/runtime/gap-detector.js +8 -10
- package/dist/runtime/gap-detector.js.map +1 -1
- package/dist/runtime/input-validator.d.ts +7 -0
- package/dist/runtime/input-validator.js +162 -0
- package/dist/runtime/input-validator.js.map +1 -0
- package/dist/runtime/model-router.d.ts +10 -0
- package/dist/runtime/model-router.js +42 -0
- package/dist/runtime/model-router.js.map +1 -0
- package/dist/runtime/pattern-extractor.js +8 -10
- package/dist/runtime/pattern-extractor.js.map +1 -1
- package/dist/runtime/planner.js +11 -16
- package/dist/runtime/planner.js.map +1 -1
- package/dist/runtime/prompt-guard.d.ts +2 -0
- package/dist/runtime/prompt-guard.js +180 -0
- package/dist/runtime/prompt-guard.js.map +1 -0
- package/dist/runtime/prompt-safety-analyzer.js +8 -13
- package/dist/runtime/prompt-safety-analyzer.js.map +1 -1
- package/dist/runtime/reasoner.js +19 -33
- package/dist/runtime/reasoner.js.map +1 -1
- package/dist/runtime/rule-meta-verifier.js +9 -11
- package/dist/runtime/rule-meta-verifier.js.map +1 -1
- package/dist/runtime/safe-executor.d.ts +23 -0
- package/dist/runtime/safe-executor.js +151 -0
- package/dist/runtime/safe-executor.js.map +1 -0
- package/dist/runtime/specialized-agent.js +10 -14
- package/dist/runtime/specialized-agent.js.map +1 -1
- package/dist/runtime/strategy-library.js +8 -10
- package/dist/runtime/strategy-library.js.map +1 -1
- package/dist/runtime/supabase-experience-store.js.map +1 -1
- package/dist/runtime/supabase-provisioner.d.ts +35 -0
- package/dist/runtime/supabase-provisioner.js +192 -0
- package/dist/runtime/supabase-provisioner.js.map +1 -0
- package/dist/runtime/types.d.ts +88 -0
- package/dist/sdk/forward-verify.js +16 -33
- package/dist/sdk/forward-verify.js.map +1 -1
- package/package.json +1 -1
- package/demo/data/demo-events.json +0 -103
- package/demo/js/demo-mode.js +0 -107
- package/demo/js/orb.js +0 -634
- package/demo/js/question-cards.js +0 -207
- package/demo/js/voice.js +0 -154
package/demo/js/chat.js
CHANGED
|
@@ -1,98 +1,62 @@
|
|
|
1
|
-
// chat.js —
|
|
2
|
-
// Renders in #
|
|
1
|
+
// chat.js — Chat-first interface for requirements gathering + build
|
|
2
|
+
// Renders in #chat-panel (left side of split screen)
|
|
3
3
|
|
|
4
4
|
const AppState = window.AppState;
|
|
5
5
|
|
|
6
6
|
let chatConfig = { apiBase: '', apiKey: '' };
|
|
7
7
|
let currentSessionId = null;
|
|
8
8
|
let pendingCardAnswers = [];
|
|
9
|
+
let pendingAttachments = [];
|
|
10
|
+
|
|
11
|
+
const MAX_FILE_SIZE = 500 * 1024; // 500KB
|
|
12
|
+
const ALLOWED_EXTENSIONS = ['.txt', '.md', '.markdown', '.csv', '.json'];
|
|
13
|
+
|
|
14
|
+
// ── Public API ──────────────────────────────────────────────
|
|
9
15
|
|
|
10
|
-
/**
|
|
11
|
-
* Configure API connection (called from sse-client.js)
|
|
12
|
-
*/
|
|
13
16
|
export function configureChat(apiBase, apiKey) {
|
|
14
17
|
chatConfig.apiBase = apiBase || '';
|
|
15
18
|
chatConfig.apiKey = apiKey || '';
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
export function initChat(apiBase, apiKey) {
|
|
22
|
+
chatConfig.apiBase = apiBase || '';
|
|
23
|
+
chatConfig.apiKey = apiKey || '';
|
|
24
|
+
|
|
25
|
+
renderWelcomeMessage();
|
|
26
|
+
wireInputHandlers();
|
|
27
|
+
}
|
|
25
28
|
|
|
29
|
+
export function openChat(sessionId) {
|
|
26
30
|
currentSessionId = sessionId;
|
|
27
31
|
pendingCardAnswers = [];
|
|
28
|
-
|
|
29
|
-
overlay.innerHTML = `
|
|
30
|
-
<div class="chat-container">
|
|
31
|
-
<div class="chat-messages" id="chat-messages"></div>
|
|
32
|
-
<div class="chat-input-bar">
|
|
33
|
-
<input
|
|
34
|
-
type="text"
|
|
35
|
-
id="chat-input"
|
|
36
|
-
placeholder="Type a message..."
|
|
37
|
-
autocomplete="off"
|
|
38
|
-
>
|
|
39
|
-
<button id="chat-send-btn">Send</button>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
`;
|
|
43
|
-
|
|
44
|
-
overlay.classList.add('visible');
|
|
45
|
-
|
|
46
|
-
// Bind send button
|
|
47
|
-
const sendBtn = document.getElementById('chat-send-btn');
|
|
48
|
-
const chatInput = document.getElementById('chat-input');
|
|
49
|
-
|
|
50
|
-
sendBtn.addEventListener('click', () => sendMessage());
|
|
51
|
-
chatInput.addEventListener('keydown', (e) => {
|
|
52
|
-
if (e.key === 'Enter') sendMessage();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Hide the header input bar during chat
|
|
56
|
-
const header = document.getElementById('header');
|
|
57
|
-
if (header) header.classList.add('chat-active');
|
|
58
32
|
}
|
|
59
33
|
|
|
60
|
-
/**
|
|
61
|
-
* Add an architect message to the chat.
|
|
62
|
-
* Called on each chat_message SSE event.
|
|
63
|
-
*/
|
|
64
34
|
export function addArchitectMessage(data) {
|
|
65
|
-
|
|
66
|
-
if (!messagesEl)
|
|
67
|
-
|
|
35
|
+
const messagesEl = document.getElementById('chat-messages');
|
|
36
|
+
if (!messagesEl) return;
|
|
37
|
+
|
|
38
|
+
if (!currentSessionId) {
|
|
68
39
|
openChat(data.sessionId || window.__appId);
|
|
69
|
-
messagesEl = document.getElementById('chat-messages');
|
|
70
|
-
if (!messagesEl) return; // openChat failed, bail
|
|
71
40
|
}
|
|
72
41
|
|
|
73
42
|
const msgDiv = document.createElement('div');
|
|
74
43
|
msgDiv.className = 'chat-msg chat-msg-architect';
|
|
75
44
|
|
|
76
|
-
// Message text
|
|
77
45
|
const textDiv = document.createElement('div');
|
|
78
46
|
textDiv.className = 'chat-msg-text';
|
|
79
47
|
textDiv.textContent = data.content;
|
|
80
48
|
msgDiv.appendChild(textDiv);
|
|
81
49
|
|
|
82
|
-
// Structured cards (if any)
|
|
83
50
|
if (data.cards && data.cards.length > 0) {
|
|
84
51
|
const cardsDiv = document.createElement('div');
|
|
85
52
|
cardsDiv.className = 'chat-cards';
|
|
86
|
-
|
|
87
53
|
for (const card of data.cards) {
|
|
88
54
|
const cardEl = renderCard(card);
|
|
89
55
|
cardsDiv.appendChild(cardEl);
|
|
90
56
|
}
|
|
91
|
-
|
|
92
57
|
msgDiv.appendChild(cardsDiv);
|
|
93
58
|
}
|
|
94
59
|
|
|
95
|
-
// "Start Building" button (if ready)
|
|
96
60
|
if (data.readiness && data.readiness.ready) {
|
|
97
61
|
const buildBtn = document.createElement('button');
|
|
98
62
|
buildBtn.className = 'chat-build-btn';
|
|
@@ -111,25 +75,374 @@ export function addArchitectMessage(data) {
|
|
|
111
75
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
112
76
|
}
|
|
113
77
|
|
|
114
|
-
/**
|
|
115
|
-
* Close the chat and proceed to build phase.
|
|
116
|
-
*/
|
|
117
78
|
export function closeChat() {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
79
|
+
currentSessionId = null;
|
|
80
|
+
pendingCardAnswers = [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function lockChat() {
|
|
84
|
+
const inputArea = document.getElementById('chat-input-area');
|
|
85
|
+
if (inputArea) inputArea.classList.add('hidden');
|
|
86
|
+
|
|
87
|
+
const messagesEl = document.getElementById('chat-messages');
|
|
88
|
+
if (messagesEl) {
|
|
89
|
+
const separator = document.createElement('div');
|
|
90
|
+
separator.className = 'activity-separator';
|
|
91
|
+
separator.innerHTML = '<span>Build started</span>';
|
|
92
|
+
messagesEl.appendChild(separator);
|
|
93
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
122
94
|
}
|
|
95
|
+
}
|
|
123
96
|
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
97
|
+
export function unlockChat() {
|
|
98
|
+
const inputArea = document.getElementById('chat-input-area');
|
|
99
|
+
if (inputArea) inputArea.classList.remove('hidden');
|
|
100
|
+
|
|
101
|
+
const chatInput = document.getElementById('chat-input');
|
|
102
|
+
const sendBtn = document.getElementById('chat-send-btn');
|
|
103
|
+
const attachBtn = document.getElementById('attach-btn');
|
|
104
|
+
|
|
105
|
+
if (chatInput) {
|
|
106
|
+
chatInput.disabled = false;
|
|
107
|
+
chatInput.value = '';
|
|
108
|
+
chatInput.placeholder = 'Build something else...';
|
|
109
|
+
}
|
|
110
|
+
if (sendBtn) sendBtn.disabled = false;
|
|
111
|
+
if (attachBtn) attachBtn.disabled = false;
|
|
127
112
|
|
|
128
113
|
currentSessionId = null;
|
|
129
|
-
|
|
114
|
+
pendingAttachments = [];
|
|
115
|
+
clearAttachmentChips();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Initialization ──────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
function renderWelcomeMessage() {
|
|
121
|
+
const messagesEl = document.getElementById('chat-messages');
|
|
122
|
+
if (!messagesEl) return;
|
|
123
|
+
|
|
124
|
+
const msgDiv = document.createElement('div');
|
|
125
|
+
msgDiv.className = 'chat-msg chat-msg-architect';
|
|
126
|
+
|
|
127
|
+
const textDiv = document.createElement('div');
|
|
128
|
+
textDiv.className = 'chat-msg-text';
|
|
129
|
+
textDiv.textContent = "What would you like to build? Describe your app and I'll help design it. You can also attach a specification document.";
|
|
130
|
+
msgDiv.appendChild(textDiv);
|
|
131
|
+
|
|
132
|
+
messagesEl.appendChild(msgDiv);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function wireInputHandlers() {
|
|
136
|
+
const sendBtn = document.getElementById('chat-send-btn');
|
|
137
|
+
const chatInput = document.getElementById('chat-input');
|
|
138
|
+
const attachBtn = document.getElementById('attach-btn');
|
|
139
|
+
const fileInput = document.getElementById('file-input');
|
|
140
|
+
|
|
141
|
+
if (sendBtn) {
|
|
142
|
+
sendBtn.addEventListener('click', () => handleSend());
|
|
143
|
+
}
|
|
144
|
+
if (chatInput) {
|
|
145
|
+
chatInput.addEventListener('keydown', (e) => {
|
|
146
|
+
if (e.key === 'Enter') handleSend();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (attachBtn && fileInput) {
|
|
150
|
+
attachBtn.addEventListener('click', () => fileInput.click());
|
|
151
|
+
fileInput.addEventListener('change', (e) => handleFileSelected(e));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Send Routing ────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
function handleSend() {
|
|
158
|
+
if (currentSessionId === null) {
|
|
159
|
+
sendInitialMessage();
|
|
160
|
+
} else {
|
|
161
|
+
sendMessage();
|
|
162
|
+
}
|
|
130
163
|
}
|
|
131
164
|
|
|
132
|
-
// ──
|
|
165
|
+
// ── Initial Message (creates session) ───────────────────────
|
|
166
|
+
|
|
167
|
+
function buildAppDescription(prompt) {
|
|
168
|
+
const name = prompt
|
|
169
|
+
.replace(/^(build|create|make|generate)\s+(a|an|me|my)?\s*/i, '')
|
|
170
|
+
.split(/[.,!?\n]/)[0]
|
|
171
|
+
.trim()
|
|
172
|
+
.slice(0, 50)
|
|
173
|
+
.replace(/\s+/g, '-')
|
|
174
|
+
.toLowerCase() || 'my-app';
|
|
175
|
+
|
|
176
|
+
const features = [];
|
|
177
|
+
const lower = prompt.toLowerCase();
|
|
178
|
+
if (/auth|login|signup|sign.?up|register|password/.test(lower)) features.push('User authentication with email/password');
|
|
179
|
+
if (/todo|task|checklist/.test(lower)) features.push('Todo/task CRUD with completion tracking');
|
|
180
|
+
if (/blog|post|article|cms/.test(lower)) features.push('Blog post management with markdown');
|
|
181
|
+
if (/chat|message|real.?time/.test(lower)) features.push('Real-time chat messaging');
|
|
182
|
+
if (/e.?commerce|shop|cart|product|payment/.test(lower)) features.push('Product catalog with shopping cart');
|
|
183
|
+
if (/dashboard|admin|analytics/.test(lower)) features.push('Admin dashboard with analytics');
|
|
184
|
+
if (/api|rest|endpoint/.test(lower)) features.push('REST API endpoints');
|
|
185
|
+
if (/search|filter/.test(lower)) features.push('Search and filtering');
|
|
186
|
+
if (/upload|file|image/.test(lower)) features.push('File upload handling');
|
|
187
|
+
if (/notification|email|alert/.test(lower)) features.push('Notification system');
|
|
188
|
+
if (features.length === 0) features.push(prompt);
|
|
189
|
+
|
|
190
|
+
let framework = 'next.js';
|
|
191
|
+
let database = 'supabase';
|
|
192
|
+
if (/express|node/.test(lower)) framework = 'express';
|
|
193
|
+
if (/svelte/.test(lower)) framework = 'sveltekit';
|
|
194
|
+
if (/electron|desktop/.test(lower)) framework = 'electron';
|
|
195
|
+
if (/postgres(?!.*supa)/.test(lower)) database = 'postgresql';
|
|
196
|
+
if (/sqlite/.test(lower)) database = 'sqlite';
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
name,
|
|
200
|
+
description: prompt,
|
|
201
|
+
techStack: { language: 'typescript', framework, database, styling: 'tailwind' },
|
|
202
|
+
features,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function sendInitialMessage() {
|
|
207
|
+
const chatInput = document.getElementById('chat-input');
|
|
208
|
+
if (!chatInput) return;
|
|
209
|
+
|
|
210
|
+
const text = chatInput.value.trim();
|
|
211
|
+
if (!text && pendingAttachments.length === 0) return;
|
|
212
|
+
|
|
213
|
+
// Render user bubble
|
|
214
|
+
renderUserBubble(text, pendingAttachments);
|
|
215
|
+
|
|
216
|
+
// Disable input during session creation
|
|
217
|
+
chatInput.value = '';
|
|
218
|
+
chatInput.disabled = true;
|
|
219
|
+
const sendBtn = document.getElementById('chat-send-btn');
|
|
220
|
+
const attachBtn = document.getElementById('attach-btn');
|
|
221
|
+
if (sendBtn) sendBtn.disabled = true;
|
|
222
|
+
if (attachBtn) attachBtn.disabled = true;
|
|
223
|
+
|
|
224
|
+
AppState.reset();
|
|
225
|
+
|
|
226
|
+
const appDesc = buildAppDescription(text);
|
|
227
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
228
|
+
if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
|
|
229
|
+
|
|
230
|
+
const body = { ...appDesc, autoApprove: false };
|
|
231
|
+
if (pendingAttachments.length > 0) {
|
|
232
|
+
body.documents = pendingAttachments.map(a => ({ name: a.name, content: a.content }));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const attachments = [...pendingAttachments];
|
|
236
|
+
pendingAttachments = [];
|
|
237
|
+
clearAttachmentChips();
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const res = await fetch(`${chatConfig.apiBase}/api/v1/app/create`, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers,
|
|
243
|
+
body: JSON.stringify(body),
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (!res.ok) {
|
|
247
|
+
const err = await res.text();
|
|
248
|
+
throw new Error(`API ${res.status}: ${err}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const data = await res.json();
|
|
252
|
+
window.__appId = data.id || data.sessionId;
|
|
253
|
+
currentSessionId = window.__appId;
|
|
254
|
+
|
|
255
|
+
window.__connectSSE(window.__appId);
|
|
256
|
+
AppState.update({ phase: 'initializing' });
|
|
257
|
+
AppState.addLog({ type: 'system', message: `Session started: ${window.__appId}` });
|
|
258
|
+
|
|
259
|
+
// Re-enable input for chat conversation
|
|
260
|
+
chatInput.disabled = false;
|
|
261
|
+
chatInput.placeholder = 'Type a message...';
|
|
262
|
+
if (sendBtn) sendBtn.disabled = false;
|
|
263
|
+
if (attachBtn) attachBtn.disabled = false;
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error('[Chat] Session creation error:', err.message);
|
|
266
|
+
showErrorInChat(`Failed to start session: ${err.message}`);
|
|
267
|
+
chatInput.disabled = false;
|
|
268
|
+
if (sendBtn) sendBtn.disabled = false;
|
|
269
|
+
if (attachBtn) attachBtn.disabled = false;
|
|
270
|
+
// Restore attachments on failure
|
|
271
|
+
pendingAttachments = attachments;
|
|
272
|
+
if (attachments.length > 0) {
|
|
273
|
+
for (const a of attachments) renderAttachmentChip(a.name);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Ongoing Chat Messages ───────────────────────────────────
|
|
279
|
+
|
|
280
|
+
async function sendMessage() {
|
|
281
|
+
const input = document.getElementById('chat-input');
|
|
282
|
+
if (!input) return;
|
|
283
|
+
|
|
284
|
+
const message = input.value.trim();
|
|
285
|
+
if (!message && pendingCardAnswers.length === 0 && pendingAttachments.length === 0) return;
|
|
286
|
+
|
|
287
|
+
const sessionId = currentSessionId || window.__appId;
|
|
288
|
+
if (!sessionId) return;
|
|
289
|
+
|
|
290
|
+
renderUserBubble(message, pendingAttachments);
|
|
291
|
+
input.value = '';
|
|
292
|
+
|
|
293
|
+
const body = { message: message || '' };
|
|
294
|
+
if (pendingCardAnswers.length > 0) {
|
|
295
|
+
body.cardAnswers = [...pendingCardAnswers];
|
|
296
|
+
pendingCardAnswers = [];
|
|
297
|
+
}
|
|
298
|
+
if (pendingAttachments.length > 0) {
|
|
299
|
+
body.documents = pendingAttachments.map(a => ({ name: a.name, content: a.content }));
|
|
300
|
+
pendingAttachments = [];
|
|
301
|
+
clearAttachmentChips();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Disable card options after sending
|
|
305
|
+
const allCards = document.querySelectorAll('.chat-card');
|
|
306
|
+
allCards.forEach(card => {
|
|
307
|
+
card.querySelectorAll('.chat-card-option').forEach(opt => {
|
|
308
|
+
opt.style.pointerEvents = 'none';
|
|
309
|
+
opt.style.opacity = '0.6';
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
314
|
+
if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const res = await fetch(`${chatConfig.apiBase}/api/v1/app/${sessionId}/chat`, {
|
|
318
|
+
method: 'POST',
|
|
319
|
+
headers,
|
|
320
|
+
body: JSON.stringify(body),
|
|
321
|
+
});
|
|
322
|
+
if (!res.ok) {
|
|
323
|
+
console.error('[Chat] Send failed:', res.status);
|
|
324
|
+
showErrorInChat(`Message failed to send (${res.status})`);
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.error('[Chat] Send error:', err);
|
|
328
|
+
showErrorInChat(`Message failed: ${err.message}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ── File Attachment ─────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
function showErrorInChat(message) {
|
|
335
|
+
const messagesEl = document.getElementById('chat-messages');
|
|
336
|
+
if (!messagesEl) return;
|
|
337
|
+
|
|
338
|
+
const msgDiv = document.createElement('div');
|
|
339
|
+
msgDiv.className = 'chat-msg chat-msg-architect';
|
|
340
|
+
|
|
341
|
+
const textDiv = document.createElement('div');
|
|
342
|
+
textDiv.className = 'chat-msg-text';
|
|
343
|
+
textDiv.style.color = 'var(--accent-red)';
|
|
344
|
+
textDiv.textContent = message;
|
|
345
|
+
msgDiv.appendChild(textDiv);
|
|
346
|
+
|
|
347
|
+
messagesEl.appendChild(msgDiv);
|
|
348
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function handleFileSelected(event) {
|
|
352
|
+
const file = event.target.files?.[0];
|
|
353
|
+
if (!file) return;
|
|
354
|
+
|
|
355
|
+
// Reset input so same file can be re-selected
|
|
356
|
+
event.target.value = '';
|
|
357
|
+
|
|
358
|
+
const ext = '.' + file.name.split('.').pop().toLowerCase();
|
|
359
|
+
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
360
|
+
showErrorInChat(`Unsupported file type: ${ext}. Allowed: ${ALLOWED_EXTENSIONS.join(', ')}`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
365
|
+
showErrorInChat(`File too large: ${(file.size / 1024).toFixed(0)}KB. Max: ${MAX_FILE_SIZE / 1024}KB`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const reader = new FileReader();
|
|
370
|
+
reader.onload = () => {
|
|
371
|
+
pendingAttachments.push({ name: file.name, content: reader.result });
|
|
372
|
+
renderAttachmentChip(file.name);
|
|
373
|
+
};
|
|
374
|
+
reader.readAsText(file);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function renderAttachmentChip(filename) {
|
|
378
|
+
const preview = document.getElementById('attachment-preview');
|
|
379
|
+
if (!preview) return;
|
|
380
|
+
|
|
381
|
+
preview.classList.remove('hidden');
|
|
382
|
+
|
|
383
|
+
const chip = document.createElement('div');
|
|
384
|
+
chip.className = 'attachment-chip';
|
|
385
|
+
chip.dataset.filename = filename;
|
|
386
|
+
|
|
387
|
+
const nameSpan = document.createElement('span');
|
|
388
|
+
nameSpan.textContent = filename;
|
|
389
|
+
chip.appendChild(nameSpan);
|
|
390
|
+
|
|
391
|
+
const removeBtn = document.createElement('button');
|
|
392
|
+
removeBtn.className = 'attachment-chip-remove';
|
|
393
|
+
removeBtn.textContent = '\u00d7';
|
|
394
|
+
removeBtn.addEventListener('click', () => {
|
|
395
|
+
pendingAttachments = pendingAttachments.filter(a => a.name !== filename);
|
|
396
|
+
chip.remove();
|
|
397
|
+
if (pendingAttachments.length === 0) {
|
|
398
|
+
preview.classList.add('hidden');
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
chip.appendChild(removeBtn);
|
|
402
|
+
|
|
403
|
+
preview.appendChild(chip);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function clearAttachmentChips() {
|
|
407
|
+
const preview = document.getElementById('attachment-preview');
|
|
408
|
+
if (!preview) return;
|
|
409
|
+
preview.innerHTML = '';
|
|
410
|
+
preview.classList.add('hidden');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ── User Bubble ─────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
function renderUserBubble(text, attachments) {
|
|
416
|
+
const messagesEl = document.getElementById('chat-messages');
|
|
417
|
+
if (!messagesEl) return;
|
|
418
|
+
if (!text && (!attachments || attachments.length === 0)) return;
|
|
419
|
+
|
|
420
|
+
const msgDiv = document.createElement('div');
|
|
421
|
+
msgDiv.className = 'chat-msg chat-msg-user';
|
|
422
|
+
|
|
423
|
+
if (text) {
|
|
424
|
+
const textDiv = document.createElement('div');
|
|
425
|
+
textDiv.className = 'chat-msg-text';
|
|
426
|
+
textDiv.textContent = text;
|
|
427
|
+
msgDiv.appendChild(textDiv);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (attachments && attachments.length > 0) {
|
|
431
|
+
for (const att of attachments) {
|
|
432
|
+
const attDiv = document.createElement('div');
|
|
433
|
+
attDiv.className = 'chat-msg-text';
|
|
434
|
+
attDiv.style.fontSize = '12px';
|
|
435
|
+
attDiv.style.opacity = '0.7';
|
|
436
|
+
attDiv.textContent = `\uD83D\uDCCE ${att.name}`;
|
|
437
|
+
msgDiv.appendChild(attDiv);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
messagesEl.appendChild(msgDiv);
|
|
442
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ── Cards ───────────────────────────────────────────────────
|
|
133
446
|
|
|
134
447
|
function renderCard(card) {
|
|
135
448
|
const cardEl = document.createElement('div');
|
|
@@ -176,99 +489,29 @@ function renderCard(card) {
|
|
|
176
489
|
|
|
177
490
|
function handleCardOptionClick(optEl, card) {
|
|
178
491
|
const cardId = optEl.dataset.cardId;
|
|
179
|
-
const label = optEl.dataset.label;
|
|
180
492
|
const isMulti = optEl.dataset.multi === 'true';
|
|
181
493
|
|
|
182
494
|
if (isMulti) {
|
|
183
495
|
optEl.classList.toggle('selected');
|
|
184
496
|
} else {
|
|
185
|
-
// Deselect siblings
|
|
186
497
|
const siblings = optEl.parentElement.querySelectorAll('.chat-card-option');
|
|
187
498
|
siblings.forEach(s => s.classList.remove('selected'));
|
|
188
499
|
optEl.classList.add('selected');
|
|
189
500
|
}
|
|
190
501
|
|
|
191
|
-
// Update pending card answers
|
|
192
502
|
const existingIdx = pendingCardAnswers.findIndex(a => a.questionId === cardId);
|
|
193
|
-
if (existingIdx >= 0)
|
|
194
|
-
pendingCardAnswers.splice(existingIdx, 1);
|
|
195
|
-
}
|
|
503
|
+
if (existingIdx >= 0) pendingCardAnswers.splice(existingIdx, 1);
|
|
196
504
|
|
|
197
|
-
// Collect all selected options for this card
|
|
198
505
|
const cardEl = optEl.closest('.chat-card');
|
|
199
506
|
const selectedOpts = cardEl.querySelectorAll('.chat-card-option.selected');
|
|
200
507
|
const selectedLabels = Array.from(selectedOpts).map(el => el.dataset.label);
|
|
201
508
|
|
|
202
509
|
if (selectedLabels.length > 0) {
|
|
203
|
-
pendingCardAnswers.push({
|
|
204
|
-
questionId: cardId,
|
|
205
|
-
selectedOptions: selectedLabels,
|
|
206
|
-
});
|
|
510
|
+
pendingCardAnswers.push({ questionId: cardId, selectedOptions: selectedLabels });
|
|
207
511
|
}
|
|
208
512
|
}
|
|
209
513
|
|
|
210
|
-
|
|
211
|
-
const input = document.getElementById('chat-input');
|
|
212
|
-
if (!input) return;
|
|
213
|
-
|
|
214
|
-
const message = input.value.trim();
|
|
215
|
-
if (!message && pendingCardAnswers.length === 0) return;
|
|
216
|
-
|
|
217
|
-
const sessionId = currentSessionId || window.__appId;
|
|
218
|
-
if (!sessionId) return;
|
|
219
|
-
|
|
220
|
-
// Add user message to chat
|
|
221
|
-
const messagesEl = document.getElementById('chat-messages');
|
|
222
|
-
if (messagesEl && message) {
|
|
223
|
-
const msgDiv = document.createElement('div');
|
|
224
|
-
msgDiv.className = 'chat-msg chat-msg-user';
|
|
225
|
-
const textDiv = document.createElement('div');
|
|
226
|
-
textDiv.className = 'chat-msg-text';
|
|
227
|
-
textDiv.textContent = message;
|
|
228
|
-
msgDiv.appendChild(textDiv);
|
|
229
|
-
messagesEl.appendChild(msgDiv);
|
|
230
|
-
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Clear input
|
|
234
|
-
input.value = '';
|
|
235
|
-
|
|
236
|
-
// Build request body
|
|
237
|
-
const body = { message: message || '' };
|
|
238
|
-
if (pendingCardAnswers.length > 0) {
|
|
239
|
-
body.cardAnswers = [...pendingCardAnswers];
|
|
240
|
-
pendingCardAnswers = [];
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Disable all card options after submitting
|
|
244
|
-
const allCards = document.querySelectorAll('.chat-card');
|
|
245
|
-
allCards.forEach(card => {
|
|
246
|
-
card.querySelectorAll('.chat-card-option').forEach(opt => {
|
|
247
|
-
opt.style.pointerEvents = 'none';
|
|
248
|
-
opt.style.opacity = '0.6';
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Send to API
|
|
253
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
254
|
-
if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
const res = await fetch(`${chatConfig.apiBase}/api/v1/app/${sessionId}/chat`, {
|
|
258
|
-
method: 'POST',
|
|
259
|
-
headers,
|
|
260
|
-
body: JSON.stringify(body),
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
if (!res.ok) {
|
|
264
|
-
console.error('[Chat] Send failed:', res.status);
|
|
265
|
-
AppState.addLog({ level: 'error', message: `Chat send failed: ${res.status}` });
|
|
266
|
-
}
|
|
267
|
-
} catch (err) {
|
|
268
|
-
console.error('[Chat] Send error:', err);
|
|
269
|
-
AppState.addLog({ level: 'error', message: `Chat error: ${err.message}` });
|
|
270
|
-
}
|
|
271
|
-
}
|
|
514
|
+
// ── Build Trigger ───────────────────────────────────────────
|
|
272
515
|
|
|
273
516
|
async function startBuilding() {
|
|
274
517
|
const sessionId = currentSessionId || window.__appId;
|
|
@@ -287,6 +530,6 @@ async function startBuilding() {
|
|
|
287
530
|
console.error('[Chat] Start build error:', err);
|
|
288
531
|
}
|
|
289
532
|
|
|
290
|
-
|
|
533
|
+
lockChat();
|
|
291
534
|
AppState.addLog({ level: 'success', message: 'Requirements confirmed — starting build...' });
|
|
292
535
|
}
|