promptlineapp 1.7.0 → 1.8.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/bin/cli.js +180 -33
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -2332,9 +2332,20 @@ import { availableBindings } from './sdk-mock'
|
|
|
2332
2332
|
const STORAGE_KEY = 'promptline_endpoints_config'
|
|
2333
2333
|
const API_KEY_STORAGE = 'promptline_api_key'
|
|
2334
2334
|
|
|
2335
|
+
// Helper to extract endpoint ID from URL or return as-is
|
|
2336
|
+
function extractEndpointId(value) {
|
|
2337
|
+
if (!value) return ''
|
|
2338
|
+
// If it's a full URL, extract the endpoint ID
|
|
2339
|
+
const match = value.match(/\\/api\\/v1\\/live\\/([^?/]+)/) || value.match(/\\/live\\/([^?/]+)/)
|
|
2340
|
+
if (match) return match[1]
|
|
2341
|
+
// If it starts with ep_ or similar, use as-is
|
|
2342
|
+
return value
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2335
2345
|
export default function DevAdmin() {
|
|
2336
2346
|
const [apiKey, setApiKey] = useState('')
|
|
2337
2347
|
const [endpoints, setEndpoints] = useState({})
|
|
2348
|
+
const [endpointStatus, setEndpointStatus] = useState({}) // { bindingName: { alive: bool, info: obj, loading: string } }
|
|
2338
2349
|
const [testBinding, setTestBinding] = useState('')
|
|
2339
2350
|
const [testInput, setTestInput] = useState('Hello, test!')
|
|
2340
2351
|
const [testResult, setTestResult] = useState('')
|
|
@@ -2352,8 +2363,14 @@ export default function DevAdmin() {
|
|
|
2352
2363
|
}, [])
|
|
2353
2364
|
|
|
2354
2365
|
const save = () => {
|
|
2366
|
+
// Clean endpoint values (extract IDs from URLs)
|
|
2367
|
+
const cleanedEndpoints = {}
|
|
2368
|
+
Object.entries(endpoints).forEach(([key, value]) => {
|
|
2369
|
+
cleanedEndpoints[key] = extractEndpointId(value)
|
|
2370
|
+
})
|
|
2371
|
+
setEndpoints(cleanedEndpoints)
|
|
2355
2372
|
localStorage.setItem(API_KEY_STORAGE, apiKey)
|
|
2356
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(
|
|
2373
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(cleanedEndpoints))
|
|
2357
2374
|
setSaved(true)
|
|
2358
2375
|
setTimeout(() => setSaved(false), 2000)
|
|
2359
2376
|
}
|
|
@@ -2362,8 +2379,75 @@ export default function DevAdmin() {
|
|
|
2362
2379
|
setEndpoints(prev => ({ ...prev, [name]: value }))
|
|
2363
2380
|
}
|
|
2364
2381
|
|
|
2382
|
+
// Check if endpoint is alive
|
|
2383
|
+
const checkIsAlive = async (bindingName) => {
|
|
2384
|
+
const endpointId = extractEndpointId(endpoints[bindingName])
|
|
2385
|
+
if (!endpointId) {
|
|
2386
|
+
setEndpointStatus(prev => ({ ...prev, [bindingName]: { ...prev[bindingName], alive: false, error: 'No endpoint ID' } }))
|
|
2387
|
+
return
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
setEndpointStatus(prev => ({ ...prev, [bindingName]: { ...prev[bindingName], loading: 'alive' } }))
|
|
2391
|
+
|
|
2392
|
+
try {
|
|
2393
|
+
const res = await fetch('/api/promptline/' + endpointId + '/isAlive', {
|
|
2394
|
+
method: 'GET',
|
|
2395
|
+
headers: apiKey ? { 'X-API-Key': apiKey } : {}
|
|
2396
|
+
})
|
|
2397
|
+
const data = await res.json().catch(() => ({}))
|
|
2398
|
+
setEndpointStatus(prev => ({
|
|
2399
|
+
...prev,
|
|
2400
|
+
[bindingName]: {
|
|
2401
|
+
...prev[bindingName],
|
|
2402
|
+
alive: res.ok && data.alive === true,
|
|
2403
|
+
loading: null,
|
|
2404
|
+
error: res.ok ? null : (data.detail || res.statusText)
|
|
2405
|
+
}
|
|
2406
|
+
}))
|
|
2407
|
+
} catch (err) {
|
|
2408
|
+
setEndpointStatus(prev => ({
|
|
2409
|
+
...prev,
|
|
2410
|
+
[bindingName]: { ...prev[bindingName], alive: false, loading: null, error: err.message }
|
|
2411
|
+
}))
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// Get endpoint info
|
|
2416
|
+
const fetchInfo = async (bindingName) => {
|
|
2417
|
+
const endpointId = extractEndpointId(endpoints[bindingName])
|
|
2418
|
+
if (!endpointId) {
|
|
2419
|
+
setEndpointStatus(prev => ({ ...prev, [bindingName]: { ...prev[bindingName], error: 'No endpoint ID' } }))
|
|
2420
|
+
return
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
setEndpointStatus(prev => ({ ...prev, [bindingName]: { ...prev[bindingName], loading: 'info' } }))
|
|
2424
|
+
|
|
2425
|
+
try {
|
|
2426
|
+
const res = await fetch('/api/promptline/' + endpointId + '/info', {
|
|
2427
|
+
method: 'GET',
|
|
2428
|
+
headers: apiKey ? { 'X-API-Key': apiKey } : {}
|
|
2429
|
+
})
|
|
2430
|
+
const data = await res.json().catch(() => ({}))
|
|
2431
|
+
setEndpointStatus(prev => ({
|
|
2432
|
+
...prev,
|
|
2433
|
+
[bindingName]: {
|
|
2434
|
+
...prev[bindingName],
|
|
2435
|
+
info: res.ok ? data : null,
|
|
2436
|
+
loading: null,
|
|
2437
|
+
error: res.ok ? null : (data.detail || res.statusText)
|
|
2438
|
+
}
|
|
2439
|
+
}))
|
|
2440
|
+
} catch (err) {
|
|
2441
|
+
setEndpointStatus(prev => ({
|
|
2442
|
+
...prev,
|
|
2443
|
+
[bindingName]: { ...prev[bindingName], info: null, loading: null, error: err.message }
|
|
2444
|
+
}))
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2365
2448
|
const testEndpoint = async () => {
|
|
2366
|
-
|
|
2449
|
+
const endpointId = extractEndpointId(endpoints[testBinding])
|
|
2450
|
+
if (!testBinding || !endpointId) {
|
|
2367
2451
|
setTestResult('Error: Endpoint not configured')
|
|
2368
2452
|
return
|
|
2369
2453
|
}
|
|
@@ -2376,7 +2460,7 @@ export default function DevAdmin() {
|
|
|
2376
2460
|
setTestResult('Calling API...')
|
|
2377
2461
|
|
|
2378
2462
|
try {
|
|
2379
|
-
const res = await fetch('/api/promptline/' +
|
|
2463
|
+
const res = await fetch('/api/promptline/' + endpointId + '?mode=sync', {
|
|
2380
2464
|
method: 'POST',
|
|
2381
2465
|
headers: {
|
|
2382
2466
|
'Content-Type': 'application/json',
|
|
@@ -2399,8 +2483,18 @@ export default function DevAdmin() {
|
|
|
2399
2483
|
|
|
2400
2484
|
const configuredCount = Object.values(endpoints).filter(Boolean).length
|
|
2401
2485
|
|
|
2486
|
+
const btnStyle = (loading) => ({
|
|
2487
|
+
padding: '4px 8px',
|
|
2488
|
+
fontSize: 11,
|
|
2489
|
+
border: '1px solid #d1d5db',
|
|
2490
|
+
borderRadius: 4,
|
|
2491
|
+
background: loading ? '#f3f4f6' : '#fff',
|
|
2492
|
+
cursor: loading ? 'wait' : 'pointer',
|
|
2493
|
+
marginRight: 4
|
|
2494
|
+
})
|
|
2495
|
+
|
|
2402
2496
|
return (
|
|
2403
|
-
<div style={{ maxWidth:
|
|
2497
|
+
<div style={{ maxWidth: 1000, margin: '0 auto', padding: 24, fontFamily: 'system-ui, sans-serif' }}>
|
|
2404
2498
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
|
|
2405
2499
|
<h1 style={{ margin: 0, color: '#f59e0b' }}>/_dev</h1>
|
|
2406
2500
|
<Link to="/" style={{ color: '#6366f1' }}>← Back to App</Link>
|
|
@@ -2418,9 +2512,12 @@ export default function DevAdmin() {
|
|
|
2418
2512
|
type="password"
|
|
2419
2513
|
value={apiKey}
|
|
2420
2514
|
onChange={e => setApiKey(e.target.value)}
|
|
2421
|
-
placeholder="sk_org_..."
|
|
2515
|
+
placeholder="sk_org_... or sk_ws_..."
|
|
2422
2516
|
style={{ width: '100%', padding: 12, border: '1px solid #d1d5db', borderRadius: 6, fontSize: 14 }}
|
|
2423
2517
|
/>
|
|
2518
|
+
<p style={{ margin: '8px 0 0', fontSize: 12, color: '#6b7280' }}>
|
|
2519
|
+
This API key will be used for Info and IsAlive checks
|
|
2520
|
+
</p>
|
|
2424
2521
|
</div>
|
|
2425
2522
|
|
|
2426
2523
|
{/* Bindings */}
|
|
@@ -2433,38 +2530,88 @@ export default function DevAdmin() {
|
|
|
2433
2530
|
<tr style={{ borderBottom: '2px solid #e5e7eb' }}>
|
|
2434
2531
|
<th style={{ textAlign: 'left', padding: 8 }}>Binding</th>
|
|
2435
2532
|
<th style={{ textAlign: 'left', padding: 8 }}>Endpoint ID</th>
|
|
2533
|
+
<th style={{ textAlign: 'center', padding: 8, width: 140 }}>Actions</th>
|
|
2436
2534
|
<th style={{ textAlign: 'center', padding: 8, width: 60 }}>Status</th>
|
|
2437
2535
|
</tr>
|
|
2438
2536
|
</thead>
|
|
2439
2537
|
<tbody>
|
|
2440
|
-
{availableBindings.map(b =>
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
<
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2538
|
+
{availableBindings.map(b => {
|
|
2539
|
+
const status = endpointStatus[b.name] || {}
|
|
2540
|
+
return (
|
|
2541
|
+
<tr key={b.name} style={{ borderBottom: '1px solid #f3f4f6' }}>
|
|
2542
|
+
<td style={{ padding: 8 }}>
|
|
2543
|
+
<div style={{ fontWeight: 500 }}>{b.name}</div>
|
|
2544
|
+
<div style={{ fontSize: 12, color: '#6b7280' }}>{b.description?.slice(0, 50) || b.type}</div>
|
|
2545
|
+
</td>
|
|
2546
|
+
<td style={{ padding: 8 }}>
|
|
2547
|
+
<input
|
|
2548
|
+
type="text"
|
|
2549
|
+
value={endpoints[b.name] || ''}
|
|
2550
|
+
onChange={e => updateEndpoint(b.name, e.target.value)}
|
|
2551
|
+
placeholder="ep_... or full URL"
|
|
2552
|
+
style={{ width: '100%', padding: 8, border: '1px solid #d1d5db', borderRadius: 4, fontSize: 12 }}
|
|
2553
|
+
/>
|
|
2554
|
+
</td>
|
|
2555
|
+
<td style={{ padding: 8, textAlign: 'center' }}>
|
|
2556
|
+
<button
|
|
2557
|
+
onClick={() => checkIsAlive(b.name)}
|
|
2558
|
+
disabled={status.loading === 'alive'}
|
|
2559
|
+
style={btnStyle(status.loading === 'alive')}
|
|
2560
|
+
title="Check if endpoint is alive"
|
|
2561
|
+
>
|
|
2562
|
+
{status.loading === 'alive' ? '...' : '♥ Alive'}
|
|
2563
|
+
</button>
|
|
2564
|
+
<button
|
|
2565
|
+
onClick={() => fetchInfo(b.name)}
|
|
2566
|
+
disabled={status.loading === 'info'}
|
|
2567
|
+
style={btnStyle(status.loading === 'info')}
|
|
2568
|
+
title="Get endpoint info"
|
|
2569
|
+
>
|
|
2570
|
+
{status.loading === 'info' ? '...' : 'ℹ Info'}
|
|
2571
|
+
</button>
|
|
2572
|
+
</td>
|
|
2573
|
+
<td style={{ padding: 8, textAlign: 'center' }}>
|
|
2574
|
+
<span
|
|
2575
|
+
style={{
|
|
2576
|
+
display: 'inline-block',
|
|
2577
|
+
width: 12,
|
|
2578
|
+
height: 12,
|
|
2579
|
+
borderRadius: '50%',
|
|
2580
|
+
background: status.alive === true ? '#22c55e' : status.alive === false ? '#ef4444' : (endpoints[b.name] ? '#fbbf24' : '#d1d5db')
|
|
2581
|
+
}}
|
|
2582
|
+
title={status.error || (status.alive ? 'Alive' : status.alive === false ? 'Not responding' : 'Not checked')}
|
|
2583
|
+
/>
|
|
2584
|
+
</td>
|
|
2585
|
+
</tr>
|
|
2586
|
+
)
|
|
2587
|
+
})}
|
|
2466
2588
|
</tbody>
|
|
2467
2589
|
</table>
|
|
2590
|
+
|
|
2591
|
+
{/* Show info panel if any endpoint has info */}
|
|
2592
|
+
{Object.entries(endpointStatus).some(([_, s]) => s.info) && (
|
|
2593
|
+
<div style={{ marginTop: 16, padding: 12, background: '#f9fafb', borderRadius: 6, fontSize: 12 }}>
|
|
2594
|
+
<strong>Endpoint Info:</strong>
|
|
2595
|
+
{Object.entries(endpointStatus).filter(([_, s]) => s.info).map(([name, s]) => (
|
|
2596
|
+
<div key={name} style={{ marginTop: 8 }}>
|
|
2597
|
+
<strong>{name}:</strong>
|
|
2598
|
+
<pre style={{ margin: '4px 0', whiteSpace: 'pre-wrap', fontSize: 11 }}>
|
|
2599
|
+
{JSON.stringify(s.info, null, 2)}
|
|
2600
|
+
</pre>
|
|
2601
|
+
</div>
|
|
2602
|
+
))}
|
|
2603
|
+
</div>
|
|
2604
|
+
)}
|
|
2605
|
+
|
|
2606
|
+
{/* Show errors */}
|
|
2607
|
+
{Object.entries(endpointStatus).some(([_, s]) => s.error) && (
|
|
2608
|
+
<div style={{ marginTop: 16, padding: 12, background: '#fef2f2', borderRadius: 6, fontSize: 12, color: '#dc2626' }}>
|
|
2609
|
+
<strong>Errors:</strong>
|
|
2610
|
+
{Object.entries(endpointStatus).filter(([_, s]) => s.error).map(([name, s]) => (
|
|
2611
|
+
<div key={name}>{name}: {s.error}</div>
|
|
2612
|
+
))}
|
|
2613
|
+
</div>
|
|
2614
|
+
)}
|
|
2468
2615
|
</div>
|
|
2469
2616
|
|
|
2470
2617
|
{/* Save Button */}
|
|
@@ -2488,7 +2635,7 @@ export default function DevAdmin() {
|
|
|
2488
2635
|
|
|
2489
2636
|
{/* Test Panel */}
|
|
2490
2637
|
<div style={{ background: '#fff', border: '1px solid #e5e7eb', borderRadius: 8, padding: 16 }}>
|
|
2491
|
-
<h2 style={{ margin: '0 0 16px', fontSize: 18 }}>🧪 Test</h2>
|
|
2638
|
+
<h2 style={{ margin: '0 0 16px', fontSize: 18 }}>🧪 Test AI Call</h2>
|
|
2492
2639
|
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
|
|
2493
2640
|
<select
|
|
2494
2641
|
value={testBinding}
|
|
@@ -2511,7 +2658,7 @@ export default function DevAdmin() {
|
|
|
2511
2658
|
cursor: testing ? 'wait' : 'pointer'
|
|
2512
2659
|
}}
|
|
2513
2660
|
>
|
|
2514
|
-
{testing ? '...' : '
|
|
2661
|
+
{testing ? '...' : 'Send'}
|
|
2515
2662
|
</button>
|
|
2516
2663
|
</div>
|
|
2517
2664
|
<textarea
|