@lyy0709/contextweaver 1.0.0
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/LICENSE +21 -0
- package/README.en.md +405 -0
- package/README.md +475 -0
- package/dist/SearchService-R7UMDQ6T.js +1623 -0
- package/dist/browser-VC5772XM.js +30 -0
- package/dist/chunk-6HF343R7.js +186 -0
- package/dist/chunk-CKN7LWEA.js +1337 -0
- package/dist/chunk-ECEVTSSZ.js +894 -0
- package/dist/chunk-LPFRFKFW.js +543 -0
- package/dist/chunk-V2USKRIC.js +310 -0
- package/dist/chunk-XVKMTPCT.js +297 -0
- package/dist/chunk-YVLGQTLG.js +170 -0
- package/dist/claude-IKIA62JA.js +42 -0
- package/dist/codebaseRetrieval-SCN3YIPM.js +11 -0
- package/dist/config-WTC56Y2R.js +22 -0
- package/dist/enhancer-QHNMR35J.js +8 -0
- package/dist/gemini-Q37K5XA5.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +202 -0
- package/dist/lock-K4TS4ENC.js +106 -0
- package/dist/logger-SF6S6GVR.js +9 -0
- package/dist/mcp/main.d.ts +1 -0
- package/dist/mcp/main.js +18 -0
- package/dist/openai-MOPZNA5I.js +34 -0
- package/dist/scanner-T7MGYXQV.js +10 -0
- package/dist/server-276GGS5G.js +614 -0
- package/dist/server-DENFYPME.js +263 -0
- package/package.json +82 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import {
|
|
2
|
+
enhancePrompt
|
|
3
|
+
} from "./chunk-6HF343R7.js";
|
|
4
|
+
import {
|
|
5
|
+
logger
|
|
6
|
+
} from "./chunk-YVLGQTLG.js";
|
|
7
|
+
import "./chunk-V2USKRIC.js";
|
|
8
|
+
|
|
9
|
+
// src/enhancer/server.ts
|
|
10
|
+
import { randomUUID } from "crypto";
|
|
11
|
+
import http from "http";
|
|
12
|
+
import net from "net";
|
|
13
|
+
|
|
14
|
+
// src/enhancer/ui.ts
|
|
15
|
+
function getEnhancePageHtml() {
|
|
16
|
+
return `<!DOCTYPE html>
|
|
17
|
+
<html lang="en">
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="UTF-8" />
|
|
20
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
21
|
+
<title>ContextWeaver Prompt Enhancer</title>
|
|
22
|
+
<style>
|
|
23
|
+
:root {
|
|
24
|
+
--bg: #0b0f19;
|
|
25
|
+
--panel: #111827;
|
|
26
|
+
--border: rgba(255, 255, 255, 0.12);
|
|
27
|
+
--text: rgba(255, 255, 255, 0.92);
|
|
28
|
+
--muted: rgba(255, 255, 255, 0.72);
|
|
29
|
+
--muted2: rgba(255, 255, 255, 0.56);
|
|
30
|
+
--btn: rgba(255, 255, 255, 0.14);
|
|
31
|
+
--btnHover: rgba(255, 255, 255, 0.22);
|
|
32
|
+
--primary: #2563eb;
|
|
33
|
+
--primaryHover: #1d4ed8;
|
|
34
|
+
--danger: #ef4444;
|
|
35
|
+
--dangerHover: #dc2626;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
* {
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
body {
|
|
43
|
+
margin: 0;
|
|
44
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
|
45
|
+
'Noto Sans', 'Apple Color Emoji', 'Segoe UI Emoji';
|
|
46
|
+
background: radial-gradient(800px circle at 0% 0%, rgba(37, 99, 235, 0.25), transparent 45%),
|
|
47
|
+
radial-gradient(800px circle at 100% 0%, rgba(99, 102, 241, 0.18), transparent 45%),
|
|
48
|
+
var(--bg);
|
|
49
|
+
color: var(--text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.container {
|
|
53
|
+
max-width: 1200px;
|
|
54
|
+
margin: 28px auto;
|
|
55
|
+
padding: 0 16px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.header {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: space-between;
|
|
62
|
+
gap: 16px;
|
|
63
|
+
margin-bottom: 14px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.title {
|
|
67
|
+
font-size: 18px;
|
|
68
|
+
font-weight: 700;
|
|
69
|
+
letter-spacing: 0.2px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.meta {
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
color: var(--muted2);
|
|
75
|
+
text-align: right;
|
|
76
|
+
line-height: 1.4;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.panel {
|
|
80
|
+
background: rgba(17, 24, 39, 0.92);
|
|
81
|
+
border: 1px solid var(--border);
|
|
82
|
+
border-radius: 14px;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.45);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.grid {
|
|
88
|
+
display: grid;
|
|
89
|
+
grid-template-columns: 1fr 1fr;
|
|
90
|
+
gap: 0;
|
|
91
|
+
min-height: 520px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@media (max-width: 920px) {
|
|
95
|
+
.grid {
|
|
96
|
+
grid-template-columns: 1fr;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.col {
|
|
101
|
+
padding: 14px;
|
|
102
|
+
border-right: 1px solid var(--border);
|
|
103
|
+
}
|
|
104
|
+
.col:last-child {
|
|
105
|
+
border-right: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.label {
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: baseline;
|
|
111
|
+
justify-content: space-between;
|
|
112
|
+
gap: 12px;
|
|
113
|
+
font-size: 12px;
|
|
114
|
+
color: var(--muted);
|
|
115
|
+
margin: 4px 2px 10px;
|
|
116
|
+
text-transform: uppercase;
|
|
117
|
+
letter-spacing: 0.8px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
textarea {
|
|
121
|
+
width: 100%;
|
|
122
|
+
height: 100%;
|
|
123
|
+
min-height: 440px;
|
|
124
|
+
padding: 12px 12px;
|
|
125
|
+
border: 1px solid var(--border);
|
|
126
|
+
border-radius: 10px;
|
|
127
|
+
background: rgba(0, 0, 0, 0.28);
|
|
128
|
+
color: var(--text);
|
|
129
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
|
130
|
+
'Courier New', monospace;
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
line-height: 1.55;
|
|
133
|
+
resize: vertical;
|
|
134
|
+
outline: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
textarea:focus {
|
|
138
|
+
border-color: rgba(37, 99, 235, 0.7);
|
|
139
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.22);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.footer {
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
justify-content: space-between;
|
|
146
|
+
gap: 12px;
|
|
147
|
+
padding: 12px 14px;
|
|
148
|
+
border-top: 1px solid var(--border);
|
|
149
|
+
background: rgba(17, 24, 39, 0.98);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.hint {
|
|
153
|
+
font-size: 12px;
|
|
154
|
+
color: var(--muted2);
|
|
155
|
+
line-height: 1.45;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.buttons {
|
|
159
|
+
display: flex;
|
|
160
|
+
gap: 10px;
|
|
161
|
+
flex-wrap: wrap;
|
|
162
|
+
justify-content: flex-end;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
button {
|
|
166
|
+
appearance: none;
|
|
167
|
+
border: 1px solid var(--border);
|
|
168
|
+
background: var(--btn);
|
|
169
|
+
color: var(--text);
|
|
170
|
+
border-radius: 10px;
|
|
171
|
+
padding: 10px 12px;
|
|
172
|
+
font-size: 13px;
|
|
173
|
+
font-weight: 650;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
transition: 120ms ease;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
button:hover {
|
|
179
|
+
background: var(--btnHover);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
button:disabled {
|
|
183
|
+
cursor: not-allowed;
|
|
184
|
+
opacity: 0.6;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.primary {
|
|
188
|
+
background: var(--primary);
|
|
189
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
190
|
+
}
|
|
191
|
+
.primary:hover {
|
|
192
|
+
background: var(--primaryHover);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.danger {
|
|
196
|
+
background: rgba(239, 68, 68, 0.12);
|
|
197
|
+
border-color: rgba(239, 68, 68, 0.4);
|
|
198
|
+
}
|
|
199
|
+
.danger:hover {
|
|
200
|
+
background: rgba(239, 68, 68, 0.18);
|
|
201
|
+
border-color: rgba(239, 68, 68, 0.5);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.status {
|
|
205
|
+
margin-top: 10px;
|
|
206
|
+
font-size: 12px;
|
|
207
|
+
color: var(--muted);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.status.error {
|
|
211
|
+
color: rgba(248, 113, 113, 0.98);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.status.success {
|
|
215
|
+
color: rgba(34, 197, 94, 0.98);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.loading {
|
|
219
|
+
opacity: 0.85;
|
|
220
|
+
}
|
|
221
|
+
</style>
|
|
222
|
+
</head>
|
|
223
|
+
<body>
|
|
224
|
+
<div class="container">
|
|
225
|
+
<div class="header">
|
|
226
|
+
<div class="title">ContextWeaver Prompt Enhancer</div>
|
|
227
|
+
<div class="meta" id="meta">Loading\u2026</div>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div class="panel">
|
|
231
|
+
<div class="grid">
|
|
232
|
+
<div class="col">
|
|
233
|
+
<div class="label">
|
|
234
|
+
<span>Original prompt (editable)</span>
|
|
235
|
+
<span id="origCount">0 chars</span>
|
|
236
|
+
</div>
|
|
237
|
+
<textarea id="original" spellcheck="false" aria-label="Original prompt"></textarea>
|
|
238
|
+
</div>
|
|
239
|
+
<div class="col">
|
|
240
|
+
<div class="label">
|
|
241
|
+
<span>Enhanced prompt (editable)</span>
|
|
242
|
+
<span id="enhCount">0 chars</span>
|
|
243
|
+
</div>
|
|
244
|
+
<textarea id="enhanced" spellcheck="false" aria-label="Enhanced prompt"></textarea>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div class="footer">
|
|
249
|
+
<div class="hint">
|
|
250
|
+
Tip: edit left box, click Re-enhance; tweak right box, click Use Edited.
|
|
251
|
+
<div class="status" id="status"></div>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="buttons">
|
|
254
|
+
<button id="useOriginalBtn">Use Original</button>
|
|
255
|
+
<button id="reEnhanceBtn">Re-enhance</button>
|
|
256
|
+
<button class="danger" id="cancelBtn">Cancel</button>
|
|
257
|
+
<button id="useEditedBtn" style="display: none">Use Edited</button>
|
|
258
|
+
<button class="primary" id="useEnhancedBtn">Use Enhanced</button>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<script>
|
|
265
|
+
const originalEl = document.getElementById('original');
|
|
266
|
+
const enhancedEl = document.getElementById('enhanced');
|
|
267
|
+
const statusEl = document.getElementById('status');
|
|
268
|
+
const metaEl = document.getElementById('meta');
|
|
269
|
+
const useOriginalBtn = document.getElementById('useOriginalBtn');
|
|
270
|
+
const useEnhancedBtn = document.getElementById('useEnhancedBtn');
|
|
271
|
+
const useEditedBtn = document.getElementById('useEditedBtn');
|
|
272
|
+
const reEnhanceBtn = document.getElementById('reEnhanceBtn');
|
|
273
|
+
const origCountEl = document.getElementById('origCount');
|
|
274
|
+
const enhCountEl = document.getElementById('enhCount');
|
|
275
|
+
const cancelBtn = document.getElementById('cancelBtn');
|
|
276
|
+
|
|
277
|
+
let baselineEnhanced = '';
|
|
278
|
+
|
|
279
|
+
function setStatus(message, kind) {
|
|
280
|
+
statusEl.textContent = message || '';
|
|
281
|
+
statusEl.className = 'status' + (kind ? ' ' + kind : '');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function updateCounts() {
|
|
285
|
+
origCountEl.textContent = (originalEl.value || '').length + ' chars';
|
|
286
|
+
enhCountEl.textContent = (enhancedEl.value || '').length + ' chars';
|
|
287
|
+
|
|
288
|
+
const edited = (enhancedEl.value || '').trim() && enhancedEl.value !== baselineEnhanced;
|
|
289
|
+
useEditedBtn.style.display = edited ? 'inline-block' : 'none';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
originalEl.addEventListener('input', updateCounts);
|
|
293
|
+
enhancedEl.addEventListener('input', updateCounts);
|
|
294
|
+
|
|
295
|
+
async function jsonFetch(path, body) {
|
|
296
|
+
const res = await fetch(path, {
|
|
297
|
+
method: 'POST',
|
|
298
|
+
headers: { 'Content-Type': 'application/json' },
|
|
299
|
+
body: JSON.stringify(body),
|
|
300
|
+
});
|
|
301
|
+
const data = await res.json();
|
|
302
|
+
if (!res.ok || data.error) {
|
|
303
|
+
throw new Error(data.error || 'Request failed');
|
|
304
|
+
}
|
|
305
|
+
return data;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
async function loadSession() {
|
|
310
|
+
setStatus('Loading session\u2026');
|
|
311
|
+
const res = await fetch('/api/session');
|
|
312
|
+
const data = await res.json();
|
|
313
|
+
if (!res.ok || data.error) {
|
|
314
|
+
throw new Error(data.error || 'Load failed');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
originalEl.value = data.original || '';
|
|
318
|
+
enhancedEl.value = data.enhanced || '';
|
|
319
|
+
baselineEnhanced = enhancedEl.value;
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
metaEl.textContent =
|
|
323
|
+
'Endpoint: ' + (data.endpoint || '-') + ' | Model: ' + (data.model || '-');
|
|
324
|
+
setStatus('Ready.');
|
|
325
|
+
updateCounts();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function submit(action, text) {
|
|
329
|
+
useOriginalBtn.disabled = true;
|
|
330
|
+
useEnhancedBtn.disabled = true;
|
|
331
|
+
useEditedBtn.disabled = true;
|
|
332
|
+
reEnhanceBtn.disabled = true;
|
|
333
|
+
cancelBtn.disabled = true;
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
setStatus('Submitting\u2026');
|
|
337
|
+
await jsonFetch('/api/submit', { action, text });
|
|
338
|
+
setStatus('Done! You can close this tab.', 'success');
|
|
339
|
+
} catch (e) {
|
|
340
|
+
const message = e && e.message ? e.message : String(e);
|
|
341
|
+
setStatus(message, 'error');
|
|
342
|
+
|
|
343
|
+
useOriginalBtn.disabled = false;
|
|
344
|
+
useEnhancedBtn.disabled = false;
|
|
345
|
+
useEditedBtn.disabled = false;
|
|
346
|
+
reEnhanceBtn.disabled = false;
|
|
347
|
+
cancelBtn.disabled = false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
useOriginalBtn.addEventListener('click', () => submit('use-original'));
|
|
352
|
+
useEnhancedBtn.addEventListener('click', () => submit('use-enhanced'));
|
|
353
|
+
useEditedBtn.addEventListener('click', () => submit('use-edited', enhancedEl.value));
|
|
354
|
+
cancelBtn.addEventListener('click', () => submit('cancel'));
|
|
355
|
+
|
|
356
|
+
reEnhanceBtn.addEventListener('click', async () => {
|
|
357
|
+
const current = (originalEl.value || '').trim();
|
|
358
|
+
if (!current) {
|
|
359
|
+
setStatus('Original prompt is empty.', 'error');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
useOriginalBtn.disabled = true;
|
|
364
|
+
useEnhancedBtn.disabled = true;
|
|
365
|
+
useEditedBtn.disabled = true;
|
|
366
|
+
reEnhanceBtn.disabled = true;
|
|
367
|
+
cancelBtn.disabled = true;
|
|
368
|
+
document.body.classList.add('loading');
|
|
369
|
+
setStatus('Enhancing\u2026');
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const data = await jsonFetch('/api/re-enhance', { prompt: current });
|
|
373
|
+
enhancedEl.value = data.enhanced;
|
|
374
|
+
baselineEnhanced = enhancedEl.value;
|
|
375
|
+
setStatus('Enhanced. You can keep editing or submit.', 'success');
|
|
376
|
+
updateCounts();
|
|
377
|
+
} catch (e) {
|
|
378
|
+
const message = e && e.message ? e.message : String(e);
|
|
379
|
+
setStatus(message, 'error');
|
|
380
|
+
} finally {
|
|
381
|
+
useOriginalBtn.disabled = false;
|
|
382
|
+
useEnhancedBtn.disabled = false;
|
|
383
|
+
useEditedBtn.disabled = false;
|
|
384
|
+
reEnhanceBtn.disabled = false;
|
|
385
|
+
cancelBtn.disabled = false;
|
|
386
|
+
document.body.classList.remove('loading');
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
loadSession().catch((e) => {
|
|
391
|
+
const message = e && e.message ? e.message : String(e);
|
|
392
|
+
setStatus(message, 'error');
|
|
393
|
+
metaEl.textContent = 'Failed to load.';
|
|
394
|
+
});
|
|
395
|
+
</script>
|
|
396
|
+
</body>
|
|
397
|
+
</html>`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/enhancer/server.ts
|
|
401
|
+
function json(res, status, data) {
|
|
402
|
+
const body = JSON.stringify(data);
|
|
403
|
+
res.statusCode = status;
|
|
404
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
405
|
+
res.end(body);
|
|
406
|
+
}
|
|
407
|
+
function text(res, status, body) {
|
|
408
|
+
res.statusCode = status;
|
|
409
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
410
|
+
res.end(body);
|
|
411
|
+
}
|
|
412
|
+
function isLocalhostOrigin(origin) {
|
|
413
|
+
if (!origin) return true;
|
|
414
|
+
try {
|
|
415
|
+
const url = new URL(origin);
|
|
416
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function setCors(req, res) {
|
|
422
|
+
const origin = req.headers.origin;
|
|
423
|
+
if (!origin) return;
|
|
424
|
+
if (!isLocalhostOrigin(origin)) return;
|
|
425
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
426
|
+
res.setHeader("Vary", "Origin");
|
|
427
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
428
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
429
|
+
}
|
|
430
|
+
async function readJsonBody(req, maxBytes = 1024 * 1024) {
|
|
431
|
+
const chunks = [];
|
|
432
|
+
let total = 0;
|
|
433
|
+
for await (const chunk of req) {
|
|
434
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
435
|
+
total += buf.length;
|
|
436
|
+
if (total > maxBytes) {
|
|
437
|
+
throw new Error(`Request body too large (max ${maxBytes} bytes)`);
|
|
438
|
+
}
|
|
439
|
+
chunks.push(buf);
|
|
440
|
+
}
|
|
441
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
442
|
+
if (!raw) {
|
|
443
|
+
throw new Error("Empty request body");
|
|
444
|
+
}
|
|
445
|
+
return JSON.parse(raw);
|
|
446
|
+
}
|
|
447
|
+
async function findAvailablePort(start = 3e3, end = 3100) {
|
|
448
|
+
for (let port = start; port <= end; port++) {
|
|
449
|
+
const canListen = await new Promise((resolve) => {
|
|
450
|
+
const tester = net.createServer();
|
|
451
|
+
tester.once("error", () => {
|
|
452
|
+
resolve(false);
|
|
453
|
+
});
|
|
454
|
+
tester.once("listening", () => {
|
|
455
|
+
tester.close(() => resolve(true));
|
|
456
|
+
});
|
|
457
|
+
tester.listen(port, "127.0.0.1");
|
|
458
|
+
});
|
|
459
|
+
if (canListen) {
|
|
460
|
+
return port;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
throw new Error(`No available port in range ${start}-${end}`);
|
|
464
|
+
}
|
|
465
|
+
async function startEnhanceServer(prompt, options = {}) {
|
|
466
|
+
const port = await findAvailablePort();
|
|
467
|
+
const initial = await enhancePrompt({
|
|
468
|
+
prompt,
|
|
469
|
+
endpointOverride: options.endpointOverride
|
|
470
|
+
});
|
|
471
|
+
const session = {
|
|
472
|
+
id: randomUUID(),
|
|
473
|
+
original: initial.original,
|
|
474
|
+
enhanced: initial.enhanced,
|
|
475
|
+
endpoint: initial.endpoint,
|
|
476
|
+
model: initial.model,
|
|
477
|
+
createdAt: Date.now()
|
|
478
|
+
};
|
|
479
|
+
let resolved = false;
|
|
480
|
+
let resolveResult;
|
|
481
|
+
let rejectResult;
|
|
482
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
483
|
+
resolveResult = resolve;
|
|
484
|
+
rejectResult = reject;
|
|
485
|
+
});
|
|
486
|
+
const server = http.createServer(async (req, res) => {
|
|
487
|
+
try {
|
|
488
|
+
setCors(req, res);
|
|
489
|
+
if (req.method === "OPTIONS") {
|
|
490
|
+
res.statusCode = 204;
|
|
491
|
+
res.end();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const url2 = new URL(req.url || "/", `http://localhost:${port}`);
|
|
495
|
+
if (req.method === "GET" && url2.pathname === "/enhance") {
|
|
496
|
+
res.statusCode = 200;
|
|
497
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
498
|
+
res.end(getEnhancePageHtml());
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (req.method === "GET" && url2.pathname === "/api/session") {
|
|
502
|
+
json(res, 200, {
|
|
503
|
+
id: session.id,
|
|
504
|
+
original: session.original,
|
|
505
|
+
enhanced: session.enhanced,
|
|
506
|
+
endpoint: session.endpoint,
|
|
507
|
+
model: session.model,
|
|
508
|
+
createdAt: session.createdAt
|
|
509
|
+
});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (req.method === "POST" && url2.pathname === "/api/submit") {
|
|
513
|
+
const body = await readJsonBody(req);
|
|
514
|
+
if (body.action === "cancel") {
|
|
515
|
+
abort("User cancelled the prompt enhancer");
|
|
516
|
+
json(res, 200, { ok: true });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (body.action === "use-original") {
|
|
520
|
+
finish(session.original);
|
|
521
|
+
json(res, 200, { ok: true });
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (body.action === "use-enhanced") {
|
|
525
|
+
finish(session.enhanced);
|
|
526
|
+
json(res, 200, { ok: true });
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (body.action === "use-edited") {
|
|
530
|
+
const edited = (body.text || "").trim();
|
|
531
|
+
if (!edited) {
|
|
532
|
+
json(res, 400, { error: "Edited text is required" });
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
finish(edited);
|
|
536
|
+
json(res, 200, { ok: true });
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
json(res, 400, { error: "Unknown action" });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (req.method === "POST" && url2.pathname === "/api/re-enhance") {
|
|
543
|
+
const body = await readJsonBody(req);
|
|
544
|
+
const nextPrompt = (body.prompt || "").trim();
|
|
545
|
+
if (!nextPrompt) {
|
|
546
|
+
json(res, 400, { error: "prompt is required" });
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const next = await enhancePrompt({
|
|
550
|
+
prompt: nextPrompt,
|
|
551
|
+
endpointOverride: options.endpointOverride
|
|
552
|
+
});
|
|
553
|
+
session.original = nextPrompt;
|
|
554
|
+
session.enhanced = next.enhanced;
|
|
555
|
+
session.endpoint = next.endpoint;
|
|
556
|
+
session.model = next.model;
|
|
557
|
+
json(res, 200, {
|
|
558
|
+
enhanced: session.enhanced,
|
|
559
|
+
endpoint: session.endpoint,
|
|
560
|
+
model: session.model
|
|
561
|
+
});
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
text(res, 404, "Not Found");
|
|
565
|
+
} catch (err) {
|
|
566
|
+
const error = err;
|
|
567
|
+
logger.error({ error: error.message, stack: error.stack }, "Enhancer server error");
|
|
568
|
+
json(res, 500, { error: error.message || "Internal error" });
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
const timeoutMs = 8 * 60 * 1e3;
|
|
572
|
+
const timeout = setTimeout(() => {
|
|
573
|
+
if (resolved) return;
|
|
574
|
+
resolved = true;
|
|
575
|
+
server.close(() => {
|
|
576
|
+
rejectResult?.(new Error("User interaction timeout (8 minutes)"));
|
|
577
|
+
});
|
|
578
|
+
}, timeoutMs);
|
|
579
|
+
function abort(message) {
|
|
580
|
+
if (resolved) return;
|
|
581
|
+
resolved = true;
|
|
582
|
+
clearTimeout(timeout);
|
|
583
|
+
server.close(() => {
|
|
584
|
+
rejectResult?.(new Error(message));
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
function finish(selectedText) {
|
|
588
|
+
if (resolved) return;
|
|
589
|
+
resolved = true;
|
|
590
|
+
clearTimeout(timeout);
|
|
591
|
+
const result = {
|
|
592
|
+
original: session.original,
|
|
593
|
+
enhanced: selectedText,
|
|
594
|
+
endpoint: session.endpoint,
|
|
595
|
+
model: session.model
|
|
596
|
+
};
|
|
597
|
+
server.close(() => {
|
|
598
|
+
resolveResult?.(result);
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
const url = `http://localhost:${port}/enhance`;
|
|
602
|
+
await new Promise((resolve, reject) => {
|
|
603
|
+
server.on("error", reject);
|
|
604
|
+
server.listen(port, "127.0.0.1", () => {
|
|
605
|
+
logger.info({ port }, "Prompt enhancer server started");
|
|
606
|
+
options.onStarted?.(url);
|
|
607
|
+
resolve();
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
return resultPromise;
|
|
611
|
+
}
|
|
612
|
+
export {
|
|
613
|
+
startEnhanceServer
|
|
614
|
+
};
|