myrlin-workbook 0.9.12 → 0.9.13
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/package.json +1 -1
- package/src/web/public/app.js +32 -4
- package/src/web/server.js +51 -0
package/package.json
CHANGED
package/src/web/public/app.js
CHANGED
|
@@ -9568,11 +9568,14 @@ class CWMApp {
|
|
|
9568
9568
|
const text = accumulatedTranscript.trim();
|
|
9569
9569
|
if (text) {
|
|
9570
9570
|
const currentTp = this.terminalPanes[slotIdx];
|
|
9571
|
-
if (currentTp
|
|
9572
|
-
currentTp.ws.send(JSON.stringify({ type: 'input', data: text + '\n' }));
|
|
9573
|
-
this.showToast('Voice input sent', 'success');
|
|
9574
|
-
} else {
|
|
9571
|
+
if (!currentTp || !currentTp.ws || currentTp.ws.readyState !== WebSocket.OPEN) {
|
|
9575
9572
|
this.showToast('Terminal not connected, voice input discarded', 'warning');
|
|
9573
|
+
} else {
|
|
9574
|
+
// Clean up punctuation and grammar before sending
|
|
9575
|
+
this._punctuateVoiceText(text).then(cleaned => {
|
|
9576
|
+
currentTp.ws.send(JSON.stringify({ type: 'input', data: cleaned + '\n' }));
|
|
9577
|
+
this.showToast('Voice input sent', 'success');
|
|
9578
|
+
});
|
|
9576
9579
|
}
|
|
9577
9580
|
}
|
|
9578
9581
|
} else {
|
|
@@ -9633,6 +9636,31 @@ class CWMApp {
|
|
|
9633
9636
|
}
|
|
9634
9637
|
}
|
|
9635
9638
|
|
|
9639
|
+
/**
|
|
9640
|
+
* Add punctuation and grammar to raw voice transcription text.
|
|
9641
|
+
* Tries the server-side AI punctuation endpoint (uses Anthropic API key if configured).
|
|
9642
|
+
* Falls back to basic rule-based cleanup if the API is unavailable.
|
|
9643
|
+
* @param {string} rawText - Raw speech-to-text output without punctuation
|
|
9644
|
+
* @returns {Promise<string>} Cleaned text with punctuation and capitalization
|
|
9645
|
+
*/
|
|
9646
|
+
async _punctuateVoiceText(rawText) {
|
|
9647
|
+
if (!rawText || !rawText.trim()) return rawText;
|
|
9648
|
+
|
|
9649
|
+
// Try AI-powered punctuation via the server
|
|
9650
|
+
try {
|
|
9651
|
+
const data = await this.api('POST', '/api/ai/punctuate', { text: rawText });
|
|
9652
|
+
if (data && data.text) return data.text;
|
|
9653
|
+
} catch (_) {
|
|
9654
|
+
// API unavailable or no key configured, fall through to rule-based
|
|
9655
|
+
}
|
|
9656
|
+
|
|
9657
|
+
// Rule-based fallback: capitalize first letter, add period at end
|
|
9658
|
+
let text = rawText.trim();
|
|
9659
|
+
text = text.charAt(0).toUpperCase() + text.slice(1);
|
|
9660
|
+
if (!/[.!?]$/.test(text)) text += '.';
|
|
9661
|
+
return text;
|
|
9662
|
+
}
|
|
9663
|
+
|
|
9636
9664
|
/**
|
|
9637
9665
|
* Swap two terminal panes in the grid.
|
|
9638
9666
|
* Swaps the xterm DOM nodes and the terminalPanes array entries.
|
package/src/web/server.js
CHANGED
|
@@ -2363,6 +2363,57 @@ app.put('/api/keys/anthropic', requireAuth, (req, res) => {
|
|
|
2363
2363
|
return res.json({ success: true, configured: !!key, masked });
|
|
2364
2364
|
});
|
|
2365
2365
|
|
|
2366
|
+
// ──────────────────────────────────────────────────────────
|
|
2367
|
+
// AI-POWERED VOICE PUNCTUATION
|
|
2368
|
+
// ──────────────────────────────────────────────────────────
|
|
2369
|
+
|
|
2370
|
+
/**
|
|
2371
|
+
* POST /api/ai/punctuate
|
|
2372
|
+
* Adds punctuation, capitalization, and grammar to raw voice dictation text.
|
|
2373
|
+
* Uses Claude Haiku for fast, low-cost cleanup. Returns 400 if no API key.
|
|
2374
|
+
* Body: { text: "raw voice text without punctuation" }
|
|
2375
|
+
* Returns: { text: "Cleaned text with proper punctuation." }
|
|
2376
|
+
*/
|
|
2377
|
+
app.post('/api/ai/punctuate', requireAuth, async (req, res) => {
|
|
2378
|
+
const { text } = req.body || {};
|
|
2379
|
+
if (!text || typeof text !== 'string' || text.trim().length < 2) {
|
|
2380
|
+
return res.status(400).json({ error: 'Text is required (at least 2 characters)' });
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
const store = getStore();
|
|
2384
|
+
const apiKey = (store.getState().settings || {}).anthropicApiKey || '';
|
|
2385
|
+
if (!apiKey) {
|
|
2386
|
+
return res.status(400).json({ error: 'No Anthropic API key configured' });
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
try {
|
|
2390
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
2391
|
+
method: 'POST',
|
|
2392
|
+
headers: {
|
|
2393
|
+
'Content-Type': 'application/json',
|
|
2394
|
+
'x-api-key': apiKey,
|
|
2395
|
+
'anthropic-version': '2023-06-01',
|
|
2396
|
+
},
|
|
2397
|
+
body: JSON.stringify({
|
|
2398
|
+
model: 'claude-haiku-4-5-20251001',
|
|
2399
|
+
max_tokens: 1024,
|
|
2400
|
+
system: `You are a punctuation and grammar fixer for voice dictation. Given raw speech-to-text output, add proper punctuation (periods, commas, question marks, exclamation points), capitalization, and fix obvious grammar issues. Keep the original meaning and wording intact. Do NOT add, remove, or rephrase words. Do NOT add quotes around the text. Return ONLY the corrected text, nothing else.`,
|
|
2401
|
+
messages: [{ role: 'user', content: text.trim() }],
|
|
2402
|
+
}),
|
|
2403
|
+
});
|
|
2404
|
+
|
|
2405
|
+
if (!response.ok) {
|
|
2406
|
+
return res.status(502).json({ error: `Claude API returned ${response.status}` });
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
const data = await response.json();
|
|
2410
|
+
const cleaned = (data.content && data.content[0] && data.content[0].text) || text;
|
|
2411
|
+
return res.json({ text: cleaned.trim() });
|
|
2412
|
+
} catch (err) {
|
|
2413
|
+
return res.status(502).json({ error: 'Failed to reach Claude API: ' + err.message });
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
|
|
2366
2417
|
// ──────────────────────────────────────────────────────────
|
|
2367
2418
|
// AI-POWERED SESSION FINDER
|
|
2368
2419
|
// ──────────────────────────────────────────────────────────
|