promptlineapp 1.4.1 → 1.5.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/README.md +46 -108
- package/bin/cli.js +421 -184
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,136 +1,74 @@
|
|
|
1
|
-
#
|
|
1
|
+
# create-promptline-app
|
|
2
2
|
|
|
3
|
-
Create AI-powered
|
|
3
|
+
Create AI-powered apps for the [PromptLine Marketplace](https://promptlineops.com).
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx
|
|
8
|
+
npx create-promptline-app my-app
|
|
9
9
|
cd my-app
|
|
10
|
+
npm run dev
|
|
10
11
|
```
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
## Local Development
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
### 1. Configure your endpoints
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Open **http://localhost:5173/_dev** and add your PromptLine endpoints:
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
You'll be prompted for:
|
|
23
|
-
- Display name
|
|
24
|
-
- Description
|
|
25
|
-
- Brand color
|
|
26
|
-
- Contact email
|
|
27
|
-
- Template preset
|
|
28
|
-
|
|
29
|
-
### Non-Interactive Mode
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npx promptlineapp my-app --yes
|
|
33
|
-
npx promptlineapp my-app --preset full-app -y
|
|
34
|
-
```
|
|
19
|
+
- **Alias**: name used in code (e.g., `chatbot`, `traduction`)
|
|
20
|
+
- **URL**: endpoint URL from Creator Studio
|
|
21
|
+
- **API Key**: your API key (`sk_org_...` or `sk_ws_...`)
|
|
35
22
|
|
|
36
|
-
|
|
23
|
+
Click "Check Health" to verify, then "Add Endpoint".
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|--------|-------------|----------|
|
|
40
|
-
| `full-app` | Complete app with landing, dashboard, settings & AI | SaaS products, internal tools (default) |
|
|
41
|
-
| `api` | Minimal frontend, backend-focused | API services, integrations |
|
|
25
|
+
### 2. Use in your code
|
|
42
26
|
|
|
43
|
-
```
|
|
44
|
-
npx promptlineapp my-app --preset full-app
|
|
45
|
-
npx promptlineapp my-api --preset api
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## What You Get
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
my-app/
|
|
52
|
-
├── public/ # Public pages (no auth required)
|
|
53
|
-
│ └── index.tsx # Landing page
|
|
54
|
-
├── private/ # Protected pages (auth required)
|
|
55
|
-
│ ├── dashboard.tsx # Dashboard
|
|
56
|
-
│ └── settings.tsx # Settings
|
|
57
|
-
├── backend/ # Custom API endpoints (FastAPI)
|
|
58
|
-
│ └── api.py
|
|
59
|
-
├── assets/ # Static files
|
|
60
|
-
├── promptline.yaml # Package configuration
|
|
61
|
-
├── README.md
|
|
62
|
-
└── .gitignore
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Configuration
|
|
66
|
-
|
|
67
|
-
Your app is configured via `promptline.yaml`:
|
|
68
|
-
|
|
69
|
-
```yaml
|
|
70
|
-
name: "My App"
|
|
71
|
-
version: "1.0.0"
|
|
72
|
-
|
|
73
|
-
# Connect AI prompts/agents
|
|
74
|
-
ai_sources:
|
|
75
|
-
main:
|
|
76
|
-
type: prompt
|
|
77
|
-
source_id: null # Set in Creator Studio
|
|
78
|
-
|
|
79
|
-
# Define data models
|
|
80
|
-
collections:
|
|
81
|
-
submissions:
|
|
82
|
-
fields:
|
|
83
|
-
- name: email
|
|
84
|
-
type: email
|
|
85
|
-
required: true
|
|
86
|
-
|
|
87
|
-
# Instance variables
|
|
88
|
-
variables:
|
|
89
|
-
app_name:
|
|
90
|
-
type: string
|
|
91
|
-
default: "My App"
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## SDK Usage
|
|
95
|
-
|
|
96
|
-
In your React pages:
|
|
97
|
-
|
|
98
|
-
```tsx
|
|
27
|
+
```jsx
|
|
99
28
|
import { usePromptLine } from '@promptline/sdk'
|
|
100
29
|
|
|
101
|
-
|
|
102
|
-
const {
|
|
103
|
-
|
|
104
|
-
// Access instance config
|
|
105
|
-
const appName = config.app_name
|
|
30
|
+
function MyComponent() {
|
|
31
|
+
const { execute, isConfigured } = usePromptLine('chatbot')
|
|
106
32
|
|
|
107
|
-
|
|
108
|
-
|
|
33
|
+
const handleClick = async () => {
|
|
34
|
+
const result = await execute({ text: 'Hello!' })
|
|
35
|
+
console.log(result.response)
|
|
36
|
+
}
|
|
109
37
|
|
|
110
|
-
|
|
111
|
-
|
|
38
|
+
if (!isConfigured) {
|
|
39
|
+
return <p>Configure 'chatbot' at <a href="/_dev">/_dev</a></p>
|
|
40
|
+
}
|
|
112
41
|
|
|
113
|
-
return <
|
|
42
|
+
return <button onClick={handleClick}>Ask AI</button>
|
|
114
43
|
}
|
|
115
44
|
```
|
|
116
45
|
|
|
117
|
-
|
|
46
|
+
### 3. Multiple endpoints
|
|
118
47
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
48
|
+
```jsx
|
|
49
|
+
const { execute: chat } = usePromptLine('chatbot')
|
|
50
|
+
const { execute: translate } = usePromptLine('traduction')
|
|
122
51
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- [API Endpoints](https://docs.promptlineops.com/packages/backend)
|
|
52
|
+
// Use them independently
|
|
53
|
+
const chatResult = await chat({ text: 'Hello' })
|
|
54
|
+
const translatedResult = await translate({ text: 'Bonjour', target: 'en' })
|
|
55
|
+
```
|
|
128
56
|
|
|
129
|
-
##
|
|
57
|
+
## Project Structure
|
|
130
58
|
|
|
131
|
-
|
|
132
|
-
-
|
|
59
|
+
```
|
|
60
|
+
my-app/
|
|
61
|
+
├── public/ # Public pages
|
|
62
|
+
├── private/ # Auth-protected pages
|
|
63
|
+
├── backend/ # Custom API (FastAPI)
|
|
64
|
+
├── dev/ # Local dev tools (not deployed)
|
|
65
|
+
│ ├── sdk-mock.jsx # SDK for local testing
|
|
66
|
+
│ └── dev-admin.jsx # /_dev page
|
|
67
|
+
└── promptline.yaml # App configuration
|
|
68
|
+
```
|
|
133
69
|
|
|
134
|
-
##
|
|
70
|
+
## Deployment
|
|
135
71
|
|
|
136
|
-
|
|
72
|
+
1. Click "Download Manifest" in `/_dev` to get `promptline.config.json`
|
|
73
|
+
2. Upload your app to [Creator Studio](https://app.promptlineops.com)
|
|
74
|
+
3. Bind your endpoints and publish
|
package/bin/cli.js
CHANGED
|
@@ -1066,6 +1066,9 @@ Thumbs.db
|
|
|
1066
1066
|
import react from '@vitejs/plugin-react'
|
|
1067
1067
|
import path from 'path'
|
|
1068
1068
|
|
|
1069
|
+
// Internal PromptLine dev mode: set PROMPTLINE_LOCAL_DEV=true to enable local API
|
|
1070
|
+
const isLocalDev = process.env.PROMPTLINE_LOCAL_DEV === 'true'
|
|
1071
|
+
|
|
1069
1072
|
export default defineConfig({
|
|
1070
1073
|
plugins: [react()],
|
|
1071
1074
|
resolve: {
|
|
@@ -1076,16 +1079,19 @@ export default defineConfig({
|
|
|
1076
1079
|
server: {
|
|
1077
1080
|
port: 5173,
|
|
1078
1081
|
host: 'localhost',
|
|
1079
|
-
open:
|
|
1082
|
+
open: '/_dev',
|
|
1080
1083
|
strictPort: false,
|
|
1081
1084
|
proxy: {
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1085
|
+
// Local API first (more specific path - requires PROMPTLINE_LOCAL_DEV=true)
|
|
1086
|
+
...(isLocalDev ? {
|
|
1087
|
+
'/api/promptline-local': {
|
|
1088
|
+
target: 'https://app.local.promptlineops.com',
|
|
1089
|
+
changeOrigin: true,
|
|
1090
|
+
rewrite: (path) => path.replace(/^\\/api\\/promptline-local/, '/api/v1/live'),
|
|
1091
|
+
secure: false
|
|
1092
|
+
}
|
|
1093
|
+
} : {}),
|
|
1094
|
+
// Production API (marketplace developers)
|
|
1089
1095
|
'/api/promptline': {
|
|
1090
1096
|
target: 'https://app.promptlineops.com',
|
|
1091
1097
|
changeOrigin: true,
|
|
@@ -1097,7 +1103,7 @@ export default defineConfig({
|
|
|
1097
1103
|
})
|
|
1098
1104
|
`,
|
|
1099
1105
|
|
|
1100
|
-
// SDK mock for local development with
|
|
1106
|
+
// SDK mock for local development with multi-endpoint support
|
|
1101
1107
|
sdkMock: (config) => {
|
|
1102
1108
|
const mockConfig = {
|
|
1103
1109
|
app_name: config.displayName,
|
|
@@ -1107,10 +1113,16 @@ export default defineConfig({
|
|
|
1107
1113
|
return `/**
|
|
1108
1114
|
* PromptLine SDK Mock for Local Development
|
|
1109
1115
|
* Supports real API calls when configured via /_dev page
|
|
1116
|
+
*
|
|
1117
|
+
* Usage:
|
|
1118
|
+
* const { execute } = usePromptLine('chatbot')
|
|
1119
|
+
* const result = await execute({ text: 'Hello!' })
|
|
1110
1120
|
*/
|
|
1111
|
-
import React, { createContext, useContext, useState } from 'react'
|
|
1121
|
+
import React, { createContext, useContext, useState, useMemo } from 'react'
|
|
1112
1122
|
import { Link as RouterLink } from 'react-router-dom'
|
|
1113
1123
|
|
|
1124
|
+
const ENDPOINTS_STORAGE_KEY = 'promptline_dev_endpoints'
|
|
1125
|
+
|
|
1114
1126
|
const mockConfig = ${JSON.stringify(mockConfig, null, 2)}
|
|
1115
1127
|
|
|
1116
1128
|
const mockUser = {
|
|
@@ -1119,21 +1131,39 @@ const mockUser = {
|
|
|
1119
1131
|
name: 'Dev User'
|
|
1120
1132
|
}
|
|
1121
1133
|
|
|
1122
|
-
// Get
|
|
1123
|
-
function
|
|
1134
|
+
// Get all configured endpoints from localStorage
|
|
1135
|
+
function getEndpoints() {
|
|
1124
1136
|
try {
|
|
1125
|
-
return JSON.parse(localStorage.getItem(
|
|
1126
|
-
} catch { return
|
|
1137
|
+
return JSON.parse(localStorage.getItem(ENDPOINTS_STORAGE_KEY) || '[]')
|
|
1138
|
+
} catch { return [] }
|
|
1127
1139
|
}
|
|
1128
1140
|
|
|
1129
|
-
//
|
|
1130
|
-
|
|
1131
|
-
const
|
|
1141
|
+
// Get a specific endpoint by alias
|
|
1142
|
+
function getEndpoint(alias) {
|
|
1143
|
+
const endpoints = getEndpoints()
|
|
1144
|
+
return endpoints.find(ep => ep.alias === alias)
|
|
1145
|
+
}
|
|
1132
1146
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1147
|
+
// Convert PromptLine URLs to use local proxy
|
|
1148
|
+
function toProxyUrl(url) {
|
|
1149
|
+
const localMatch = url.match(/https?:\\/\\/app\\.local\\.promptlineops\\.com\\/api\\/v1\\/live\\/([^?]+)/)
|
|
1150
|
+
if (localMatch) {
|
|
1151
|
+
return '/api/promptline-local/' + localMatch[1]
|
|
1152
|
+
}
|
|
1153
|
+
const prodMatch = url.match(/https?:\\/\\/app\\.promptlineops\\.com\\/api\\/v1\\/live\\/([^?]+)/)
|
|
1154
|
+
if (prodMatch) {
|
|
1155
|
+
return '/api/promptline/' + prodMatch[1]
|
|
1156
|
+
}
|
|
1157
|
+
return url
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Call PromptLine API (sync mode)
|
|
1161
|
+
async function callPromptLineAPI(endpoint, input, variables = {}) {
|
|
1162
|
+
const { url, apiKey } = endpoint
|
|
1163
|
+
const proxyUrl = toProxyUrl(url)
|
|
1164
|
+
const fullUrl = proxyUrl + '?mode=sync'
|
|
1135
1165
|
|
|
1136
|
-
const res = await fetch(
|
|
1166
|
+
const res = await fetch(fullUrl, {
|
|
1137
1167
|
method: 'POST',
|
|
1138
1168
|
headers: {
|
|
1139
1169
|
'Content-Type': 'application/json',
|
|
@@ -1141,25 +1171,28 @@ async function callPromptLineAPI(config, input) {
|
|
|
1141
1171
|
},
|
|
1142
1172
|
body: JSON.stringify({
|
|
1143
1173
|
input: typeof input === 'string' ? { text: input } : input,
|
|
1144
|
-
variables
|
|
1174
|
+
variables
|
|
1145
1175
|
})
|
|
1146
1176
|
})
|
|
1177
|
+
|
|
1147
1178
|
const data = await res.json()
|
|
1148
|
-
if (!res.ok || data.error)
|
|
1149
|
-
|
|
1179
|
+
if (!res.ok || data.error) {
|
|
1180
|
+
throw new Error(data.detail || data.error?.message || data.error || res.statusText)
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1150
1183
|
const output = data.data?.output || data.data?.response || data.output || data.response
|
|
1151
|
-
return {
|
|
1184
|
+
return {
|
|
1185
|
+
response: typeof output === 'string' ? output : JSON.stringify(output),
|
|
1186
|
+
data: data.data || data
|
|
1187
|
+
}
|
|
1152
1188
|
}
|
|
1153
1189
|
|
|
1154
1190
|
const PromptLineContext = createContext(null)
|
|
1155
1191
|
|
|
1156
1192
|
export function PromptLineProvider({ children }) {
|
|
1157
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
1158
|
-
|
|
1159
1193
|
const value = {
|
|
1160
1194
|
config: mockConfig,
|
|
1161
1195
|
user: mockUser,
|
|
1162
|
-
isLoading,
|
|
1163
1196
|
submitForm: async (collection, data) => {
|
|
1164
1197
|
console.log('[PromptLine SDK] submitForm:', collection, data)
|
|
1165
1198
|
alert('Form submitted to "' + collection + '"\\n\\nData: ' + JSON.stringify(data, null, 2))
|
|
@@ -1168,23 +1201,6 @@ export function PromptLineProvider({ children }) {
|
|
|
1168
1201
|
fetchCollection: async (collection, query) => {
|
|
1169
1202
|
console.log('[PromptLine SDK] fetchCollection:', collection, query)
|
|
1170
1203
|
return { items: [], total: 0 }
|
|
1171
|
-
},
|
|
1172
|
-
callAI: async (sourceId, input) => {
|
|
1173
|
-
console.log('[PromptLine SDK] callAI:', sourceId, input)
|
|
1174
|
-
const config = getAPIConfig()
|
|
1175
|
-
|
|
1176
|
-
if (config && config.endpoint) {
|
|
1177
|
-
console.log('[PromptLine SDK] Using PromptLine API:', config.endpoint)
|
|
1178
|
-
try {
|
|
1179
|
-
return await callPromptLineAPI(config, input)
|
|
1180
|
-
} catch (err) {
|
|
1181
|
-
console.error('[PromptLine SDK] API Error:', err)
|
|
1182
|
-
return { response: '[API Error] ' + err.message, error: true }
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
console.log('[PromptLine SDK] No config found, using mock. Configure at /_dev')
|
|
1187
|
-
return { response: '[Mock] Configure your PromptLine endpoint at /_dev' }
|
|
1188
1204
|
}
|
|
1189
1205
|
}
|
|
1190
1206
|
|
|
@@ -1195,48 +1211,105 @@ export function PromptLineProvider({ children }) {
|
|
|
1195
1211
|
)
|
|
1196
1212
|
}
|
|
1197
1213
|
|
|
1198
|
-
export function usePromptLine() {
|
|
1214
|
+
export function usePromptLine(alias) {
|
|
1199
1215
|
const context = useContext(PromptLineContext)
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1216
|
+
|
|
1217
|
+
const endpointData = useMemo(() => {
|
|
1218
|
+
if (!alias) return null
|
|
1219
|
+
return getEndpoint(alias)
|
|
1220
|
+
}, [alias])
|
|
1221
|
+
|
|
1222
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
1223
|
+
|
|
1224
|
+
const execute = async (input, variables = {}) => {
|
|
1225
|
+
if (!endpointData) {
|
|
1226
|
+
console.warn('[PromptLine SDK] Endpoint "' + alias + '" not configured. Go to /_dev to configure it.')
|
|
1227
|
+
return {
|
|
1228
|
+
response: '[Not Configured] Endpoint "' + alias + '" not found. Configure it at /_dev',
|
|
1229
|
+
error: true
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
console.log('[PromptLine SDK] Calling endpoint "' + alias + '":', endpointData.url)
|
|
1234
|
+
setIsLoading(true)
|
|
1235
|
+
|
|
1236
|
+
try {
|
|
1237
|
+
const result = await callPromptLineAPI(endpointData, input, variables)
|
|
1238
|
+
return result
|
|
1239
|
+
} catch (err) {
|
|
1240
|
+
console.error('[PromptLine SDK] Error calling "' + alias + '":', err)
|
|
1241
|
+
return { response: '[API Error] ' + err.message, error: true }
|
|
1242
|
+
} finally {
|
|
1243
|
+
setIsLoading(false)
|
|
1208
1244
|
}
|
|
1209
1245
|
}
|
|
1210
|
-
|
|
1246
|
+
|
|
1247
|
+
return {
|
|
1248
|
+
execute,
|
|
1249
|
+
isLoading,
|
|
1250
|
+
isConfigured: !!endpointData,
|
|
1251
|
+
endpointInfo: endpointData?.info || null,
|
|
1252
|
+
config: context?.config || mockConfig,
|
|
1253
|
+
user: context?.user || mockUser,
|
|
1254
|
+
submitForm: context?.submitForm || (async () => ({ success: true })),
|
|
1255
|
+
fetchCollection: context?.fetchCollection || (async () => ({ items: [], total: 0 })),
|
|
1256
|
+
callAI: async (sourceIdOrInput, inputOrVariables) => {
|
|
1257
|
+
let targetAlias = alias
|
|
1258
|
+
let input = sourceIdOrInput
|
|
1259
|
+
if (typeof sourceIdOrInput === 'string' && typeof inputOrVariables === 'object') {
|
|
1260
|
+
targetAlias = sourceIdOrInput
|
|
1261
|
+
input = inputOrVariables
|
|
1262
|
+
}
|
|
1263
|
+
const ep = getEndpoint(targetAlias) || getEndpoints()[0]
|
|
1264
|
+
if (!ep) {
|
|
1265
|
+
return { response: '[Not Configured] No endpoints configured. Go to /_dev', error: true }
|
|
1266
|
+
}
|
|
1267
|
+
try {
|
|
1268
|
+
return await callPromptLineAPI(ep, input)
|
|
1269
|
+
} catch (err) {
|
|
1270
|
+
return { response: '[API Error] ' + err.message, error: true }
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
export function usePromptLineEndpoints() {
|
|
1277
|
+
return useMemo(() => getEndpoints(), [])
|
|
1211
1278
|
}
|
|
1212
1279
|
|
|
1213
1280
|
export const Link = ({ to, children, ...props }) => (
|
|
1214
1281
|
<RouterLink to={to} {...props}>{children}</RouterLink>
|
|
1215
1282
|
)
|
|
1216
1283
|
|
|
1217
|
-
export default { usePromptLine, PromptLineProvider, Link }
|
|
1284
|
+
export default { usePromptLine, usePromptLineEndpoints, PromptLineProvider, Link }
|
|
1218
1285
|
`},
|
|
1219
1286
|
|
|
1220
|
-
// Dev admin page for configuring PromptLine endpoint
|
|
1287
|
+
// Dev admin page for configuring PromptLine endpoints (multi-endpoint)
|
|
1221
1288
|
devAdmin: () => `/**
|
|
1222
|
-
* PromptLine Dev Admin - Endpoint Configuration
|
|
1289
|
+
* PromptLine Dev Admin - Multi-Endpoint Configuration
|
|
1223
1290
|
*
|
|
1224
1291
|
* This page is ONLY for local development.
|
|
1225
|
-
* Configure your PromptLine
|
|
1292
|
+
* Configure your PromptLine endpoints here to test with real API.
|
|
1226
1293
|
*
|
|
1227
1294
|
* NEVER commit API keys to git!
|
|
1228
1295
|
*/
|
|
1229
1296
|
import React, { useState, useEffect } from 'react'
|
|
1230
1297
|
import { Link } from 'react-router-dom'
|
|
1231
1298
|
|
|
1299
|
+
const STORAGE_KEY = 'promptline_dev_endpoints'
|
|
1300
|
+
|
|
1232
1301
|
export default function DevAdminPage() {
|
|
1233
|
-
const [
|
|
1234
|
-
const [
|
|
1235
|
-
const [
|
|
1302
|
+
const [endpoints, setEndpoints] = useState([])
|
|
1303
|
+
const [formAlias, setFormAlias] = useState('')
|
|
1304
|
+
const [formUrl, setFormUrl] = useState('')
|
|
1305
|
+
const [formApiKey, setFormApiKey] = useState('')
|
|
1306
|
+
const [formInfo, setFormInfo] = useState(null)
|
|
1307
|
+
const [formStatus, setFormStatus] = useState('')
|
|
1308
|
+
const [editingIndex, setEditingIndex] = useState(-1)
|
|
1309
|
+
const [selectedEndpoint, setSelectedEndpoint] = useState('')
|
|
1236
1310
|
const [testInput, setTestInput] = useState('Hello! What is 2+2?')
|
|
1237
1311
|
const [testResult, setTestResult] = useState('')
|
|
1238
1312
|
const [testing, setTesting] = useState(false)
|
|
1239
|
-
const [status, setStatus] = useState('') // 'connected' | 'error' | 'testing' | ''
|
|
1240
1313
|
const [logs, setLogs] = useState([])
|
|
1241
1314
|
|
|
1242
1315
|
const addLog = (type, message, details = null) => {
|
|
@@ -1245,121 +1318,145 @@ export default function DevAdminPage() {
|
|
|
1245
1318
|
}
|
|
1246
1319
|
|
|
1247
1320
|
useEffect(() => {
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
setApiKey(config.apiKey || '')
|
|
1252
|
-
setSaved(true)
|
|
1253
|
-
}
|
|
1321
|
+
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
|
|
1322
|
+
setEndpoints(saved)
|
|
1323
|
+
if (saved.length > 0) setSelectedEndpoint(saved[0].alias)
|
|
1254
1324
|
}, [])
|
|
1255
1325
|
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
addLog('info', 'Configuration saved')
|
|
1326
|
+
const saveEndpoints = (newEndpoints) => {
|
|
1327
|
+
setEndpoints(newEndpoints)
|
|
1328
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(newEndpoints))
|
|
1329
|
+
generateManifest(newEndpoints)
|
|
1330
|
+
addLog('info', 'Saved ' + newEndpoints.length + ' endpoint(s)')
|
|
1262
1331
|
}
|
|
1263
1332
|
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1333
|
+
const generateManifest = (eps) => {
|
|
1334
|
+
const manifest = { name: 'my-promptline-app', version: '1.0.0', endpoints: {} }
|
|
1335
|
+
eps.forEach(ep => {
|
|
1336
|
+
manifest.endpoints[ep.alias] = {
|
|
1337
|
+
name: ep.info?.name || ep.alias,
|
|
1338
|
+
description: ep.info?.description || '',
|
|
1339
|
+
type: ep.info?.endpoint_type || 'prompt',
|
|
1340
|
+
required_variables: ep.info?.required_variables || []
|
|
1341
|
+
}
|
|
1342
|
+
})
|
|
1343
|
+
localStorage.setItem('promptline_manifest', JSON.stringify(manifest, null, 2))
|
|
1271
1344
|
}
|
|
1272
1345
|
|
|
1273
|
-
// Convert PromptLine URLs to use local proxy (avoids CORS in development)
|
|
1274
1346
|
const toProxyUrl = (url) => {
|
|
1275
|
-
// Match app.local.promptlineops.com (local dev environment)
|
|
1276
1347
|
const localMatch = url.match(/https?:\\/\\/app\\.local\\.promptlineops\\.com\\/api\\/v1\\/live\\/([^?]+)/)
|
|
1277
|
-
if (localMatch)
|
|
1278
|
-
return '/api/promptline-local/' + localMatch[1]
|
|
1279
|
-
}
|
|
1280
|
-
// Match app.promptlineops.com (production environment)
|
|
1348
|
+
if (localMatch) return '/api/promptline-local/' + localMatch[1]
|
|
1281
1349
|
const prodMatch = url.match(/https?:\\/\\/app\\.promptlineops\\.com\\/api\\/v1\\/live\\/([^?]+)/)
|
|
1282
|
-
if (prodMatch)
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
return url // Non-PromptLine URLs pass through unchanged
|
|
1350
|
+
if (prodMatch) return '/api/promptline/' + prodMatch[1]
|
|
1351
|
+
return url
|
|
1286
1352
|
}
|
|
1287
1353
|
|
|
1288
|
-
const
|
|
1289
|
-
if (!
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1354
|
+
const checkHealth = async () => {
|
|
1355
|
+
if (!formUrl || !formApiKey) return
|
|
1356
|
+
setFormStatus('checking')
|
|
1357
|
+
const proxyUrl = toProxyUrl(formUrl)
|
|
1358
|
+
addLog('info', 'Checking health: ' + (formAlias || 'new endpoint'))
|
|
1359
|
+
try {
|
|
1360
|
+
const res = await fetch(proxyUrl + '/isAlive', { method: 'GET', headers: { 'X-API-Key': formApiKey } })
|
|
1361
|
+
const data = await res.json().catch(() => ({}))
|
|
1362
|
+
if (res.ok && data.alive === true) {
|
|
1363
|
+
setFormStatus('valid')
|
|
1364
|
+
addLog('success', 'Endpoint is alive')
|
|
1365
|
+
} else {
|
|
1366
|
+
setFormStatus('error')
|
|
1367
|
+
addLog('error', 'Health check failed: ' + res.status, data)
|
|
1368
|
+
}
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
setFormStatus('error')
|
|
1371
|
+
addLog('error', 'Network error', { message: err.message })
|
|
1294
1372
|
}
|
|
1295
|
-
|
|
1296
|
-
const proxyUrl = toProxyUrl(endpoint)
|
|
1297
|
-
// Use /isAlive endpoint for health check (no LLM execution, no token consumption)
|
|
1298
|
-
const isAliveUrl = proxyUrl + '/isAlive'
|
|
1299
|
-
addLog('info', 'Testing connection (isAlive)...', { endpoint: isAliveUrl, original: endpoint })
|
|
1373
|
+
}
|
|
1300
1374
|
|
|
1301
|
-
|
|
1375
|
+
const fetchInfo = async () => {
|
|
1376
|
+
if (!formUrl || !formApiKey) return
|
|
1377
|
+
const proxyUrl = toProxyUrl(formUrl)
|
|
1378
|
+
addLog('info', 'Fetching info: ' + (formAlias || 'new endpoint'))
|
|
1302
1379
|
try {
|
|
1303
|
-
const res = await fetch(
|
|
1304
|
-
method: 'GET',
|
|
1305
|
-
headers: {
|
|
1306
|
-
'X-API-Key': apiKey
|
|
1307
|
-
}
|
|
1308
|
-
})
|
|
1380
|
+
const res = await fetch(proxyUrl + '/info', { method: 'GET', headers: { 'X-API-Key': formApiKey } })
|
|
1309
1381
|
const data = await res.json().catch(() => ({}))
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
setStatus('connected')
|
|
1314
|
-
addLog('success', 'Endpoint is alive (' + duration + 'ms)', { alive: true })
|
|
1315
|
-
} else if (res.ok && data.alive === false) {
|
|
1316
|
-
setStatus('error')
|
|
1317
|
-
addLog('error', 'Endpoint exists but is inactive (' + duration + 'ms)', { alive: false })
|
|
1382
|
+
if (res.ok) {
|
|
1383
|
+
setFormInfo(data)
|
|
1384
|
+
addLog('success', 'Info retrieved', data)
|
|
1318
1385
|
} else {
|
|
1319
|
-
|
|
1320
|
-
addLog('error', res.status + ' ' + res.statusText + ' (' + duration + 'ms)', data)
|
|
1386
|
+
addLog('error', 'Info fetch failed: ' + res.status, data)
|
|
1321
1387
|
}
|
|
1322
1388
|
} catch (err) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1389
|
+
addLog('error', 'Network error', { message: err.message })
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const saveEndpoint = () => {
|
|
1394
|
+
if (!formAlias || !formUrl) {
|
|
1395
|
+
addLog('error', 'Alias and URL are required')
|
|
1396
|
+
return
|
|
1329
1397
|
}
|
|
1398
|
+
const existingIndex = endpoints.findIndex(ep => ep.alias === formAlias)
|
|
1399
|
+
if (existingIndex !== -1 && existingIndex !== editingIndex) {
|
|
1400
|
+
addLog('error', 'Alias "' + formAlias + '" already exists')
|
|
1401
|
+
return
|
|
1402
|
+
}
|
|
1403
|
+
const newEndpoint = { alias: formAlias, url: formUrl, apiKey: formApiKey, info: formInfo, status: formStatus === 'valid' ? 'valid' : 'unchecked' }
|
|
1404
|
+
let newEndpoints = editingIndex >= 0 ? [...endpoints] : [...endpoints, newEndpoint]
|
|
1405
|
+
if (editingIndex >= 0) newEndpoints[editingIndex] = newEndpoint
|
|
1406
|
+
saveEndpoints(newEndpoints)
|
|
1407
|
+
clearForm()
|
|
1408
|
+
if (!selectedEndpoint) setSelectedEndpoint(formAlias)
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
const editEndpoint = (index) => {
|
|
1412
|
+
const ep = endpoints[index]
|
|
1413
|
+
setFormAlias(ep.alias)
|
|
1414
|
+
setFormUrl(ep.url)
|
|
1415
|
+
setFormApiKey(ep.apiKey)
|
|
1416
|
+
setFormInfo(ep.info)
|
|
1417
|
+
setFormStatus(ep.status === 'valid' ? 'valid' : '')
|
|
1418
|
+
setEditingIndex(index)
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
const deleteEndpoint = (index) => {
|
|
1422
|
+
const ep = endpoints[index]
|
|
1423
|
+
const newEndpoints = endpoints.filter((_, i) => i !== index)
|
|
1424
|
+
saveEndpoints(newEndpoints)
|
|
1425
|
+
if (selectedEndpoint === ep.alias) setSelectedEndpoint(newEndpoints[0]?.alias || '')
|
|
1426
|
+
if (editingIndex === index) clearForm()
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
const clearForm = () => {
|
|
1430
|
+
setFormAlias('')
|
|
1431
|
+
setFormUrl('')
|
|
1432
|
+
setFormApiKey('')
|
|
1433
|
+
setFormInfo(null)
|
|
1434
|
+
setFormStatus('')
|
|
1435
|
+
setEditingIndex(-1)
|
|
1330
1436
|
}
|
|
1331
1437
|
|
|
1332
1438
|
const testAI = async () => {
|
|
1333
|
-
|
|
1439
|
+
const ep = endpoints.find(e => e.alias === selectedEndpoint)
|
|
1440
|
+
if (!ep) return
|
|
1334
1441
|
setTesting(true)
|
|
1335
1442
|
setTestResult('')
|
|
1336
|
-
const proxyUrl = toProxyUrl(
|
|
1337
|
-
|
|
1338
|
-
addLog('request', 'POST ' + url, { input: testInput })
|
|
1339
|
-
|
|
1340
|
-
const startTime = Date.now()
|
|
1443
|
+
const proxyUrl = toProxyUrl(ep.url)
|
|
1444
|
+
addLog('request', 'POST ' + proxyUrl + ' (' + ep.alias + ')', { input: testInput })
|
|
1341
1445
|
try {
|
|
1342
|
-
const res = await fetch(
|
|
1446
|
+
const res = await fetch(proxyUrl + '?mode=sync', {
|
|
1343
1447
|
method: 'POST',
|
|
1344
|
-
headers: {
|
|
1345
|
-
'Content-Type': 'application/json',
|
|
1346
|
-
...(apiKey ? { 'X-API-Key': apiKey } : {})
|
|
1347
|
-
},
|
|
1448
|
+
headers: { 'Content-Type': 'application/json', ...(ep.apiKey ? { 'X-API-Key': ep.apiKey } : {}) },
|
|
1348
1449
|
body: JSON.stringify({ input: { text: testInput }, variables: {} })
|
|
1349
1450
|
})
|
|
1350
1451
|
const data = await res.json()
|
|
1351
|
-
const duration = Date.now() - startTime
|
|
1352
|
-
|
|
1353
1452
|
if (!res.ok || data.error) {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
addLog('error', res.status + ' (' + duration + 'ms)', data)
|
|
1453
|
+
setTestResult('Error: ' + (data.detail || data.error?.message || data.error || res.statusText))
|
|
1454
|
+
addLog('error', res.status + '', data)
|
|
1357
1455
|
} else {
|
|
1358
|
-
// Sync mode returns data.data.output or data.data.response
|
|
1359
1456
|
const output = data.data?.output || data.data?.response || data.output || data.response || JSON.stringify(data, null, 2)
|
|
1360
1457
|
const outputStr = typeof output === 'string' ? output : JSON.stringify(output, null, 2)
|
|
1361
1458
|
setTestResult(outputStr)
|
|
1362
|
-
addLog('success', '200 OK
|
|
1459
|
+
addLog('success', '200 OK', { output: outputStr.substring(0, 200) })
|
|
1363
1460
|
}
|
|
1364
1461
|
} catch (err) {
|
|
1365
1462
|
setTestResult('Error: ' + err.message)
|
|
@@ -1368,17 +1465,39 @@ export default function DevAdminPage() {
|
|
|
1368
1465
|
setTesting(false)
|
|
1369
1466
|
}
|
|
1370
1467
|
|
|
1468
|
+
const downloadManifest = () => {
|
|
1469
|
+
const manifest = localStorage.getItem('promptline_manifest')
|
|
1470
|
+
if (!manifest) return
|
|
1471
|
+
const blob = new Blob([manifest], { type: 'application/json' })
|
|
1472
|
+
const url = URL.createObjectURL(blob)
|
|
1473
|
+
const a = document.createElement('a')
|
|
1474
|
+
a.href = url
|
|
1475
|
+
a.download = 'promptline.config.json'
|
|
1476
|
+
a.click()
|
|
1477
|
+
URL.revokeObjectURL(url)
|
|
1478
|
+
addLog('info', 'Downloaded promptline.config.json')
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1371
1481
|
return (
|
|
1372
1482
|
<div className="min-h-screen bg-gray-900 text-white p-8">
|
|
1373
|
-
<div className="max-w-
|
|
1483
|
+
<div className="max-w-4xl mx-auto">
|
|
1374
1484
|
<div className="flex items-center justify-between mb-8">
|
|
1375
1485
|
<div>
|
|
1376
1486
|
<h1 className="text-3xl font-bold text-yellow-400">/_dev</h1>
|
|
1377
|
-
<p className="text-gray-400 mt-1">PromptLine
|
|
1487
|
+
<p className="text-gray-400 mt-1">PromptLine Endpoints (Local Only)</p>
|
|
1488
|
+
</div>
|
|
1489
|
+
<div className="flex gap-3">
|
|
1490
|
+
<button
|
|
1491
|
+
onClick={downloadManifest}
|
|
1492
|
+
disabled={endpoints.length === 0}
|
|
1493
|
+
className="px-4 py-2 bg-indigo-600 rounded hover:bg-indigo-500 disabled:opacity-50"
|
|
1494
|
+
>
|
|
1495
|
+
Download Manifest
|
|
1496
|
+
</button>
|
|
1497
|
+
<Link to="/" className="px-4 py-2 bg-gray-700 rounded hover:bg-gray-600">
|
|
1498
|
+
Back to App
|
|
1499
|
+
</Link>
|
|
1378
1500
|
</div>
|
|
1379
|
-
<Link to="/" className="px-4 py-2 bg-gray-700 rounded hover:bg-gray-600">
|
|
1380
|
-
Back to App
|
|
1381
|
-
</Link>
|
|
1382
1501
|
</div>
|
|
1383
1502
|
|
|
1384
1503
|
<div className="bg-yellow-900/30 border border-yellow-600 rounded-lg p-4 mb-8">
|
|
@@ -1387,73 +1506,186 @@ export default function DevAdminPage() {
|
|
|
1387
1506
|
</p>
|
|
1388
1507
|
</div>
|
|
1389
1508
|
|
|
1390
|
-
{/*
|
|
1509
|
+
{/* Your Pages */}
|
|
1391
1510
|
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
1392
|
-
<
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
<
|
|
1396
|
-
|
|
1511
|
+
<h2 className="text-xl font-semibold mb-4">Your Pages</h2>
|
|
1512
|
+
<div className="grid grid-cols-2 gap-4">
|
|
1513
|
+
<div>
|
|
1514
|
+
<h3 className="text-sm font-medium text-gray-400 mb-2">Public</h3>
|
|
1515
|
+
<div className="space-y-2">
|
|
1516
|
+
<Link to="/" className="block px-3 py-2 bg-gray-700 rounded hover:bg-gray-600 text-blue-400">
|
|
1517
|
+
/ → index.tsx
|
|
1518
|
+
</Link>
|
|
1519
|
+
</div>
|
|
1520
|
+
</div>
|
|
1521
|
+
<div>
|
|
1522
|
+
<h3 className="text-sm font-medium text-gray-400 mb-2">Private (auth required)</h3>
|
|
1523
|
+
<div className="space-y-2">
|
|
1524
|
+
<Link to="/dashboard" className="block px-3 py-2 bg-gray-700 rounded hover:bg-gray-600 text-blue-400">
|
|
1525
|
+
/dashboard → dashboard.tsx
|
|
1526
|
+
</Link>
|
|
1527
|
+
<Link to="/settings" className="block px-3 py-2 bg-gray-700 rounded hover:bg-gray-600 text-blue-400">
|
|
1528
|
+
/settings → settings.tsx
|
|
1529
|
+
</Link>
|
|
1530
|
+
</div>
|
|
1531
|
+
</div>
|
|
1397
1532
|
</div>
|
|
1533
|
+
</div>
|
|
1534
|
+
|
|
1535
|
+
{/* Saved Endpoints List */}
|
|
1536
|
+
{endpoints.length > 0 && (
|
|
1537
|
+
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
1538
|
+
<h2 className="text-xl font-semibold mb-4">Configured Endpoints ({endpoints.length})</h2>
|
|
1539
|
+
<div className="space-y-3">
|
|
1540
|
+
{endpoints.map((ep, i) => (
|
|
1541
|
+
<div key={ep.alias} className="flex items-center justify-between bg-gray-700 rounded p-4">
|
|
1542
|
+
<div className="flex items-center gap-4">
|
|
1543
|
+
<span className={
|
|
1544
|
+
ep.status === 'valid' ? 'text-green-400' :
|
|
1545
|
+
ep.status === 'error' ? 'text-red-400' :
|
|
1546
|
+
'text-gray-400'
|
|
1547
|
+
}>●</span>
|
|
1548
|
+
<div>
|
|
1549
|
+
<div className="font-medium text-white">{ep.alias}</div>
|
|
1550
|
+
<div className="text-sm text-gray-400">
|
|
1551
|
+
{ep.info?.name || 'No info'} • {ep.info?.endpoint_type || 'unknown'}
|
|
1552
|
+
</div>
|
|
1553
|
+
</div>
|
|
1554
|
+
</div>
|
|
1555
|
+
<div className="flex gap-2">
|
|
1556
|
+
<button
|
|
1557
|
+
onClick={() => editEndpoint(i)}
|
|
1558
|
+
className="px-3 py-1 bg-gray-600 rounded text-sm hover:bg-gray-500"
|
|
1559
|
+
>
|
|
1560
|
+
Edit
|
|
1561
|
+
</button>
|
|
1562
|
+
<button
|
|
1563
|
+
onClick={() => deleteEndpoint(i)}
|
|
1564
|
+
className="px-3 py-1 bg-red-600/30 text-red-400 rounded text-sm hover:bg-red-600/50"
|
|
1565
|
+
>
|
|
1566
|
+
Delete
|
|
1567
|
+
</button>
|
|
1568
|
+
</div>
|
|
1569
|
+
</div>
|
|
1570
|
+
))}
|
|
1571
|
+
</div>
|
|
1572
|
+
</div>
|
|
1573
|
+
)}
|
|
1574
|
+
|
|
1575
|
+
{/* Add/Edit Endpoint Form */}
|
|
1576
|
+
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
1577
|
+
<h2 className="text-xl font-semibold mb-4">
|
|
1578
|
+
{editingIndex >= 0 ? $BACKTICK}Edit Endpoint: \${formAlias}$BACKTICK : 'Add New Endpoint'}
|
|
1579
|
+
</h2>
|
|
1398
1580
|
|
|
1399
1581
|
<div className="space-y-4">
|
|
1582
|
+
<div>
|
|
1583
|
+
<label className="block text-sm text-gray-400 mb-1">Alias (used in code)</label>
|
|
1584
|
+
<input
|
|
1585
|
+
value={formAlias}
|
|
1586
|
+
onChange={(e) => setFormAlias(e.target.value.replace(/[^a-zA-Z0-9_-]/g, ''))}
|
|
1587
|
+
className="w-full bg-gray-700 p-3 rounded text-white font-mono text-sm"
|
|
1588
|
+
placeholder="chatbot, traduction, analyse..."
|
|
1589
|
+
/>
|
|
1590
|
+
<p className="text-xs text-gray-500 mt-1">Used in usePromptLine('{formAlias || 'alias'}')</p>
|
|
1591
|
+
</div>
|
|
1592
|
+
|
|
1400
1593
|
<div>
|
|
1401
1594
|
<label className="block text-sm text-gray-400 mb-1">Endpoint URL</label>
|
|
1402
1595
|
<input
|
|
1403
|
-
value={
|
|
1404
|
-
onChange={(e) =>
|
|
1596
|
+
value={formUrl}
|
|
1597
|
+
onChange={(e) => setFormUrl(e.target.value)}
|
|
1405
1598
|
className="w-full bg-gray-700 p-3 rounded text-white font-mono text-sm"
|
|
1406
1599
|
placeholder="https://app.promptlineops.com/api/v1/live/..."
|
|
1407
1600
|
/>
|
|
1408
|
-
<p className="text-xs text-gray-500 mt-1">Copy the Live API URL from Creator Studio</p>
|
|
1409
1601
|
</div>
|
|
1410
1602
|
|
|
1411
1603
|
<div>
|
|
1412
1604
|
<label className="block text-sm text-gray-400 mb-1">API Key</label>
|
|
1413
1605
|
<input
|
|
1414
1606
|
type="password"
|
|
1415
|
-
value={
|
|
1416
|
-
onChange={(e) =>
|
|
1607
|
+
value={formApiKey}
|
|
1608
|
+
onChange={(e) => setFormApiKey(e.target.value)}
|
|
1417
1609
|
className="w-full bg-gray-700 p-3 rounded text-white font-mono text-sm"
|
|
1418
|
-
placeholder="
|
|
1610
|
+
placeholder="sk_org_... or sk_ws_..."
|
|
1419
1611
|
/>
|
|
1420
|
-
<p className="text-xs text-gray-500 mt-1">Required for health check. Get from /api-keys</p>
|
|
1421
1612
|
</div>
|
|
1422
1613
|
|
|
1423
|
-
<div className="flex gap-3">
|
|
1614
|
+
<div className="flex gap-3 flex-wrap">
|
|
1424
1615
|
<button
|
|
1425
|
-
onClick={
|
|
1426
|
-
disabled={!
|
|
1427
|
-
className="px-
|
|
1616
|
+
onClick={checkHealth}
|
|
1617
|
+
disabled={!formUrl || !formApiKey || formStatus === 'checking'}
|
|
1618
|
+
className="px-4 py-2 bg-gray-600 rounded hover:bg-gray-500 disabled:opacity-50 flex items-center gap-2"
|
|
1428
1619
|
>
|
|
1429
|
-
|
|
1620
|
+
{formStatus === 'checking' ? 'Checking...' : 'Check Health'}
|
|
1621
|
+
{formStatus === 'valid' && <span className="text-green-400">●</span>}
|
|
1622
|
+
{formStatus === 'error' && <span className="text-red-400">●</span>}
|
|
1430
1623
|
</button>
|
|
1431
1624
|
<button
|
|
1432
|
-
onClick={
|
|
1433
|
-
disabled={!
|
|
1434
|
-
className="px-
|
|
1435
|
-
title="Uses /isAlive endpoint (no LLM cost)"
|
|
1625
|
+
onClick={fetchInfo}
|
|
1626
|
+
disabled={!formUrl || !formApiKey}
|
|
1627
|
+
className="px-4 py-2 bg-gray-600 rounded hover:bg-gray-500 disabled:opacity-50"
|
|
1436
1628
|
>
|
|
1437
|
-
|
|
1438
|
-
{status === 'connected' && <span className="text-green-400">●</span>}
|
|
1439
|
-
{status === 'error' && <span className="text-red-400">●</span>}
|
|
1629
|
+
Fetch Info
|
|
1440
1630
|
</button>
|
|
1441
|
-
|
|
1631
|
+
<button
|
|
1632
|
+
onClick={saveEndpoint}
|
|
1633
|
+
disabled={!formAlias || !formUrl}
|
|
1634
|
+
className="px-6 py-2 bg-indigo-600 rounded font-medium hover:bg-indigo-500 disabled:opacity-50"
|
|
1635
|
+
>
|
|
1636
|
+
{editingIndex >= 0 ? 'Update' : 'Add Endpoint'}
|
|
1637
|
+
</button>
|
|
1638
|
+
{(formAlias || formUrl || editingIndex >= 0) && (
|
|
1442
1639
|
<button
|
|
1443
|
-
onClick={
|
|
1444
|
-
className="px-4 py-2 bg-
|
|
1640
|
+
onClick={clearForm}
|
|
1641
|
+
className="px-4 py-2 bg-gray-700 rounded hover:bg-gray-600"
|
|
1445
1642
|
>
|
|
1446
|
-
|
|
1643
|
+
Cancel
|
|
1447
1644
|
</button>
|
|
1448
1645
|
)}
|
|
1449
1646
|
</div>
|
|
1450
1647
|
</div>
|
|
1648
|
+
|
|
1649
|
+
{/* Info Preview */}
|
|
1650
|
+
{formInfo && (
|
|
1651
|
+
<div className="mt-6 p-4 bg-gray-700 rounded">
|
|
1652
|
+
<h3 className="font-medium mb-3 text-gray-300">Endpoint Info</h3>
|
|
1653
|
+
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
1654
|
+
<div><span className="text-gray-400">Name:</span> <span className="text-white">{formInfo.name}</span></div>
|
|
1655
|
+
<div><span className="text-gray-400">Status:</span> <span className={formInfo.status === 'active' ? 'text-green-400' : 'text-red-400'}>{formInfo.status}</span></div>
|
|
1656
|
+
<div><span className="text-gray-400">Type:</span> <span className="text-white">{formInfo.endpoint_type}</span></div>
|
|
1657
|
+
<div><span className="text-gray-400">Model:</span> <span className="text-white">{formInfo.ai_model}</span></div>
|
|
1658
|
+
{formInfo.required_variables?.length > 0 && (
|
|
1659
|
+
<div className="col-span-2">
|
|
1660
|
+
<span className="text-gray-400">Variables:</span>
|
|
1661
|
+
<span className="ml-2">{formInfo.required_variables.map(v => (
|
|
1662
|
+
<span key={v} className="ml-1 px-2 py-0.5 bg-gray-600 rounded text-xs text-blue-300">{v}</span>
|
|
1663
|
+
))}</span>
|
|
1664
|
+
</div>
|
|
1665
|
+
)}
|
|
1666
|
+
</div>
|
|
1667
|
+
</div>
|
|
1668
|
+
)}
|
|
1451
1669
|
</div>
|
|
1452
1670
|
|
|
1453
1671
|
{/* Test AI */}
|
|
1454
|
-
{
|
|
1672
|
+
{endpoints.length > 0 && (
|
|
1455
1673
|
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
1456
1674
|
<h2 className="text-xl font-semibold mb-4">Test AI</h2>
|
|
1675
|
+
|
|
1676
|
+
<div className="mb-4">
|
|
1677
|
+
<label className="block text-sm text-gray-400 mb-1">Select Endpoint</label>
|
|
1678
|
+
<select
|
|
1679
|
+
value={selectedEndpoint}
|
|
1680
|
+
onChange={(e) => setSelectedEndpoint(e.target.value)}
|
|
1681
|
+
className="w-full bg-gray-700 p-3 rounded text-white"
|
|
1682
|
+
>
|
|
1683
|
+
{endpoints.map(ep => (
|
|
1684
|
+
<option key={ep.alias} value={ep.alias}>{ep.alias} - {ep.info?.name || 'Unknown'}</option>
|
|
1685
|
+
))}
|
|
1686
|
+
</select>
|
|
1687
|
+
</div>
|
|
1688
|
+
|
|
1457
1689
|
<textarea
|
|
1458
1690
|
value={testInput}
|
|
1459
1691
|
onChange={(e) => setTestInput(e.target.value)}
|
|
@@ -1463,7 +1695,7 @@ export default function DevAdminPage() {
|
|
|
1463
1695
|
/>
|
|
1464
1696
|
<button
|
|
1465
1697
|
onClick={testAI}
|
|
1466
|
-
disabled={testing}
|
|
1698
|
+
disabled={testing || !selectedEndpoint}
|
|
1467
1699
|
className="px-6 py-2 bg-green-600 rounded font-medium hover:bg-green-500 disabled:opacity-50"
|
|
1468
1700
|
>
|
|
1469
1701
|
{testing ? 'Sending...' : 'Send Request'}
|
|
@@ -1478,7 +1710,7 @@ export default function DevAdminPage() {
|
|
|
1478
1710
|
)}
|
|
1479
1711
|
|
|
1480
1712
|
{/* Logs */}
|
|
1481
|
-
<div className="bg-gray-800 rounded-lg p-6">
|
|
1713
|
+
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
1482
1714
|
<div className="flex items-center justify-between mb-4">
|
|
1483
1715
|
<h2 className="text-xl font-semibold">Logs</h2>
|
|
1484
1716
|
<button onClick={() => setLogs([])} className="px-3 py-1 bg-gray-700 rounded text-sm hover:bg-gray-600">
|
|
@@ -1513,13 +1745,18 @@ export default function DevAdminPage() {
|
|
|
1513
1745
|
</div>
|
|
1514
1746
|
|
|
1515
1747
|
{/* Usage */}
|
|
1516
|
-
<div className="
|
|
1748
|
+
<div className="bg-gray-800 rounded-lg p-6">
|
|
1517
1749
|
<h2 className="text-xl font-semibold mb-4">Usage in Your Code</h2>
|
|
1518
1750
|
<pre className="bg-gray-900 p-4 rounded text-sm overflow-x-auto">
|
|
1519
|
-
{$BACKTICK}
|
|
1751
|
+
{$BACKTICK}// Use specific endpoint by alias
|
|
1752
|
+
const { execute } = usePromptLine('\${endpoints[0]?.alias || 'chatbot'}')
|
|
1753
|
+
|
|
1754
|
+
const result = await execute({ text: 'Hello!' })
|
|
1755
|
+
console.log(result.response)
|
|
1520
1756
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1757
|
+
// Multiple endpoints
|
|
1758
|
+
const { execute: chat } = usePromptLine('chatbot')
|
|
1759
|
+
const { execute: translate } = usePromptLine('traduction')$BACKTICK}
|
|
1523
1760
|
</pre>
|
|
1524
1761
|
</div>
|
|
1525
1762
|
</div>
|