@flowselections/ai-authenticatie 1.0.2

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.
Files changed (159) hide show
  1. package/dist-lib/_core-safelist.d.ts +2 -0
  2. package/dist-lib/_core-safelist.d.ts.map +1 -0
  3. package/dist-lib/_core-safelist.js +15 -0
  4. package/dist-lib/components/AiAuthenticatieLogo.d.ts +3 -0
  5. package/dist-lib/components/AiAuthenticatieLogo.d.ts.map +1 -0
  6. package/dist-lib/components/AiAuthenticatieLogo.js +4 -0
  7. package/dist-lib/components/AiAuthenticatiePage.d.ts +4 -0
  8. package/dist-lib/components/AiAuthenticatiePage.d.ts.map +1 -0
  9. package/dist-lib/components/AiAuthenticatiePage.js +145 -0
  10. package/dist-lib/components/AiModuleDetailPage.d.ts +2 -0
  11. package/dist-lib/components/AiModuleDetailPage.d.ts.map +1 -0
  12. package/dist-lib/components/AiModuleDetailPage.js +43 -0
  13. package/dist-lib/components/AiModuleTestPanel.d.ts +4 -0
  14. package/dist-lib/components/AiModuleTestPanel.d.ts.map +1 -0
  15. package/dist-lib/components/AiModuleTestPanel.js +38 -0
  16. package/dist-lib/components/AiPeriodFilter.d.ts +7 -0
  17. package/dist-lib/components/AiPeriodFilter.d.ts.map +1 -0
  18. package/dist-lib/components/AiPeriodFilter.js +14 -0
  19. package/dist-lib/components/AiStatTile.d.ts +6 -0
  20. package/dist-lib/components/AiStatTile.d.ts.map +1 -0
  21. package/dist-lib/components/AiStatTile.js +5 -0
  22. package/dist-lib/components/ModuleAgentsDialog.d.ts +14 -0
  23. package/dist-lib/components/ModuleAgentsDialog.d.ts.map +1 -0
  24. package/dist-lib/components/ModuleAgentsDialog.js +78 -0
  25. package/dist-lib/components/ProviderLogo.d.ts +8 -0
  26. package/dist-lib/components/ProviderLogo.d.ts.map +1 -0
  27. package/dist-lib/components/ProviderLogo.js +24 -0
  28. package/dist-lib/components/ProviderManageDialog.d.ts +11 -0
  29. package/dist-lib/components/ProviderManageDialog.d.ts.map +1 -0
  30. package/dist-lib/components/ProviderManageDialog.js +173 -0
  31. package/dist-lib/components/settings/AiAuthenticatieSettingsCard.d.ts +2 -0
  32. package/dist-lib/components/settings/AiAuthenticatieSettingsCard.d.ts.map +1 -0
  33. package/dist-lib/components/settings/AiAuthenticatieSettingsCard.js +7 -0
  34. package/dist-lib/components/settings/MijnModuleSettingsCard.d.ts +2 -0
  35. package/dist-lib/components/settings/MijnModuleSettingsCard.d.ts.map +1 -0
  36. package/dist-lib/components/settings/MijnModuleSettingsCard.js +42 -0
  37. package/dist-lib/hooks/useAdminCredentials.d.ts +14 -0
  38. package/dist-lib/hooks/useAdminCredentials.d.ts.map +1 -0
  39. package/dist-lib/hooks/useAdminCredentials.js +17 -0
  40. package/dist-lib/hooks/useAiAdmin.d.ts +44 -0
  41. package/dist-lib/hooks/useAiAdmin.d.ts.map +1 -0
  42. package/dist-lib/hooks/useAiAdmin.js +32 -0
  43. package/dist-lib/hooks/useAiCostOverview.d.ts +7 -0
  44. package/dist-lib/hooks/useAiCostOverview.d.ts.map +1 -0
  45. package/dist-lib/hooks/useAiCostOverview.js +28 -0
  46. package/dist-lib/hooks/useAiForGoal.d.ts +21 -0
  47. package/dist-lib/hooks/useAiForGoal.d.ts.map +1 -0
  48. package/dist-lib/hooks/useAiForGoal.js +40 -0
  49. package/dist-lib/hooks/useAiForModule.d.ts +11 -0
  50. package/dist-lib/hooks/useAiForModule.d.ts.map +1 -0
  51. package/dist-lib/hooks/useAiForModule.js +11 -0
  52. package/dist-lib/hooks/useAiGoals.d.ts +8 -0
  53. package/dist-lib/hooks/useAiGoals.d.ts.map +1 -0
  54. package/dist-lib/hooks/useAiGoals.js +22 -0
  55. package/dist-lib/hooks/useAiModules.d.ts +15 -0
  56. package/dist-lib/hooks/useAiModules.d.ts.map +1 -0
  57. package/dist-lib/hooks/useAiModules.js +59 -0
  58. package/dist-lib/hooks/useAiPricing.d.ts +20 -0
  59. package/dist-lib/hooks/useAiPricing.d.ts.map +1 -0
  60. package/dist-lib/hooks/useAiPricing.js +97 -0
  61. package/dist-lib/hooks/useAiUsage.d.ts +12 -0
  62. package/dist-lib/hooks/useAiUsage.d.ts.map +1 -0
  63. package/dist-lib/hooks/useAiUsage.js +90 -0
  64. package/dist-lib/hooks/useAiUsageByModule.d.ts +8 -0
  65. package/dist-lib/hooks/useAiUsageByModule.d.ts.map +1 -0
  66. package/dist-lib/hooks/useAiUsageByModule.js +22 -0
  67. package/dist-lib/hooks/useIsAiAdmin.d.ts +6 -0
  68. package/dist-lib/hooks/useIsAiAdmin.d.ts.map +1 -0
  69. package/dist-lib/hooks/useIsAiAdmin.js +5 -0
  70. package/dist-lib/hooks/useMijnModuleData.d.ts +13 -0
  71. package/dist-lib/hooks/useMijnModuleData.d.ts.map +1 -0
  72. package/dist-lib/hooks/useMijnModuleData.js +49 -0
  73. package/dist-lib/hooks/useModules.d.ts +14 -0
  74. package/dist-lib/hooks/useModules.d.ts.map +1 -0
  75. package/dist-lib/hooks/useModules.js +47 -0
  76. package/dist-lib/index.d.ts +29 -0
  77. package/dist-lib/index.d.ts.map +1 -0
  78. package/dist-lib/index.js +33 -0
  79. package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
  80. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
  81. package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
  82. package/dist-lib/integrations/supabase/auth-middleware.d.ts +15971 -0
  83. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
  84. package/dist-lib/integrations/supabase/auth-middleware.js +49 -0
  85. package/dist-lib/integrations/supabase/client.d.ts +15967 -0
  86. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  87. package/dist-lib/integrations/supabase/client.js +13 -0
  88. package/dist-lib/integrations/supabase/client.server.d.ts +15967 -0
  89. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
  90. package/dist-lib/integrations/supabase/client.server.js +30 -0
  91. package/dist-lib/integrations/supabase/types.d.ts +16477 -0
  92. package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
  93. package/dist-lib/integrations/supabase/types.js +78 -0
  94. package/dist-lib/lib/aiChat.functions.d.ts +31948 -0
  95. package/dist-lib/lib/aiChat.functions.d.ts.map +1 -0
  96. package/dist-lib/lib/aiChat.functions.js +51 -0
  97. package/dist-lib/lib/aiChat.server.d.ts +8 -0
  98. package/dist-lib/lib/aiChat.server.d.ts.map +1 -0
  99. package/dist-lib/lib/aiChat.server.js +347 -0
  100. package/dist-lib/lib/aiChat.stream.d.ts +33 -0
  101. package/dist-lib/lib/aiChat.stream.d.ts.map +1 -0
  102. package/dist-lib/lib/aiChat.stream.js +77 -0
  103. package/dist-lib/lib/aiChat.stream.server.d.ts +38 -0
  104. package/dist-lib/lib/aiChat.stream.server.d.ts.map +1 -0
  105. package/dist-lib/lib/aiChat.stream.server.js +259 -0
  106. package/dist-lib/lib/aiChat.types.d.ts +63 -0
  107. package/dist-lib/lib/aiChat.types.d.ts.map +1 -0
  108. package/dist-lib/lib/aiChat.types.js +1 -0
  109. package/dist-lib/lib/aiCredentialTest.functions.d.ts +31959 -0
  110. package/dist-lib/lib/aiCredentialTest.functions.d.ts.map +1 -0
  111. package/dist-lib/lib/aiCredentialTest.functions.js +714 -0
  112. package/dist-lib/lib/aiGoals.functions.d.ts +12912 -0
  113. package/dist-lib/lib/aiGoals.functions.d.ts.map +1 -0
  114. package/dist-lib/lib/aiGoals.functions.js +14 -0
  115. package/dist-lib/lib/aiModules.functions.d.ts +95851 -0
  116. package/dist-lib/lib/aiModules.functions.d.ts.map +1 -0
  117. package/dist-lib/lib/aiModules.functions.js +41 -0
  118. package/dist-lib/lib/aiModules.server.d.ts +65 -0
  119. package/dist-lib/lib/aiModules.server.d.ts.map +1 -0
  120. package/dist-lib/lib/aiModules.server.js +119 -0
  121. package/dist-lib/lib/aiPeriod.d.ts +11 -0
  122. package/dist-lib/lib/aiPeriod.d.ts.map +1 -0
  123. package/dist-lib/lib/aiPeriod.js +53 -0
  124. package/dist-lib/lib/aiPricing.functions.d.ts +31943 -0
  125. package/dist-lib/lib/aiPricing.functions.d.ts.map +1 -0
  126. package/dist-lib/lib/aiPricing.functions.js +13 -0
  127. package/dist-lib/lib/aiPricing.server.d.ts +19 -0
  128. package/dist-lib/lib/aiPricing.server.d.ts.map +1 -0
  129. package/dist-lib/lib/aiPricing.server.js +97 -0
  130. package/dist-lib/lib/aiUsage.functions.d.ts +5 -0
  131. package/dist-lib/lib/aiUsage.functions.d.ts.map +1 -0
  132. package/dist-lib/lib/aiUsage.functions.js +12 -0
  133. package/dist-lib/lib/aiUsage.server.d.ts +3 -0
  134. package/dist-lib/lib/aiUsage.server.d.ts.map +1 -0
  135. package/dist-lib/lib/aiUsage.server.js +229 -0
  136. package/dist-lib/lib/aiUsage.types.d.ts +21 -0
  137. package/dist-lib/lib/aiUsage.types.d.ts.map +1 -0
  138. package/dist-lib/lib/aiUsage.types.js +1 -0
  139. package/dist-lib/lib/aiUsageByModule.functions.d.ts +31952 -0
  140. package/dist-lib/lib/aiUsageByModule.functions.d.ts.map +1 -0
  141. package/dist-lib/lib/aiUsageByModule.functions.js +37 -0
  142. package/dist-lib/lib/modules.functions.d.ts +47924 -0
  143. package/dist-lib/lib/modules.functions.d.ts.map +1 -0
  144. package/dist-lib/lib/modules.functions.js +21 -0
  145. package/dist-lib/lib/modules.server.d.ts +23 -0
  146. package/dist-lib/lib/modules.server.d.ts.map +1 -0
  147. package/dist-lib/lib/modules.server.js +52 -0
  148. package/dist-lib/lib/utils.d.ts +3 -0
  149. package/dist-lib/lib/utils.d.ts.map +1 -0
  150. package/dist-lib/lib/utils.js +5 -0
  151. package/dist-lib/lib/validationSchemas.d.ts +15 -0
  152. package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
  153. package/dist-lib/lib/validationSchemas.js +25 -0
  154. package/dist-lib/styles.css +1 -0
  155. package/package.json +92 -0
  156. package/public/flowselections-assets/ai-authenticatie/claude.png +0 -0
  157. package/public/flowselections-assets/ai-authenticatie/gemini.png +0 -0
  158. package/public/flowselections-assets/ai-authenticatie/openai.png +0 -0
  159. package/public/logo.png +0 -0
