tryassay 0.21.2 → 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/.claude/.truth_last_prompt +1 -0
- package/demo/.claude/truth_status +1 -0
- package/demo/css/style.css +840 -0
- package/demo/index.html +78 -0
- package/demo/js/chat.js +535 -0
- package/demo/js/code-panel.js +206 -0
- package/demo/js/preview.js +456 -0
- package/demo/js/sse-client.js +600 -0
- package/demo/js/state.js +172 -0
- package/demo/js/timeline.js +80 -0
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.js +127 -20
- package/dist/api/server.js.map +1 -1
- package/dist/cli.js +11 -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.d.ts +5 -0
- package/dist/commands/demo.js +357 -0
- package/dist/commands/demo.js.map +1 -0
- 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/agents/planner-agent.d.ts +5 -2
- package/dist/runtime/agents/planner-agent.js +232 -1
- package/dist/runtime/agents/planner-agent.js.map +1 -1
- package/dist/runtime/app-create-orchestrator.d.ts +9 -1
- package/dist/runtime/app-create-orchestrator.js +265 -87
- 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 +116 -0
- package/dist/sdk/forward-verify.js +16 -33
- package/dist/sdk/forward-verify.js.map +1 -1
- package/package.json +2 -1
package/demo/index.html
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Assay Verified Agent Runtime</title>
|
|
7
|
+
<link rel="stylesheet" href="css/style.css">
|
|
8
|
+
<meta http-equiv="Cross-Origin-Embedder-Policy" content="credentialless">
|
|
9
|
+
<meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="app">
|
|
13
|
+
<header id="header">
|
|
14
|
+
<div id="header-left">
|
|
15
|
+
<span class="header-title">assay</span>
|
|
16
|
+
</div>
|
|
17
|
+
<div id="header-right">
|
|
18
|
+
<div id="file-badge" class="hidden">
|
|
19
|
+
<span>files:</span>
|
|
20
|
+
<span id="file-count">0</span>
|
|
21
|
+
</div>
|
|
22
|
+
<div id="global-clock" class="global-clock hidden">
|
|
23
|
+
<span class="clock-value" id="clock-value">00:00</span>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</header>
|
|
27
|
+
|
|
28
|
+
<div id="chat-panel">
|
|
29
|
+
<div id="chat-messages"></div>
|
|
30
|
+
<div id="attachment-preview" class="hidden"></div>
|
|
31
|
+
<div id="chat-input-area">
|
|
32
|
+
<input type="file" id="file-input" hidden accept=".txt,.md,.markdown,.csv,.json">
|
|
33
|
+
<button id="attach-btn" title="Attach specification document">📎</button>
|
|
34
|
+
<input type="text" id="chat-input" placeholder="Describe an application to build..." autocomplete="off">
|
|
35
|
+
<button id="chat-send-btn">Send</button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div id="preview-panel">
|
|
40
|
+
<div id="preview-overlay">
|
|
41
|
+
<div id="preview-status" data-state="cold">Setting up environment...</div>
|
|
42
|
+
</div>
|
|
43
|
+
<iframe id="preview-iframe"></iframe>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div id="completion-overlay"></div>
|
|
47
|
+
|
|
48
|
+
<div id="status-bar">
|
|
49
|
+
<span id="status-phase">phase: idle</span>
|
|
50
|
+
<span id="status-elapsed">elapsed: 00:00</span>
|
|
51
|
+
<span id="status-tasks">tasks: 0/0</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<script type="module" src="js/state.js"></script>
|
|
56
|
+
<script type="module" src="js/chat.js"></script>
|
|
57
|
+
<script type="module" src="js/sse-client.js"></script>
|
|
58
|
+
<script type="module" src="js/preview.js"></script>
|
|
59
|
+
<script type="module" src="js/timeline.js"></script>
|
|
60
|
+
|
|
61
|
+
<script type="module">
|
|
62
|
+
import './js/state.js';
|
|
63
|
+
import { initSSEClient } from './js/sse-client.js';
|
|
64
|
+
import { initChat } from './js/chat.js';
|
|
65
|
+
import { initPreview } from './js/preview.js';
|
|
66
|
+
import { initTimeline } from './js/timeline.js';
|
|
67
|
+
|
|
68
|
+
const params = new URLSearchParams(window.location.search);
|
|
69
|
+
const API_BASE = params.get('api') || 'http://localhost:3800';
|
|
70
|
+
const API_KEY = params.get('key') || '';
|
|
71
|
+
|
|
72
|
+
initSSEClient(API_BASE, API_KEY);
|
|
73
|
+
initChat(API_BASE, API_KEY);
|
|
74
|
+
initTimeline();
|
|
75
|
+
initPreview();
|
|
76
|
+
</script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
package/demo/js/chat.js
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
// chat.js — Chat-first interface for requirements gathering + build
|
|
2
|
+
// Renders in #chat-panel (left side of split screen)
|
|
3
|
+
|
|
4
|
+
const AppState = window.AppState;
|
|
5
|
+
|
|
6
|
+
let chatConfig = { apiBase: '', apiKey: '' };
|
|
7
|
+
let currentSessionId = null;
|
|
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 ──────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export function configureChat(apiBase, apiKey) {
|
|
17
|
+
chatConfig.apiBase = apiBase || '';
|
|
18
|
+
chatConfig.apiKey = apiKey || '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function initChat(apiBase, apiKey) {
|
|
22
|
+
chatConfig.apiBase = apiBase || '';
|
|
23
|
+
chatConfig.apiKey = apiKey || '';
|
|
24
|
+
|
|
25
|
+
renderWelcomeMessage();
|
|
26
|
+
wireInputHandlers();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function openChat(sessionId) {
|
|
30
|
+
currentSessionId = sessionId;
|
|
31
|
+
pendingCardAnswers = [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function addArchitectMessage(data) {
|
|
35
|
+
const messagesEl = document.getElementById('chat-messages');
|
|
36
|
+
if (!messagesEl) return;
|
|
37
|
+
|
|
38
|
+
if (!currentSessionId) {
|
|
39
|
+
openChat(data.sessionId || window.__appId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const msgDiv = document.createElement('div');
|
|
43
|
+
msgDiv.className = 'chat-msg chat-msg-architect';
|
|
44
|
+
|
|
45
|
+
const textDiv = document.createElement('div');
|
|
46
|
+
textDiv.className = 'chat-msg-text';
|
|
47
|
+
textDiv.textContent = data.content;
|
|
48
|
+
msgDiv.appendChild(textDiv);
|
|
49
|
+
|
|
50
|
+
if (data.cards && data.cards.length > 0) {
|
|
51
|
+
const cardsDiv = document.createElement('div');
|
|
52
|
+
cardsDiv.className = 'chat-cards';
|
|
53
|
+
for (const card of data.cards) {
|
|
54
|
+
const cardEl = renderCard(card);
|
|
55
|
+
cardsDiv.appendChild(cardEl);
|
|
56
|
+
}
|
|
57
|
+
msgDiv.appendChild(cardsDiv);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (data.readiness && data.readiness.ready) {
|
|
61
|
+
const buildBtn = document.createElement('button');
|
|
62
|
+
buildBtn.className = 'chat-build-btn';
|
|
63
|
+
buildBtn.textContent = 'Start Building';
|
|
64
|
+
buildBtn.addEventListener('click', () => startBuilding());
|
|
65
|
+
|
|
66
|
+
const summaryDiv = document.createElement('div');
|
|
67
|
+
summaryDiv.className = 'chat-ready-summary';
|
|
68
|
+
summaryDiv.textContent = data.readiness.summary || '';
|
|
69
|
+
|
|
70
|
+
msgDiv.appendChild(summaryDiv);
|
|
71
|
+
msgDiv.appendChild(buildBtn);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
messagesEl.appendChild(msgDiv);
|
|
75
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function closeChat() {
|
|
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;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
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;
|
|
112
|
+
|
|
113
|
+
currentSessionId = null;
|
|
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
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
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 ───────────────────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
function renderCard(card) {
|
|
448
|
+
const cardEl = document.createElement('div');
|
|
449
|
+
cardEl.className = 'chat-card';
|
|
450
|
+
cardEl.dataset.cardId = card.id;
|
|
451
|
+
|
|
452
|
+
const headerEl = document.createElement('div');
|
|
453
|
+
headerEl.className = 'chat-card-header';
|
|
454
|
+
headerEl.textContent = card.header || card.question;
|
|
455
|
+
cardEl.appendChild(headerEl);
|
|
456
|
+
|
|
457
|
+
const questionEl = document.createElement('div');
|
|
458
|
+
questionEl.className = 'chat-card-question';
|
|
459
|
+
questionEl.textContent = card.question;
|
|
460
|
+
cardEl.appendChild(questionEl);
|
|
461
|
+
|
|
462
|
+
const optionsEl = document.createElement('div');
|
|
463
|
+
optionsEl.className = 'chat-card-options';
|
|
464
|
+
|
|
465
|
+
for (const opt of card.options) {
|
|
466
|
+
const optEl = document.createElement('div');
|
|
467
|
+
optEl.className = 'chat-card-option';
|
|
468
|
+
optEl.dataset.cardId = card.id;
|
|
469
|
+
optEl.dataset.label = opt.label;
|
|
470
|
+
optEl.dataset.multi = card.multiSelect ? 'true' : 'false';
|
|
471
|
+
|
|
472
|
+
const labelEl = document.createElement('div');
|
|
473
|
+
labelEl.className = 'chat-card-option-label';
|
|
474
|
+
labelEl.textContent = opt.label;
|
|
475
|
+
optEl.appendChild(labelEl);
|
|
476
|
+
|
|
477
|
+
const descEl = document.createElement('div');
|
|
478
|
+
descEl.className = 'chat-card-option-desc';
|
|
479
|
+
descEl.textContent = opt.description;
|
|
480
|
+
optEl.appendChild(descEl);
|
|
481
|
+
|
|
482
|
+
optEl.addEventListener('click', () => handleCardOptionClick(optEl, card));
|
|
483
|
+
optionsEl.appendChild(optEl);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
cardEl.appendChild(optionsEl);
|
|
487
|
+
return cardEl;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function handleCardOptionClick(optEl, card) {
|
|
491
|
+
const cardId = optEl.dataset.cardId;
|
|
492
|
+
const isMulti = optEl.dataset.multi === 'true';
|
|
493
|
+
|
|
494
|
+
if (isMulti) {
|
|
495
|
+
optEl.classList.toggle('selected');
|
|
496
|
+
} else {
|
|
497
|
+
const siblings = optEl.parentElement.querySelectorAll('.chat-card-option');
|
|
498
|
+
siblings.forEach(s => s.classList.remove('selected'));
|
|
499
|
+
optEl.classList.add('selected');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const existingIdx = pendingCardAnswers.findIndex(a => a.questionId === cardId);
|
|
503
|
+
if (existingIdx >= 0) pendingCardAnswers.splice(existingIdx, 1);
|
|
504
|
+
|
|
505
|
+
const cardEl = optEl.closest('.chat-card');
|
|
506
|
+
const selectedOpts = cardEl.querySelectorAll('.chat-card-option.selected');
|
|
507
|
+
const selectedLabels = Array.from(selectedOpts).map(el => el.dataset.label);
|
|
508
|
+
|
|
509
|
+
if (selectedLabels.length > 0) {
|
|
510
|
+
pendingCardAnswers.push({ questionId: cardId, selectedOptions: selectedLabels });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ── Build Trigger ───────────────────────────────────────────
|
|
515
|
+
|
|
516
|
+
async function startBuilding() {
|
|
517
|
+
const sessionId = currentSessionId || window.__appId;
|
|
518
|
+
if (!sessionId) return;
|
|
519
|
+
|
|
520
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
521
|
+
if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
await fetch(`${chatConfig.apiBase}/api/v1/app/${sessionId}/chat`, {
|
|
525
|
+
method: 'POST',
|
|
526
|
+
headers,
|
|
527
|
+
body: JSON.stringify({ message: '__START_BUILD__' }),
|
|
528
|
+
});
|
|
529
|
+
} catch (err) {
|
|
530
|
+
console.error('[Chat] Start build error:', err);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
lockChat();
|
|
534
|
+
AppState.addLog({ level: 'success', message: 'Requirements confirmed — starting build...' });
|
|
535
|
+
}
|