@kohryan/moodui 0.0.2 → 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,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 jsx = renderNode(spec.root, { indent: 2, onActionProp: "onAction" });
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
- jsx,
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/llm/ollama.ts
353
- function createOllamaClient(options = {}) {
354
- const baseUrl = options.baseUrl ?? "http://localhost:11434";
355
- const fetchFn = options.fetchFn ?? fetch;
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
- async function safeReadText(res) {
380
- try {
381
- return await res.text();
382
- } catch {
383
- 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
+ }
384
417
  }
385
418
  }
386
-
387
- // src/llm/openaiCompatible.ts
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
- async chat(req) {
395
- const res = await fetchFn(`${baseUrl}/v1/chat/completions`, {
396
- method: "POST",
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
- async function safeReadText2(res) {
420
- try {
421
- return await res.text();
422
- } catch {
423
- 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;
424
448
  }
425
- }
426
-
427
- // src/llm/gemini.ts
428
- function createGeminiClient(options) {
429
- const baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com").replace(/\/+$/, "");
430
- const fetchFn = options.fetchFn ?? fetch;
431
- const apiKey = options.apiKey;
432
- return {
433
- async chat(req) {
434
- const { systemInstruction, contents } = toGeminiContents(req.messages);
435
- const url = `${baseUrl}/v1beta/models/${encodeURIComponent(req.model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
436
- const res = await fetchFn(url, {
437
- method: "POST",
438
- headers: { "content-type": "application/json" },
439
- body: JSON.stringify({
440
- ...systemInstruction ? { systemInstruction } : {},
441
- contents,
442
- generationConfig: req.temperature == null ? void 0 : { temperature: req.temperature }
443
- })
444
- });
445
- if (!res.ok) {
446
- const text2 = await safeReadText3(res);
447
- throw new Error(`Gemini generateContent failed (${res.status}): ${text2}`);
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
- 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;
468
491
  }
469
- async function safeReadText3(res) {
470
- try {
471
- return await res.text();
472
- } catch {
473
- 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;
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);
474
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);
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,176 @@ 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 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
+ }
593
934
  // Annotate the CommonJS export names for ESM import in node:
594
935
  0 && (module.exports = {
936
+ MoodUIPromptPlayground,
937
+ MoodUIRuntime,
595
938
  assertMoodUISpec,
596
939
  createGeminiClient,
597
940
  createOllamaClient,