portos-ai-toolkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/package.json +76 -0
- package/src/client/api.js +102 -0
- package/src/client/components/ProviderDropdown.jsx +39 -0
- package/src/client/components/index.js +5 -0
- package/src/client/hooks/index.js +6 -0
- package/src/client/hooks/useProviders.js +96 -0
- package/src/client/hooks/useRuns.js +94 -0
- package/src/client/index.js +11 -0
- package/src/client/pages/AIProviders.jsx +665 -0
- package/src/index.js +8 -0
- package/src/server/index.d.ts +87 -0
- package/src/server/index.js +95 -0
- package/src/server/prompts.js +234 -0
- package/src/server/providers.js +253 -0
- package/src/server/providers.test.js +120 -0
- package/src/server/routes/prompts.js +96 -0
- package/src/server/routes/providers.js +105 -0
- package/src/server/routes/runs.js +157 -0
- package/src/server/runner.js +475 -0
- package/src/server/validation.js +51 -0
- package/src/shared/constants.js +26 -0
- package/src/shared/index.js +5 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Adam Eivy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# portos-ai-toolkit
|
|
2
|
+
|
|
3
|
+
Shared AI provider, model, and prompt template patterns for PortOS-style applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install portos-ai-toolkit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Provider Management**: Support for CLI-based (Claude Code, Codex, etc.) and API-based (OpenAI-compatible) AI providers
|
|
14
|
+
- **Prompt Templates**: Reusable prompt template system with variable interpolation
|
|
15
|
+
- **Run History**: Track and manage AI run history with streaming support
|
|
16
|
+
- **React Components**: Ready-to-use React components and hooks for AI provider management
|
|
17
|
+
- **Express Routes**: Pre-built Express route handlers for provider, prompt, and run management
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Server-side
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
import { createAIRoutes, ProvidersService, RunnerService } from 'portos-ai-toolkit/server';
|
|
25
|
+
import express from 'express';
|
|
26
|
+
|
|
27
|
+
const app = express();
|
|
28
|
+
|
|
29
|
+
// Initialize services
|
|
30
|
+
const providersService = new ProvidersService('./data');
|
|
31
|
+
const runnerService = new RunnerService(providersService);
|
|
32
|
+
|
|
33
|
+
// Mount AI routes
|
|
34
|
+
app.use('/api', createAIRoutes({ providersService, runnerService }));
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Client-side
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import { AIProviders, useProviders, createApiClient } from 'portos-ai-toolkit/client';
|
|
41
|
+
|
|
42
|
+
// Use the full-featured AIProviders page component
|
|
43
|
+
function ProvidersPage() {
|
|
44
|
+
return <AIProviders onError={console.error} colorPrefix="app" />;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Or use hooks for custom implementations
|
|
48
|
+
function CustomComponent() {
|
|
49
|
+
const { providers, loading, refresh } = useProviders();
|
|
50
|
+
// ...
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Shared utilities
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import { PROVIDER_TYPES, DEFAULT_TIMEOUT } from 'portos-ai-toolkit/shared';
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Provider Types
|
|
61
|
+
|
|
62
|
+
### CLI Providers
|
|
63
|
+
Execute AI commands via CLI tools like Claude Code or Codex:
|
|
64
|
+
- Claude Code CLI (`claude`)
|
|
65
|
+
- Codex CLI (`codex`)
|
|
66
|
+
- Custom CLI tools
|
|
67
|
+
|
|
68
|
+
### API Providers
|
|
69
|
+
Connect to OpenAI-compatible APIs:
|
|
70
|
+
- LM Studio
|
|
71
|
+
- Ollama
|
|
72
|
+
- OpenAI
|
|
73
|
+
- Any OpenAI-compatible endpoint
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
76
|
+
|
|
77
|
+
### Server Exports (`portos-ai-toolkit/server`)
|
|
78
|
+
|
|
79
|
+
- `createAIRoutes(options)` - Create Express router with all AI routes
|
|
80
|
+
- `ProvidersService` - Manage AI provider configurations
|
|
81
|
+
- `RunnerService` - Execute prompts and manage runs
|
|
82
|
+
- `PromptsService` - Manage prompt templates
|
|
83
|
+
- Route handlers: `createProvidersRoutes`, `createRunsRoutes`, `createPromptsRoutes`
|
|
84
|
+
|
|
85
|
+
### Client Exports (`portos-ai-toolkit/client`)
|
|
86
|
+
|
|
87
|
+
- `AIProviders` - Full-featured provider management page component
|
|
88
|
+
- `ProviderDropdown` - Dropdown for selecting providers
|
|
89
|
+
- `useProviders()` - Hook for provider state management
|
|
90
|
+
- `useRuns()` - Hook for run history
|
|
91
|
+
- `createApiClient(baseUrl)` - Create API client instance
|
|
92
|
+
|
|
93
|
+
### Shared Exports (`portos-ai-toolkit/shared`)
|
|
94
|
+
|
|
95
|
+
- `PROVIDER_TYPES` - Provider type constants
|
|
96
|
+
- `DEFAULT_TIMEOUT` - Default timeout value
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "portos-ai-toolkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared AI provider, model, and prompt template patterns for PortOS-style applications",
|
|
5
|
+
"author": "Adam Eivy <adam@eivy.com> (https://atomantic.com)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./src/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js",
|
|
11
|
+
"./server": "./src/server/index.js",
|
|
12
|
+
"./client": "./src/client/index.js",
|
|
13
|
+
"./shared": "./src/shared/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src/**/*"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/atomantic/portos-ai-toolkit.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/atomantic/portos-ai-toolkit#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/atomantic/portos-ai-toolkit/issues"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"test:coverage": "vitest run --coverage",
|
|
33
|
+
"prepublishOnly": "npm test"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"zod": "^3.24.1",
|
|
37
|
+
"uuid": "^11.0.3"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"express": "^4.21.2 || ^5.2.1",
|
|
41
|
+
"socket.io": "^4.8.3",
|
|
42
|
+
"react": "^18.3.1",
|
|
43
|
+
"react-dom": "^18.3.1"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"express": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"socket.io": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"react": {
|
|
53
|
+
"optional": true
|
|
54
|
+
},
|
|
55
|
+
"react-dom": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
61
|
+
"supertest": "^7.1.4",
|
|
62
|
+
"vitest": "^4.0.16"
|
|
63
|
+
},
|
|
64
|
+
"keywords": [
|
|
65
|
+
"ai",
|
|
66
|
+
"providers",
|
|
67
|
+
"llm",
|
|
68
|
+
"claude",
|
|
69
|
+
"openai",
|
|
70
|
+
"ollama",
|
|
71
|
+
"lm-studio",
|
|
72
|
+
"prompts",
|
|
73
|
+
"templates",
|
|
74
|
+
"portos"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client for AI Toolkit
|
|
3
|
+
* Configurable API requests for providers and runs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create an API client with configurable base URL and error handler
|
|
8
|
+
*/
|
|
9
|
+
export function createApiClient(config = {}) {
|
|
10
|
+
const {
|
|
11
|
+
baseUrl = '/api',
|
|
12
|
+
onError = (error) => console.error(error)
|
|
13
|
+
} = config;
|
|
14
|
+
|
|
15
|
+
async function request(endpoint, options = {}) {
|
|
16
|
+
const url = `${baseUrl}${endpoint}`;
|
|
17
|
+
const requestConfig = {
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
...options.headers
|
|
21
|
+
},
|
|
22
|
+
...options
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const response = await fetch(url, requestConfig);
|
|
26
|
+
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const error = await response.json().catch(() => ({ error: 'Request failed' }));
|
|
29
|
+
const errorMessage = error.error || `HTTP ${response.status}`;
|
|
30
|
+
onError(errorMessage);
|
|
31
|
+
throw new Error(errorMessage);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle 204 No Content
|
|
35
|
+
if (response.status === 204) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Handle text/plain responses
|
|
40
|
+
const contentType = response.headers.get('content-type');
|
|
41
|
+
if (contentType?.includes('text/plain')) {
|
|
42
|
+
return response.text();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return response.json();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
// Providers
|
|
50
|
+
providers: {
|
|
51
|
+
getAll: () => request('/providers'),
|
|
52
|
+
getActive: () => request('/providers/active'),
|
|
53
|
+
setActive: (id) => request('/providers/active', {
|
|
54
|
+
method: 'PUT',
|
|
55
|
+
body: JSON.stringify({ id })
|
|
56
|
+
}),
|
|
57
|
+
getById: (id) => request(`/providers/${id}`),
|
|
58
|
+
create: (data) => request('/providers', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
body: JSON.stringify(data)
|
|
61
|
+
}),
|
|
62
|
+
update: (id, data) => request(`/providers/${id}`, {
|
|
63
|
+
method: 'PUT',
|
|
64
|
+
body: JSON.stringify(data)
|
|
65
|
+
}),
|
|
66
|
+
delete: (id) => request(`/providers/${id}`, {
|
|
67
|
+
method: 'DELETE'
|
|
68
|
+
}),
|
|
69
|
+
test: (id) => request(`/providers/${id}/test`, {
|
|
70
|
+
method: 'POST'
|
|
71
|
+
}),
|
|
72
|
+
refreshModels: (id) => request(`/providers/${id}/refresh-models`, {
|
|
73
|
+
method: 'POST'
|
|
74
|
+
})
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Runs
|
|
78
|
+
runs: {
|
|
79
|
+
list: (limit = 50, offset = 0, source = 'all') =>
|
|
80
|
+
request(`/runs?limit=${limit}&offset=${offset}&source=${source}`),
|
|
81
|
+
create: (data) => request('/runs', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
body: JSON.stringify(data)
|
|
84
|
+
}),
|
|
85
|
+
getById: (id) => request(`/runs/${id}`),
|
|
86
|
+
getOutput: (id) => request(`/runs/${id}/output`),
|
|
87
|
+
getPrompt: (id) => request(`/runs/${id}/prompt`),
|
|
88
|
+
stop: (id) => request(`/runs/${id}/stop`, {
|
|
89
|
+
method: 'POST'
|
|
90
|
+
}),
|
|
91
|
+
delete: (id) => request(`/runs/${id}`, {
|
|
92
|
+
method: 'DELETE'
|
|
93
|
+
}),
|
|
94
|
+
deleteFailedRuns: () => request('/runs?filter=failed', {
|
|
95
|
+
method: 'DELETE'
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Default export with default config
|
|
102
|
+
export default createApiClient();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Dropdown Component
|
|
3
|
+
* Dropdown for selecting an AI provider
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function ProviderDropdown({
|
|
7
|
+
providers = [],
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
filter = null,
|
|
11
|
+
showType = false,
|
|
12
|
+
placeholder = 'Select provider...',
|
|
13
|
+
className = '',
|
|
14
|
+
theme = {}
|
|
15
|
+
}) {
|
|
16
|
+
const {
|
|
17
|
+
bg = 'bg-port-bg',
|
|
18
|
+
border = 'border-port-border',
|
|
19
|
+
text = 'text-white'
|
|
20
|
+
} = theme;
|
|
21
|
+
|
|
22
|
+
const filteredProviders = filter ? providers.filter(filter) : providers;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<select
|
|
26
|
+
value={value || ''}
|
|
27
|
+
onChange={(e) => onChange(e.target.value)}
|
|
28
|
+
className={`px-3 py-2 ${bg} border ${border} rounded-lg ${text} ${className}`}
|
|
29
|
+
>
|
|
30
|
+
<option value="">{placeholder}</option>
|
|
31
|
+
{filteredProviders.map(provider => (
|
|
32
|
+
<option key={provider.id} value={provider.id}>
|
|
33
|
+
{provider.name}
|
|
34
|
+
{showType && ` (${provider.type})`}
|
|
35
|
+
</option>
|
|
36
|
+
))}
|
|
37
|
+
</select>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook for managing AI providers
|
|
5
|
+
*/
|
|
6
|
+
export function useProviders(apiClient, options = {}) {
|
|
7
|
+
const { autoLoad = true } = options;
|
|
8
|
+
|
|
9
|
+
const [providers, setProviders] = useState([]);
|
|
10
|
+
const [activeProvider, setActiveProvider] = useState(null);
|
|
11
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
|
|
14
|
+
const loadProviders = useCallback(async () => {
|
|
15
|
+
if (!apiClient) {
|
|
16
|
+
setError('API client not configured');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setIsLoading(true);
|
|
21
|
+
setError(null);
|
|
22
|
+
|
|
23
|
+
const data = await apiClient.providers.getAll().catch(err => {
|
|
24
|
+
setError(err.message);
|
|
25
|
+
return { providers: [], activeProvider: null };
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
setProviders(data.providers || []);
|
|
29
|
+
setActiveProvider(data.activeProvider);
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}, [apiClient]);
|
|
32
|
+
|
|
33
|
+
const setActive = useCallback(async (id) => {
|
|
34
|
+
if (!apiClient) return;
|
|
35
|
+
|
|
36
|
+
await apiClient.providers.setActive(id);
|
|
37
|
+
setActiveProvider(id);
|
|
38
|
+
}, [apiClient]);
|
|
39
|
+
|
|
40
|
+
const createProvider = useCallback(async (data) => {
|
|
41
|
+
if (!apiClient) return null;
|
|
42
|
+
|
|
43
|
+
const provider = await apiClient.providers.create(data);
|
|
44
|
+
await loadProviders();
|
|
45
|
+
return provider;
|
|
46
|
+
}, [apiClient, loadProviders]);
|
|
47
|
+
|
|
48
|
+
const updateProvider = useCallback(async (id, data) => {
|
|
49
|
+
if (!apiClient) return null;
|
|
50
|
+
|
|
51
|
+
const provider = await apiClient.providers.update(id, data);
|
|
52
|
+
await loadProviders();
|
|
53
|
+
return provider;
|
|
54
|
+
}, [apiClient, loadProviders]);
|
|
55
|
+
|
|
56
|
+
const deleteProvider = useCallback(async (id) => {
|
|
57
|
+
if (!apiClient) return;
|
|
58
|
+
|
|
59
|
+
await apiClient.providers.delete(id);
|
|
60
|
+
await loadProviders();
|
|
61
|
+
}, [apiClient, loadProviders]);
|
|
62
|
+
|
|
63
|
+
const testProvider = useCallback(async (id) => {
|
|
64
|
+
if (!apiClient) return null;
|
|
65
|
+
|
|
66
|
+
return apiClient.providers.test(id);
|
|
67
|
+
}, [apiClient]);
|
|
68
|
+
|
|
69
|
+
const refreshModels = useCallback(async (id) => {
|
|
70
|
+
if (!apiClient) return null;
|
|
71
|
+
|
|
72
|
+
const provider = await apiClient.providers.refreshModels(id);
|
|
73
|
+
await loadProviders();
|
|
74
|
+
return provider;
|
|
75
|
+
}, [apiClient, loadProviders]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (autoLoad) {
|
|
79
|
+
loadProviders();
|
|
80
|
+
}
|
|
81
|
+
}, [autoLoad, loadProviders]);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
providers,
|
|
85
|
+
activeProvider,
|
|
86
|
+
isLoading,
|
|
87
|
+
error,
|
|
88
|
+
refetch: loadProviders,
|
|
89
|
+
setActive,
|
|
90
|
+
createProvider,
|
|
91
|
+
updateProvider,
|
|
92
|
+
deleteProvider,
|
|
93
|
+
testProvider,
|
|
94
|
+
refreshModels
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook for managing AI runs
|
|
5
|
+
*/
|
|
6
|
+
export function useRuns(apiClient, options = {}) {
|
|
7
|
+
const { autoLoad = true, limit = 50, offset = 0, source = 'all' } = options;
|
|
8
|
+
|
|
9
|
+
const [runs, setRuns] = useState([]);
|
|
10
|
+
const [total, setTotal] = useState(0);
|
|
11
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
|
|
14
|
+
const loadRuns = useCallback(async () => {
|
|
15
|
+
if (!apiClient) {
|
|
16
|
+
setError('API client not configured');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setIsLoading(true);
|
|
21
|
+
setError(null);
|
|
22
|
+
|
|
23
|
+
const data = await apiClient.runs.list(limit, offset, source).catch(err => {
|
|
24
|
+
setError(err.message);
|
|
25
|
+
return { runs: [], total: 0 };
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
setRuns(data.runs || []);
|
|
29
|
+
setTotal(data.total || 0);
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}, [apiClient, limit, offset, source]);
|
|
32
|
+
|
|
33
|
+
const createRun = useCallback(async (data) => {
|
|
34
|
+
if (!apiClient) return null;
|
|
35
|
+
|
|
36
|
+
const result = await apiClient.runs.create(data);
|
|
37
|
+
await loadRuns();
|
|
38
|
+
return result;
|
|
39
|
+
}, [apiClient, loadRuns]);
|
|
40
|
+
|
|
41
|
+
const stopRun = useCallback(async (id) => {
|
|
42
|
+
if (!apiClient) return;
|
|
43
|
+
|
|
44
|
+
await apiClient.runs.stop(id);
|
|
45
|
+
await loadRuns();
|
|
46
|
+
}, [apiClient, loadRuns]);
|
|
47
|
+
|
|
48
|
+
const deleteRun = useCallback(async (id) => {
|
|
49
|
+
if (!apiClient) return;
|
|
50
|
+
|
|
51
|
+
await apiClient.runs.delete(id);
|
|
52
|
+
await loadRuns();
|
|
53
|
+
}, [apiClient, loadRuns]);
|
|
54
|
+
|
|
55
|
+
const deleteFailedRuns = useCallback(async () => {
|
|
56
|
+
if (!apiClient) return;
|
|
57
|
+
|
|
58
|
+
const result = await apiClient.runs.deleteFailedRuns();
|
|
59
|
+
await loadRuns();
|
|
60
|
+
return result;
|
|
61
|
+
}, [apiClient, loadRuns]);
|
|
62
|
+
|
|
63
|
+
const getRunOutput = useCallback(async (id) => {
|
|
64
|
+
if (!apiClient) return null;
|
|
65
|
+
|
|
66
|
+
return apiClient.runs.getOutput(id);
|
|
67
|
+
}, [apiClient]);
|
|
68
|
+
|
|
69
|
+
const getRunPrompt = useCallback(async (id) => {
|
|
70
|
+
if (!apiClient) return null;
|
|
71
|
+
|
|
72
|
+
return apiClient.runs.getPrompt(id);
|
|
73
|
+
}, [apiClient]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (autoLoad) {
|
|
77
|
+
loadRuns();
|
|
78
|
+
}
|
|
79
|
+
}, [autoLoad, loadRuns]);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
runs,
|
|
83
|
+
total,
|
|
84
|
+
isLoading,
|
|
85
|
+
error,
|
|
86
|
+
refetch: loadRuns,
|
|
87
|
+
createRun,
|
|
88
|
+
stopRun,
|
|
89
|
+
deleteRun,
|
|
90
|
+
deleteFailedRuns,
|
|
91
|
+
getRunOutput,
|
|
92
|
+
getRunPrompt
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Toolkit Client
|
|
3
|
+
* React components, hooks, and API client for AI providers and runs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { createApiClient } from './api.js';
|
|
7
|
+
export * from './hooks/index.js';
|
|
8
|
+
export * from './components/index.js';
|
|
9
|
+
|
|
10
|
+
// Pages
|
|
11
|
+
export { default as AIProviders } from './pages/AIProviders.jsx';
|