openbot 0.1.29 → 0.2.1
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/dist/agents/agent-creator.js +35 -0
- package/dist/assets/logo.js +24 -0
- package/dist/cli.js +1 -1
- package/dist/config.js +4 -0
- package/dist/model-catalog.js +132 -0
- package/dist/model-defaults.js +25 -0
- package/dist/models.js +2 -1
- package/dist/open-bot.js +86 -189
- package/dist/orchestrator/direct-invocation.js +13 -0
- package/dist/orchestrator/events.js +36 -0
- package/dist/orchestrator/state.js +54 -0
- package/dist/orchestrator.js +226 -0
- package/dist/plugins/approval/index.js +10 -26
- package/dist/plugins/brain/index.js +2 -1
- package/dist/plugins/brain/prompt.js +20 -33
- package/dist/plugins/file-system/index.js +2 -1
- package/dist/plugins/llm/index.js +80 -18
- package/dist/plugins/shell/index.js +2 -1
- package/dist/plugins/skills/index.js +4 -2
- package/dist/registry/yaml-agent-loader.js +4 -2
- package/dist/server.js +282 -41
- package/dist/ui/widgets/action-list.js +9 -0
- package/dist/ui/widgets/approval-card.js +15 -0
- package/dist/ui/widgets/code-snippet.js +2 -0
- package/dist/ui/widgets/data-block.js +5 -0
- package/dist/ui/widgets/data-table.js +8 -0
- package/dist/ui/widgets/empty-state.js +7 -0
- package/dist/ui/widgets/index.js +19 -0
- package/dist/ui/widgets/key-value.js +12 -0
- package/dist/ui/widgets/progress-step.js +5 -0
- package/dist/ui/widgets/resource-card.js +10 -0
- package/dist/ui/widgets/status.js +5 -0
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -3,55 +3,295 @@ import express from "express";
|
|
|
3
3
|
import cors from "cors";
|
|
4
4
|
import { generateId } from "melony";
|
|
5
5
|
import { createOpenBot } from "./open-bot.js";
|
|
6
|
-
import { loadConfig } from "./config.js";
|
|
7
|
-
import { loadSession, saveSession, logEvent, loadEvents } from "./session.js";
|
|
6
|
+
import { loadConfig, saveConfig, isConfigured, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
7
|
+
import { loadSession, saveSession, logEvent, loadEvents, listSessions } from "./session.js";
|
|
8
|
+
import { listYamlAgents } from "./registry/index.js";
|
|
9
|
+
import { exec } from "node:child_process";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import fs from "node:fs/promises";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { fetchProviderModels, getModelCatalog } from "./model-catalog.js";
|
|
15
|
+
import { DEFAULT_MODEL_BY_PROVIDER, DEFAULT_MODEL_ID } from "./model-defaults.js";
|
|
8
16
|
export async function startServer(options = {}) {
|
|
9
17
|
const config = loadConfig();
|
|
10
18
|
const PORT = Number(options.port ?? config.port ?? process.env.PORT ?? 4001);
|
|
11
19
|
const app = express();
|
|
12
|
-
|
|
13
|
-
const openBotAgent = await createOpenBot({
|
|
20
|
+
const orchestrator = await createOpenBot({
|
|
14
21
|
openaiApiKey: options.openaiApiKey,
|
|
15
22
|
anthropicApiKey: options.anthropicApiKey,
|
|
16
23
|
});
|
|
17
24
|
app.use(cors());
|
|
18
|
-
app.use(express.json());
|
|
19
|
-
|
|
25
|
+
app.use(express.json({ limit: "20mb" }));
|
|
26
|
+
const getUploadsDir = () => {
|
|
27
|
+
const cfg = loadConfig();
|
|
28
|
+
const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
|
|
29
|
+
const resolvedBaseDir = resolvePath(baseDir);
|
|
30
|
+
return path.join(resolvedBaseDir, "uploads");
|
|
31
|
+
};
|
|
32
|
+
const MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
33
|
+
const allowedMimeTypes = new Set([
|
|
34
|
+
"image/png",
|
|
35
|
+
"image/jpeg",
|
|
36
|
+
"image/webp",
|
|
37
|
+
"image/gif",
|
|
38
|
+
"image/svg+xml",
|
|
39
|
+
]);
|
|
40
|
+
const extensionByMimeType = {
|
|
41
|
+
"image/png": ".png",
|
|
42
|
+
"image/jpeg": ".jpg",
|
|
43
|
+
"image/webp": ".webp",
|
|
44
|
+
"image/gif": ".gif",
|
|
45
|
+
"image/svg+xml": ".svg",
|
|
46
|
+
};
|
|
47
|
+
// Return available models to the client.
|
|
48
|
+
// It prefers fresh provider APIs and falls back to bundled defaults.
|
|
49
|
+
app.get("/api/models", async (_req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
const models = await getModelCatalog();
|
|
52
|
+
res.json(models);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error("Failed to load models:", err);
|
|
56
|
+
res.json([]);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
app.post("/api/models/preview", async (req, res) => {
|
|
60
|
+
const { provider, apiKey } = req.body;
|
|
61
|
+
if (provider !== "openai" && provider !== "anthropic") {
|
|
62
|
+
return res.status(400).json({ error: "Invalid provider" });
|
|
63
|
+
}
|
|
64
|
+
if (!apiKey || typeof apiKey !== "string" || !apiKey.trim()) {
|
|
65
|
+
return res.status(400).json({ error: "API key is required" });
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const models = await fetchProviderModels(provider, apiKey.trim());
|
|
69
|
+
res.json(models);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error("Failed to preview models:", err);
|
|
73
|
+
res.status(502).json({ error: "Failed to fetch models from provider" });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
app.get("/", async (_req, res) => {
|
|
20
77
|
res.json({
|
|
21
|
-
message: "
|
|
22
|
-
|
|
23
|
-
|
|
78
|
+
message: "OpenBot API server",
|
|
79
|
+
version: "2.0",
|
|
80
|
+
endpoints: {
|
|
81
|
+
chat: "POST /api/chat",
|
|
82
|
+
config: "GET|POST /api/config",
|
|
83
|
+
sessions: "GET /api/sessions",
|
|
84
|
+
agents: "GET /api/agents",
|
|
85
|
+
prompts: "GET /api/prompts",
|
|
86
|
+
},
|
|
24
87
|
});
|
|
25
88
|
});
|
|
26
|
-
//
|
|
27
|
-
app.get("/api/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
89
|
+
// ─── REST API ───────────────────────────────────────────────────
|
|
90
|
+
app.get("/api/prompts", async (_req, res) => {
|
|
91
|
+
res.json([
|
|
92
|
+
{ label: "Who are you?", icon: "user" },
|
|
93
|
+
{ label: "Who am I?", icon: "help-circle" },
|
|
94
|
+
{ label: "How can you help me?", icon: "sparkles" },
|
|
95
|
+
{ label: "What is the weather in Tokyo?", icon: "sun" },
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
app.post("/api/uploads/image", async (req, res) => {
|
|
99
|
+
const { name, mimeType, dataBase64 } = req.body;
|
|
100
|
+
if (!mimeType || !allowedMimeTypes.has(mimeType)) {
|
|
101
|
+
return res.status(400).json({ error: "Unsupported image mime type" });
|
|
102
|
+
}
|
|
103
|
+
if (!dataBase64 || typeof dataBase64 !== "string") {
|
|
104
|
+
return res.status(400).json({ error: "Image payload is required" });
|
|
105
|
+
}
|
|
106
|
+
const bytes = Buffer.from(dataBase64, "base64");
|
|
107
|
+
if (!bytes.length) {
|
|
108
|
+
return res.status(400).json({ error: "Invalid image payload" });
|
|
109
|
+
}
|
|
110
|
+
if (bytes.length > MAX_IMAGE_BYTES) {
|
|
111
|
+
return res.status(413).json({ error: "Image too large (max 8MB)" });
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const ext = extensionByMimeType[mimeType] ?? ".bin";
|
|
115
|
+
const now = new Date();
|
|
116
|
+
const y = now.getFullYear().toString();
|
|
117
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
118
|
+
const datePath = path.join(y, m);
|
|
119
|
+
const fileName = `${Date.now()}-${randomUUID()}${ext}`;
|
|
120
|
+
const id = path.posix.join(y, m, fileName);
|
|
121
|
+
const uploadsDir = getUploadsDir();
|
|
122
|
+
const datedDir = path.join(uploadsDir, datePath);
|
|
123
|
+
await fs.mkdir(datedDir, { recursive: true });
|
|
124
|
+
await fs.writeFile(path.join(datedDir, fileName), bytes);
|
|
125
|
+
const origin = `${req.protocol}://${req.get("host")}`;
|
|
126
|
+
const encodedId = id.split("/").map(encodeURIComponent).join("/");
|
|
127
|
+
res.json({
|
|
128
|
+
id,
|
|
129
|
+
name: typeof name === "string" && name.trim() ? name.trim() : `image${ext}`,
|
|
130
|
+
mimeType,
|
|
131
|
+
size: bytes.length,
|
|
132
|
+
url: `${origin}/api/uploads/${encodedId}`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error("Image upload failed:", error);
|
|
137
|
+
res.status(500).json({ error: "Failed to store image" });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
app.get("/api/uploads/*", async (req, res) => {
|
|
141
|
+
const rawPath = req.params[0];
|
|
142
|
+
if (!rawPath || rawPath.includes("\\")) {
|
|
143
|
+
return res.status(400).send("Invalid upload id");
|
|
144
|
+
}
|
|
145
|
+
const normalized = path.posix.normalize(rawPath);
|
|
146
|
+
if (normalized.startsWith("../") || normalized === "..") {
|
|
147
|
+
return res.status(400).send("Invalid upload id");
|
|
148
|
+
}
|
|
149
|
+
const uploadsDir = getUploadsDir();
|
|
150
|
+
const filePath = path.join(uploadsDir, normalized);
|
|
151
|
+
try {
|
|
152
|
+
await fs.access(filePath);
|
|
153
|
+
res.sendFile(filePath);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
res.status(404).send("Upload not found");
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
app.get("/api/config", async (_req, res) => {
|
|
160
|
+
const cfg = loadConfig();
|
|
47
161
|
res.json({
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
162
|
+
configured: isConfigured(),
|
|
163
|
+
model: cfg.model || DEFAULT_MODEL_ID,
|
|
164
|
+
defaultModelId: DEFAULT_MODEL_ID,
|
|
165
|
+
defaultModels: DEFAULT_MODEL_BY_PROVIDER,
|
|
166
|
+
hasOpenAIKey: !!cfg.openaiApiKey,
|
|
167
|
+
hasAnthropicKey: !!cfg.anthropicApiKey,
|
|
52
168
|
});
|
|
53
169
|
});
|
|
54
|
-
|
|
170
|
+
app.post("/api/config", async (req, res) => {
|
|
171
|
+
const { openai_api_key, anthropic_api_key, model } = req.body;
|
|
172
|
+
const updates = {};
|
|
173
|
+
if (model)
|
|
174
|
+
updates.model = model.trim();
|
|
175
|
+
if (openai_api_key && openai_api_key !== "••••••••••••••••")
|
|
176
|
+
updates.openaiApiKey = openai_api_key.trim();
|
|
177
|
+
if (anthropic_api_key && anthropic_api_key !== "••••••••••••••••")
|
|
178
|
+
updates.anthropicApiKey = anthropic_api_key.trim();
|
|
179
|
+
if (Object.keys(updates).length > 0) {
|
|
180
|
+
saveConfig(updates);
|
|
181
|
+
}
|
|
182
|
+
res.json({ success: true });
|
|
183
|
+
});
|
|
184
|
+
app.get("/api/sessions", async (_req, res) => {
|
|
185
|
+
const sessions = await listSessions();
|
|
186
|
+
res.json(sessions);
|
|
187
|
+
});
|
|
188
|
+
app.get("/api/sessions/:id/events", async (req, res) => {
|
|
189
|
+
const events = await loadEvents(req.params.id);
|
|
190
|
+
res.json(events);
|
|
191
|
+
});
|
|
192
|
+
app.get("/api/agents", async (_req, res) => {
|
|
193
|
+
const cfg = loadConfig();
|
|
194
|
+
const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
|
|
195
|
+
const resolvedBaseDir = resolvePath(baseDir);
|
|
196
|
+
const agentsDir = path.join(resolvedBaseDir, "agents");
|
|
197
|
+
try {
|
|
198
|
+
const agents = await listYamlAgents(agentsDir);
|
|
199
|
+
res.json(agents);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
res.json([]);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
app.get("/api/agents/:name/yaml", async (req, res) => {
|
|
206
|
+
const { name } = req.params;
|
|
207
|
+
const cfg = loadConfig();
|
|
208
|
+
const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
|
|
209
|
+
const resolvedBaseDir = resolvePath(baseDir);
|
|
210
|
+
const yamlPath = path.join(resolvedBaseDir, "agents", name, "agent.yaml");
|
|
211
|
+
try {
|
|
212
|
+
const content = await fs.readFile(yamlPath, "utf-8");
|
|
213
|
+
res.send(content);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
res.status(404).send("Agent not found or has no agent.yaml");
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
app.put("/api/agents/:name/yaml", async (req, res) => {
|
|
220
|
+
const { name } = req.params;
|
|
221
|
+
const { yaml } = req.body;
|
|
222
|
+
if (!yaml || typeof yaml !== "string") {
|
|
223
|
+
return res.status(400).json({ error: "YAML content is required" });
|
|
224
|
+
}
|
|
225
|
+
const cfg = loadConfig();
|
|
226
|
+
const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
|
|
227
|
+
const resolvedBaseDir = resolvePath(baseDir);
|
|
228
|
+
const agentDir = path.join(resolvedBaseDir, "agents", name);
|
|
229
|
+
const yamlPath = path.join(agentDir, "agent.yaml");
|
|
230
|
+
try {
|
|
231
|
+
await fs.mkdir(agentDir, { recursive: true });
|
|
232
|
+
await fs.writeFile(yamlPath, yaml, "utf-8");
|
|
233
|
+
// Optionally, hot-reload openBotAgent if needed here.
|
|
234
|
+
// But OpenBot runtime loads agents at startup or dynamically per request?
|
|
235
|
+
// createOpenBot builds the Melony App. Since createOpenBot is called at startup:
|
|
236
|
+
// openBotAgent = await createOpenBot(...) happens once.
|
|
237
|
+
// Restarting server is required unless we hot-reload. We can just leave it as is
|
|
238
|
+
// and instruct the user to restart, or implement a simple hot reload. Let's stick to simple file write first.
|
|
239
|
+
res.json({ success: true });
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
console.error(err);
|
|
243
|
+
res.status(500).json({ error: "Failed to write agent.yaml" });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
app.post("/api/actions/open-folder", async (req, res) => {
|
|
247
|
+
const { folder } = req.body;
|
|
248
|
+
if (folder) {
|
|
249
|
+
const command = os.platform() === "win32"
|
|
250
|
+
? `explorer "${folder}"`
|
|
251
|
+
: os.platform() === "darwin"
|
|
252
|
+
? `open "${folder}"`
|
|
253
|
+
: `xdg-open "${folder}"`;
|
|
254
|
+
exec(command, (error) => {
|
|
255
|
+
if (error)
|
|
256
|
+
console.error(`Failed to open folder: ${error.message}`);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
res.json({ success: true });
|
|
260
|
+
});
|
|
261
|
+
app.get("/api/agents/:name/avatar", async (req, res) => {
|
|
262
|
+
const { name } = req.params;
|
|
263
|
+
const cfg = loadConfig();
|
|
264
|
+
const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
|
|
265
|
+
const resolvedBaseDir = resolvePath(baseDir);
|
|
266
|
+
const extensions = [".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif"];
|
|
267
|
+
const fileNames = ["avatar", "icon", "image", "logo"];
|
|
268
|
+
const searchDirs = [
|
|
269
|
+
path.join(resolvedBaseDir, "agents", name, "assets"),
|
|
270
|
+
path.join(process.cwd(), "server", "src", "agents", name, "assets"),
|
|
271
|
+
path.join(process.cwd(), "server", "src", "assets", "agents", name),
|
|
272
|
+
path.join(process.cwd(), "server", "src", "agents", "assets"),
|
|
273
|
+
path.join(process.cwd(), "server", "src", "assets")
|
|
274
|
+
];
|
|
275
|
+
for (const dir of searchDirs) {
|
|
276
|
+
for (const fileName of fileNames) {
|
|
277
|
+
for (const ext of extensions) {
|
|
278
|
+
const baseName = (dir.endsWith("assets") && !dir.includes(name)) ? name : fileName;
|
|
279
|
+
const p = path.join(dir, `${baseName}${ext}`);
|
|
280
|
+
try {
|
|
281
|
+
await fs.access(p);
|
|
282
|
+
return res.sendFile(p);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// continue
|
|
286
|
+
}
|
|
287
|
+
if (baseName === name)
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
res.status(404).send("Avatar not found");
|
|
293
|
+
});
|
|
294
|
+
// ─── Chat SSE endpoint ──────────────────────────────────────────
|
|
55
295
|
app.post("/api/chat", async (req, res) => {
|
|
56
296
|
const body = req.body;
|
|
57
297
|
if (!body.event || typeof body.event.type !== "string") {
|
|
@@ -65,12 +305,15 @@ export async function startServer(options = {}) {
|
|
|
65
305
|
Connection: "keep-alive",
|
|
66
306
|
});
|
|
67
307
|
res.flushHeaders?.();
|
|
68
|
-
const runtime = openBotAgent.build();
|
|
69
308
|
const sessionId = body.sessionId ?? "default";
|
|
70
309
|
const runId = body.runId ?? `run_${generateId()}`;
|
|
71
310
|
const state = (await loadSession(sessionId)) ?? {};
|
|
72
311
|
state.sessionId = sessionId;
|
|
73
|
-
|
|
312
|
+
if (!state.cwd)
|
|
313
|
+
state.cwd = process.cwd();
|
|
314
|
+
if (!state.workspaceRoot)
|
|
315
|
+
state.workspaceRoot = process.cwd();
|
|
316
|
+
const iterator = orchestrator.run(body.event, {
|
|
74
317
|
runId,
|
|
75
318
|
state,
|
|
76
319
|
});
|
|
@@ -80,14 +323,11 @@ export async function startServer(options = {}) {
|
|
|
80
323
|
res.on("close", stopStreaming);
|
|
81
324
|
try {
|
|
82
325
|
for await (const chunk of iterator) {
|
|
83
|
-
if (res.writableEnded)
|
|
326
|
+
if (res.writableEnded)
|
|
84
327
|
break;
|
|
85
|
-
}
|
|
86
|
-
// Log each event to the persistent file
|
|
87
328
|
await logEvent(sessionId, runId, chunk);
|
|
88
329
|
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
89
330
|
}
|
|
90
|
-
// After the run finishes, save the final state back to disk
|
|
91
331
|
await saveSession(sessionId, state);
|
|
92
332
|
}
|
|
93
333
|
catch (error) {
|
|
@@ -109,6 +349,7 @@ export async function startServer(options = {}) {
|
|
|
109
349
|
app.listen(PORT, () => {
|
|
110
350
|
console.log(`OpenBot server listening at http://localhost:${PORT}`);
|
|
111
351
|
console.log(` - Chat endpoint: POST /api/chat`);
|
|
352
|
+
console.log(` - REST endpoints: /api/config, /api/sessions, /api/agents`);
|
|
112
353
|
if (options.openaiApiKey)
|
|
113
354
|
console.log(" - Using OpenAI API Key from CLI");
|
|
114
355
|
if (options.anthropicApiKey)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ui } from '@melony/ui-kit/server';
|
|
2
|
+
export const actionList = (title, actions) => ui.box({ border: true, radius: 'md', padding: 'md' }, [
|
|
3
|
+
ui.col({ gap: 'md' }, [
|
|
4
|
+
ui.heading(title, { level: 4 }),
|
|
5
|
+
ui.row({ gap: 'sm', wrap: 'wrap' }, actions.map(a => ui.button({ variant: a.variant || 'outline', onClickAction: a.action }, [
|
|
6
|
+
ui.text(a.label, { size: 'sm' })
|
|
7
|
+
])))
|
|
8
|
+
])
|
|
9
|
+
]);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ui } from '@melony/ui-kit/server';
|
|
2
|
+
export const approvalCard = (title, description, approveAction, denyAction) => ui.box({ border: true, radius: 'md', padding: 'md' }, [
|
|
3
|
+
ui.col({ gap: 'sm' }, [
|
|
4
|
+
ui.heading(title, { level: 4 }),
|
|
5
|
+
ui.text(description, { size: 'sm', color: 'muted' }),
|
|
6
|
+
ui.row({ gap: 'sm', justify: 'end' }, [
|
|
7
|
+
ui.button({ variant: 'outline', onClickAction: denyAction }, [
|
|
8
|
+
ui.text('Deny', { size: 'xs' })
|
|
9
|
+
]),
|
|
10
|
+
ui.button({ variant: 'primary', onClickAction: approveAction }, [
|
|
11
|
+
ui.text('Approve', { size: 'xs' })
|
|
12
|
+
])
|
|
13
|
+
])
|
|
14
|
+
])
|
|
15
|
+
]);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ui } from "@melony/ui-kit";
|
|
2
|
+
export const dataBlockWidget = (data) => ui.col({ gap: 'xs' }, Object.entries(data).filter(([_, v]) => v !== undefined && v !== null).map(([key, value]) => ui.row({ gap: 'sm', align: 'start' }, [
|
|
3
|
+
ui.text(`${key}:`, { weight: 'semibold', size: 'xs', color: 'muted' }),
|
|
4
|
+
ui.text(String(value), { size: 'xs' }),
|
|
5
|
+
])));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ui } from '@melony/ui-kit/server';
|
|
2
|
+
export const dataTable = (headers, rows) => {
|
|
3
|
+
const headerRow = `| ${headers.join(' | ')} |`;
|
|
4
|
+
const separator = `| ${headers.map(() => '---').join(' | ')} |`;
|
|
5
|
+
const dataRows = rows.map(row => `| ${row.join(' | ')} |`).join('\n');
|
|
6
|
+
const markdownTable = `${headerRow}\n${separator}\n${dataRows}`;
|
|
7
|
+
return ui.markdown(markdownTable);
|
|
8
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ui } from '@melony/ui-kit/server';
|
|
2
|
+
export const emptyState = (message, iconName) => ui.box({ padding: 'lg', border: true, radius: 'md' }, [
|
|
3
|
+
ui.col({ align: 'center', justify: 'center', gap: 'sm' }, [
|
|
4
|
+
iconName ? ui.icon(iconName) : ui.text('∅', { size: 'lg', color: 'muted' }),
|
|
5
|
+
ui.text(message, { size: 'sm', color: 'muted' })
|
|
6
|
+
])
|
|
7
|
+
]);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { keyValue } from './key-value.js';
|
|
2
|
+
import { dataTable } from './data-table.js';
|
|
3
|
+
import { codeSnippet } from './code-snippet.js';
|
|
4
|
+
import { statusWidget as status } from './status.js';
|
|
5
|
+
import { progressStep } from './progress-step.js';
|
|
6
|
+
import { approvalCard } from './approval-card.js';
|
|
7
|
+
import { actionList } from './action-list.js';
|
|
8
|
+
import { emptyState } from './empty-state.js';
|
|
9
|
+
export const widgets = {
|
|
10
|
+
keyValue,
|
|
11
|
+
dataTable,
|
|
12
|
+
codeSnippet,
|
|
13
|
+
status,
|
|
14
|
+
progressStep,
|
|
15
|
+
approvalCard,
|
|
16
|
+
actionList,
|
|
17
|
+
emptyState,
|
|
18
|
+
};
|
|
19
|
+
export default widgets;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ui } from '@melony/ui-kit/server';
|
|
2
|
+
export const keyValue = (title, data) => ui.box({ border: true, radius: 'md', padding: 'md' }, [
|
|
3
|
+
ui.col({ gap: 'md' }, [
|
|
4
|
+
ui.heading(title, { level: 4 }),
|
|
5
|
+
ui.col({ gap: 'xs' }, Object.entries(data)
|
|
6
|
+
.filter(([_, v]) => v !== undefined && v !== null)
|
|
7
|
+
.map(([key, value]) => ui.row({ gap: 'sm', align: 'start' }, [
|
|
8
|
+
ui.text(`${key}:`, { weight: 'bold', size: 'sm', color: 'muted' }),
|
|
9
|
+
ui.text(String(value), { size: 'sm' })
|
|
10
|
+
])))
|
|
11
|
+
])
|
|
12
|
+
]);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ui } from '@melony/ui-kit/server';
|
|
2
|
+
export const progressStep = (currentStep, totalSteps, label) => ui.row({ gap: 'md', align: 'center', padding: 'sm' }, [
|
|
3
|
+
ui.text(`Step ${currentStep} of ${totalSteps}`, { weight: 'bold', size: 'sm' }),
|
|
4
|
+
ui.text(label, { size: 'sm', color: 'muted' })
|
|
5
|
+
]);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ui } from '@melony/ui-kit';
|
|
2
|
+
export const resourceCardWidget = (title, subtitle, children = []) => ui.box({
|
|
3
|
+
border: true,
|
|
4
|
+
radius: 'lg',
|
|
5
|
+
padding: 'md',
|
|
6
|
+
}, [
|
|
7
|
+
ui.heading(title, { level: 4 }),
|
|
8
|
+
ui.text(subtitle ?? '', { size: 'sm', color: 'mutedForeground' }),
|
|
9
|
+
...children,
|
|
10
|
+
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbot",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@ai-sdk/anthropic": "^3.0.33",
|
|
11
11
|
"@ai-sdk/openai": "^3.0.13",
|
|
12
|
-
"@melony/ui-kit": "^0.1.
|
|
12
|
+
"@melony/ui-kit": "^0.1.30",
|
|
13
13
|
"@types/cors": "^2.8.19",
|
|
14
14
|
"ai": "^6.0.42",
|
|
15
15
|
"commander": "^14.0.2",
|