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.
Files changed (2) hide show
  1. package/bin/cli.js +180 -33
  2. 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(endpoints))
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
- if (!testBinding || !endpoints[testBinding]) {
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/' + endpoints[testBinding] + '?mode=sync', {
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: 900, margin: '0 auto', padding: 24, fontFamily: 'system-ui, sans-serif' }}>
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
- <tr key={b.name} style={{ borderBottom: '1px solid #f3f4f6' }}>
2442
- <td style={{ padding: 8 }}>
2443
- <div style={{ fontWeight: 500 }}>{b.name}</div>
2444
- <div style={{ fontSize: 12, color: '#6b7280' }}>{b.description?.slice(0, 60) || b.type}</div>
2445
- </td>
2446
- <td style={{ padding: 8 }}>
2447
- <input
2448
- type="text"
2449
- value={endpoints[b.name] || ''}
2450
- onChange={e => updateEndpoint(b.name, e.target.value)}
2451
- placeholder="ep_..."
2452
- style={{ width: '100%', padding: 8, border: '1px solid #d1d5db', borderRadius: 4, fontSize: 13 }}
2453
- />
2454
- </td>
2455
- <td style={{ padding: 8, textAlign: 'center' }}>
2456
- <span style={{
2457
- display: 'inline-block',
2458
- width: 12,
2459
- height: 12,
2460
- borderRadius: '50%',
2461
- background: endpoints[b.name] ? '#22c55e' : '#d1d5db'
2462
- }} />
2463
- </td>
2464
- </tr>
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 ? '...' : 'Test'}
2661
+ {testing ? '...' : 'Send'}
2515
2662
  </button>
2516
2663
  </div>
2517
2664
  <textarea
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptlineapp",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Create PromptLine applications with ease",
5
5
  "author": "PromptLine <support@promptlineops.com>",
6
6
  "license": "MIT",