yaml-flow 5.2.0 → 5.2.2
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.
|
@@ -1,215 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* demo-chat-handler.js — LLM-based chat handler for example-board.
|
|
1
|
+
/**
|
|
2
|
+
* demo-chat-handler.js - Chat handler for example-board.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* Invoked by reusable-server-runtime after a user message is persisted:
|
|
6
5
|
* node demo-chat-handler.js --boardId <id> --cardId <id> --extraEncJson <base64json>
|
|
7
6
|
*
|
|
8
|
-
*
|
|
7
|
+
* extraEncJson decodes to: { chatDir, boardDir, lastChatFile }
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* It reads the full conversation history from chatDir, builds a grounded system prompt
|
|
13
|
-
* (scoped to the card and board), and calls the LLM directly (Copilot CLI).
|
|
14
|
-
* Copilot is invoked from boardDir (cwd), so it naturally has access to board files.
|
|
15
|
-
*
|
|
16
|
-
* The LLM is the sole decision-maker. No rule-based fallback is used here — if the LLM
|
|
17
|
-
* is unavailable, the handler writes a short error acknowledgment so the user isn't left
|
|
18
|
-
* with a silent failure.
|
|
9
|
+
* If the card has a source entry containing a "copilot" key, invokes copilot_wrapper.bat.
|
|
10
|
+
* Session dir is per-card: os.tmpdir()/demo-chat-handler-sessions/<boardId>_<cardId>
|
|
19
11
|
*/
|
|
20
12
|
|
|
21
|
-
import * as fs
|
|
13
|
+
import * as fs from 'node:fs';
|
|
22
14
|
import * as path from 'node:path';
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function getArg(name) {
|
|
28
|
-
const idx = args.indexOf(name);
|
|
29
|
-
return idx !== -1 && args[idx + 1] !== undefined ? args[idx + 1] : null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const boardId = getArg('--boardId') || '';
|
|
33
|
-
const cardId = getArg('--cardId') || '';
|
|
34
|
-
const extraStr = getArg('--extraEncJson') || '';
|
|
35
|
-
|
|
36
|
-
let extra = {};
|
|
37
|
-
try {
|
|
38
|
-
extra = JSON.parse(Buffer.from(extraStr, 'base64').toString('utf-8'));
|
|
39
|
-
} catch {
|
|
40
|
-
console.error('[demo-chat-handler] could not parse --extraEncJson');
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const { chatDir, boardDir, lastChatFile } = extra;
|
|
45
|
-
|
|
46
|
-
if (!chatDir || !lastChatFile) {
|
|
47
|
-
console.error('[demo-chat-handler] --extraEncJson must contain chatDir and lastChatFile');
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Read full conversation history from chatDir (all user + assistant turns)
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
function readConversationHistory(dir) {
|
|
55
|
-
let files;
|
|
56
|
-
try {
|
|
57
|
-
files = fs.readdirSync(dir).filter(f => /^\d+[-_](user|assistant)\.txt$/i.test(f));
|
|
58
|
-
files.sort();
|
|
59
|
-
} catch {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
return files.map(f => {
|
|
63
|
-
const role = /user/i.test(f) ? 'User' : 'Assistant';
|
|
64
|
-
let text = '';
|
|
65
|
-
try { text = fs.readFileSync(path.join(dir, f), 'utf-8').trim(); } catch {}
|
|
66
|
-
return `${role}: ${text}`;
|
|
67
|
-
});
|
|
68
|
-
}
|
|
15
|
+
import * as os from 'node:os';
|
|
16
|
+
import { spawnSync } from 'node:child_process';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
69
18
|
|
|
70
|
-
|
|
71
|
-
// Build prompt: system instruction + conversation turns
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
function buildPrompt(bId, cId, history) {
|
|
74
|
-
return [
|
|
75
|
-
`You are a helpful assistant embedded in a live data card (card: "${cId}", board: "${bId}").`,
|
|
76
|
-
'Help the user understand and act on the data shown in this card.',
|
|
77
|
-
'Be concise — this is an inline card chat, not a full conversation window.',
|
|
78
|
-
'Ground answers in the card\'s data context. Ask one short question if the intent is ambiguous.',
|
|
79
|
-
'',
|
|
80
|
-
...history,
|
|
81
|
-
'Assistant:',
|
|
82
|
-
].join('\n');
|
|
83
|
-
}
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
84
20
|
|
|
85
21
|
// ---------------------------------------------------------------------------
|
|
86
|
-
//
|
|
22
|
+
// Args
|
|
87
23
|
// ---------------------------------------------------------------------------
|
|
88
|
-
function stripCopilotFooter(rawText) {
|
|
89
|
-
const lines = String(rawText ?? '').split(/\r?\n/);
|
|
90
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
|
|
91
|
-
if (
|
|
92
|
-
lines.length >= 3 &&
|
|
93
|
-
/^Changes\b/i.test(lines[lines.length - 3]) &&
|
|
94
|
-
/^Requests\b/i.test(lines[lines.length - 2]) &&
|
|
95
|
-
/^Tokens\b/i.test(lines[lines.length - 1])
|
|
96
|
-
) {
|
|
97
|
-
lines.splice(lines.length - 3, 3);
|
|
98
|
-
}
|
|
99
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
|
|
100
|
-
return lines.join('\n');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function resolveCopilotExecutable() {
|
|
104
|
-
const envBin = process.env.COPILOT_BIN;
|
|
105
|
-
if (envBin && fs.existsSync(envBin)) return envBin;
|
|
106
|
-
if (process.platform === 'win32') {
|
|
107
|
-
try {
|
|
108
|
-
const out = execFileSync('where.exe', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
109
|
-
const candidates = out.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
110
|
-
return candidates.find(p => /\.(cmd|exe|bat)$/i.test(p)) ?? candidates[0] ?? 'copilot';
|
|
111
|
-
} catch {}
|
|
112
|
-
} else {
|
|
113
|
-
try {
|
|
114
|
-
const out = execFileSync('which', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
115
|
-
return out.split(/\r?\n/).map(s => s.trim()).find(Boolean) ?? 'copilot';
|
|
116
|
-
} catch {}
|
|
117
|
-
}
|
|
118
|
-
return 'copilot';
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function runCopilotPrompt(prompt, cwd) {
|
|
122
|
-
const copilotBin = resolveCopilotExecutable();
|
|
123
|
-
const opts = {
|
|
124
|
-
input: String(prompt),
|
|
125
|
-
encoding: 'utf-8',
|
|
126
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
127
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
128
|
-
timeout: 60000,
|
|
129
|
-
...(cwd ? { cwd } : {}),
|
|
130
|
-
};
|
|
131
|
-
try {
|
|
132
|
-
return execFileSync(copilotBin, ['--allow-all'], opts);
|
|
133
|
-
} catch (directErr) {
|
|
134
|
-
if (process.platform === 'win32') {
|
|
135
|
-
try {
|
|
136
|
-
return execFileSync('cmd.exe', ['/d', '/c', 'copilot --allow-all'], opts);
|
|
137
|
-
} catch (cmdErr) {
|
|
138
|
-
const msg = [
|
|
139
|
-
directErr?.stderr?.trim?.(),
|
|
140
|
-
cmdErr?.stderr?.trim?.(),
|
|
141
|
-
String(cmdErr?.message ?? cmdErr),
|
|
142
|
-
].filter(Boolean).join(' | ');
|
|
143
|
-
throw new Error(msg || 'copilot invocation failed');
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const msg = [directErr?.stderr?.trim?.(), String(directErr?.message ?? directErr)].filter(Boolean).join(' | ');
|
|
147
|
-
throw new Error(msg || 'copilot invocation failed');
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
// Main
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
const history = readConversationHistory(chatDir);
|
|
155
|
-
const prompt = buildPrompt(boardId, cardId, history);
|
|
156
|
-
const cwd = boardDir && fs.existsSync(boardDir) ? boardDir : undefined;
|
|
157
|
-
|
|
158
|
-
let response = '';
|
|
159
|
-
try {
|
|
160
|
-
response = stripCopilotFooter(runCopilotPrompt(prompt, cwd)).trim();
|
|
161
|
-
} catch (err) {
|
|
162
|
-
const lastUser = [...history].reverse().find(l => l.startsWith('User:')) ?? '';
|
|
163
|
-
response = `Sorry, I could not reach the LLM right now. (${String(err?.message ?? err).slice(0, 120)})`;
|
|
164
|
-
console.error(`[demo-chat-handler] LLM call failed: ${err?.message ?? err}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Write assistant response as next serial file
|
|
168
|
-
const serialMatch = String(lastChatFile).match(/^(\d+)/);
|
|
169
|
-
const nextSerial = serialMatch ? parseInt(serialMatch[1], 10) + 1 : 1;
|
|
170
|
-
const nextName = `${String(nextSerial).padStart(3, '0')}-assistant.txt`;
|
|
171
|
-
const nextPath = path.join(chatDir, nextName);
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
fs.writeFileSync(nextPath, response + '\n', 'utf-8');
|
|
175
|
-
console.log(`[demo-chat-handler] boardId="${boardId}" cardId="${cardId}" → ${nextPath}`);
|
|
176
|
-
} catch (err) {
|
|
177
|
-
console.error(`[demo-chat-handler] write failed: ${err.message}`);
|
|
178
|
-
}
|
|
179
|
-
*
|
|
180
|
-
* Protocol (invoked by reusable-server-runtime after a user message is persisted):
|
|
181
|
-
* node demo-chat-handler.js --boardId <id> --cardId <id> --extraEncJson <base64json>
|
|
182
|
-
*
|
|
183
|
-
* --extraEncJson decodes to: { chatDir: "<abs>", boardDir: "<abs>", lastChatFile: "<filename>" }
|
|
184
|
-
*
|
|
185
|
-
* Responsibilities:
|
|
186
|
-
* 1. Read the full conversation history from chatDir (all *_user.txt / *-assistant.txt files).
|
|
187
|
-
* 2. Read the current card state from boardDir/board-graph.json (card_data, fetched_sources,
|
|
188
|
-
* computed_values for cardId) to use as grounding context.
|
|
189
|
-
* 3. Build a system prompt that situates the LLM as an assistant for this specific card,
|
|
190
|
-
* including the card's current data as context.
|
|
191
|
-
* 4. Send the conversation + context to the LLM (Copilot via CLI).
|
|
192
|
-
* 5. Write the response as <nextSerial>-assistant.txt to chatDir.
|
|
193
|
-
*
|
|
194
|
-
* Design principle:
|
|
195
|
-
* The chat is always scoped to the card where the chat button is embedded.
|
|
196
|
-
* The card's current state (card_data, computed_values, fetched_sources) is the primary
|
|
197
|
-
* grounding context. The LLM should help the user understand, explore, or act on that card's
|
|
198
|
-
* data — not give generic answers disconnected from the card's content.
|
|
199
|
-
*
|
|
200
|
-
* The system prompt should encourage the LLM to:
|
|
201
|
-
* - Reference the card's actual values when answering
|
|
202
|
-
* - Ask clarifying questions if the user's intent is ambiguous
|
|
203
|
-
* - Suggest next steps relevant to the card's domain
|
|
204
|
-
* - Be concise (the chat is embedded in a card, not a full chat window)
|
|
205
|
-
*/
|
|
206
|
-
|
|
207
|
-
import * as fs from 'node:fs';
|
|
208
|
-
import * as path from 'node:path';
|
|
209
|
-
import { execFileSync } from 'node:child_process';
|
|
210
|
-
|
|
211
24
|
const args = process.argv.slice(2);
|
|
212
|
-
|
|
213
25
|
function getArg(name) {
|
|
214
26
|
const idx = args.indexOf(name);
|
|
215
27
|
return idx !== -1 && args[idx + 1] !== undefined ? args[idx + 1] : null;
|
|
@@ -220,190 +32,120 @@ const cardId = getArg('--cardId') || '';
|
|
|
220
32
|
const extraStr = getArg('--extraEncJson') || '';
|
|
221
33
|
|
|
222
34
|
let extra = {};
|
|
223
|
-
try {
|
|
224
|
-
|
|
225
|
-
} catch {
|
|
226
|
-
console.error('[demo-chat-handler] could not parse --extraEncJson');
|
|
227
|
-
process.exit(0);
|
|
228
|
-
}
|
|
35
|
+
try { extra = JSON.parse(Buffer.from(extraStr, 'base64').toString('utf-8')); }
|
|
36
|
+
catch { console.error('[demo-chat-handler] bad --extraEncJson'); process.exit(0); }
|
|
229
37
|
|
|
230
38
|
const { chatDir, boardDir, lastChatFile } = extra;
|
|
231
|
-
|
|
232
|
-
if (!chatDir || !lastChatFile) {
|
|
233
|
-
console.error('[demo-chat-handler] --extraEncJson must contain chatDir and lastChatFile');
|
|
39
|
+
console.error('[demo-chat-handler] missing chatDir/lastChatFile');
|
|
234
40
|
process.exit(0);
|
|
235
41
|
}
|
|
236
42
|
|
|
237
43
|
// ---------------------------------------------------------------------------
|
|
238
|
-
//
|
|
44
|
+
// Read conversation history
|
|
239
45
|
// ---------------------------------------------------------------------------
|
|
240
|
-
function
|
|
241
|
-
let files;
|
|
46
|
+
function readHistory(dir) {
|
|
242
47
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
48
|
+
return fs.readdirSync(dir)
|
|
49
|
+
.filter(f => /^\d+[-_](user|assistant)\.txt$/i.test(f))
|
|
50
|
+
.sort()
|
|
51
|
+
.map(f => {
|
|
52
|
+
const role = /user/i.test(f) ? 'User' : 'Assistant';
|
|
53
|
+
let text = '';
|
|
54
|
+
try { text = fs.readFileSync(path.join(dir, f), 'utf-8').trim(); } catch {}
|
|
55
|
+
return role + ': ' + text;
|
|
56
|
+
});
|
|
57
|
+
} catch { return []; }
|
|
254
58
|
}
|
|
255
59
|
|
|
256
60
|
// ---------------------------------------------------------------------------
|
|
257
|
-
//
|
|
61
|
+
// Check if card has a 'copilot' source entry
|
|
258
62
|
// ---------------------------------------------------------------------------
|
|
259
|
-
function
|
|
260
|
-
if (!bDir) return null;
|
|
63
|
+
function hasCopilotSource(bDir, cId) {
|
|
261
64
|
try {
|
|
262
65
|
const boardGraph = JSON.parse(fs.readFileSync(path.join(bDir, 'board-graph.json'), 'utf-8'));
|
|
263
|
-
// board-graph.json wraps a LiveGraph snapshot; cards live under graph.nodes
|
|
264
66
|
const nodes = boardGraph?.graph?.nodes ?? boardGraph?.nodes ?? {};
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return
|
|
268
|
-
|
|
67
|
+
const card = nodes[cId];
|
|
68
|
+
const sources = card.sources ?? card.card_data?.sources ?? [];
|
|
69
|
+
if (Array.isArray(sources)) return sources.some(s => s && typeof s === 'object' && 'copilot' in s);
|
|
70
|
+
if (typeof sources === 'object') return 'copilot' in sources;
|
|
71
|
+
return false;
|
|
72
|
+
} catch { return false; }
|
|
269
73
|
}
|
|
270
74
|
|
|
271
75
|
// ---------------------------------------------------------------------------
|
|
272
|
-
//
|
|
76
|
+
// Build prompt
|
|
273
77
|
// ---------------------------------------------------------------------------
|
|
274
|
-
function
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
'
|
|
278
|
-
'
|
|
279
|
-
'If the user\'s question is ambiguous, ask one short clarifying question.',
|
|
280
|
-
'Suggest relevant next steps or insights when appropriate.',
|
|
78
|
+
function buildPrompt(cId, bId, history) {
|
|
79
|
+
return [
|
|
80
|
+
'You are a helpful assistant embedded in a live card (card: "' + cId + '", board: "' + bId + '").',
|
|
81
|
+
'Help the user understand and act on the data shown in this card.',
|
|
82
|
+
'Be concise.',
|
|
281
83
|
'',
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (cardState) {
|
|
286
|
-
if (cardState.card_data && Object.keys(cardState.card_data).length > 0) {
|
|
287
|
-
lines.push('card_data: ' + JSON.stringify(cardState.card_data, null, 2));
|
|
288
|
-
}
|
|
289
|
-
if (cardState.computed_values && Object.keys(cardState.computed_values).length > 0) {
|
|
290
|
-
lines.push('computed_values: ' + JSON.stringify(cardState.computed_values, null, 2));
|
|
291
|
-
}
|
|
292
|
-
if (cardState.fetched_sources && Object.keys(cardState.fetched_sources).length > 0) {
|
|
293
|
-
lines.push('fetched_sources: ' + JSON.stringify(cardState.fetched_sources, null, 2));
|
|
294
|
-
}
|
|
295
|
-
} else {
|
|
296
|
-
lines.push('(card state not available)');
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
lines.push('--- End card state ---');
|
|
300
|
-
return lines.join('\n');
|
|
84
|
+
...history,
|
|
85
|
+
'Assistant:',
|
|
86
|
+
].join('\n');
|
|
301
87
|
}
|
|
302
88
|
|
|
303
89
|
// ---------------------------------------------------------------------------
|
|
304
|
-
//
|
|
90
|
+
// Invoke copilot_wrapper.bat
|
|
305
91
|
// ---------------------------------------------------------------------------
|
|
306
|
-
function
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return parts.join('\n');
|
|
313
|
-
}
|
|
92
|
+
function runWrapper(prompt, sessionDir, workingDir) {
|
|
93
|
+
const wrapperPath = path.join(__dirname, 'scripts', 'copilot_wrapper.bat');
|
|
94
|
+
const tmpBase = os.tmpdir();
|
|
95
|
+
const ts = Date.now();
|
|
96
|
+
const outFile = path.join(tmpBase, 'dch-out-' + cardId + '-' + ts + '.txt');
|
|
97
|
+
const promptFile = path.join(tmpBase, 'dch-prompt-' + cardId + '-' + ts + '.txt');
|
|
314
98
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// ---------------------------------------------------------------------------
|
|
318
|
-
function resolveCopilotExecutable() {
|
|
319
|
-
const envBin = process.env.COPILOT_BIN;
|
|
320
|
-
if (envBin && fs.existsSync(envBin)) return envBin;
|
|
99
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
100
|
+
fs.writeFileSync(promptFile, prompt, 'utf-8');
|
|
321
101
|
|
|
322
|
-
if (process.platform === 'win32') {
|
|
323
|
-
try {
|
|
324
|
-
const out = execFileSync('where.exe', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
325
|
-
const candidates = out.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
326
|
-
return candidates.find(p => /\.(cmd|exe|bat)$/i.test(p)) ?? candidates[0] ?? 'copilot';
|
|
327
|
-
} catch {}
|
|
328
|
-
} else {
|
|
329
|
-
try {
|
|
330
|
-
const out = execFileSync('which', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
331
|
-
return out.split(/\r?\n/).map(s => s.trim()).find(Boolean) ?? 'copilot';
|
|
332
|
-
} catch {}
|
|
333
|
-
}
|
|
334
|
-
return 'copilot';
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function stripCopilotFooter(rawText) {
|
|
338
|
-
const lines = String(rawText ?? '').split(/\r?\n/);
|
|
339
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
|
|
340
|
-
if (
|
|
341
|
-
lines.length >= 3 &&
|
|
342
|
-
/^Changes\b/i.test(lines[lines.length - 3]) &&
|
|
343
|
-
/^Requests\b/i.test(lines[lines.length - 2]) &&
|
|
344
|
-
/^Tokens\b/i.test(lines[lines.length - 1])
|
|
345
|
-
) {
|
|
346
|
-
lines.splice(lines.length - 3, 3);
|
|
347
|
-
}
|
|
348
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
|
|
349
|
-
return lines.join('\n');
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function runLLM(prompt) {
|
|
353
|
-
const copilotBin = resolveCopilotExecutable();
|
|
354
102
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
370
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
371
|
-
timeout: 60000,
|
|
372
|
-
});
|
|
373
|
-
return stripCopilotFooter(raw).trim();
|
|
374
|
-
} catch {}
|
|
375
|
-
}
|
|
376
|
-
throw err;
|
|
103
|
+
spawnSync('cmd.exe', [
|
|
104
|
+
'/c', wrapperPath,
|
|
105
|
+
outFile,
|
|
106
|
+
sessionDir,
|
|
107
|
+
workingDir,
|
|
108
|
+
'@' + promptFile,
|
|
109
|
+
'raw',
|
|
110
|
+
'demo-chat',
|
|
111
|
+
], { stdio: 'inherit', timeout: 120000 });
|
|
112
|
+
|
|
113
|
+
return fs.existsSync(outFile) ? fs.readFileSync(outFile, 'utf-8').trim() : '';
|
|
114
|
+
} finally {
|
|
115
|
+
try { fs.unlinkSync(promptFile); } catch {}
|
|
116
|
+
try { fs.unlinkSync(outFile); } catch {}
|
|
377
117
|
}
|
|
378
118
|
}
|
|
379
119
|
|
|
380
120
|
// ---------------------------------------------------------------------------
|
|
381
121
|
// Main
|
|
382
122
|
// ---------------------------------------------------------------------------
|
|
383
|
-
const history
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
const
|
|
123
|
+
const history = readHistory(chatDir);
|
|
124
|
+
const sessionDir = path.join(os.tmpdir(), 'demo-chat-handler-sessions', boardId + '_' + cardId);
|
|
125
|
+
const workingDir = boardDir || process.cwd();
|
|
126
|
+
const prompt = buildPrompt(cardId, boardId, history);
|
|
387
127
|
|
|
388
128
|
let response = '';
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
129
|
+
if (hasCopilotSource(boardDir, cardId)) {
|
|
130
|
+
try {
|
|
131
|
+
response = runWrapper(prompt, sessionDir, workingDir);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
response = 'Sorry, I could not reach the LLM right now. (' + String(err?.message ?? err).slice(0, 120) + ')';
|
|
134
|
+
console.error('[demo-chat-handler] wrapper failed: ' + (err?.message ?? err));
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
response = 'No copilot source configured for this card.';
|
|
396
138
|
}
|
|
397
139
|
|
|
398
|
-
//
|
|
140
|
+
// Write assistant response as next serial file
|
|
399
141
|
const serialMatch = String(lastChatFile).match(/^(\d+)/);
|
|
400
142
|
const nextSerial = serialMatch ? parseInt(serialMatch[1], 10) + 1 : 1;
|
|
401
|
-
const nextName =
|
|
143
|
+
const nextName = String(nextSerial).padStart(3, '0') + '-assistant.txt';
|
|
402
144
|
const nextPath = path.join(chatDir, nextName);
|
|
403
145
|
|
|
404
146
|
try {
|
|
405
147
|
fs.writeFileSync(nextPath, response + '\n', 'utf-8');
|
|
406
|
-
console.log(
|
|
148
|
+
console.log('[demo-chat-handler] cardId="' + cardId + '" wrote response -> ' + nextPath);
|
|
407
149
|
} catch (err) {
|
|
408
|
-
console.error(
|
|
409
|
-
}
|
|
150
|
+
console.error('[demo-chat-handler] write failed: ' + err.message);
|
|
151
|
+
}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<title>Example Board Demo (Browser Runtime)</title>
|
|
7
7
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
8
8
|
<script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
|
|
9
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/card-compute.js"></script>
|
|
10
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/live-cards.js"></script>
|
|
11
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/board-livegraph-engine.js"></script>
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/card-compute.js"></script>
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/live-cards.js"></script>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/board-livegraph-engine.js"></script>
|
|
12
12
|
</head>
|
|
13
13
|
<body class="bg-light">
|
|
14
14
|
<div class="container-fluid py-3">
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
</style>
|
|
17
17
|
<script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
|
|
18
18
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
19
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/card-compute.js"></script>
|
|
20
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/live-cards.js"></script>
|
|
21
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/board-livegraph-engine.js"></script>
|
|
22
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/board-livecards-runtime-client.js"></script>
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/card-compute.js"></script>
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/live-cards.js"></script>
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/board-livegraph-engine.js"></script>
|
|
22
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/board-livecards-runtime-client.js"></script>
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-light">
|
|
25
25
|
<div class="container-fluid py-3">
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
|
|
68
68
|
<script>
|
|
69
69
|
(function () {
|
|
70
|
+
const { buildLiveCardModelsFromArtifacts } = window.BoardLiveGraph || {};
|
|
70
71
|
let currentMode = 'board';
|
|
71
72
|
let bootstrapCompleted = false;
|
|
72
73
|
let activeBoardId = 'default';
|