promptlineapp 1.4.0 → 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 +422 -177
- 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)
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
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
|
+
}
|
|
1208
1272
|
}
|
|
1209
1273
|
}
|
|
1210
|
-
|
|
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,114 +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
|
-
const proxyUrl = toProxyUrl(
|
|
1292
|
-
|
|
1293
|
-
addLog('info', 'Testing connection...', { endpoint: url, original: endpoint })
|
|
1294
|
-
|
|
1295
|
-
const startTime = Date.now()
|
|
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'))
|
|
1296
1359
|
try {
|
|
1297
|
-
const res = await fetch(
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
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 })
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1306
1374
|
|
|
1375
|
+
const fetchInfo = async () => {
|
|
1376
|
+
if (!formUrl || !formApiKey) return
|
|
1377
|
+
const proxyUrl = toProxyUrl(formUrl)
|
|
1378
|
+
addLog('info', 'Fetching info: ' + (formAlias || 'new endpoint'))
|
|
1379
|
+
try {
|
|
1380
|
+
const res = await fetch(proxyUrl + '/info', { method: 'GET', headers: { 'X-API-Key': formApiKey } })
|
|
1381
|
+
const data = await res.json().catch(() => ({}))
|
|
1307
1382
|
if (res.ok) {
|
|
1308
|
-
|
|
1309
|
-
addLog('success', '
|
|
1383
|
+
setFormInfo(data)
|
|
1384
|
+
addLog('success', 'Info retrieved', data)
|
|
1310
1385
|
} else {
|
|
1311
|
-
|
|
1312
|
-
setStatus('error')
|
|
1313
|
-
addLog('error', res.status + ' ' + res.statusText + ' (' + duration + 'ms)', data)
|
|
1386
|
+
addLog('error', 'Info fetch failed: ' + res.status, data)
|
|
1314
1387
|
}
|
|
1315
1388
|
} catch (err) {
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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
|
|
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
|
|
1322
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)
|
|
1323
1436
|
}
|
|
1324
1437
|
|
|
1325
1438
|
const testAI = async () => {
|
|
1326
|
-
|
|
1439
|
+
const ep = endpoints.find(e => e.alias === selectedEndpoint)
|
|
1440
|
+
if (!ep) return
|
|
1327
1441
|
setTesting(true)
|
|
1328
1442
|
setTestResult('')
|
|
1329
|
-
const proxyUrl = toProxyUrl(
|
|
1330
|
-
|
|
1331
|
-
addLog('request', 'POST ' + url, { input: testInput })
|
|
1332
|
-
|
|
1333
|
-
const startTime = Date.now()
|
|
1443
|
+
const proxyUrl = toProxyUrl(ep.url)
|
|
1444
|
+
addLog('request', 'POST ' + proxyUrl + ' (' + ep.alias + ')', { input: testInput })
|
|
1334
1445
|
try {
|
|
1335
|
-
const res = await fetch(
|
|
1446
|
+
const res = await fetch(proxyUrl + '?mode=sync', {
|
|
1336
1447
|
method: 'POST',
|
|
1337
|
-
headers: {
|
|
1338
|
-
'Content-Type': 'application/json',
|
|
1339
|
-
...(apiKey ? { 'X-API-Key': apiKey } : {})
|
|
1340
|
-
},
|
|
1448
|
+
headers: { 'Content-Type': 'application/json', ...(ep.apiKey ? { 'X-API-Key': ep.apiKey } : {}) },
|
|
1341
1449
|
body: JSON.stringify({ input: { text: testInput }, variables: {} })
|
|
1342
1450
|
})
|
|
1343
1451
|
const data = await res.json()
|
|
1344
|
-
const duration = Date.now() - startTime
|
|
1345
|
-
|
|
1346
1452
|
if (!res.ok || data.error) {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
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)
|
|
1350
1455
|
} else {
|
|
1351
|
-
// Sync mode returns data.data.output or data.data.response
|
|
1352
1456
|
const output = data.data?.output || data.data?.response || data.output || data.response || JSON.stringify(data, null, 2)
|
|
1353
1457
|
const outputStr = typeof output === 'string' ? output : JSON.stringify(output, null, 2)
|
|
1354
1458
|
setTestResult(outputStr)
|
|
1355
|
-
addLog('success', '200 OK
|
|
1459
|
+
addLog('success', '200 OK', { output: outputStr.substring(0, 200) })
|
|
1356
1460
|
}
|
|
1357
1461
|
} catch (err) {
|
|
1358
1462
|
setTestResult('Error: ' + err.message)
|
|
@@ -1361,17 +1465,39 @@ export default function DevAdminPage() {
|
|
|
1361
1465
|
setTesting(false)
|
|
1362
1466
|
}
|
|
1363
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
|
+
|
|
1364
1481
|
return (
|
|
1365
1482
|
<div className="min-h-screen bg-gray-900 text-white p-8">
|
|
1366
|
-
<div className="max-w-
|
|
1483
|
+
<div className="max-w-4xl mx-auto">
|
|
1367
1484
|
<div className="flex items-center justify-between mb-8">
|
|
1368
1485
|
<div>
|
|
1369
1486
|
<h1 className="text-3xl font-bold text-yellow-400">/_dev</h1>
|
|
1370
|
-
<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>
|
|
1371
1500
|
</div>
|
|
1372
|
-
<Link to="/" className="px-4 py-2 bg-gray-700 rounded hover:bg-gray-600">
|
|
1373
|
-
Back to App
|
|
1374
|
-
</Link>
|
|
1375
1501
|
</div>
|
|
1376
1502
|
|
|
1377
1503
|
<div className="bg-yellow-900/30 border border-yellow-600 rounded-lg p-4 mb-8">
|
|
@@ -1380,72 +1506,186 @@ export default function DevAdminPage() {
|
|
|
1380
1506
|
</p>
|
|
1381
1507
|
</div>
|
|
1382
1508
|
|
|
1383
|
-
{/*
|
|
1509
|
+
{/* Your Pages */}
|
|
1384
1510
|
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
1385
|
-
<
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
<
|
|
1389
|
-
|
|
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>
|
|
1390
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>
|
|
1391
1580
|
|
|
1392
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
|
+
|
|
1393
1593
|
<div>
|
|
1394
1594
|
<label className="block text-sm text-gray-400 mb-1">Endpoint URL</label>
|
|
1395
1595
|
<input
|
|
1396
|
-
value={
|
|
1397
|
-
onChange={(e) =>
|
|
1596
|
+
value={formUrl}
|
|
1597
|
+
onChange={(e) => setFormUrl(e.target.value)}
|
|
1398
1598
|
className="w-full bg-gray-700 p-3 rounded text-white font-mono text-sm"
|
|
1399
1599
|
placeholder="https://app.promptlineops.com/api/v1/live/..."
|
|
1400
1600
|
/>
|
|
1401
|
-
<p className="text-xs text-gray-500 mt-1">Copy the Live API URL from Creator Studio</p>
|
|
1402
1601
|
</div>
|
|
1403
1602
|
|
|
1404
1603
|
<div>
|
|
1405
1604
|
<label className="block text-sm text-gray-400 mb-1">API Key</label>
|
|
1406
1605
|
<input
|
|
1407
1606
|
type="password"
|
|
1408
|
-
value={
|
|
1409
|
-
onChange={(e) =>
|
|
1607
|
+
value={formApiKey}
|
|
1608
|
+
onChange={(e) => setFormApiKey(e.target.value)}
|
|
1410
1609
|
className="w-full bg-gray-700 p-3 rounded text-white font-mono text-sm"
|
|
1411
|
-
placeholder="
|
|
1610
|
+
placeholder="sk_org_... or sk_ws_..."
|
|
1412
1611
|
/>
|
|
1413
|
-
<p className="text-xs text-gray-500 mt-1">Get your API key from /api-keys</p>
|
|
1414
1612
|
</div>
|
|
1415
1613
|
|
|
1416
|
-
<div className="flex gap-3">
|
|
1614
|
+
<div className="flex gap-3 flex-wrap">
|
|
1417
1615
|
<button
|
|
1418
|
-
onClick={
|
|
1419
|
-
disabled={!
|
|
1420
|
-
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"
|
|
1619
|
+
>
|
|
1620
|
+
{formStatus === 'checking' ? 'Checking...' : 'Check Health'}
|
|
1621
|
+
{formStatus === 'valid' && <span className="text-green-400">●</span>}
|
|
1622
|
+
{formStatus === 'error' && <span className="text-red-400">●</span>}
|
|
1623
|
+
</button>
|
|
1624
|
+
<button
|
|
1625
|
+
onClick={fetchInfo}
|
|
1626
|
+
disabled={!formUrl || !formApiKey}
|
|
1627
|
+
className="px-4 py-2 bg-gray-600 rounded hover:bg-gray-500 disabled:opacity-50"
|
|
1421
1628
|
>
|
|
1422
|
-
|
|
1629
|
+
Fetch Info
|
|
1423
1630
|
</button>
|
|
1424
1631
|
<button
|
|
1425
|
-
onClick={
|
|
1426
|
-
disabled={!
|
|
1427
|
-
className="px-6 py-2 bg-
|
|
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"
|
|
1428
1635
|
>
|
|
1429
|
-
{
|
|
1430
|
-
{status === 'connected' && <span className="text-green-400">●</span>}
|
|
1431
|
-
{status === 'error' && <span className="text-red-400">●</span>}
|
|
1636
|
+
{editingIndex >= 0 ? 'Update' : 'Add Endpoint'}
|
|
1432
1637
|
</button>
|
|
1433
|
-
{
|
|
1638
|
+
{(formAlias || formUrl || editingIndex >= 0) && (
|
|
1434
1639
|
<button
|
|
1435
|
-
onClick={
|
|
1436
|
-
className="px-4 py-2 bg-
|
|
1640
|
+
onClick={clearForm}
|
|
1641
|
+
className="px-4 py-2 bg-gray-700 rounded hover:bg-gray-600"
|
|
1437
1642
|
>
|
|
1438
|
-
|
|
1643
|
+
Cancel
|
|
1439
1644
|
</button>
|
|
1440
1645
|
)}
|
|
1441
1646
|
</div>
|
|
1442
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
|
+
)}
|
|
1443
1669
|
</div>
|
|
1444
1670
|
|
|
1445
1671
|
{/* Test AI */}
|
|
1446
|
-
{
|
|
1672
|
+
{endpoints.length > 0 && (
|
|
1447
1673
|
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
1448
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
|
+
|
|
1449
1689
|
<textarea
|
|
1450
1690
|
value={testInput}
|
|
1451
1691
|
onChange={(e) => setTestInput(e.target.value)}
|
|
@@ -1455,7 +1695,7 @@ export default function DevAdminPage() {
|
|
|
1455
1695
|
/>
|
|
1456
1696
|
<button
|
|
1457
1697
|
onClick={testAI}
|
|
1458
|
-
disabled={testing}
|
|
1698
|
+
disabled={testing || !selectedEndpoint}
|
|
1459
1699
|
className="px-6 py-2 bg-green-600 rounded font-medium hover:bg-green-500 disabled:opacity-50"
|
|
1460
1700
|
>
|
|
1461
1701
|
{testing ? 'Sending...' : 'Send Request'}
|
|
@@ -1470,7 +1710,7 @@ export default function DevAdminPage() {
|
|
|
1470
1710
|
)}
|
|
1471
1711
|
|
|
1472
1712
|
{/* Logs */}
|
|
1473
|
-
<div className="bg-gray-800 rounded-lg p-6">
|
|
1713
|
+
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
1474
1714
|
<div className="flex items-center justify-between mb-4">
|
|
1475
1715
|
<h2 className="text-xl font-semibold">Logs</h2>
|
|
1476
1716
|
<button onClick={() => setLogs([])} className="px-3 py-1 bg-gray-700 rounded text-sm hover:bg-gray-600">
|
|
@@ -1505,13 +1745,18 @@ export default function DevAdminPage() {
|
|
|
1505
1745
|
</div>
|
|
1506
1746
|
|
|
1507
1747
|
{/* Usage */}
|
|
1508
|
-
<div className="
|
|
1748
|
+
<div className="bg-gray-800 rounded-lg p-6">
|
|
1509
1749
|
<h2 className="text-xl font-semibold mb-4">Usage in Your Code</h2>
|
|
1510
1750
|
<pre className="bg-gray-900 p-4 rounded text-sm overflow-x-auto">
|
|
1511
|
-
{$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)
|
|
1512
1756
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1757
|
+
// Multiple endpoints
|
|
1758
|
+
const { execute: chat } = usePromptLine('chatbot')
|
|
1759
|
+
const { execute: translate } = usePromptLine('traduction')$BACKTICK}
|
|
1515
1760
|
</pre>
|
|
1516
1761
|
</div>
|
|
1517
1762
|
</div>
|