@kohryan/moodui 0.0.2 → 0.0.4
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/chunk-5D6KNM5J.mjs +570 -0
- package/dist/cli.d.mts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +715 -0
- package/dist/cli.mjs +137 -0
- package/dist/index.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +489 -114
- package/dist/index.mjs +307 -493
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,11 +17,21 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
33
|
+
MoodUIPromptPlayground: () => MoodUIPromptPlayground,
|
|
34
|
+
MoodUIRuntime: () => MoodUIRuntime,
|
|
23
35
|
assertMoodUISpec: () => assertMoodUISpec,
|
|
24
36
|
createGeminiClient: () => createGeminiClient,
|
|
25
37
|
createOllamaClient: () => createOllamaClient,
|
|
@@ -120,7 +132,7 @@ function isString(value) {
|
|
|
120
132
|
function renderReact(specInput, options = {}) {
|
|
121
133
|
const spec = assertMoodUISpec(specInput);
|
|
122
134
|
const componentName = options.componentName ?? "MoodUIScreen";
|
|
123
|
-
const
|
|
135
|
+
const jsx3 = renderNode(spec.root, { indent: 2, onActionProp: "onAction" });
|
|
124
136
|
return [
|
|
125
137
|
'import * as React from "react";',
|
|
126
138
|
"",
|
|
@@ -133,7 +145,7 @@ function renderReact(specInput, options = {}) {
|
|
|
133
145
|
`export function ${componentName}(props: MoodUIScreenProps) {`,
|
|
134
146
|
" const { onAction } = props;",
|
|
135
147
|
" return (",
|
|
136
|
-
|
|
148
|
+
jsx3,
|
|
137
149
|
" );",
|
|
138
150
|
"}",
|
|
139
151
|
""
|
|
@@ -349,130 +361,168 @@ function indent(spaces) {
|
|
|
349
361
|
return " ".repeat(spaces);
|
|
350
362
|
}
|
|
351
363
|
|
|
352
|
-
// src/
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
async chat(req) {
|
|
358
|
-
const res = await fetchFn(`${baseUrl}/api/chat`, {
|
|
359
|
-
method: "POST",
|
|
360
|
-
headers: { "content-type": "application/json" },
|
|
361
|
-
body: JSON.stringify({
|
|
362
|
-
model: req.model,
|
|
363
|
-
messages: req.messages,
|
|
364
|
-
stream: false,
|
|
365
|
-
options: req.temperature == null ? void 0 : { temperature: req.temperature }
|
|
366
|
-
})
|
|
367
|
-
});
|
|
368
|
-
if (!res.ok) {
|
|
369
|
-
const text = await safeReadText(res);
|
|
370
|
-
throw new Error(`Ollama chat failed (${res.status}): ${text}`);
|
|
371
|
-
}
|
|
372
|
-
const json = await res.json();
|
|
373
|
-
const content = json?.message?.content;
|
|
374
|
-
if (typeof content !== "string") throw new Error("Ollama response missing message.content");
|
|
375
|
-
return content;
|
|
376
|
-
}
|
|
377
|
-
};
|
|
364
|
+
// src/react/MoodUIRuntime.tsx
|
|
365
|
+
var React = __toESM(require("react"));
|
|
366
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
367
|
+
function MoodUIRuntime(props) {
|
|
368
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: renderNode2(props.spec.root, props.onAction) });
|
|
378
369
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
370
|
+
function renderNode2(node, onAction) {
|
|
371
|
+
switch (node.type) {
|
|
372
|
+
case "box": {
|
|
373
|
+
const style = computeStyle2(node);
|
|
374
|
+
const children = node.children?.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(React.Fragment, { children: renderNode2(c, onAction) }, i));
|
|
375
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...commonAttrs(node), style, children });
|
|
376
|
+
}
|
|
377
|
+
case "text": {
|
|
378
|
+
const Tag = node.props?.as ?? "p";
|
|
379
|
+
const style = computeStyle2(node);
|
|
380
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag, { ...commonAttrs(node), style, children: node.props?.value ?? "" });
|
|
381
|
+
}
|
|
382
|
+
case "button": {
|
|
383
|
+
const style = computeStyle2(node);
|
|
384
|
+
const actionId = node.props?.actionId;
|
|
385
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
386
|
+
"button",
|
|
387
|
+
{
|
|
388
|
+
...commonAttrs(node),
|
|
389
|
+
style,
|
|
390
|
+
disabled: node.props?.disabled ?? false,
|
|
391
|
+
onClick: actionId ? () => onAction?.(actionId) : void 0,
|
|
392
|
+
children: node.props?.label ?? ""
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
case "input": {
|
|
397
|
+
const style = computeStyle2(node);
|
|
398
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
399
|
+
"input",
|
|
400
|
+
{
|
|
401
|
+
...commonAttrs(node),
|
|
402
|
+
style,
|
|
403
|
+
name: node.props?.name,
|
|
404
|
+
placeholder: node.props?.placeholder,
|
|
405
|
+
defaultValue: node.props?.defaultValue
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
case "image": {
|
|
410
|
+
const style = computeStyle2(node);
|
|
411
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { ...commonAttrs(node), style, src: node.props?.src, alt: node.props?.alt ?? "" });
|
|
412
|
+
}
|
|
413
|
+
case "spacer": {
|
|
414
|
+
const size = node.props?.size ?? 8;
|
|
415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: size, height: size } });
|
|
416
|
+
}
|
|
384
417
|
}
|
|
385
418
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
function createOpenAICompatibleClient(options) {
|
|
389
|
-
const fetchFn = options.fetchFn ?? fetch;
|
|
390
|
-
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
391
|
-
const apiKey = options.apiKey;
|
|
392
|
-
const defaultHeaders = options.defaultHeaders ?? {};
|
|
419
|
+
function commonAttrs(node) {
|
|
420
|
+
const p = node.props ?? {};
|
|
393
421
|
return {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
headers: {
|
|
398
|
-
"content-type": "application/json",
|
|
399
|
-
authorization: `Bearer ${apiKey}`,
|
|
400
|
-
...defaultHeaders
|
|
401
|
-
},
|
|
402
|
-
body: JSON.stringify({
|
|
403
|
-
model: req.model,
|
|
404
|
-
messages: req.messages,
|
|
405
|
-
temperature: req.temperature
|
|
406
|
-
})
|
|
407
|
-
});
|
|
408
|
-
if (!res.ok) {
|
|
409
|
-
const text = await safeReadText2(res);
|
|
410
|
-
throw new Error(`Chat completion failed (${res.status}): ${text}`);
|
|
411
|
-
}
|
|
412
|
-
const json = await res.json();
|
|
413
|
-
const content = json?.choices?.[0]?.message?.content;
|
|
414
|
-
if (typeof content !== "string") throw new Error("Response missing choices[0].message.content");
|
|
415
|
-
return content;
|
|
416
|
-
}
|
|
422
|
+
id: typeof p.id === "string" ? p.id : void 0,
|
|
423
|
+
className: typeof p.className === "string" ? p.className : void 0,
|
|
424
|
+
"data-testid": typeof p.testId === "string" ? p.testId : void 0
|
|
417
425
|
};
|
|
418
426
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
427
|
+
function computeStyle2(node) {
|
|
428
|
+
const props = node.props ?? {};
|
|
429
|
+
const style = {};
|
|
430
|
+
const width = toCssValue(props.width);
|
|
431
|
+
const height = toCssValue(props.height);
|
|
432
|
+
const borderRadius = toCssValue(props.borderRadius);
|
|
433
|
+
if (width != null) style.width = width;
|
|
434
|
+
if (height != null) style.height = height;
|
|
435
|
+
if (typeof props.background === "string") style.background = props.background;
|
|
436
|
+
if (borderRadius != null) style.borderRadius = borderRadius;
|
|
437
|
+
applySpacing2(style, "margin", props.margin);
|
|
438
|
+
applySpacing2(style, "padding", props.padding);
|
|
439
|
+
if (node.type === "box") {
|
|
440
|
+
style.display = "flex";
|
|
441
|
+
if (props.direction === "row" || props.direction === "column") style.flexDirection = props.direction;
|
|
442
|
+
else style.flexDirection = "column";
|
|
443
|
+
const gap = toCssValue(props.gap);
|
|
444
|
+
if (gap != null) style.gap = gap;
|
|
445
|
+
if (typeof props.align === "string") style.alignItems = props.align;
|
|
446
|
+
if (typeof props.justify === "string") style.justifyContent = props.justify;
|
|
447
|
+
if (props.wrap === "nowrap" || props.wrap === "wrap") style.flexWrap = props.wrap;
|
|
424
448
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const json = await res.json();
|
|
450
|
-
const parts = json?.candidates?.[0]?.content?.parts;
|
|
451
|
-
const text = Array.isArray(parts) ? parts.map((p) => typeof p?.text === "string" ? p.text : "").join("") : void 0;
|
|
452
|
-
if (typeof text !== "string" || text.length === 0) throw new Error("Gemini response missing candidates[0].content.parts[].text");
|
|
453
|
-
return text;
|
|
449
|
+
if (node.type === "text") {
|
|
450
|
+
if (typeof props.color === "string") style.color = props.color;
|
|
451
|
+
const fontSize = toCssValue(props.fontSize);
|
|
452
|
+
if (fontSize != null) style.fontSize = fontSize;
|
|
453
|
+
if (typeof props.fontWeight === "string" || typeof props.fontWeight === "number") style.fontWeight = props.fontWeight;
|
|
454
|
+
if (typeof props.textAlign === "string") style.textAlign = props.textAlign;
|
|
455
|
+
const asTag = node.props?.as;
|
|
456
|
+
if (typeof asTag === "string" && asTag.startsWith("h")) style.margin = 0;
|
|
457
|
+
}
|
|
458
|
+
if (node.type === "button") {
|
|
459
|
+
style.cursor = node.props?.disabled ? "not-allowed" : "pointer";
|
|
460
|
+
style.border = "none";
|
|
461
|
+
if (style.padding == null) style.padding = "10px 14px";
|
|
462
|
+
if (style.borderRadius == null) style.borderRadius = 10;
|
|
463
|
+
const variant = node.props?.variant ?? "primary";
|
|
464
|
+
if (variant === "primary") {
|
|
465
|
+
if (style.background == null) style.background = "#111827";
|
|
466
|
+
if (style.color == null) style.color = "#ffffff";
|
|
467
|
+
} else if (variant === "secondary") {
|
|
468
|
+
if (style.background == null) style.background = "#e5e7eb";
|
|
469
|
+
if (style.color == null) style.color = "#111827";
|
|
470
|
+
} else {
|
|
471
|
+
if (style.background == null) style.background = "transparent";
|
|
472
|
+
if (style.color == null) style.color = "#111827";
|
|
454
473
|
}
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
function toGeminiContents(messages) {
|
|
458
|
-
const systemTexts = messages.filter((m) => m.role === "system").map((m) => m.content).filter((t) => t.trim().length > 0);
|
|
459
|
-
const systemInstruction = systemTexts.length > 0 ? { role: "system", parts: [{ text: systemTexts.join("\n") }] } : void 0;
|
|
460
|
-
const contents = messages.filter((m) => m.role !== "system").map((m) => ({
|
|
461
|
-
role: m.role === "assistant" ? "model" : "user",
|
|
462
|
-
parts: [{ text: m.content }]
|
|
463
|
-
}));
|
|
464
|
-
if (contents.length === 0) {
|
|
465
|
-
contents.push({ role: "user", parts: [{ text: "" }] });
|
|
474
|
+
if (node.props?.disabled) style.opacity = 0.6;
|
|
466
475
|
}
|
|
467
|
-
|
|
476
|
+
if (node.type === "input") {
|
|
477
|
+
if (style.padding == null) style.padding = "10px 12px";
|
|
478
|
+
if (style.borderRadius == null) style.borderRadius = 10;
|
|
479
|
+
if (style.border == null) style.border = "1px solid #d1d5db";
|
|
480
|
+
if (style.outline == null) style.outline = "none";
|
|
481
|
+
}
|
|
482
|
+
if (node.type === "image") {
|
|
483
|
+
if (style.maxWidth == null) style.maxWidth = "100%";
|
|
484
|
+
const fit = node.props?.fit;
|
|
485
|
+
if (fit != null) style.objectFit = fit;
|
|
486
|
+
}
|
|
487
|
+
if (props.style && typeof props.style === "object" && !Array.isArray(props.style)) {
|
|
488
|
+
Object.assign(style, props.style);
|
|
489
|
+
}
|
|
490
|
+
return style;
|
|
468
491
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
492
|
+
function applySpacing2(style, kind, value) {
|
|
493
|
+
if (value == null) return;
|
|
494
|
+
const s = style;
|
|
495
|
+
if (typeof value === "number" || typeof value === "string") {
|
|
496
|
+
s[kind] = toCssValue(value);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (typeof value !== "object" || Array.isArray(value)) return;
|
|
500
|
+
const v = value;
|
|
501
|
+
const all = v.all;
|
|
502
|
+
const x = v.x;
|
|
503
|
+
const y = v.y;
|
|
504
|
+
if (all != null) s[kind] = toCssValue(all);
|
|
505
|
+
if (x != null) {
|
|
506
|
+
s[`${kind}Left`] = toCssValue(x);
|
|
507
|
+
s[`${kind}Right`] = toCssValue(x);
|
|
508
|
+
}
|
|
509
|
+
if (y != null) {
|
|
510
|
+
s[`${kind}Top`] = toCssValue(y);
|
|
511
|
+
s[`${kind}Bottom`] = toCssValue(y);
|
|
474
512
|
}
|
|
513
|
+
if (v.top != null) s[`${kind}Top`] = toCssValue(v.top);
|
|
514
|
+
if (v.right != null) s[`${kind}Right`] = toCssValue(v.right);
|
|
515
|
+
if (v.bottom != null) s[`${kind}Bottom`] = toCssValue(v.bottom);
|
|
516
|
+
if (v.left != null) s[`${kind}Left`] = toCssValue(v.left);
|
|
475
517
|
}
|
|
518
|
+
function toCssValue(value) {
|
|
519
|
+
if (typeof value === "number") return value;
|
|
520
|
+
if (typeof value === "string") return value;
|
|
521
|
+
return void 0;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/react/MoodUIPromptPlayground.tsx
|
|
525
|
+
var React2 = __toESM(require("react"));
|
|
476
526
|
|
|
477
527
|
// src/ai/generateSpec.ts
|
|
478
528
|
async function generateMoodUISpec(llm, options) {
|
|
@@ -578,6 +628,131 @@ function parseFirstJsonObject(text) {
|
|
|
578
628
|
throw new Error("Unterminated JSON object");
|
|
579
629
|
}
|
|
580
630
|
|
|
631
|
+
// src/llm/gemini.ts
|
|
632
|
+
function createGeminiClient(options) {
|
|
633
|
+
const baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com").replace(/\/+$/, "");
|
|
634
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
635
|
+
const apiKey = options.apiKey;
|
|
636
|
+
return {
|
|
637
|
+
async chat(req) {
|
|
638
|
+
const { systemInstruction, contents } = toGeminiContents(req.messages);
|
|
639
|
+
const url = `${baseUrl}/v1beta/models/${encodeURIComponent(req.model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
|
|
640
|
+
const res = await fetchFn(url, {
|
|
641
|
+
method: "POST",
|
|
642
|
+
headers: { "content-type": "application/json" },
|
|
643
|
+
body: JSON.stringify({
|
|
644
|
+
...systemInstruction ? { systemInstruction } : {},
|
|
645
|
+
contents,
|
|
646
|
+
generationConfig: req.temperature == null ? void 0 : { temperature: req.temperature }
|
|
647
|
+
})
|
|
648
|
+
});
|
|
649
|
+
if (!res.ok) {
|
|
650
|
+
const text2 = await safeReadText(res);
|
|
651
|
+
throw new Error(`Gemini generateContent failed (${res.status}): ${text2}`);
|
|
652
|
+
}
|
|
653
|
+
const json = await res.json();
|
|
654
|
+
const parts = json?.candidates?.[0]?.content?.parts;
|
|
655
|
+
const text = Array.isArray(parts) ? parts.map((p) => typeof p?.text === "string" ? p.text : "").join("") : void 0;
|
|
656
|
+
if (typeof text !== "string" || text.length === 0) throw new Error("Gemini response missing candidates[0].content.parts[].text");
|
|
657
|
+
return text;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function toGeminiContents(messages) {
|
|
662
|
+
const systemTexts = messages.filter((m) => m.role === "system").map((m) => m.content).filter((t) => t.trim().length > 0);
|
|
663
|
+
const systemInstruction = systemTexts.length > 0 ? { role: "system", parts: [{ text: systemTexts.join("\n") }] } : void 0;
|
|
664
|
+
const contents = messages.filter((m) => m.role !== "system").map((m) => ({
|
|
665
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
666
|
+
parts: [{ text: m.content }]
|
|
667
|
+
}));
|
|
668
|
+
if (contents.length === 0) {
|
|
669
|
+
contents.push({ role: "user", parts: [{ text: "" }] });
|
|
670
|
+
}
|
|
671
|
+
return { systemInstruction, contents };
|
|
672
|
+
}
|
|
673
|
+
async function safeReadText(res) {
|
|
674
|
+
try {
|
|
675
|
+
return await res.text();
|
|
676
|
+
} catch {
|
|
677
|
+
return "";
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// src/llm/ollama.ts
|
|
682
|
+
function createOllamaClient(options = {}) {
|
|
683
|
+
const baseUrl = options.baseUrl ?? "http://localhost:11434";
|
|
684
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
685
|
+
return {
|
|
686
|
+
async chat(req) {
|
|
687
|
+
const res = await fetchFn(`${baseUrl}/api/chat`, {
|
|
688
|
+
method: "POST",
|
|
689
|
+
headers: { "content-type": "application/json" },
|
|
690
|
+
body: JSON.stringify({
|
|
691
|
+
model: req.model,
|
|
692
|
+
messages: req.messages,
|
|
693
|
+
stream: false,
|
|
694
|
+
options: req.temperature == null ? void 0 : { temperature: req.temperature }
|
|
695
|
+
})
|
|
696
|
+
});
|
|
697
|
+
if (!res.ok) {
|
|
698
|
+
const text = await safeReadText2(res);
|
|
699
|
+
throw new Error(`Ollama chat failed (${res.status}): ${text}`);
|
|
700
|
+
}
|
|
701
|
+
const json = await res.json();
|
|
702
|
+
const content = json?.message?.content;
|
|
703
|
+
if (typeof content !== "string") throw new Error("Ollama response missing message.content");
|
|
704
|
+
return content;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
async function safeReadText2(res) {
|
|
709
|
+
try {
|
|
710
|
+
return await res.text();
|
|
711
|
+
} catch {
|
|
712
|
+
return "";
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/llm/openaiCompatible.ts
|
|
717
|
+
function createOpenAICompatibleClient(options) {
|
|
718
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
719
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
720
|
+
const apiKey = options.apiKey;
|
|
721
|
+
const defaultHeaders = options.defaultHeaders ?? {};
|
|
722
|
+
return {
|
|
723
|
+
async chat(req) {
|
|
724
|
+
const res = await fetchFn(`${baseUrl}/v1/chat/completions`, {
|
|
725
|
+
method: "POST",
|
|
726
|
+
headers: {
|
|
727
|
+
"content-type": "application/json",
|
|
728
|
+
authorization: `Bearer ${apiKey}`,
|
|
729
|
+
...defaultHeaders
|
|
730
|
+
},
|
|
731
|
+
body: JSON.stringify({
|
|
732
|
+
model: req.model,
|
|
733
|
+
messages: req.messages,
|
|
734
|
+
temperature: req.temperature
|
|
735
|
+
})
|
|
736
|
+
});
|
|
737
|
+
if (!res.ok) {
|
|
738
|
+
const text = await safeReadText3(res);
|
|
739
|
+
throw new Error(`Chat completion failed (${res.status}): ${text}`);
|
|
740
|
+
}
|
|
741
|
+
const json = await res.json();
|
|
742
|
+
const content = json?.choices?.[0]?.message?.content;
|
|
743
|
+
if (typeof content !== "string") throw new Error("Response missing choices[0].message.content");
|
|
744
|
+
return content;
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
async function safeReadText3(res) {
|
|
749
|
+
try {
|
|
750
|
+
return await res.text();
|
|
751
|
+
} catch {
|
|
752
|
+
return "";
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
581
756
|
// src/ai/generateReactFromPrompt.ts
|
|
582
757
|
async function generateReactFromPrompt(options) {
|
|
583
758
|
const llm = options.provider === "gemini" ? createGeminiClient({ apiKey: options.apiKey, baseUrl: options.baseUrl }) : options.provider === "ollama" ? createOllamaClient({ baseUrl: options.baseUrl }) : createOpenAICompatibleClient({ apiKey: options.apiKey, baseUrl: options.baseUrl });
|
|
@@ -590,8 +765,208 @@ async function generateReactFromPrompt(options) {
|
|
|
590
765
|
const code = renderReact(spec, { componentName: options.componentName });
|
|
591
766
|
return { spec, raw, code };
|
|
592
767
|
}
|
|
768
|
+
|
|
769
|
+
// src/react/MoodUIPromptPlayground.tsx
|
|
770
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
771
|
+
function MoodUIPromptPlayground(props) {
|
|
772
|
+
const provider = props.provider ?? "gemini";
|
|
773
|
+
const [model, setModel] = React2.useState(props.model ?? "gemini-3-flash-preview");
|
|
774
|
+
const [apiKey, setApiKey] = React2.useState(props.apiKey ?? "");
|
|
775
|
+
const [baseUrl, setBaseUrl] = React2.useState(props.baseUrl ?? "");
|
|
776
|
+
const [prompt, setPrompt] = React2.useState(
|
|
777
|
+
props.defaultPrompt ?? "Buat UI mood tracker: judul, input mood, tombol Simpan (actionId save_mood), dan section riwayat."
|
|
778
|
+
);
|
|
779
|
+
const [loading, setLoading] = React2.useState(false);
|
|
780
|
+
const [error, setError] = React2.useState(null);
|
|
781
|
+
const [spec, setSpec] = React2.useState(null);
|
|
782
|
+
const [code, setCode] = React2.useState("");
|
|
783
|
+
const [copied, setCopied] = React2.useState(false);
|
|
784
|
+
const onGenerate = React2.useCallback(async () => {
|
|
785
|
+
setLoading(true);
|
|
786
|
+
setError(null);
|
|
787
|
+
setCopied(false);
|
|
788
|
+
try {
|
|
789
|
+
const result = provider === "ollama" ? await generateReactFromPrompt({
|
|
790
|
+
provider: "ollama",
|
|
791
|
+
model,
|
|
792
|
+
baseUrl: baseUrl || void 0,
|
|
793
|
+
prompt,
|
|
794
|
+
componentName: props.componentName
|
|
795
|
+
}) : provider === "openai-compatible" ? await generateReactFromPrompt({
|
|
796
|
+
provider: "openai-compatible",
|
|
797
|
+
model,
|
|
798
|
+
baseUrl: baseUrl || "",
|
|
799
|
+
apiKey,
|
|
800
|
+
prompt,
|
|
801
|
+
componentName: props.componentName
|
|
802
|
+
}) : await generateReactFromPrompt({
|
|
803
|
+
provider: "gemini",
|
|
804
|
+
model,
|
|
805
|
+
apiKey,
|
|
806
|
+
baseUrl: baseUrl || void 0,
|
|
807
|
+
prompt,
|
|
808
|
+
componentName: props.componentName
|
|
809
|
+
});
|
|
810
|
+
setSpec(result.spec);
|
|
811
|
+
setCode(result.code);
|
|
812
|
+
} catch (e) {
|
|
813
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
814
|
+
setError(msg);
|
|
815
|
+
} finally {
|
|
816
|
+
setLoading(false);
|
|
817
|
+
}
|
|
818
|
+
}, [apiKey, baseUrl, model, prompt, props.componentName, provider]);
|
|
819
|
+
const onCopyCode = React2.useCallback(async () => {
|
|
820
|
+
if (!code) return;
|
|
821
|
+
try {
|
|
822
|
+
await navigator.clipboard.writeText(code);
|
|
823
|
+
setCopied(true);
|
|
824
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
825
|
+
} catch (e) {
|
|
826
|
+
console.error("Failed to copy:", e);
|
|
827
|
+
}
|
|
828
|
+
}, [code]);
|
|
829
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }, children: [
|
|
830
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
831
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 700, fontSize: 16 }, children: "MoodUI Prompt" }),
|
|
832
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
|
|
833
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
834
|
+
"input",
|
|
835
|
+
{
|
|
836
|
+
value: provider,
|
|
837
|
+
readOnly: true,
|
|
838
|
+
style: {
|
|
839
|
+
padding: "10px 12px",
|
|
840
|
+
borderRadius: 10,
|
|
841
|
+
border: "1px solid #d1d5db",
|
|
842
|
+
background: "#f3f4f6",
|
|
843
|
+
width: 200
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
),
|
|
847
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
848
|
+
"input",
|
|
849
|
+
{
|
|
850
|
+
value: model,
|
|
851
|
+
onChange: (e) => setModel(e.target.value),
|
|
852
|
+
placeholder: "model",
|
|
853
|
+
style: { padding: "10px 12px", borderRadius: 10, border: "1px solid #d1d5db", flex: "1 1 220px" }
|
|
854
|
+
}
|
|
855
|
+
)
|
|
856
|
+
] }),
|
|
857
|
+
provider !== "ollama" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
858
|
+
"input",
|
|
859
|
+
{
|
|
860
|
+
value: apiKey,
|
|
861
|
+
onChange: (e) => setApiKey(e.target.value),
|
|
862
|
+
placeholder: "API key",
|
|
863
|
+
type: "password",
|
|
864
|
+
style: { padding: "10px 12px", borderRadius: 10, border: "1px solid #d1d5db" }
|
|
865
|
+
}
|
|
866
|
+
) : null,
|
|
867
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
868
|
+
"input",
|
|
869
|
+
{
|
|
870
|
+
value: baseUrl,
|
|
871
|
+
onChange: (e) => setBaseUrl(e.target.value),
|
|
872
|
+
placeholder: provider === "ollama" ? "baseUrl (optional) e.g. http://localhost:11434" : "baseUrl (optional)",
|
|
873
|
+
style: { padding: "10px 12px", borderRadius: 10, border: "1px solid #d1d5db" }
|
|
874
|
+
}
|
|
875
|
+
),
|
|
876
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
877
|
+
"textarea",
|
|
878
|
+
{
|
|
879
|
+
value: prompt,
|
|
880
|
+
onChange: (e) => setPrompt(e.target.value),
|
|
881
|
+
rows: 10,
|
|
882
|
+
style: {
|
|
883
|
+
padding: 12,
|
|
884
|
+
borderRadius: 12,
|
|
885
|
+
border: "1px solid #d1d5db",
|
|
886
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
887
|
+
fontSize: 12
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
),
|
|
891
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
892
|
+
"button",
|
|
893
|
+
{
|
|
894
|
+
type: "button",
|
|
895
|
+
onClick: onGenerate,
|
|
896
|
+
disabled: loading,
|
|
897
|
+
style: {
|
|
898
|
+
padding: "10px 12px",
|
|
899
|
+
borderRadius: 10,
|
|
900
|
+
border: "1px solid #111827",
|
|
901
|
+
background: "#111827",
|
|
902
|
+
color: "#ffffff",
|
|
903
|
+
cursor: loading ? "not-allowed" : "pointer"
|
|
904
|
+
},
|
|
905
|
+
children: loading ? "Generating..." : "Generate"
|
|
906
|
+
}
|
|
907
|
+
),
|
|
908
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
909
|
+
"pre",
|
|
910
|
+
{
|
|
911
|
+
style: {
|
|
912
|
+
margin: 0,
|
|
913
|
+
padding: 12,
|
|
914
|
+
borderRadius: 12,
|
|
915
|
+
border: "1px solid #7f1d1d",
|
|
916
|
+
background: "#fef2f2",
|
|
917
|
+
color: "#7f1d1d",
|
|
918
|
+
whiteSpace: "pre-wrap"
|
|
919
|
+
},
|
|
920
|
+
children: error
|
|
921
|
+
}
|
|
922
|
+
) : null
|
|
923
|
+
] }),
|
|
924
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
925
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 700, fontSize: 16 }, children: "Preview" }),
|
|
926
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { minHeight: 240, padding: 16, borderRadius: 16, background: "#ffffff", border: "1px solid #e5e7eb" }, children: spec ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MoodUIRuntime, { spec }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "#6b7280" }, children: "Belum ada hasil" }) }),
|
|
927
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
928
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 700, fontSize: 16 }, children: "React Code" }),
|
|
929
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
930
|
+
"button",
|
|
931
|
+
{
|
|
932
|
+
type: "button",
|
|
933
|
+
onClick: onCopyCode,
|
|
934
|
+
disabled: !code,
|
|
935
|
+
style: {
|
|
936
|
+
padding: "6px 12px",
|
|
937
|
+
borderRadius: 8,
|
|
938
|
+
border: copied ? "1px solid #16a34a" : "1px solid #6b7280",
|
|
939
|
+
background: copied ? "#dcfce7" : "#ffffff",
|
|
940
|
+
color: copied ? "#16a34a" : "#374151",
|
|
941
|
+
cursor: !code ? "not-allowed" : "pointer",
|
|
942
|
+
fontSize: 12
|
|
943
|
+
},
|
|
944
|
+
children: copied ? "Copied!" : "Copy"
|
|
945
|
+
}
|
|
946
|
+
)
|
|
947
|
+
] }),
|
|
948
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
949
|
+
"textarea",
|
|
950
|
+
{
|
|
951
|
+
value: code,
|
|
952
|
+
readOnly: true,
|
|
953
|
+
rows: 12,
|
|
954
|
+
style: {
|
|
955
|
+
padding: 12,
|
|
956
|
+
borderRadius: 12,
|
|
957
|
+
border: "1px solid #d1d5db",
|
|
958
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
959
|
+
fontSize: 12
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
)
|
|
963
|
+
] })
|
|
964
|
+
] });
|
|
965
|
+
}
|
|
593
966
|
// Annotate the CommonJS export names for ESM import in node:
|
|
594
967
|
0 && (module.exports = {
|
|
968
|
+
MoodUIPromptPlayground,
|
|
969
|
+
MoodUIRuntime,
|
|
595
970
|
assertMoodUISpec,
|
|
596
971
|
createGeminiClient,
|
|
597
972
|
createOllamaClient,
|