synthos 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 +71 -0
- package/bin/synthos.js +3 -0
- package/default-pages/[application].html +87 -0
- package/default-pages/[markdown].html +261 -0
- package/default-pages/[sidebar].html +89 -0
- package/default-pages/[split-application].html +133 -0
- package/default-pages/json_tools.html +176 -0
- package/default-scripts/android.terminal.json +7 -0
- package/default-scripts/linux-terminal.json +7 -0
- package/default-scripts/mac-terminal.json +7 -0
- package/default-scripts/windows-terminal.json +7 -0
- package/dist/files.d.ts +9 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +79 -0
- package/dist/files.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +9 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +59 -0
- package/dist/init.js.map +1 -0
- package/dist/pages.d.ts +6 -0
- package/dist/pages.d.ts.map +1 -0
- package/dist/pages.js +55 -0
- package/dist/pages.js.map +1 -0
- package/dist/scripts.d.ts +14 -0
- package/dist/scripts.d.ts.map +1 -0
- package/dist/scripts.js +103 -0
- package/dist/scripts.js.map +1 -0
- package/dist/service/createCompletePrompt.d.ts +4 -0
- package/dist/service/createCompletePrompt.d.ts.map +1 -0
- package/dist/service/createCompletePrompt.js +42 -0
- package/dist/service/createCompletePrompt.js.map +1 -0
- package/dist/service/generateImage.d.ts +32 -0
- package/dist/service/generateImage.d.ts.map +1 -0
- package/dist/service/generateImage.js +71 -0
- package/dist/service/generateImage.js.map +1 -0
- package/dist/service/index.d.ts +8 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/service/index.js +24 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/requiresSettings.d.ts +3 -0
- package/dist/service/requiresSettings.d.ts.map +1 -0
- package/dist/service/requiresSettings.js +24 -0
- package/dist/service/requiresSettings.js.map +1 -0
- package/dist/service/server.d.ts +4 -0
- package/dist/service/server.d.ts.map +1 -0
- package/dist/service/server.js +26 -0
- package/dist/service/server.js.map +1 -0
- package/dist/service/transformPage.d.ts +11 -0
- package/dist/service/transformPage.d.ts.map +1 -0
- package/dist/service/transformPage.js +119 -0
- package/dist/service/transformPage.js.map +1 -0
- package/dist/service/useApiRoutes.d.ts +4 -0
- package/dist/service/useApiRoutes.d.ts.map +1 -0
- package/dist/service/useApiRoutes.js +95 -0
- package/dist/service/useApiRoutes.js.map +1 -0
- package/dist/service/useDataRoutes.d.ts +4 -0
- package/dist/service/useDataRoutes.d.ts.map +1 -0
- package/dist/service/useDataRoutes.js +98 -0
- package/dist/service/useDataRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +5 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -0
- package/dist/service/usePageRoutes.js +132 -0
- package/dist/service/usePageRoutes.js.map +1 -0
- package/dist/settings.d.ts +13 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +55 -0
- package/dist/settings.js.map +1 -0
- package/dist/synthos-cli.d.ts +2 -0
- package/dist/synthos-cli.d.ts.map +1 -0
- package/dist/synthos-cli.js +43 -0
- package/dist/synthos-cli.js.map +1 -0
- package/images/home.png +0 -0
- package/images/page-management.png +0 -0
- package/images/settings.png +0 -0
- package/images/synthos-square.png +0 -0
- package/package.json +58 -0
- package/required-pages/apis.html +347 -0
- package/required-pages/home.html +83 -0
- package/required-pages/pages.html +135 -0
- package/required-pages/scripts.html +335 -0
- package/required-pages/settings.html +158 -0
- package/src/files.ts +49 -0
- package/src/index.ts +6 -0
- package/src/init.ts +66 -0
- package/src/pages.ts +55 -0
- package/src/scripts.ts +130 -0
- package/src/service/createCompletePrompt.ts +40 -0
- package/src/service/generateImage.ts +101 -0
- package/src/service/index.ts +7 -0
- package/src/service/requiresSettings.ts +22 -0
- package/src/service/server.ts +26 -0
- package/src/service/transformPage.ts +135 -0
- package/src/service/useApiRoutes.ts +95 -0
- package/src/service/useDataRoutes.ts +97 -0
- package/src/service/usePageRoutes.ts +147 -0
- package/src/settings.ts +60 -0
- package/src/synthos-cli.ts +38 -0
package/src/pages.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {checkIfExists, listFiles, loadFile, saveFile} from './files';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// Page State Cache
|
|
5
|
+
const _pages: { [name: string]: string } = {};
|
|
6
|
+
|
|
7
|
+
export async function listPages(pagesFolder: string, fallbackPagesFolder: string): Promise<string[]> {
|
|
8
|
+
// Load all pages from primary pages folder
|
|
9
|
+
const all = (await listFiles(pagesFolder)).filter(file => file.endsWith('.html'));
|
|
10
|
+
|
|
11
|
+
// Add pages from fallback pages folder that don't already exist
|
|
12
|
+
(await listFiles(fallbackPagesFolder)).forEach(file => {
|
|
13
|
+
if (file.endsWith('.html') && !all.includes(file)) {
|
|
14
|
+
all.push(file);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Remove .html and sort all files
|
|
19
|
+
const sorted = all.map(file => file.substring(0, file.length - 5)).sort();
|
|
20
|
+
|
|
21
|
+
// Move [templates] to end of array
|
|
22
|
+
const pages = sorted.filter(page => !page.startsWith('['));
|
|
23
|
+
const templates = sorted.filter(page => page.startsWith('['));
|
|
24
|
+
pages.push(...templates);
|
|
25
|
+
|
|
26
|
+
return pages;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function loadPageState(pagesFolder: string, name: string, reset: boolean): Promise<string|undefined> {
|
|
30
|
+
if (!_pages[name] || reset) {
|
|
31
|
+
// Check for file to exist
|
|
32
|
+
const filename = path.join(pagesFolder, `${name}.html`);
|
|
33
|
+
if (!(await checkIfExists(filename))) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Load file
|
|
38
|
+
_pages[name] = await loadFile(filename);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return _pages[name];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizePageName(name: string|undefined): string|undefined {
|
|
45
|
+
return typeof name == 'string' && name.length > 0 ? name.replace(/[^a-z0-9\-_\[\]\(\)\{\}@#\$%&]/gi, '_').toLowerCase() : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function savePageState(pagesFolder: string, name: string, content: string): Promise<void> {
|
|
49
|
+
_pages[name] = content;
|
|
50
|
+
await saveFile(path.join(pagesFolder, `${name}.html`), content);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function updatePageState(name: string, content: string): void {
|
|
54
|
+
_pages[name] = content;
|
|
55
|
+
}
|
package/src/scripts.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { AgentCompletion, variableToString } from 'agentm-core';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { loadFile, listFiles, checkIfExists } from './files';
|
|
5
|
+
|
|
6
|
+
export interface ExecuteScriptArgs {
|
|
7
|
+
pagesFolder: string;
|
|
8
|
+
scriptId: string;
|
|
9
|
+
variables: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ExecuteScriptResponse {
|
|
13
|
+
output: string;
|
|
14
|
+
errors: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function listScripts(pagesFolder: string): Promise<string> {
|
|
18
|
+
if (!scriptsList) {
|
|
19
|
+
let list: string[] = [];
|
|
20
|
+
const folder = path.join(pagesFolder, 'scripts');
|
|
21
|
+
if (await checkIfExists(folder)) {
|
|
22
|
+
const files = (await listFiles(folder)).filter(f => f.endsWith('.json'));
|
|
23
|
+
for (const file of files) {
|
|
24
|
+
const script = await loadFile(path.join(folder, file));
|
|
25
|
+
if (script) {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(script) as ParsedScript;
|
|
28
|
+
const description = parsed.description ? `description: ${parsed.description}\n` : '';
|
|
29
|
+
const request = parsed.variables ? `request: ${parsed.variables}\n` : '';
|
|
30
|
+
const response = parsed.response ? `response: ${parsed.response}` : 'response: string';
|
|
31
|
+
list.push(`POST /api/scripts/${parsed.id}\n${description}${request}${response}`);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(err);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Cache the list
|
|
40
|
+
scriptsList = list.join('\n\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return scriptsList;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function clearCachedScripts(): void {
|
|
47
|
+
scriptsList = undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
export async function executeScript(args: ExecuteScriptArgs): Promise<AgentCompletion<ExecuteScriptResponse>> {
|
|
52
|
+
const { pagesFolder, scriptId, variables } = args;
|
|
53
|
+
|
|
54
|
+
// Load the script
|
|
55
|
+
const script = await loadFile(path.join(pagesFolder, `scripts`, `${scriptId}.json`));
|
|
56
|
+
if (!script) {
|
|
57
|
+
return { completed: false, error: new Error(`Script not found: ${scriptId}`) };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Parse the script
|
|
61
|
+
let parsed: ParsedScript;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(script) as ParsedScript;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
return { completed: false, error: err as Error };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Substitute variables
|
|
69
|
+
const commandText = composeArguments(parsed.command, variables);
|
|
70
|
+
console.log(`Executing: ${commandText}`);
|
|
71
|
+
|
|
72
|
+
// Split into an array while honoring double quotes
|
|
73
|
+
const argv = commandText.match(/"[^"]*"|\S+/g) || [];
|
|
74
|
+
if (argv.length === 0) {
|
|
75
|
+
return { completed: false, error: new Error('No command specified') };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Execute the process
|
|
79
|
+
try {
|
|
80
|
+
const command = argv.shift() as string;
|
|
81
|
+
const value = await spawnProcess(command, argv);
|
|
82
|
+
|
|
83
|
+
return { completed: true, value };
|
|
84
|
+
} catch (err: unknown) {
|
|
85
|
+
return { completed: false, error: err as Error };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let scriptsList: string|undefined = undefined;
|
|
90
|
+
|
|
91
|
+
interface ParsedScript {
|
|
92
|
+
id: string;
|
|
93
|
+
type: 'command';
|
|
94
|
+
command: string;
|
|
95
|
+
description?: string;
|
|
96
|
+
variables?: string;
|
|
97
|
+
response?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function spawnProcess(command: string, args: string[]): Promise<ExecuteScriptResponse> {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
const child = spawn(command, args, { shell: true });
|
|
103
|
+
|
|
104
|
+
let output = '';
|
|
105
|
+
const errors: string[] = [];
|
|
106
|
+
|
|
107
|
+
child.stdout.on('data', (data) => {
|
|
108
|
+
output += data;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
child.stderr.on('data', (data) => {
|
|
112
|
+
errors.push(data.toString());
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
child.on('close', (code) => {
|
|
116
|
+
resolve({ output, errors });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
child.on('error', (err) => {
|
|
120
|
+
reject(err);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function composeArguments(template: string, variables: Record<string, any>): string {
|
|
126
|
+
return template.replace(/{{\s*([^}\s]+)\s*}}/g, (match, name) => {
|
|
127
|
+
// Convert the variable to a string and replace double quotes and line feeds with spaces
|
|
128
|
+
return variableToString(variables[name]);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {anthropic, completePrompt, logCompletePrompt, openai} from 'agentm-core';
|
|
2
|
+
import { loadSettings } from '../settings';
|
|
3
|
+
|
|
4
|
+
export const availableModels = [
|
|
5
|
+
'claude-3-5-sonnet-20240620',
|
|
6
|
+
'gpt-4o-mini',
|
|
7
|
+
'gpt-4o-2024-08-06',
|
|
8
|
+
'o1-mini',
|
|
9
|
+
'o1-preview'
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export async function createCompletePrompt(pagesFolder: string, model?: string): Promise<completePrompt> {
|
|
13
|
+
// Get configuration settings
|
|
14
|
+
const settings = await loadSettings(pagesFolder);
|
|
15
|
+
if (!settings.serviceApiKey) {
|
|
16
|
+
throw new Error('OpenAI API key not configured');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Validate model
|
|
20
|
+
model = model || settings.model;
|
|
21
|
+
if (!model) {
|
|
22
|
+
throw new Error('Model not configured');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create completion functions
|
|
26
|
+
let modelInstance: completePrompt;
|
|
27
|
+
const apiKey = settings.serviceApiKey;
|
|
28
|
+
if (model.startsWith('claude-')) {
|
|
29
|
+
modelInstance = anthropic({apiKey, model});
|
|
30
|
+
} else {
|
|
31
|
+
modelInstance = openai({apiKey, model});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Return new model instance
|
|
35
|
+
if (settings.logCompletions) {
|
|
36
|
+
return logCompletePrompt(modelInstance, true);
|
|
37
|
+
} else {
|
|
38
|
+
return modelInstance;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { AgentCompletion, RequestError } from "agentm-core";
|
|
3
|
+
import { loadFile } from "../files";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Arguments to configure an OpenAI chat model.
|
|
8
|
+
*/
|
|
9
|
+
export interface OpenaiGenerateImageArgs {
|
|
10
|
+
/**
|
|
11
|
+
* OpenAI API key to use for completions.
|
|
12
|
+
*/
|
|
13
|
+
apiKey: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Prompt to use for generating image.
|
|
17
|
+
*/
|
|
18
|
+
prompt: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Requested size of the generated image.
|
|
22
|
+
*/
|
|
23
|
+
shape: 'square' | 'landscape' | 'portrait';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Requested quality of the generated image.
|
|
27
|
+
*/
|
|
28
|
+
quality: 'standard' | 'hd';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Style of the generated image.
|
|
32
|
+
*/
|
|
33
|
+
style: 'vivid' | 'natural';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface GeneratedImage {
|
|
37
|
+
url: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function generateDefaultImage(): Promise<AgentCompletion<GeneratedImage>> {
|
|
41
|
+
if (!_defaultImage) {
|
|
42
|
+
_defaultImage = new Promise<AgentCompletion<GeneratedImage>>(async resolve => {
|
|
43
|
+
const file = await loadFile(path.join(__dirname, '../../defaultImage.json'));
|
|
44
|
+
const value = JSON.parse(file) as GeneratedImage;
|
|
45
|
+
resolve({ completed: true, value });
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return _defaultImage;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function generateImage(args: OpenaiGenerateImageArgs): Promise<AgentCompletion<GeneratedImage>> {
|
|
53
|
+
const { apiKey, prompt, shape, quality, style } = args;
|
|
54
|
+
|
|
55
|
+
// Create client
|
|
56
|
+
const client = new OpenAI({ apiKey });
|
|
57
|
+
|
|
58
|
+
// Identify image size
|
|
59
|
+
let size: "1024x1024" | "1792x1024" | "1024x1792";
|
|
60
|
+
switch (shape) {
|
|
61
|
+
case 'square':
|
|
62
|
+
default:
|
|
63
|
+
size = "1024x1024";
|
|
64
|
+
break;
|
|
65
|
+
case 'landscape':
|
|
66
|
+
size = "1792x1024";
|
|
67
|
+
break;
|
|
68
|
+
case 'portrait':
|
|
69
|
+
size = "1024x1792";
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const response = await client.images.generate({
|
|
75
|
+
model: "dall-e-3",
|
|
76
|
+
response_format: "b64_json",
|
|
77
|
+
n: 1,
|
|
78
|
+
prompt,
|
|
79
|
+
size,
|
|
80
|
+
quality,
|
|
81
|
+
style
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (response.data.length > 0 && response.data[0].b64_json !== undefined) {
|
|
85
|
+
const url = `data:image/png;base64,${response.data[0].b64_json}`
|
|
86
|
+
return { completed: true, value: { url } };
|
|
87
|
+
} else {
|
|
88
|
+
return { completed: false, error: new Error('No image URL returned') };
|
|
89
|
+
}
|
|
90
|
+
} catch (err: unknown) {
|
|
91
|
+
let error: Error;
|
|
92
|
+
if (err instanceof OpenAI.APIError && err.status !== undefined) {
|
|
93
|
+
error = new RequestError(err.message, err.status, err.name);
|
|
94
|
+
} else {
|
|
95
|
+
error = err as Error;
|
|
96
|
+
}
|
|
97
|
+
return { completed: false, error };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let _defaultImage: Promise<AgentCompletion<GeneratedImage>> | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { hasConfiguredSettings, loadSettings, Settings } from "../settings";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export async function requiresSettings(res: any, folder: string, cb: (settings: Settings) => Promise<void>) {
|
|
5
|
+
try {
|
|
6
|
+
// Ensure settings configured
|
|
7
|
+
const isConfigured = await hasConfiguredSettings(folder);
|
|
8
|
+
if (!isConfigured) {
|
|
9
|
+
res.status(400).send('Settings not configured');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Load settings
|
|
14
|
+
const settings = await loadSettings(folder);
|
|
15
|
+
|
|
16
|
+
// Call the callback
|
|
17
|
+
await cb(settings);
|
|
18
|
+
} catch (err: unknown) {
|
|
19
|
+
console.error(err);
|
|
20
|
+
res.status(500).send((err as Error).message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import express, {Application} from 'express';
|
|
2
|
+
import { usePageRoutes } from './usePageRoutes';
|
|
3
|
+
import { useApiRoutes } from './useApiRoutes';
|
|
4
|
+
import { SynthOSConfig } from '../init';
|
|
5
|
+
import { useDataRoutes } from './useDataRoutes';
|
|
6
|
+
|
|
7
|
+
export function server(config: SynthOSConfig): Application {
|
|
8
|
+
const app = express();
|
|
9
|
+
|
|
10
|
+
// Middleware to parse URL-encoded data (form data)
|
|
11
|
+
app.use(express.urlencoded({ extended: true }));
|
|
12
|
+
|
|
13
|
+
// Middleware to parse JSON data
|
|
14
|
+
app.use(express.json());
|
|
15
|
+
|
|
16
|
+
// Page handling routes
|
|
17
|
+
usePageRoutes(config, app);
|
|
18
|
+
|
|
19
|
+
// API routes
|
|
20
|
+
useApiRoutes(config, app);
|
|
21
|
+
|
|
22
|
+
// Data routes
|
|
23
|
+
useDataRoutes(config, app);
|
|
24
|
+
|
|
25
|
+
return app;
|
|
26
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { AgentArgs, AgentCompletion, generateObject, JsonSchema, SystemMessage, UserMessage } from "agentm-core";
|
|
2
|
+
import { listScripts } from "../scripts";
|
|
3
|
+
|
|
4
|
+
export interface TransformPageArgs extends AgentArgs {
|
|
5
|
+
pagesFolder: string;
|
|
6
|
+
pageState: string;
|
|
7
|
+
message: string;
|
|
8
|
+
maxTokens: number;
|
|
9
|
+
instructions?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function transformPage(args: TransformPageArgs): Promise<AgentCompletion<string>> {
|
|
13
|
+
// Get list of registered scripts
|
|
14
|
+
const scripts = await listScripts(args.pagesFolder);
|
|
15
|
+
const serverScripts = scripts.length > 0 ? `<SERVER_SCRIPTS>\n${scripts}\n\n` : '';
|
|
16
|
+
|
|
17
|
+
// Define system message
|
|
18
|
+
const { pageState, message, maxTokens, completePrompt } = args;
|
|
19
|
+
const system: SystemMessage = {
|
|
20
|
+
role: 'system',
|
|
21
|
+
content: `<CURRENT_PAGE>\n${pageState}\n\n<SERVER_APIS>\n${serverAPIs}\n\n${serverScripts}<USER_MESSAGE>\n${message}`
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Create prompt
|
|
25
|
+
const instructions = args.instructions ? `\n\n<INSTRUCTIONS>\n${args.instructions}` : '';
|
|
26
|
+
const prompt: UserMessage = {
|
|
27
|
+
role: 'user',
|
|
28
|
+
content: `${goal}${instructions}`
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Complete prompt
|
|
32
|
+
const result = await completePrompt({ prompt, system, maxTokens });
|
|
33
|
+
if (result.completed) {
|
|
34
|
+
// Find html content
|
|
35
|
+
let start = result.value.indexOf(`<!DOCTYPE`);
|
|
36
|
+
start = start >= 0 ? start : result.value.indexOf('<html');
|
|
37
|
+
const end = result.value.lastIndexOf('</html>');
|
|
38
|
+
if (start >= 0 && end >= start) {
|
|
39
|
+
const value = result.value.substring(start, end + 7);
|
|
40
|
+
return { completed: true, value };
|
|
41
|
+
} else {
|
|
42
|
+
return { completed: false, error: new Error('Failed to find html content') };
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
return { completed: false, error: result.error };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function transformPageAsObject(args: TransformPageArgs): Promise<AgentCompletion<string>> {
|
|
50
|
+
// Get list of registered scripts
|
|
51
|
+
const scripts = await listScripts(args.pagesFolder);
|
|
52
|
+
const serverScripts = scripts.length > 0 ? `<SERVER_SCRIPTS>\n${scripts}\n\n` : '';
|
|
53
|
+
|
|
54
|
+
// Provide additional context
|
|
55
|
+
const { pageState, message, maxTokens, instructions, completePrompt, shouldContinue } = args;
|
|
56
|
+
const context = `<CURRENT_PAGE>\n${pageState}\n\n<SERVER_APIS>\n${serverAPIs}\n\n${serverScripts}<USER_MESSAGE>\n${message}`;
|
|
57
|
+
|
|
58
|
+
// Generate next page
|
|
59
|
+
const result = await generateObject<HtmlPage>({ goal, jsonSchema, maxTokens, context, instructions, completePrompt, shouldContinue });
|
|
60
|
+
if (result.completed) {
|
|
61
|
+
return { completed: true, value: result.value?.content! };
|
|
62
|
+
} else {
|
|
63
|
+
return { completed: false, error: result.error };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Define output shape
|
|
68
|
+
interface HtmlPage {
|
|
69
|
+
content: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const jsonSchema: JsonSchema = {
|
|
73
|
+
name: 'HtmlPage',
|
|
74
|
+
schema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
content: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'html page content to return'
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
required: ['content'],
|
|
83
|
+
additionalProperties: false
|
|
84
|
+
},
|
|
85
|
+
strict: true
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const goal =
|
|
89
|
+
`Generate a new web page that represents the next state of the chat based on the users message.
|
|
90
|
+
Append the the users message and a brief response from the AI to the chat panel.
|
|
91
|
+
Maintain the full conversation history in the chat panel unless asked to clear it.
|
|
92
|
+
Any details or visualizations should be rendered to the viewer panel.
|
|
93
|
+
The basic layout structure of the page needs to be maintained.
|
|
94
|
+
You're free to write any additional CSS or JavaScript to enhance the page.
|
|
95
|
+
Write an explication of your reasoning or any hidden thoughts to the thoughts div.
|
|
96
|
+
If the user asks to create something like an app, tool, game, or ui create it in the viewer panel.
|
|
97
|
+
If the user asks to draw something use canvas to draw it in the viewer panel.
|
|
98
|
+
Always return the full html content of the page.`;
|
|
99
|
+
|
|
100
|
+
const serverAPIs =
|
|
101
|
+
`GET /api/data/:table
|
|
102
|
+
description: Retrieve all rows from a table
|
|
103
|
+
response: Array of JSON rows [{ id: string, ... }]
|
|
104
|
+
|
|
105
|
+
GET /api/data/:table/:id
|
|
106
|
+
description: Retrieve a single row from a table
|
|
107
|
+
response: JSON row { id: string, ... }
|
|
108
|
+
|
|
109
|
+
POST /api/data/:table
|
|
110
|
+
description: Replaces or adds a single row to a table and returns the row
|
|
111
|
+
request: JSON row { id?: string, ... }
|
|
112
|
+
response: { id: string, ... }
|
|
113
|
+
|
|
114
|
+
DELETE /api/data/:table/:id
|
|
115
|
+
description: Delete a single row from a table
|
|
116
|
+
response: { success: true }
|
|
117
|
+
|
|
118
|
+
POST /api/generate/image
|
|
119
|
+
description: Generate an image based on a prompt
|
|
120
|
+
request: { prompt: string, shape: 'square' | 'portrait' | 'landscape', style: 'vivid' | 'natural' }
|
|
121
|
+
response: { url: string }
|
|
122
|
+
|
|
123
|
+
POST /api/generate/completion
|
|
124
|
+
description: Generates a text completion based on a prompt
|
|
125
|
+
request: { prompt: string, temperature?: number }
|
|
126
|
+
response: { answer: string, explanation: string }
|
|
127
|
+
|
|
128
|
+
GET /api/pages
|
|
129
|
+
description: Retrieve a list of all pages
|
|
130
|
+
response: Array of page names [string]
|
|
131
|
+
|
|
132
|
+
POST /api/scripts/:id
|
|
133
|
+
description: Execute a script with the passed in variables
|
|
134
|
+
request: { [key: string]: string }
|
|
135
|
+
response: string`;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { listPages } from "../pages";
|
|
2
|
+
import {loadSettings, saveSettings } from "../settings";
|
|
3
|
+
import { Application } from 'express';
|
|
4
|
+
import { SynthOSConfig } from "../init";
|
|
5
|
+
import { availableModels, createCompletePrompt } from "./createCompletePrompt";
|
|
6
|
+
import { generateDefaultImage, generateImage } from "./generateImage";
|
|
7
|
+
import { chainOfThought } from "agentm-core";
|
|
8
|
+
import { requiresSettings } from "./requiresSettings";
|
|
9
|
+
import { executeScript } from "../scripts";
|
|
10
|
+
|
|
11
|
+
export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
12
|
+
// List pages
|
|
13
|
+
app.get('/api/pages', async (req, res) => {
|
|
14
|
+
const pages = await listPages(config.pagesFolder, config.requiredPagesFolder);
|
|
15
|
+
res.json(pages);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Define a route to return settings
|
|
19
|
+
app.get('/api/settings', async (req, res) => {
|
|
20
|
+
const settings = await loadSettings(config.pagesFolder);
|
|
21
|
+
res.json({...settings, availableModels});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Define a route to save settings
|
|
25
|
+
app.post('/api/settings', async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
// Covert non-string values
|
|
28
|
+
const settings = req.body as Record<string, any>;
|
|
29
|
+
if (typeof settings.maxTokens === 'string') {
|
|
30
|
+
settings.maxTokens = parseInt(settings.maxTokens);
|
|
31
|
+
}
|
|
32
|
+
if (typeof settings.logCompletions === 'string') {
|
|
33
|
+
settings.logCompletions = settings.logCompletions === 'true';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Save settings
|
|
37
|
+
await saveSettings(config.pagesFolder, settings);
|
|
38
|
+
res.redirect('/home');
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
console.error(err);
|
|
41
|
+
res.status(500).send((err as Error).message);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Define a route to generate an image
|
|
46
|
+
app.post('/api/generate/image', async (req, res) => {
|
|
47
|
+
await requiresSettings(res, config.pagesFolder, async (settings) => {
|
|
48
|
+
const { prompt, shape, style } = req.body;
|
|
49
|
+
const { serviceApiKey, imageQuality, model } = settings;
|
|
50
|
+
const response = model.startsWith('gpt-') ?
|
|
51
|
+
await generateImage({ apiKey: serviceApiKey, prompt, shape, quality: imageQuality, style }) :
|
|
52
|
+
await generateDefaultImage();
|
|
53
|
+
if (response.completed) {
|
|
54
|
+
res.json(response.value);
|
|
55
|
+
} else {
|
|
56
|
+
res.status(500).send(response.error?.message);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Define a route to generate a completion using chain-of-thought
|
|
62
|
+
app.post('/api/generate/completion', async (req, res) => {
|
|
63
|
+
await requiresSettings(res, config.pagesFolder, async (settings) => {
|
|
64
|
+
const { prompt, temperature } = req.body;
|
|
65
|
+
const { maxTokens } = settings;
|
|
66
|
+
const completePrompt = await createCompletePrompt(config.pagesFolder, req.body.model);
|
|
67
|
+
const response = await chainOfThought({ question: prompt, temperature, maxTokens, completePrompt });
|
|
68
|
+
if (response.completed) {
|
|
69
|
+
res.json(response.value ?? {});
|
|
70
|
+
} else {
|
|
71
|
+
console.error(response.error);
|
|
72
|
+
res.status(500).send(response.error?.message);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Define a route for running configured scripts
|
|
78
|
+
app.post('/api/scripts/:id', async (req, res) => {
|
|
79
|
+
await requiresSettings(res, config.pagesFolder, async (settings) => {
|
|
80
|
+
const { id } = req.params;
|
|
81
|
+
const pagesFolder = config.pagesFolder;
|
|
82
|
+
const scriptId = id;
|
|
83
|
+
const response = await executeScript({ pagesFolder, scriptId, variables: req.body });
|
|
84
|
+
if (response.completed) {
|
|
85
|
+
// Return the response as text
|
|
86
|
+
const value = (response.value?.output ?? (response.value?.errors ?? []).join('\n')).trim();
|
|
87
|
+
res.set('Content-Type', 'text/plain');
|
|
88
|
+
res.send(value);
|
|
89
|
+
} else {
|
|
90
|
+
console.error(response.error);
|
|
91
|
+
res.status(500).send(response.error);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|