@@ -0,0 +1,173 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, Button, Input, Label, Badge, Separator, Switch, Skeleton, supabase, toast, } from '@flowselections/core';
4
+ import { useServerFn } from '@tanstack/react-start';
5
+ import { Plus, Trash2, Eye, EyeOff, Zap, Loader2, ShieldCheck, TrendingUp, ExternalLink } from 'lucide-react';
6
+ import { ProviderLogo } from './ProviderLogo';
7
+ import { maskKey } from '../hooks/useAiAdmin';
8
+ import { testAiCredential, syncAiCredentialModels } from '../lib/aiCredentialTest.functions';
9
+ import { useModelPricing, pricingByModelSlug, formatUsd, formatTokens } from '../hooks/useAiPricing';
10
+ import { useProviderUsage } from '../hooks/useAiUsage';
11
+ import { useAdminCredentials, findAdminCred } from '../hooks/useAdminCredentials';
12
+ export function ProviderManageDialog({ provider, credentials, open, onOpenChange, onChange, }) {
13
+ const safeCreds = Array.isArray(credentials) ? credentials : [];
14
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-2xl max-h-[90vh] overflow-y-auto", children: [_jsx(DialogHeader, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(ProviderLogo, { slug: provider.slug, name: provider.name, className: "h-10 w-10" }), _jsxs("div", { children: [_jsx(DialogTitle, { children: provider.name }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Beheer sleutels, bekijk tarieven en actueel verbruik." })] })] }) }), _jsxs("div", { className: "mt-2 space-y-6", children: [_jsx(UsageSection, { provider: provider }), _jsx(AdminKeySection, { provider: provider }), _jsxs("div", { children: [_jsx("h4", { className: "text-sm font-semibold mb-2", children: "API-sleutels" }), _jsx(CredentialList, { provider: provider, credentials: safeCreds, onChange: onChange })] }), _jsx(PricingSection, { provider: provider }), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border bg-muted/40 p-3", children: [_jsx(ShieldCheck, { className: "h-4 w-4 mt-0.5 text-muted-foreground shrink-0" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Alle sleutels worden veilig en versleuteld opgeslagen. Admin-sleutels worden alleen server-side gebruikt om kosten op te halen." })] })] })] }) }));
15
+ }
16
+ /* ───────────────────────── Verbruik ───────────────────────── */
17
+ function UsageSection({ provider }) {
18
+ const { data, loading } = useProviderUsage(provider.slug.toLowerCase());
19
+ return (_jsxs("section", { className: "rounded-xl border bg-muted/20 p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(TrendingUp, { className: "h-4 w-4 text-primary" }), _jsx("h4", { className: "text-sm font-semibold", children: "Verbruik deze maand" })] }), loading && !data ? (_jsx(Skeleton, { className: "h-16 w-full" })) : data?.supported === false ? (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Google biedt geen kosten via API.", ' ', _jsxs("a", { href: "https://console.cloud.google.com/billing", target: "_blank", rel: "noreferrer", className: "text-primary inline-flex items-center gap-1 hover:underline", children: ["Bekijk in Google Cloud ", _jsx(ExternalLink, { className: "h-3 w-3" })] })] })) : data?.ok ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-baseline gap-4", children: [_jsx("p", { className: "text-3xl font-bold tracking-tight", children: formatUsd(data.total_cost_usd) }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [_jsxs("p", { children: [formatTokens(data.input_tokens), " tokens in"] }), _jsxs("p", { children: [formatTokens(data.output_tokens), " tokens uit"] })] })] }), data.lines.length > 0 && (_jsxs("div", { className: "pt-2 border-t space-y-1.5", children: [_jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Per model" }), data.lines.slice(0, 6).map(l => (_jsxs("div", { className: "flex items-center justify-between text-xs", children: [_jsx("span", { className: "truncate mr-3", children: l.model }), _jsx("span", { className: "font-medium", children: formatUsd(l.cost_usd, 4) })] }, l.model)))] }))] })) : (_jsx("p", { className: "text-xs text-muted-foreground", children: data?.message ?? 'Nog geen admin-sleutel opgegeven — voeg toe hieronder.' }))] }));
20
+ }
21
+ /* ───────────────────────── Admin-key ───────────────────────── */
22
+ function AdminKeySection({ provider }) {
23
+ const { rows, loading, reload } = useAdminCredentials();
24
+ const existing = findAdminCred(rows, provider.id);
25
+ const [editing, setEditing] = useState(false);
26
+ const [reveal, setReveal] = useState(false);
27
+ const [label, setLabel] = useState('');
28
+ const [key, setKey] = useState('');
29
+ const [saving, setSaving] = useState(false);
30
+ const isGemini = provider.slug.toLowerCase().includes('gemini') || provider.slug.toLowerCase().includes('google');
31
+ if (isGemini) {
32
+ return (_jsxs("section", { className: "rounded-xl border p-4 space-y-2", children: [_jsx("h4", { className: "text-sm font-semibold", children: "Admin-sleutel voor kosten" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Google biedt geen kosten-API \u2014 verbruik is niet beschikbaar in deze module." })] }));
33
+ }
34
+ const save = async () => {
35
+ if (!label.trim() || !key.trim())
36
+ return;
37
+ setSaving(true);
38
+ try {
39
+ if (existing) {
40
+ const { error } = await supabase.from('ai_admin_credentials').update({
41
+ label: label.trim(), admin_key: key.trim(), active: true,
42
+ }).eq('id', existing.id);
43
+ if (error) {
44
+ toast.error(error.message);
45
+ return;
46
+ }
47
+ }
48
+ else {
49
+ const { error } = await supabase.from('ai_admin_credentials').insert({
50
+ provider_id: provider.id, label: label.trim(), admin_key: key.trim(),
51
+ });
52
+ if (error) {
53
+ toast.error(error.message);
54
+ return;
55
+ }
56
+ }
57
+ toast.success('Admin-sleutel opgeslagen');
58
+ setEditing(false);
59
+ setLabel('');
60
+ setKey('');
61
+ await reload();
62
+ }
63
+ finally {
64
+ setSaving(false);
65
+ }
66
+ };
67
+ const remove = async (row) => {
68
+ if (!confirm('Admin-sleutel verwijderen?'))
69
+ return;
70
+ const { error } = await supabase.from('ai_admin_credentials').delete().eq('id', row.id);
71
+ if (error) {
72
+ toast.error(error.message);
73
+ return;
74
+ }
75
+ toast.success('Verwijderd');
76
+ await reload();
77
+ };
78
+ return (_jsxs("section", { className: "rounded-xl border p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h4", { className: "text-sm font-semibold", children: "Admin-sleutel voor kosten" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Aparte sleutel met alleen leesrechten voor verbruik. Wordt server-side bewaard." })] }), existing && !editing && (_jsx(Button, { variant: "outline", size: "sm", onClick: () => { setEditing(true); setLabel(existing.label); setKey(''); }, children: "Wijzigen" }))] }), loading ? (_jsx(Skeleton, { className: "h-10 w-full" })) : existing && !editing ? (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border p-2.5", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium text-sm", children: existing.label }), _jsx(Badge, { variant: "secondary", className: "text-xs", children: "Actief" })] }), _jsx("p", { className: "text-xs text-muted-foreground font-mono truncate mt-0.5", children: reveal ? existing.admin_key : maskKey(existing.admin_key) })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setReveal(r => !r), children: reveal ? _jsx(EyeOff, { className: "h-4 w-4" }) : _jsx(Eye, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => remove(existing), children: _jsx(Trash2, { className: "h-4 w-4" }) })] })) : editing || !existing ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "Label" }), _jsx(Input, { value: label, onChange: e => setLabel(e.target.value), placeholder: "Bijv. Kosten-uitlezer", className: "mt-1" })] }), _jsxs("div", { children: [_jsxs(Label, { className: "text-xs", children: ["Admin-sleutel", provider.slug.toLowerCase().includes('openai') && ' (sk-admin-…)', provider.slug.toLowerCase().includes('anthropic') && ' (sk-ant-admin-…)'] }), _jsx(Input, { type: "password", value: key, onChange: e => setKey(e.target.value), placeholder: "Plak hier de admin-sleutel", className: "mt-1" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { size: "sm", onClick: save, disabled: saving || !label || !key, children: [saving ? _jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" }) : _jsx(Plus, { className: "h-4 w-4 mr-1" }), "Opslaan"] }), editing && (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => { setEditing(false); setLabel(''); setKey(''); }, children: "Annuleren" }))] })] })) : null] }));
79
+ }
80
+ /* ───────────────────────── Tarieven per model ───────────────────────── */
81
+ function PricingSection({ provider }) {
82
+ const { rows, loading } = useModelPricing();
83
+ const byModel = useMemo(() => pricingByModelSlug(rows), [rows]);
84
+ const providerRows = useMemo(() => rows.filter(r => r.provider_slug === provider.slug.toLowerCase())
85
+ .sort((a, b) => (a.input_per_mtok ?? 0) - (b.input_per_mtok ?? 0)), [rows, provider.slug]);
86
+ return (_jsxs("section", { children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("h4", { className: "text-sm font-semibold", children: "Tarieven per model" }), providerRows[0]?.fetched_at && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Bijgewerkt ", new Date(providerRows[0].fetched_at).toLocaleDateString('nl-NL')] }))] }), loading && providerRows.length === 0 ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-8 w-full" })] })) : providerRows.length === 0 ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "Nog geen tarieven bekend voor deze provider." })) : (_jsxs("div", { className: "rounded-lg border overflow-hidden", children: [_jsxs("div", { className: "grid grid-cols-[1fr_auto_auto] gap-3 px-3 py-2 bg-muted/30 text-xs font-medium border-b", children: [_jsx("span", { children: "Model" }), _jsx("span", { className: "text-right w-24", children: "Input / 1M" }), _jsx("span", { className: "text-right w-24", children: "Output / 1M" })] }), _jsx("ul", { className: "divide-y max-h-64 overflow-y-auto", children: providerRows.map(r => (_jsxs("li", { className: "grid grid-cols-[1fr_auto_auto] gap-3 px-3 py-2 text-sm items-center", children: [_jsx("span", { className: "font-mono text-xs truncate", children: r.model_slug }), _jsx("span", { className: "text-right w-24 tabular-nums", children: formatUsd(r.input_per_mtok, 2) }), _jsx("span", { className: "text-right w-24 tabular-nums", children: formatUsd(r.output_per_mtok, 2) })] }, r.model_slug))) })] })), _jsx("p", { className: "text-xs text-muted-foreground mt-2", children: "Bron: models.dev \u2014 bedragen in USD per 1 miljoen tokens." }), _jsx("span", { className: "hidden", children: byModel.size })] }));
87
+ }
88
+ /* ───────────────────────── API-sleutels ───────────────────────── */
89
+ function CredentialList({ provider, credentials, onChange, }) {
90
+ const [label, setLabel] = useState('');
91
+ const [key, setKey] = useState('');
92
+ const [reveal, setReveal] = useState({});
93
+ const [testing, setTesting] = useState({});
94
+ const runTest = useServerFn(testAiCredential);
95
+ const runSync = useServerFn(syncAiCredentialModels);
96
+ const syncModelsSilently = async (credentialId) => {
97
+ try {
98
+ const res = await runSync({ data: { credentialId } });
99
+ if (!res.ok)
100
+ return res.message;
101
+ return `${res.synced} modellen beschikbaar.`;
102
+ }
103
+ catch (e) {
104
+ return e instanceof Error ? e.message : 'Modellen synchroniseren mislukt.';
105
+ }
106
+ };
107
+ const add = async () => {
108
+ if (!label.trim() || !key.trim())
109
+ return;
110
+ const { data: inserted, error } = await supabase
111
+ .from('ai_credentials')
112
+ .insert({ provider_id: provider.id, label: label.trim(), api_key: key.trim() })
113
+ .select('id')
114
+ .maybeSingle();
115
+ if (error || !inserted) {
116
+ toast.error(error?.message ?? 'Opslaan mislukt');
117
+ return;
118
+ }
119
+ setLabel('');
120
+ setKey('');
121
+ toast.success('API-sleutel opgeslagen');
122
+ onChange();
123
+ // Direct modellen ophalen zodat het model-dropdown gevuld is.
124
+ const syncMsg = await syncModelsSilently(inserted.id);
125
+ if (syncMsg)
126
+ toast.info(syncMsg);
127
+ onChange();
128
+ };
129
+ const toggle = async (c) => {
130
+ const { error } = await supabase.from('ai_credentials').update({ active: !c.active }).eq('id', c.id);
131
+ if (error)
132
+ toast.error(error.message);
133
+ else
134
+ onChange();
135
+ };
136
+ const remove = async (id) => {
137
+ if (!confirm('Deze API-sleutel verwijderen?'))
138
+ return;
139
+ const { error } = await supabase.from('ai_credentials').delete().eq('id', id);
140
+ if (error)
141
+ toast.error(error.message);
142
+ else
143
+ onChange();
144
+ };
145
+ const test = async (id) => {
146
+ setTesting(t => ({ ...t, [id]: true }));
147
+ try {
148
+ const res = await runTest({ data: { credentialId: id } });
149
+ if (res.ok) {
150
+ toast.success(res.message);
151
+ // Modellen ook meteen opnieuw synchroniseren als de key werkt.
152
+ const syncMsg = await syncModelsSilently(id);
153
+ if (syncMsg)
154
+ toast.info(syncMsg);
155
+ onChange();
156
+ }
157
+ else {
158
+ toast.error(res.message);
159
+ }
160
+ }
161
+ catch (e) {
162
+ toast.error(e instanceof Error ? e.message : 'Test mislukt');
163
+ }
164
+ finally {
165
+ setTesting(t => ({ ...t, [id]: false }));
166
+ }
167
+ };
168
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-2", children: [credentials.length === 0 && (_jsx("div", { className: "rounded-lg border border-dashed py-6 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen API-sleutels toegevoegd." }) })), credentials.map(c => (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border p-3 hover:bg-muted/50 transition-colors", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium text-sm truncate", children: c.label }), c.active
169
+ ? _jsx(Badge, { variant: "secondary", className: "text-xs", children: "Actief" })
170
+ : _jsx(Badge, { variant: "outline", className: "text-xs", children: "Uit" })] }), _jsx("p", { className: "text-xs text-muted-foreground font-mono truncate mt-0.5", children: reveal[c.id] ? c.api_key : maskKey(c.api_key) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setReveal(r => ({ ...r, [c.id]: !r[c.id] })), title: reveal[c.id] ? 'Verbergen' : 'Tonen', children: reveal[c.id] ? _jsx(EyeOff, { className: "h-4 w-4" }) : _jsx(Eye, { className: "h-4 w-4" }) }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: () => test(c.id), disabled: testing[c.id], children: [testing[c.id]
171
+ ? _jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" })
172
+ : _jsx(Zap, { className: "h-4 w-4 mr-1" }), "Testen"] }), _jsx(Switch, { checked: c.active, onCheckedChange: () => toggle(c) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => remove(c.id), title: "Verwijderen", children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }, c.id)))] }), _jsx(Separator, { className: "my-3" }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium", children: "Nieuwe API-sleutel" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "Label" }), _jsx(Input, { value: label, onChange: e => setLabel(e.target.value), placeholder: "Bijv. Productie", className: "mt-1" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "API-sleutel" }), _jsx(Input, { type: "password", value: key, onChange: e => setKey(e.target.value), placeholder: "sk-\u2026", className: "mt-1" })] })] }), _jsxs(Button, { onClick: add, size: "sm", disabled: !label || !key, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), " Sleutel toevoegen"] })] })] }));
173
+ }
@@ -0,0 +1,2 @@
1
+ export declare function AiAuthenticatieSettingsCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=AiAuthenticatieSettingsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiAuthenticatieSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AiAuthenticatieSettingsCard.tsx"],"names":[],"mappings":"AAIA,wBAAgB,2BAA2B,gCAkB1C"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@flowselections/core';
3
+ import { KeyRound } from 'lucide-react';
4
+ import { AiAuthenticatiePage } from '../AiAuthenticatiePage';
5
+ export function AiAuthenticatieSettingsCard() {
6
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(KeyRound, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "AI-Authenticatie" })] }), _jsx(CardDescription, { children: "Beheer per module welke AI-agents gebruikt worden, bekijk kosten en API sleutels." })] }), _jsx(CardContent, { children: _jsx(AiAuthenticatiePage, { embedded: true }) })] }));
7
+ }
@@ -0,0 +1,2 @@
1
+ export declare function MijnModuleSettingsCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=MijnModuleSettingsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MijnModuleSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/MijnModuleSettingsCard.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,sBAAsB,gCA+CrC"}
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/settings/MijnModuleSettingsCard.tsx — EXAMPLE SETTINGS CARD
4
+ // ============================================================================
5
+ // This is an example of a settings card for your module.
6
+ // It appears on the main settings page when registered in src/index.ts.
7
+ //
8
+ // RULES:
9
+ // - Must return a <Card> as its root element
10
+ // - Handles its own data fetching — no props are passed by the core
11
+ // - Use the same Card primitives from @flowselections/core
12
+ //
13
+ // WHAT TO DO:
14
+ // - Rename this file to match your card's purpose
15
+ // - Add your real settings UI
16
+ // - Register it in src/index.ts under settingsCards
17
+ // - Delete this file entirely if your module has no settings
18
+ // ============================================================================
19
+ import { useState } from "react";
20
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Input, Label, toast, } from "@flowselections/core";
21
+ import { Settings2 } from "lucide-react";
22
+ export function MijnModuleSettingsCard() {
23
+ // Replace with your real settings state
24
+ const [setting, setSetting] = useState("");
25
+ const [saving, setSaving] = useState(false);
26
+ const handleSave = async () => {
27
+ setSaving(true);
28
+ try {
29
+ // Example: save to a table — replace with your real query
30
+ // const { error } = await supabase.from("my_settings").upsert({ ... });
31
+ // if (error) throw error;
32
+ toast.success("Instellingen opgeslagen");
33
+ }
34
+ catch {
35
+ toast.error("Fout bij het opslaan");
36
+ }
37
+ finally {
38
+ setSaving(false);
39
+ }
40
+ };
41
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Settings2, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Module Instellingen" }), " "] }), _jsx(CardDescription, { children: "Beheer de instellingen voor deze module " })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "setting", children: "Instelling" }), _jsx(Input, { id: "setting", value: setting, onChange: (e) => setSetting(e.target.value), placeholder: "Waarde..." })] }), _jsx(Button, { onClick: handleSave, disabled: saving, children: saving ? "Opslaan..." : "Opslaan" })] })] }));
42
+ }
@@ -0,0 +1,14 @@
1
+ export type AiAdminCredential = {
2
+ id: string;
3
+ provider_id: string;
4
+ label: string;
5
+ admin_key: string;
6
+ active: boolean;
7
+ };
8
+ export declare function useAdminCredentials(): {
9
+ rows: AiAdminCredential[];
10
+ loading: boolean;
11
+ reload: () => Promise<void>;
12
+ };
13
+ export declare function findAdminCred(rows: AiAdminCredential[], providerId: string): AiAdminCredential | undefined;
14
+ //# sourceMappingURL=useAdminCredentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAdminCredentials.d.ts","sourceRoot":"","sources":["../../src/hooks/useAdminCredentials.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,mBAAmB;;;;EAalC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAE1G"}
@@ -0,0 +1,17 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { supabase } from '@flowselections/core';
3
+ export function useAdminCredentials() {
4
+ const [rows, setRows] = useState([]);
5
+ const [loading, setLoading] = useState(true);
6
+ const reload = useCallback(async () => {
7
+ setLoading(true);
8
+ const { data } = await supabase.from('ai_admin_credentials').select('*');
9
+ setRows(Array.isArray(data) ? data : []);
10
+ setLoading(false);
11
+ }, []);
12
+ useEffect(() => { void reload(); }, [reload]);
13
+ return { rows, loading, reload };
14
+ }
15
+ export function findAdminCred(rows, providerId) {
16
+ return rows.find(r => r.provider_id === providerId);
17
+ }
@@ -0,0 +1,44 @@
1
+ export type AiProvider = {
2
+ id: string;
3
+ slug: string;
4
+ name: string;
5
+ base_url: string | null;
6
+ auth_type: string;
7
+ active: boolean;
8
+ };
9
+ export type AiModel = {
10
+ id: string;
11
+ provider_id: string;
12
+ slug: string;
13
+ display_name: string;
14
+ modality: string;
15
+ context_window: number | null;
16
+ active: boolean;
17
+ is_default: boolean;
18
+ };
19
+ export type AiCredential = {
20
+ id: string;
21
+ provider_id: string;
22
+ label: string;
23
+ api_key: string;
24
+ active: boolean;
25
+ };
26
+ export type AiBinding = {
27
+ id: string;
28
+ consumer_module_id: string;
29
+ model_id: string;
30
+ credential_id: string;
31
+ notes: string | null;
32
+ label?: string | null;
33
+ is_default?: boolean;
34
+ };
35
+ export declare function useAiAdminData(): {
36
+ providers: AiProvider[];
37
+ models: AiModel[];
38
+ credentials: AiCredential[];
39
+ bindings: AiBinding[];
40
+ loading: boolean;
41
+ reload: () => Promise<void>;
42
+ };
43
+ export declare function maskKey(key: string): string;
44
+ //# sourceMappingURL=useAiAdmin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiAdmin.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiAdmin.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAGF,wBAAgB,cAAc;;;;;;;EAyB7B;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI3C"}
@@ -0,0 +1,32 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { supabase } from '@flowselections/core';
3
+ export function useAiAdminData() {
4
+ const [providers, setProviders] = useState([]);
5
+ const [models, setModels] = useState([]);
6
+ const [credentials, setCredentials] = useState([]);
7
+ const [bindings, setBindings] = useState([]);
8
+ const [loading, setLoading] = useState(true);
9
+ const reload = useCallback(async () => {
10
+ setLoading(true);
11
+ const [pRes, mRes, cRes, bRes] = await Promise.all([
12
+ supabase.from('ai_providers').select('*').order('name'),
13
+ supabase.from('ai_models').select('*').order('display_name'),
14
+ supabase.from('ai_credentials').select('*').order('label'),
15
+ supabase.from('ai_module_bindings').select('*').order('consumer_module_id'),
16
+ ]);
17
+ setProviders(Array.isArray(pRes.data) ? pRes.data : []);
18
+ setModels(Array.isArray(mRes.data) ? mRes.data : []);
19
+ setCredentials(Array.isArray(cRes.data) ? cRes.data : []);
20
+ setBindings(Array.isArray(bRes.data) ? bRes.data : []);
21
+ setLoading(false);
22
+ }, []);
23
+ useEffect(() => { reload(); }, [reload]);
24
+ return { providers, models, credentials, bindings, loading, reload };
25
+ }
26
+ export function maskKey(key) {
27
+ if (!key)
28
+ return '';
29
+ if (key.length <= 8)
30
+ return '••••';
31
+ return key.slice(0, 4) + '••••' + key.slice(-4);
32
+ }
@@ -0,0 +1,7 @@
1
+ import { type AiCostOverview } from '../lib/aiModules.functions';
2
+ export declare function useAiCostOverview(from: string, to: string): {
3
+ overview: AiCostOverview;
4
+ loading: boolean;
5
+ reload: () => Promise<void>;
6
+ };
7
+ //# sourceMappingURL=useAiCostOverview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiCostOverview.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCostOverview.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAQpF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;EAiBzD"}
@@ -0,0 +1,28 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useServerFn } from '@tanstack/react-start';
3
+ import { getAiCostOverview } from '../lib/aiModules.functions';
4
+ const EMPTY = {
5
+ from: '', to: '',
6
+ totals: { calls: 0, errors: 0, input_tokens: 0, output_tokens: 0, total_tokens: 0, cost_usd: 0, avg_cost_per_call: 0 },
7
+ byModule: [],
8
+ };
9
+ export function useAiCostOverview(from, to) {
10
+ const fn = useServerFn(getAiCostOverview);
11
+ const [overview, setOverview] = useState(EMPTY);
12
+ const [loading, setLoading] = useState(true);
13
+ const reload = useCallback(async () => {
14
+ setLoading(true);
15
+ try {
16
+ const res = await fn({ data: { from, to } });
17
+ setOverview(res && Array.isArray(res.byModule) ? res : EMPTY);
18
+ }
19
+ catch {
20
+ setOverview(EMPTY);
21
+ }
22
+ finally {
23
+ setLoading(false);
24
+ }
25
+ }, [fn, from, to]);
26
+ useEffect(() => { void reload(); }, [reload]);
27
+ return { overview, loading, reload };
28
+ }
@@ -0,0 +1,21 @@
1
+ import { type AiStreamHandlers } from '../lib/aiChat.stream';
2
+ import type { AiChatBindingInfo, AiChatMessage, AiChatOptions, AiChatResult } from '../lib/aiChat.types';
3
+ export type UseAiForGoalReturn = {
4
+ ready: boolean;
5
+ loading: boolean;
6
+ provider: AiChatBindingInfo['provider'];
7
+ model: AiChatBindingInfo['model'];
8
+ reason?: string;
9
+ reload: () => Promise<void>;
10
+ run: (messages: AiChatMessage[], options?: AiChatOptions) => Promise<AiChatResult>;
11
+ stream: (messages: AiChatMessage[], handlers: AiStreamHandlers, options?: AiChatOptions) => Promise<{
12
+ ok: boolean;
13
+ text: string;
14
+ }>;
15
+ };
16
+ /**
17
+ * Consumer-hook: geeft de AI-agent terug die aan een module is gekoppeld.
18
+ * `consumerModuleId` is de `module_key` uit de AI-instellingen.
19
+ */
20
+ export declare function useAiForGoal(consumerModuleId: string): UseAiForGoalReturn;
21
+ //# sourceMappingURL=useAiForGoal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiForGoal.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiForGoal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEzG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACxC,KAAK,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,GAAG,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACnF,MAAM,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpI,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,gBAAgB,EAAE,MAAM,GAAG,kBAAkB,CA4CzE"}
@@ -0,0 +1,40 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useServerFn } from '@tanstack/react-start';
3
+ import { runAiChat, describeAiBinding } from '../lib/aiChat.functions';
4
+ import { streamAiChat } from '../lib/aiChat.stream';
5
+ /**
6
+ * Consumer-hook: geeft de AI-agent terug die aan een module is gekoppeld.
7
+ * `consumerModuleId` is de `module_key` uit de AI-instellingen.
8
+ */
9
+ export function useAiForGoal(consumerModuleId) {
10
+ const describe = useServerFn(describeAiBinding);
11
+ const chat = useServerFn(runAiChat);
12
+ const [info, setInfo] = useState({ ready: false, provider: null, model: null });
13
+ const [loading, setLoading] = useState(true);
14
+ const reload = useCallback(async () => {
15
+ setLoading(true);
16
+ try {
17
+ const res = await describe({ data: { consumerModuleId } });
18
+ setInfo(res ?? { ready: false, provider: null, model: null });
19
+ }
20
+ catch (e) {
21
+ setInfo({ ready: false, provider: null, model: null, reason: e instanceof Error ? e.message : 'onbekende fout' });
22
+ }
23
+ finally {
24
+ setLoading(false);
25
+ }
26
+ }, [describe, consumerModuleId]);
27
+ useEffect(() => { void reload(); }, [reload]);
28
+ const run = useCallback((messages, options) => chat({ data: { consumerModuleId, messages, options } }), [chat, consumerModuleId]);
29
+ const stream = useCallback((messages, handlers, options) => streamAiChat({ consumerModuleId, messages, options, ...handlers }), [consumerModuleId]);
30
+ return {
31
+ ready: info.ready,
32
+ loading,
33
+ provider: info.provider,
34
+ model: info.model,
35
+ reason: info.reason,
36
+ reload,
37
+ run,
38
+ stream,
39
+ };
40
+ }
@@ -0,0 +1,11 @@
1
+ import { type UseAiForGoalReturn } from './useAiForGoal';
2
+ export type UseAiForModuleReturn = UseAiForGoalReturn;
3
+ /**
4
+ * Consumer-hook voor andere modules.
5
+ * Geeft de AI-agent terug die in AI-instellingen aan deze `module_key` is gekoppeld.
6
+ *
7
+ * const ai = useAiForModule('mailbox-bestellingen');
8
+ * const res = await ai.run([{ role: 'user', content: 'Hallo' }]);
9
+ */
10
+ export declare function useAiForModule(moduleKey: string): UseAiForModuleReturn;
11
+ //# sourceMappingURL=useAiForModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiForModule.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiForModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAEtD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,CAEtE"}
@@ -0,0 +1,11 @@
1
+ import { useAiForGoal } from './useAiForGoal';
2
+ /**
3
+ * Consumer-hook voor andere modules.
4
+ * Geeft de AI-agent terug die in AI-instellingen aan deze `module_key` is gekoppeld.
5
+ *
6
+ * const ai = useAiForModule('mailbox-bestellingen');
7
+ * const res = await ai.run([{ role: 'user', content: 'Hallo' }]);
8
+ */
9
+ export function useAiForModule(moduleKey) {
10
+ return useAiForGoal(moduleKey);
11
+ }
@@ -0,0 +1,8 @@
1
+ import { type AiGoal } from '../lib/aiGoals.functions';
2
+ export type { AiGoal };
3
+ export declare function useAiGoals(): {
4
+ goals: AiGoal[];
5
+ loading: boolean;
6
+ reload: () => Promise<void>;
7
+ };
8
+ //# sourceMappingURL=useAiGoals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiGoals.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiGoals.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAEpE,YAAY,EAAE,MAAM,EAAE,CAAC;AAEvB,wBAAgB,UAAU;;;;EAczB"}
@@ -0,0 +1,22 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useServerFn } from '@tanstack/react-start';
3
+ import { listAiGoals } from '../lib/aiGoals.functions';
4
+ export function useAiGoals() {
5
+ const fn = useServerFn(listAiGoals);
6
+ const [goals, setGoals] = useState([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const reload = useCallback(async () => {
9
+ setLoading(true);
10
+ try {
11
+ setGoals(await fn());
12
+ }
13
+ catch {
14
+ setGoals([]);
15
+ }
16
+ finally {
17
+ setLoading(false);
18
+ }
19
+ }, [fn]);
20
+ useEffect(() => { void reload(); }, [reload]);
21
+ return { goals, loading, reload };
22
+ }
@@ -0,0 +1,15 @@
1
+ import { type AiModuleRow, type AiMutationResult } from '../lib/aiModules.functions';
2
+ export type { AiModuleRow };
3
+ export declare function useAiModules(): {
4
+ modules: AiModuleRow[];
5
+ loading: boolean;
6
+ reload: () => Promise<void>;
7
+ save: (input: {
8
+ module_key?: string;
9
+ module_name: string;
10
+ ai_enabled: boolean;
11
+ }) => Promise<AiMutationResult>;
12
+ setEnabled: (module_key: string, ai_enabled: boolean) => Promise<AiMutationResult>;
13
+ remove: (module_key: string) => Promise<AiMutationResult>;
14
+ };
15
+ //# sourceMappingURL=useAiModules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiModules.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiModules.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,WAAW,EAAE,KAAK,gBAAgB,EACxC,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,wBAAgB,YAAY;;;;kBAmBjB;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,KACvE,OAAO,CAAC,gBAAgB,CAAC;6BAUsB,MAAM,cAAc,OAAO,KAAG,OAAO,CAAC,gBAAgB,CAAC;yBAU3D,MAAM,KAAG,OAAO,CAAC,gBAAgB,CAAC;EAWjF"}
@@ -0,0 +1,59 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useServerFn } from '@tanstack/react-start';
3
+ import { listAiModules, saveAiModule, setAiModuleEnabled, deleteAiModule, } from '../lib/aiModules.functions';
4
+ export function useAiModules() {
5
+ const listFn = useServerFn(listAiModules);
6
+ const saveFn = useServerFn(saveAiModule);
7
+ const toggleFn = useServerFn(setAiModuleEnabled);
8
+ const deleteFn = useServerFn(deleteAiModule);
9
+ const [modules, setModules] = useState([]);
10
+ const [loading, setLoading] = useState(true);
11
+ const reload = useCallback(async () => {
12
+ setLoading(true);
13
+ try {
14
+ const rows = await listFn();
15
+ setModules(Array.isArray(rows) ? rows : []);
16
+ }
17
+ catch {
18
+ setModules([]);
19
+ }
20
+ finally {
21
+ setLoading(false);
22
+ }
23
+ }, [listFn]);
24
+ useEffect(() => { void reload(); }, [reload]);
25
+ const save = useCallback(async (input) => {
26
+ try {
27
+ const res = await saveFn({ data: input });
28
+ if (res?.ok)
29
+ await reload();
30
+ return res ?? { ok: false, message: 'Opslaan mislukt.' };
31
+ }
32
+ catch (e) {
33
+ return { ok: false, message: e instanceof Error ? e.message : 'Opslaan mislukt.' };
34
+ }
35
+ }, [saveFn, reload]);
36
+ const setEnabled = useCallback(async (module_key, ai_enabled) => {
37
+ try {
38
+ const res = await toggleFn({ data: { module_key, ai_enabled } });
39
+ if (res?.ok)
40
+ await reload();
41
+ return res ?? { ok: false, message: 'Opslaan mislukt.' };
42
+ }
43
+ catch (e) {
44
+ return { ok: false, message: e instanceof Error ? e.message : 'Opslaan mislukt.' };
45
+ }
46
+ }, [toggleFn, reload]);
47
+ const remove = useCallback(async (module_key) => {
48
+ try {
49
+ const res = await deleteFn({ data: { module_key } });
50
+ if (res?.ok)
51
+ await reload();
52
+ return res ?? { ok: false, message: 'Verwijderen mislukt.' };
53
+ }
54
+ catch (e) {
55
+ return { ok: false, message: e instanceof Error ? e.message : 'Verwijderen mislukt.' };
56
+ }
57
+ }, [deleteFn, reload]);
58
+ return { modules, loading, reload, save, setEnabled, remove };
59
+ }