@kohryan/moodui 0.0.1 → 0.0.3
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 +47 -1
- package/dist/index.d.ts +47 -1
- package/dist/index.js +482 -116
- package/dist/index.mjs +279 -475
- 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,16 +17,27 @@ 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,
|
|
26
38
|
createOpenAICompatibleClient: () => createOpenAICompatibleClient,
|
|
27
39
|
generateMoodUISpec: () => generateMoodUISpec,
|
|
40
|
+
generateReactFromPrompt: () => generateReactFromPrompt,
|
|
28
41
|
renderReact: () => renderReact,
|
|
29
42
|
renderReactJSX: () => renderReactJSX,
|
|
30
43
|
validateMoodUISpec: () => validateMoodUISpec
|
|
@@ -119,7 +132,7 @@ function isString(value) {
|
|
|
119
132
|
function renderReact(specInput, options = {}) {
|
|
120
133
|
const spec = assertMoodUISpec(specInput);
|
|
121
134
|
const componentName = options.componentName ?? "MoodUIScreen";
|
|
122
|
-
const
|
|
135
|
+
const jsx3 = renderNode(spec.root, { indent: 2, onActionProp: "onAction" });
|
|
123
136
|
return [
|
|
124
137
|
'import * as React from "react";',
|
|
125
138
|
"",
|
|
@@ -132,7 +145,7 @@ function renderReact(specInput, options = {}) {
|
|
|
132
145
|
`export function ${componentName}(props: MoodUIScreenProps) {`,
|
|
133
146
|
" const { onAction } = props;",
|
|
134
147
|
" return (",
|
|
135
|
-
|
|
148
|
+
jsx3,
|
|
136
149
|
" );",
|
|
137
150
|
"}",
|
|
138
151
|
""
|
|
@@ -215,7 +228,9 @@ function buildProps(tag, node, extraProps) {
|
|
|
215
228
|
if (props.alt) out.push(`alt=${serializeJsxAttrValue(props.alt)}`);
|
|
216
229
|
if (props.fit) mergedStyle.objectFit = props.fit;
|
|
217
230
|
}
|
|
218
|
-
if (Object.keys(mergedStyle).length > 0)
|
|
231
|
+
if (Object.keys(mergedStyle).length > 0) {
|
|
232
|
+
out.push(`style={(${serializeJsValue(mergedStyle)} as React.CSSProperties)}`);
|
|
233
|
+
}
|
|
219
234
|
if (extraProps) {
|
|
220
235
|
for (const [k, v] of Object.entries(extraProps)) out.push(`${k}={${v}}`);
|
|
221
236
|
}
|
|
@@ -232,7 +247,7 @@ function computeStyle(tag, node) {
|
|
|
232
247
|
applySpacing(style, "padding", props.padding);
|
|
233
248
|
if (node.type === "box") {
|
|
234
249
|
style.display = "flex";
|
|
235
|
-
style.flexDirection = props.direction ?? "column";
|
|
250
|
+
style.flexDirection = normalizeFlexDirection(props.direction) ?? "column";
|
|
236
251
|
if (props.gap != null) style.gap = normalizeCssValue(props.gap);
|
|
237
252
|
if (props.align != null) style.alignItems = props.align;
|
|
238
253
|
if (props.justify != null) style.justifyContent = props.justify;
|
|
@@ -314,6 +329,12 @@ function normalizeCssValue(value) {
|
|
|
314
329
|
if (typeof value === "string") return value;
|
|
315
330
|
return value;
|
|
316
331
|
}
|
|
332
|
+
function normalizeFlexDirection(value) {
|
|
333
|
+
if (value === "row" || value === "column") return value;
|
|
334
|
+
if (value === "horizontal") return "row";
|
|
335
|
+
if (value === "vertical") return "column";
|
|
336
|
+
return void 0;
|
|
337
|
+
}
|
|
317
338
|
function serializeJsxAttrValue(value) {
|
|
318
339
|
return `{${serializeJsValue(value)}}`;
|
|
319
340
|
}
|
|
@@ -340,131 +361,169 @@ function indent(spaces) {
|
|
|
340
361
|
return " ".repeat(spaces);
|
|
341
362
|
}
|
|
342
363
|
|
|
343
|
-
// src/
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return {
|
|
348
|
-
async chat(req) {
|
|
349
|
-
const res = await fetchFn(`${baseUrl}/api/chat`, {
|
|
350
|
-
method: "POST",
|
|
351
|
-
headers: { "content-type": "application/json" },
|
|
352
|
-
body: JSON.stringify({
|
|
353
|
-
model: req.model,
|
|
354
|
-
messages: req.messages,
|
|
355
|
-
stream: false,
|
|
356
|
-
options: req.temperature == null ? void 0 : { temperature: req.temperature }
|
|
357
|
-
})
|
|
358
|
-
});
|
|
359
|
-
if (!res.ok) {
|
|
360
|
-
const text = await safeReadText(res);
|
|
361
|
-
throw new Error(`Ollama chat failed (${res.status}): ${text}`);
|
|
362
|
-
}
|
|
363
|
-
const json = await res.json();
|
|
364
|
-
const content = json?.message?.content;
|
|
365
|
-
if (typeof content !== "string") throw new Error("Ollama response missing message.content");
|
|
366
|
-
return content;
|
|
367
|
-
}
|
|
368
|
-
};
|
|
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) });
|
|
369
369
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
+
}
|
|
375
417
|
}
|
|
376
418
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
function createOpenAICompatibleClient(options) {
|
|
380
|
-
const fetchFn = options.fetchFn ?? fetch;
|
|
381
|
-
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
382
|
-
const apiKey = options.apiKey;
|
|
383
|
-
const defaultHeaders = options.defaultHeaders ?? {};
|
|
419
|
+
function commonAttrs(node) {
|
|
420
|
+
const p = node.props ?? {};
|
|
384
421
|
return {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
headers: {
|
|
389
|
-
"content-type": "application/json",
|
|
390
|
-
authorization: `Bearer ${apiKey}`,
|
|
391
|
-
...defaultHeaders
|
|
392
|
-
},
|
|
393
|
-
body: JSON.stringify({
|
|
394
|
-
model: req.model,
|
|
395
|
-
messages: req.messages,
|
|
396
|
-
temperature: req.temperature
|
|
397
|
-
})
|
|
398
|
-
});
|
|
399
|
-
if (!res.ok) {
|
|
400
|
-
const text = await safeReadText2(res);
|
|
401
|
-
throw new Error(`Chat completion failed (${res.status}): ${text}`);
|
|
402
|
-
}
|
|
403
|
-
const json = await res.json();
|
|
404
|
-
const content = json?.choices?.[0]?.message?.content;
|
|
405
|
-
if (typeof content !== "string") throw new Error("Response missing choices[0].message.content");
|
|
406
|
-
return content;
|
|
407
|
-
}
|
|
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
|
|
408
425
|
};
|
|
409
426
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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;
|
|
415
448
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const json = await res.json();
|
|
441
|
-
const parts = json?.candidates?.[0]?.content?.parts;
|
|
442
|
-
const text = Array.isArray(parts) ? parts.map((p) => typeof p?.text === "string" ? p.text : "").join("") : void 0;
|
|
443
|
-
if (typeof text !== "string" || text.length === 0) throw new Error("Gemini response missing candidates[0].content.parts[].text");
|
|
444
|
-
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";
|
|
445
473
|
}
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
function toGeminiContents(messages) {
|
|
449
|
-
const systemTexts = messages.filter((m) => m.role === "system").map((m) => m.content).filter((t) => t.trim().length > 0);
|
|
450
|
-
const systemInstruction = systemTexts.length > 0 ? { role: "system", parts: [{ text: systemTexts.join("\n") }] } : void 0;
|
|
451
|
-
const contents = messages.filter((m) => m.role !== "system").map((m) => ({
|
|
452
|
-
role: m.role === "assistant" ? "model" : "user",
|
|
453
|
-
parts: [{ text: m.content }]
|
|
454
|
-
}));
|
|
455
|
-
if (contents.length === 0) {
|
|
456
|
-
contents.push({ role: "user", parts: [{ text: "" }] });
|
|
474
|
+
if (node.props?.disabled) style.opacity = 0.6;
|
|
457
475
|
}
|
|
458
|
-
|
|
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;
|
|
459
491
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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;
|
|
465
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);
|
|
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);
|
|
517
|
+
}
|
|
518
|
+
function toCssValue(value) {
|
|
519
|
+
if (typeof value === "number") return value;
|
|
520
|
+
if (typeof value === "string") return value;
|
|
521
|
+
return void 0;
|
|
466
522
|
}
|
|
467
523
|
|
|
524
|
+
// src/react/MoodUIPromptPlayground.tsx
|
|
525
|
+
var React2 = __toESM(require("react"));
|
|
526
|
+
|
|
468
527
|
// src/ai/generateSpec.ts
|
|
469
528
|
async function generateMoodUISpec(llm, options) {
|
|
470
529
|
const maxAttempts = options.maxAttempts ?? 2;
|
|
@@ -568,13 +627,320 @@ function parseFirstJsonObject(text) {
|
|
|
568
627
|
}
|
|
569
628
|
throw new Error("Unterminated JSON object");
|
|
570
629
|
}
|
|
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
|
+
|
|
756
|
+
// src/ai/generateReactFromPrompt.ts
|
|
757
|
+
async function generateReactFromPrompt(options) {
|
|
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 });
|
|
759
|
+
const { spec, raw } = await generateMoodUISpec(llm, {
|
|
760
|
+
model: options.model,
|
|
761
|
+
prompt: options.prompt,
|
|
762
|
+
temperature: options.temperature,
|
|
763
|
+
maxAttempts: options.maxAttempts
|
|
764
|
+
});
|
|
765
|
+
const code = renderReact(spec, { componentName: options.componentName });
|
|
766
|
+
return { spec, raw, code };
|
|
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 onGenerate = React2.useCallback(async () => {
|
|
784
|
+
setLoading(true);
|
|
785
|
+
setError(null);
|
|
786
|
+
try {
|
|
787
|
+
const result = provider === "ollama" ? await generateReactFromPrompt({
|
|
788
|
+
provider: "ollama",
|
|
789
|
+
model,
|
|
790
|
+
baseUrl: baseUrl || void 0,
|
|
791
|
+
prompt,
|
|
792
|
+
componentName: props.componentName
|
|
793
|
+
}) : provider === "openai-compatible" ? await generateReactFromPrompt({
|
|
794
|
+
provider: "openai-compatible",
|
|
795
|
+
model,
|
|
796
|
+
baseUrl: baseUrl || "",
|
|
797
|
+
apiKey,
|
|
798
|
+
prompt,
|
|
799
|
+
componentName: props.componentName
|
|
800
|
+
}) : await generateReactFromPrompt({
|
|
801
|
+
provider: "gemini",
|
|
802
|
+
model,
|
|
803
|
+
apiKey,
|
|
804
|
+
baseUrl: baseUrl || void 0,
|
|
805
|
+
prompt,
|
|
806
|
+
componentName: props.componentName
|
|
807
|
+
});
|
|
808
|
+
setSpec(result.spec);
|
|
809
|
+
setCode(result.code);
|
|
810
|
+
} catch (e) {
|
|
811
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
812
|
+
setError(msg);
|
|
813
|
+
} finally {
|
|
814
|
+
setLoading(false);
|
|
815
|
+
}
|
|
816
|
+
}, [apiKey, baseUrl, model, prompt, props.componentName, provider]);
|
|
817
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }, children: [
|
|
818
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 700, fontSize: 16 }, children: "MoodUI Prompt" }),
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
822
|
+
"input",
|
|
823
|
+
{
|
|
824
|
+
value: provider,
|
|
825
|
+
readOnly: true,
|
|
826
|
+
style: {
|
|
827
|
+
padding: "10px 12px",
|
|
828
|
+
borderRadius: 10,
|
|
829
|
+
border: "1px solid #d1d5db",
|
|
830
|
+
background: "#f3f4f6",
|
|
831
|
+
width: 200
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
),
|
|
835
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
836
|
+
"input",
|
|
837
|
+
{
|
|
838
|
+
value: model,
|
|
839
|
+
onChange: (e) => setModel(e.target.value),
|
|
840
|
+
placeholder: "model",
|
|
841
|
+
style: { padding: "10px 12px", borderRadius: 10, border: "1px solid #d1d5db", flex: "1 1 220px" }
|
|
842
|
+
}
|
|
843
|
+
)
|
|
844
|
+
] }),
|
|
845
|
+
provider !== "ollama" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
846
|
+
"input",
|
|
847
|
+
{
|
|
848
|
+
value: apiKey,
|
|
849
|
+
onChange: (e) => setApiKey(e.target.value),
|
|
850
|
+
placeholder: "API key",
|
|
851
|
+
type: "password",
|
|
852
|
+
style: { padding: "10px 12px", borderRadius: 10, border: "1px solid #d1d5db" }
|
|
853
|
+
}
|
|
854
|
+
) : null,
|
|
855
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
856
|
+
"input",
|
|
857
|
+
{
|
|
858
|
+
value: baseUrl,
|
|
859
|
+
onChange: (e) => setBaseUrl(e.target.value),
|
|
860
|
+
placeholder: provider === "ollama" ? "baseUrl (optional) e.g. http://localhost:11434" : "baseUrl (optional)",
|
|
861
|
+
style: { padding: "10px 12px", borderRadius: 10, border: "1px solid #d1d5db" }
|
|
862
|
+
}
|
|
863
|
+
),
|
|
864
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
865
|
+
"textarea",
|
|
866
|
+
{
|
|
867
|
+
value: prompt,
|
|
868
|
+
onChange: (e) => setPrompt(e.target.value),
|
|
869
|
+
rows: 10,
|
|
870
|
+
style: {
|
|
871
|
+
padding: 12,
|
|
872
|
+
borderRadius: 12,
|
|
873
|
+
border: "1px solid #d1d5db",
|
|
874
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
875
|
+
fontSize: 12
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
880
|
+
"button",
|
|
881
|
+
{
|
|
882
|
+
type: "button",
|
|
883
|
+
onClick: onGenerate,
|
|
884
|
+
disabled: loading,
|
|
885
|
+
style: {
|
|
886
|
+
padding: "10px 12px",
|
|
887
|
+
borderRadius: 10,
|
|
888
|
+
border: "1px solid #111827",
|
|
889
|
+
background: "#111827",
|
|
890
|
+
color: "#ffffff",
|
|
891
|
+
cursor: loading ? "not-allowed" : "pointer"
|
|
892
|
+
},
|
|
893
|
+
children: loading ? "Generating..." : "Generate"
|
|
894
|
+
}
|
|
895
|
+
),
|
|
896
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
897
|
+
"pre",
|
|
898
|
+
{
|
|
899
|
+
style: {
|
|
900
|
+
margin: 0,
|
|
901
|
+
padding: 12,
|
|
902
|
+
borderRadius: 12,
|
|
903
|
+
border: "1px solid #7f1d1d",
|
|
904
|
+
background: "#fef2f2",
|
|
905
|
+
color: "#7f1d1d",
|
|
906
|
+
whiteSpace: "pre-wrap"
|
|
907
|
+
},
|
|
908
|
+
children: error
|
|
909
|
+
}
|
|
910
|
+
) : null
|
|
911
|
+
] }),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 700, fontSize: 16 }, children: "Preview" }),
|
|
914
|
+
/* @__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" }) }),
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 700, fontSize: 16 }, children: "React Code" }),
|
|
916
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
917
|
+
"textarea",
|
|
918
|
+
{
|
|
919
|
+
value: code,
|
|
920
|
+
readOnly: true,
|
|
921
|
+
rows: 12,
|
|
922
|
+
style: {
|
|
923
|
+
padding: 12,
|
|
924
|
+
borderRadius: 12,
|
|
925
|
+
border: "1px solid #d1d5db",
|
|
926
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
927
|
+
fontSize: 12
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
)
|
|
931
|
+
] })
|
|
932
|
+
] });
|
|
933
|
+
}
|
|
571
934
|
// Annotate the CommonJS export names for ESM import in node:
|
|
572
935
|
0 && (module.exports = {
|
|
936
|
+
MoodUIPromptPlayground,
|
|
937
|
+
MoodUIRuntime,
|
|
573
938
|
assertMoodUISpec,
|
|
574
939
|
createGeminiClient,
|
|
575
940
|
createOllamaClient,
|
|
576
941
|
createOpenAICompatibleClient,
|
|
577
942
|
generateMoodUISpec,
|
|
943
|
+
generateReactFromPrompt,
|
|
578
944
|
renderReact,
|
|
579
945
|
renderReactJSX,
|
|
580
946
|
validateMoodUISpec
|