@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/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 jsx = renderNode(spec.root, { indent: 2, onActionProp: "onAction" });
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
- jsx,
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) out.push(`style={${serializeJsValue(mergedStyle)}}`);
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/llm/ollama.ts
344
- function createOllamaClient(options = {}) {
345
- const baseUrl = options.baseUrl ?? "http://localhost:11434";
346
- const fetchFn = options.fetchFn ?? fetch;
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
- async function safeReadText(res) {
371
- try {
372
- return await res.text();
373
- } catch {
374
- return "";
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
- // src/llm/openaiCompatible.ts
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
- async chat(req) {
386
- const res = await fetchFn(`${baseUrl}/v1/chat/completions`, {
387
- method: "POST",
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
- async function safeReadText2(res) {
411
- try {
412
- return await res.text();
413
- } catch {
414
- return "";
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
- // src/llm/gemini.ts
419
- function createGeminiClient(options) {
420
- const baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com").replace(/\/+$/, "");
421
- const fetchFn = options.fetchFn ?? fetch;
422
- const apiKey = options.apiKey;
423
- return {
424
- async chat(req) {
425
- const { systemInstruction, contents } = toGeminiContents(req.messages);
426
- const url = `${baseUrl}/v1beta/models/${encodeURIComponent(req.model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
427
- const res = await fetchFn(url, {
428
- method: "POST",
429
- headers: { "content-type": "application/json" },
430
- body: JSON.stringify({
431
- ...systemInstruction ? { systemInstruction } : {},
432
- contents,
433
- generationConfig: req.temperature == null ? void 0 : { temperature: req.temperature }
434
- })
435
- });
436
- if (!res.ok) {
437
- const text2 = await safeReadText3(res);
438
- throw new Error(`Gemini generateContent failed (${res.status}): ${text2}`);
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
- return { systemInstruction, contents };
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
- async function safeReadText3(res) {
461
- try {
462
- return await res.text();
463
- } catch {
464
- return "";
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