@farming-labs/theme 0.0.3-beta.2 → 0.0.3-beta.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/ai-search-dialog.d.mts +19 -3
- package/dist/ai-search-dialog.mjs +162 -50
- package/dist/docs-ai-features.d.mts +8 -1
- package/dist/docs-ai-features.mjs +21 -9
- package/dist/docs-api.d.mts +17 -1
- package/dist/docs-api.mjs +24 -7
- package/dist/docs-layout.mjs +10 -1
- package/package.json +2 -2
- package/styles/ai.css +137 -1
- package/styles/base.css +9 -3
- package/styles/darksharp.css +17 -0
- package/styles/default.css +3 -0
- package/styles/greentree.css +18 -9
- package/styles/pixel-border.css +211 -80
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
//#region src/ai-search-dialog.d.ts
|
|
2
|
+
type AIModelOption = {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
2
6
|
type LoaderVariant = "shimmer-dots" | "circular" | "dots" | "typing" | "wave" | "bars" | "pulse" | "pulse-dot" | "terminal" | "text-blink" | "text-shimmer" | "loading-dots";
|
|
3
7
|
declare function DocsSearchDialog({
|
|
4
8
|
open,
|
|
@@ -7,7 +11,9 @@ declare function DocsSearchDialog({
|
|
|
7
11
|
suggestedQuestions,
|
|
8
12
|
aiLabel,
|
|
9
13
|
loaderVariant,
|
|
10
|
-
loadingComponentHtml
|
|
14
|
+
loadingComponentHtml,
|
|
15
|
+
models,
|
|
16
|
+
defaultModelId
|
|
11
17
|
}: {
|
|
12
18
|
open: boolean;
|
|
13
19
|
onOpenChange: (open: boolean) => void;
|
|
@@ -16,6 +22,8 @@ declare function DocsSearchDialog({
|
|
|
16
22
|
aiLabel?: string;
|
|
17
23
|
loaderVariant?: LoaderVariant;
|
|
18
24
|
loadingComponentHtml?: string;
|
|
25
|
+
models?: AIModelOption[];
|
|
26
|
+
defaultModelId?: string;
|
|
19
27
|
}): any;
|
|
20
28
|
type FloatingPosition = "bottom-right" | "bottom-left" | "bottom-center";
|
|
21
29
|
type FloatingStyle = "panel" | "modal" | "popover" | "full-modal";
|
|
@@ -27,7 +35,9 @@ declare function FloatingAIChat({
|
|
|
27
35
|
suggestedQuestions,
|
|
28
36
|
aiLabel,
|
|
29
37
|
loaderVariant,
|
|
30
|
-
loadingComponentHtml
|
|
38
|
+
loadingComponentHtml,
|
|
39
|
+
models,
|
|
40
|
+
defaultModelId
|
|
31
41
|
}: {
|
|
32
42
|
api?: string;
|
|
33
43
|
position?: FloatingPosition;
|
|
@@ -37,6 +47,8 @@ declare function FloatingAIChat({
|
|
|
37
47
|
aiLabel?: string;
|
|
38
48
|
loaderVariant?: LoaderVariant;
|
|
39
49
|
loadingComponentHtml?: string;
|
|
50
|
+
models?: AIModelOption[];
|
|
51
|
+
defaultModelId?: string;
|
|
40
52
|
}): any;
|
|
41
53
|
declare function AIModalDialog({
|
|
42
54
|
open,
|
|
@@ -45,7 +57,9 @@ declare function AIModalDialog({
|
|
|
45
57
|
suggestedQuestions,
|
|
46
58
|
aiLabel,
|
|
47
59
|
loaderVariant,
|
|
48
|
-
loadingComponentHtml
|
|
60
|
+
loadingComponentHtml,
|
|
61
|
+
models,
|
|
62
|
+
defaultModelId
|
|
49
63
|
}: {
|
|
50
64
|
open: boolean;
|
|
51
65
|
onOpenChange: (open: boolean) => void;
|
|
@@ -54,6 +68,8 @@ declare function AIModalDialog({
|
|
|
54
68
|
aiLabel?: string;
|
|
55
69
|
loaderVariant?: LoaderVariant;
|
|
56
70
|
loadingComponentHtml?: string;
|
|
71
|
+
models?: AIModelOption[];
|
|
72
|
+
defaultModelId?: string;
|
|
57
73
|
}): any;
|
|
58
74
|
//#endregion
|
|
59
75
|
export { AIModalDialog, DocsSearchDialog, FloatingAIChat };
|
|
@@ -281,10 +281,77 @@ function InlineLoaderDots() {
|
|
|
281
281
|
]
|
|
282
282
|
});
|
|
283
283
|
}
|
|
284
|
-
function
|
|
284
|
+
function ModelSelector({ models, selectedId, onChange, disabled }) {
|
|
285
|
+
const [open, setOpen] = useState(false);
|
|
286
|
+
const ref = useRef(null);
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (!open) return;
|
|
289
|
+
function handleClick(e) {
|
|
290
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
291
|
+
}
|
|
292
|
+
document.addEventListener("mousedown", handleClick);
|
|
293
|
+
return () => document.removeEventListener("mousedown", handleClick);
|
|
294
|
+
}, [open]);
|
|
295
|
+
const current = models.find((m) => m.id === selectedId) ?? models[0];
|
|
296
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
297
|
+
ref,
|
|
298
|
+
className: "fd-ai-model-dropdown",
|
|
299
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
300
|
+
type: "button",
|
|
301
|
+
className: "fd-ai-model-dropdown-btn",
|
|
302
|
+
onClick: () => !disabled && setOpen(!open),
|
|
303
|
+
"aria-expanded": open,
|
|
304
|
+
disabled,
|
|
305
|
+
children: [/* @__PURE__ */ jsx("span", { children: current?.label ?? "Select model" }), /* @__PURE__ */ jsx("svg", {
|
|
306
|
+
width: "12",
|
|
307
|
+
height: "12",
|
|
308
|
+
viewBox: "0 0 24 24",
|
|
309
|
+
fill: "none",
|
|
310
|
+
stroke: "currentColor",
|
|
311
|
+
strokeWidth: "2",
|
|
312
|
+
strokeLinecap: "round",
|
|
313
|
+
strokeLinejoin: "round",
|
|
314
|
+
children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
315
|
+
})]
|
|
316
|
+
}), open && /* @__PURE__ */ jsx("div", {
|
|
317
|
+
className: "fd-ai-model-dropdown-menu",
|
|
318
|
+
role: "menu",
|
|
319
|
+
children: models.map((m) => /* @__PURE__ */ jsxs("button", {
|
|
320
|
+
type: "button",
|
|
321
|
+
role: "menuitem",
|
|
322
|
+
className: "fd-ai-model-dropdown-item",
|
|
323
|
+
"data-active": m.id === selectedId,
|
|
324
|
+
onClick: () => {
|
|
325
|
+
onChange(m.id);
|
|
326
|
+
setOpen(false);
|
|
327
|
+
},
|
|
328
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
329
|
+
className: "fd-ai-model-dropdown-label",
|
|
330
|
+
children: m.label
|
|
331
|
+
}), m.id === selectedId && /* @__PURE__ */ jsx("svg", {
|
|
332
|
+
width: "14",
|
|
333
|
+
height: "14",
|
|
334
|
+
viewBox: "0 0 24 24",
|
|
335
|
+
fill: "none",
|
|
336
|
+
stroke: "currentColor",
|
|
337
|
+
strokeWidth: "2.5",
|
|
338
|
+
strokeLinecap: "round",
|
|
339
|
+
strokeLinejoin: "round",
|
|
340
|
+
children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
|
|
341
|
+
})]
|
|
342
|
+
}, m.id))
|
|
343
|
+
})]
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
285
347
|
const label = aiLabel || "AI";
|
|
286
348
|
const aiInputRef = useRef(null);
|
|
287
349
|
const messagesEndRef = useRef(null);
|
|
350
|
+
const [selectedModel, setSelectedModel] = useState(() => {
|
|
351
|
+
const trimmed = (defaultModelId ?? "").trim();
|
|
352
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
353
|
+
});
|
|
354
|
+
const effectiveModelId = selectedModel || (Array.isArray(models) && models.length > 0 ? models[0].id : void 0);
|
|
288
355
|
useEffect(() => {
|
|
289
356
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
290
357
|
}, [messages]);
|
|
@@ -308,10 +375,13 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
308
375
|
const res = await fetch(api, {
|
|
309
376
|
method: "POST",
|
|
310
377
|
headers: { "Content-Type": "application/json" },
|
|
311
|
-
body: JSON.stringify({
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
378
|
+
body: JSON.stringify({
|
|
379
|
+
messages: newMessages.map((m) => ({
|
|
380
|
+
role: m.role,
|
|
381
|
+
content: m.content
|
|
382
|
+
})),
|
|
383
|
+
model: effectiveModelId
|
|
384
|
+
})
|
|
315
385
|
});
|
|
316
386
|
if (!res.ok) {
|
|
317
387
|
let errMsg = "Something went wrong.";
|
|
@@ -367,7 +437,8 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
367
437
|
isStreaming,
|
|
368
438
|
setMessages,
|
|
369
439
|
setAiInput,
|
|
370
|
-
setIsStreaming
|
|
440
|
+
setIsStreaming,
|
|
441
|
+
effectiveModelId
|
|
371
442
|
]);
|
|
372
443
|
const handleAskAI = useCallback(async () => {
|
|
373
444
|
await submitQuestion(aiInput);
|
|
@@ -434,44 +505,56 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
|
|
|
434
505
|
}, i)), /* @__PURE__ */ jsx("div", { ref: messagesEndRef })]
|
|
435
506
|
}), /* @__PURE__ */ jsxs("div", {
|
|
436
507
|
className: "fd-ai-chat-footer",
|
|
437
|
-
children: [
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
508
|
+
children: [
|
|
509
|
+
Array.isArray(models) && models.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
510
|
+
className: "fd-ai-model-select-row",
|
|
511
|
+
children: /* @__PURE__ */ jsx(ModelSelector, {
|
|
512
|
+
models,
|
|
513
|
+
selectedId: effectiveModelId ?? models[0].id,
|
|
514
|
+
onChange: setSelectedModel,
|
|
515
|
+
disabled: isStreaming
|
|
516
|
+
})
|
|
517
|
+
}),
|
|
518
|
+
messages.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
519
|
+
style: {
|
|
520
|
+
display: "flex",
|
|
521
|
+
justifyContent: "flex-end",
|
|
522
|
+
paddingBottom: 8
|
|
447
523
|
},
|
|
448
|
-
|
|
449
|
-
|
|
524
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
525
|
+
onClick: () => {
|
|
526
|
+
setMessages([]);
|
|
527
|
+
setAiInput("");
|
|
528
|
+
},
|
|
529
|
+
className: "fd-ai-clear-btn",
|
|
530
|
+
children: "Clear chat"
|
|
531
|
+
})
|
|
532
|
+
}),
|
|
533
|
+
/* @__PURE__ */ jsxs("div", {
|
|
534
|
+
className: "fd-ai-input-wrap",
|
|
535
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
536
|
+
ref: aiInputRef,
|
|
537
|
+
type: "text",
|
|
538
|
+
placeholder: "Ask a question...",
|
|
539
|
+
value: aiInput,
|
|
540
|
+
onChange: (e) => setAiInput(e.target.value),
|
|
541
|
+
onKeyDown: handleAIKeyDown,
|
|
542
|
+
disabled: isStreaming,
|
|
543
|
+
className: "fd-ai-input",
|
|
544
|
+
style: { opacity: isStreaming ? .5 : 1 }
|
|
545
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
546
|
+
onClick: handleAskAI,
|
|
547
|
+
disabled: !canSend,
|
|
548
|
+
className: "fd-ai-send-btn",
|
|
549
|
+
"data-active": canSend,
|
|
550
|
+
children: /* @__PURE__ */ jsx(ArrowUpIcon, {})
|
|
551
|
+
})]
|
|
450
552
|
})
|
|
451
|
-
|
|
452
|
-
className: "fd-ai-input-wrap",
|
|
453
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
454
|
-
ref: aiInputRef,
|
|
455
|
-
type: "text",
|
|
456
|
-
placeholder: "Ask a question...",
|
|
457
|
-
value: aiInput,
|
|
458
|
-
onChange: (e) => setAiInput(e.target.value),
|
|
459
|
-
onKeyDown: handleAIKeyDown,
|
|
460
|
-
disabled: isStreaming,
|
|
461
|
-
className: "fd-ai-input",
|
|
462
|
-
style: { opacity: isStreaming ? .5 : 1 }
|
|
463
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
464
|
-
onClick: handleAskAI,
|
|
465
|
-
disabled: !canSend,
|
|
466
|
-
className: "fd-ai-send-btn",
|
|
467
|
-
"data-active": canSend,
|
|
468
|
-
children: /* @__PURE__ */ jsx(ArrowUpIcon, {})
|
|
469
|
-
})]
|
|
470
|
-
})]
|
|
553
|
+
]
|
|
471
554
|
})]
|
|
472
555
|
});
|
|
473
556
|
}
|
|
474
|
-
function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
557
|
+
function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
475
558
|
const [tab, setTab] = useState("search");
|
|
476
559
|
const [searchQuery, setSearchQuery] = useState("");
|
|
477
560
|
const [searchResults, setSearchResults] = useState([]);
|
|
@@ -481,6 +564,11 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
481
564
|
const [messages, setMessages] = useState([]);
|
|
482
565
|
const [aiInput, setAiInput] = useState("");
|
|
483
566
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
567
|
+
const [selectedModel, setSelectedModel] = useState(() => {
|
|
568
|
+
const trimmed = (defaultModelId ?? "").trim();
|
|
569
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
570
|
+
});
|
|
571
|
+
const effectiveModelId = selectedModel || (Array.isArray(models) && models.length > 0 ? models[0].id : void 0);
|
|
484
572
|
useEffect(() => {
|
|
485
573
|
if (open) {
|
|
486
574
|
setSearchQuery("");
|
|
@@ -647,7 +735,9 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
|
|
|
647
735
|
suggestedQuestions,
|
|
648
736
|
aiLabel,
|
|
649
737
|
loaderVariant,
|
|
650
|
-
loadingComponentHtml
|
|
738
|
+
loadingComponentHtml,
|
|
739
|
+
models,
|
|
740
|
+
defaultModelId: effectiveModelId
|
|
651
741
|
})
|
|
652
742
|
]
|
|
653
743
|
})] }), document.body);
|
|
@@ -721,7 +811,7 @@ function getContainerStyles(style, position) {
|
|
|
721
811
|
function getAnimation(style) {
|
|
722
812
|
return style === "modal" ? "fd-ai-float-center-in 200ms ease-out" : "fd-ai-float-in 200ms ease-out";
|
|
723
813
|
}
|
|
724
|
-
function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
814
|
+
function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
725
815
|
const [mounted, setMounted] = useState(false);
|
|
726
816
|
const [isOpen, setIsOpen] = useState(false);
|
|
727
817
|
const [messages, setMessages] = useState([]);
|
|
@@ -762,7 +852,9 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
762
852
|
loaderVariant,
|
|
763
853
|
loadingComponentHtml,
|
|
764
854
|
triggerComponentHtml,
|
|
765
|
-
position
|
|
855
|
+
position,
|
|
856
|
+
models,
|
|
857
|
+
defaultModelId
|
|
766
858
|
});
|
|
767
859
|
const btnPosition = BTN_POSITIONS[position] || BTN_POSITIONS["bottom-right"];
|
|
768
860
|
const isModal = floatingStyle === "modal";
|
|
@@ -826,11 +918,16 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
|
|
|
826
918
|
}))
|
|
827
919
|
] }), document.body);
|
|
828
920
|
}
|
|
829
|
-
function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, triggerComponentHtml, position }) {
|
|
921
|
+
function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, triggerComponentHtml, position, models, defaultModelId }) {
|
|
830
922
|
const label = aiLabel || "AI";
|
|
831
923
|
const inputRef = useRef(null);
|
|
832
924
|
const listRef = useRef(null);
|
|
833
925
|
const btnPosition = BTN_POSITIONS[position] || BTN_POSITIONS["bottom-right"];
|
|
926
|
+
const [selectedModel, setSelectedModel] = useState(() => {
|
|
927
|
+
const trimmed = (defaultModelId ?? "").trim();
|
|
928
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
929
|
+
});
|
|
930
|
+
const effectiveModelId = selectedModel || (Array.isArray(models) && models.length > 0 ? models[0].id : void 0);
|
|
834
931
|
useEffect(() => {
|
|
835
932
|
if (isOpen) setTimeout(() => inputRef.current?.focus(), 100);
|
|
836
933
|
}, [isOpen]);
|
|
@@ -857,10 +954,13 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
857
954
|
const res = await fetch(api, {
|
|
858
955
|
method: "POST",
|
|
859
956
|
headers: { "Content-Type": "application/json" },
|
|
860
|
-
body: JSON.stringify({
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
957
|
+
body: JSON.stringify({
|
|
958
|
+
messages: newMessages.map((m) => ({
|
|
959
|
+
role: m.role,
|
|
960
|
+
content: m.content
|
|
961
|
+
})),
|
|
962
|
+
model: effectiveModelId
|
|
963
|
+
})
|
|
864
964
|
});
|
|
865
965
|
if (!res.ok) {
|
|
866
966
|
let errMsg = "Something went wrong.";
|
|
@@ -916,7 +1016,8 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
916
1016
|
isStreaming,
|
|
917
1017
|
setMessages,
|
|
918
1018
|
setAiInput,
|
|
919
|
-
setIsStreaming
|
|
1019
|
+
setIsStreaming,
|
|
1020
|
+
effectiveModelId
|
|
920
1021
|
]);
|
|
921
1022
|
const canSend = !!(aiInput.trim() && !isStreaming);
|
|
922
1023
|
const showSuggestions = messages.length === 0 && !isStreaming;
|
|
@@ -977,6 +1078,15 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
|
|
|
977
1078
|
}) : /* @__PURE__ */ jsxs("div", {
|
|
978
1079
|
className: "fd-ai-fm-input-container",
|
|
979
1080
|
children: [
|
|
1081
|
+
Array.isArray(models) && models.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1082
|
+
className: "fd-ai-model-select-row fd-ai-model-select-row--fm",
|
|
1083
|
+
children: /* @__PURE__ */ jsx(ModelSelector, {
|
|
1084
|
+
models,
|
|
1085
|
+
selectedId: effectiveModelId ?? models[0].id,
|
|
1086
|
+
onChange: setSelectedModel,
|
|
1087
|
+
disabled: isStreaming
|
|
1088
|
+
})
|
|
1089
|
+
}),
|
|
980
1090
|
/* @__PURE__ */ jsxs("div", {
|
|
981
1091
|
className: "fd-ai-fm-input-wrap",
|
|
982
1092
|
children: [/* @__PURE__ */ jsx("textarea", {
|
|
@@ -1052,7 +1162,7 @@ function TrashIcon() {
|
|
|
1052
1162
|
]
|
|
1053
1163
|
});
|
|
1054
1164
|
}
|
|
1055
|
-
function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
1165
|
+
function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
1056
1166
|
const [messages, setMessages] = useState([]);
|
|
1057
1167
|
const [aiInput, setAiInput] = useState("");
|
|
1058
1168
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
@@ -1120,7 +1230,9 @@ function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestio
|
|
|
1120
1230
|
suggestedQuestions,
|
|
1121
1231
|
aiLabel,
|
|
1122
1232
|
loaderVariant,
|
|
1123
|
-
loadingComponentHtml
|
|
1233
|
+
loadingComponentHtml,
|
|
1234
|
+
models,
|
|
1235
|
+
defaultModelId
|
|
1124
1236
|
}),
|
|
1125
1237
|
/* @__PURE__ */ jsx("div", {
|
|
1126
1238
|
className: "fd-ai-modal-footer",
|
|
@@ -10,6 +10,11 @@ interface DocsAIFeaturesProps {
|
|
|
10
10
|
aiLabel?: string;
|
|
11
11
|
loaderVariant?: string;
|
|
12
12
|
loadingComponentHtml?: string;
|
|
13
|
+
models?: {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}[];
|
|
17
|
+
defaultModelId?: string;
|
|
13
18
|
}
|
|
14
19
|
declare function DocsAIFeatures({
|
|
15
20
|
mode,
|
|
@@ -19,7 +24,9 @@ declare function DocsAIFeatures({
|
|
|
19
24
|
suggestedQuestions,
|
|
20
25
|
aiLabel,
|
|
21
26
|
loaderVariant,
|
|
22
|
-
loadingComponentHtml
|
|
27
|
+
loadingComponentHtml,
|
|
28
|
+
models,
|
|
29
|
+
defaultModelId
|
|
23
30
|
}: DocsAIFeaturesProps): react_jsx_runtime0.JSX.Element;
|
|
24
31
|
//#endregion
|
|
25
32
|
export { DocsAIFeatures };
|
|
@@ -19,18 +19,22 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
19
19
|
* This component is rendered inside the docs layout so the user's root layout
|
|
20
20
|
* never needs to be modified — AI features work purely from `docs.config.tsx`.
|
|
21
21
|
*/
|
|
22
|
-
function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
22
|
+
function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
23
23
|
if (mode === "search") return /* @__PURE__ */ jsx(SearchModeAI, {
|
|
24
24
|
suggestedQuestions,
|
|
25
25
|
aiLabel,
|
|
26
26
|
loaderVariant,
|
|
27
|
-
loadingComponentHtml
|
|
27
|
+
loadingComponentHtml,
|
|
28
|
+
models,
|
|
29
|
+
defaultModelId
|
|
28
30
|
});
|
|
29
31
|
if (mode === "sidebar-icon") return /* @__PURE__ */ jsx(SidebarIconModeAI, {
|
|
30
32
|
suggestedQuestions,
|
|
31
33
|
aiLabel,
|
|
32
34
|
loaderVariant,
|
|
33
|
-
loadingComponentHtml
|
|
35
|
+
loadingComponentHtml,
|
|
36
|
+
models,
|
|
37
|
+
defaultModelId
|
|
34
38
|
});
|
|
35
39
|
return /* @__PURE__ */ jsx(FloatingAIChat, {
|
|
36
40
|
api: "/api/docs",
|
|
@@ -40,10 +44,12 @@ function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "pane
|
|
|
40
44
|
suggestedQuestions,
|
|
41
45
|
aiLabel,
|
|
42
46
|
loaderVariant,
|
|
43
|
-
loadingComponentHtml
|
|
47
|
+
loadingComponentHtml,
|
|
48
|
+
models,
|
|
49
|
+
defaultModelId
|
|
44
50
|
});
|
|
45
51
|
}
|
|
46
|
-
function SearchModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
52
|
+
function SearchModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
47
53
|
const [open, setOpen] = useState(false);
|
|
48
54
|
useEffect(() => {
|
|
49
55
|
function handler(e) {
|
|
@@ -79,10 +85,12 @@ function SearchModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingCompo
|
|
|
79
85
|
suggestedQuestions,
|
|
80
86
|
aiLabel,
|
|
81
87
|
loaderVariant,
|
|
82
|
-
loadingComponentHtml
|
|
88
|
+
loadingComponentHtml,
|
|
89
|
+
models,
|
|
90
|
+
defaultModelId
|
|
83
91
|
});
|
|
84
92
|
}
|
|
85
|
-
function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
93
|
+
function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, models, defaultModelId }) {
|
|
86
94
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
87
95
|
const [aiOpen, setAiOpen] = useState(false);
|
|
88
96
|
useEffect(() => {
|
|
@@ -118,7 +126,9 @@ function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loading
|
|
|
118
126
|
suggestedQuestions,
|
|
119
127
|
aiLabel,
|
|
120
128
|
loaderVariant,
|
|
121
|
-
loadingComponentHtml
|
|
129
|
+
loadingComponentHtml,
|
|
130
|
+
models,
|
|
131
|
+
defaultModelId
|
|
122
132
|
}), /* @__PURE__ */ jsx(AIModalDialog, {
|
|
123
133
|
open: aiOpen,
|
|
124
134
|
onOpenChange: setAiOpen,
|
|
@@ -126,7 +136,9 @@ function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loading
|
|
|
126
136
|
suggestedQuestions,
|
|
127
137
|
aiLabel,
|
|
128
138
|
loaderVariant,
|
|
129
|
-
loadingComponentHtml
|
|
139
|
+
loadingComponentHtml,
|
|
140
|
+
models,
|
|
141
|
+
defaultModelId
|
|
130
142
|
})] });
|
|
131
143
|
}
|
|
132
144
|
|
package/dist/docs-api.d.mts
CHANGED
|
@@ -18,11 +18,27 @@
|
|
|
18
18
|
* export const revalidate = false;
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
+
interface AIProviderConfig {
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
apiKey?: string;
|
|
24
|
+
}
|
|
25
|
+
interface AIModelEntry {
|
|
26
|
+
id: string;
|
|
27
|
+
label: string;
|
|
28
|
+
provider?: string;
|
|
29
|
+
}
|
|
30
|
+
interface AIModelConfig {
|
|
31
|
+
models?: AIModelEntry[];
|
|
32
|
+
defaultModel?: string;
|
|
33
|
+
}
|
|
21
34
|
interface AIOptions {
|
|
22
35
|
enabled?: boolean;
|
|
23
|
-
model?: string;
|
|
36
|
+
model?: string | AIModelConfig;
|
|
37
|
+
providers?: Record<string, AIProviderConfig>;
|
|
24
38
|
systemPrompt?: string;
|
|
39
|
+
/** Default baseUrl when no per-model provider is configured. */
|
|
25
40
|
baseUrl?: string;
|
|
41
|
+
/** Default apiKey when no per-model provider is configured. */
|
|
26
42
|
apiKey?: string;
|
|
27
43
|
maxResults?: number;
|
|
28
44
|
}
|
package/dist/docs-api.mjs
CHANGED
|
@@ -108,9 +108,26 @@ function scanDocsDir(docsDir, entry) {
|
|
|
108
108
|
return indexes;
|
|
109
109
|
}
|
|
110
110
|
const DEFAULT_SYSTEM_PROMPT = `You are a helpful documentation assistant. Answer questions based on the provided documentation context. Be concise and accurate. If the answer is not in the context, say so honestly. Use markdown formatting for code examples and links.`;
|
|
111
|
+
function resolveModelAndProvider(aiConfig, requestedModelId) {
|
|
112
|
+
const raw = aiConfig.model;
|
|
113
|
+
const modelList = typeof raw === "object" && raw?.models || [];
|
|
114
|
+
let modelId = requestedModelId;
|
|
115
|
+
if (!modelId) {
|
|
116
|
+
if (typeof raw === "string") modelId = raw;
|
|
117
|
+
else if (typeof raw === "object") modelId = raw.defaultModel ?? raw.models?.[0]?.id;
|
|
118
|
+
if (!modelId) modelId = "gpt-4o-mini";
|
|
119
|
+
}
|
|
120
|
+
const providerKey = modelList.find((m) => m.id === modelId)?.provider;
|
|
121
|
+
const providerConfig = providerKey && aiConfig.providers?.[providerKey];
|
|
122
|
+
const baseUrl = (providerConfig && providerConfig.baseUrl || aiConfig.baseUrl || "https://api.openai.com/v1").replace(/\/$/, "");
|
|
123
|
+
const apiKey = providerConfig && providerConfig.apiKey || aiConfig.apiKey || process.env.OPENAI_API_KEY;
|
|
124
|
+
return {
|
|
125
|
+
model: modelId,
|
|
126
|
+
baseUrl,
|
|
127
|
+
apiKey
|
|
128
|
+
};
|
|
129
|
+
}
|
|
111
130
|
async function handleAskAI(request, indexes, searchServer, aiConfig) {
|
|
112
|
-
const apiKey = aiConfig.apiKey ?? process.env.OPENAI_API_KEY;
|
|
113
|
-
if (!apiKey) return Response.json({ error: `AI is enabled but no API key was found. Either set apiKey in your docs.config or add OPENAI_API_KEY to your .env.local file.` }, { status: 500 });
|
|
114
131
|
let body;
|
|
115
132
|
try {
|
|
116
133
|
body = await request.json();
|
|
@@ -139,16 +156,16 @@ async function handleAskAI(request, indexes, searchServer, aiConfig) {
|
|
|
139
156
|
role: "system",
|
|
140
157
|
content: context ? `${systemPrompt}\n\n---\n\nDocumentation context:\n\n${context}` : systemPrompt
|
|
141
158
|
}, ...messages.filter((m) => m.role !== "system")];
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
const llmResponse = await fetch(`${baseUrl}/chat/completions`, {
|
|
159
|
+
const resolved = resolveModelAndProvider(aiConfig, typeof body.model === "string" && body.model.trim().length > 0 ? body.model.trim() : void 0);
|
|
160
|
+
if (!resolved.apiKey) return Response.json({ error: `AI is enabled but no API key was found. Either set apiKey in your docs.config ai section, configure a provider, or add OPENAI_API_KEY to your .env.local file.` }, { status: 500 });
|
|
161
|
+
const llmResponse = await fetch(`${resolved.baseUrl}/chat/completions`, {
|
|
145
162
|
method: "POST",
|
|
146
163
|
headers: {
|
|
147
164
|
"Content-Type": "application/json",
|
|
148
|
-
Authorization: `Bearer ${apiKey}`
|
|
165
|
+
Authorization: `Bearer ${resolved.apiKey}`
|
|
149
166
|
},
|
|
150
167
|
body: JSON.stringify({
|
|
151
|
-
model,
|
|
168
|
+
model: resolved.model,
|
|
152
169
|
stream: true,
|
|
153
170
|
messages: llmMessages
|
|
154
171
|
})
|
package/dist/docs-layout.mjs
CHANGED
|
@@ -434,6 +434,13 @@ function createDocsLayout(config) {
|
|
|
434
434
|
const aiLabel = aiConfig?.aiLabel;
|
|
435
435
|
const aiLoaderVariant = aiConfig?.loader;
|
|
436
436
|
const aiLoadingComponentHtml = typeof aiConfig?.loadingComponent === "function" ? serializeIcon(aiConfig.loadingComponent({ name: aiLabel || "AI" })) : void 0;
|
|
437
|
+
const rawModelConfig = aiConfig?.model;
|
|
438
|
+
let aiModels = aiConfig?.models;
|
|
439
|
+
let aiDefaultModelId = aiConfig?.defaultModel ?? (typeof aiConfig?.model === "string" ? aiConfig.model : void 0);
|
|
440
|
+
if (rawModelConfig && typeof rawModelConfig === "object") {
|
|
441
|
+
aiModels = rawModelConfig.models ?? aiModels;
|
|
442
|
+
aiDefaultModelId = rawModelConfig.defaultModel ?? rawModelConfig.models?.[0]?.id ?? aiDefaultModelId;
|
|
443
|
+
}
|
|
437
444
|
const lastModifiedMap = buildLastModifiedMap(config.entry);
|
|
438
445
|
const descriptionMap = buildDescriptionMap(config.entry);
|
|
439
446
|
return function DocsLayoutWrapper({ children }) {
|
|
@@ -467,7 +474,9 @@ function createDocsLayout(config) {
|
|
|
467
474
|
suggestedQuestions: aiSuggestedQuestions,
|
|
468
475
|
aiLabel,
|
|
469
476
|
loaderVariant: aiLoaderVariant,
|
|
470
|
-
loadingComponentHtml: aiLoadingComponentHtml
|
|
477
|
+
loadingComponentHtml: aiLoadingComponentHtml,
|
|
478
|
+
models: aiModels,
|
|
479
|
+
defaultModelId: aiDefaultModelId
|
|
471
480
|
}),
|
|
472
481
|
/* @__PURE__ */ jsx(DocsPageClient, {
|
|
473
482
|
tocEnabled,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.0.3-beta.
|
|
3
|
+
"version": "0.0.3-beta.4",
|
|
4
4
|
"description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"next": ">=14.0.0",
|
|
99
99
|
"tsdown": "^0.20.3",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
|
-
"@farming-labs/docs": "0.0.3-beta.
|
|
101
|
+
"@farming-labs/docs": "0.0.3-beta.4"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
104
|
"@farming-labs/docs": ">=0.0.1",
|