@mvriu5/payload-ai 1.3.2 → 1.4.0
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/README.md +76 -13
- package/dist/ai/providerOptions.d.ts +24 -1
- package/dist/ai/providerOptions.js +81 -0
- package/dist/ai/providerRuntime.d.ts +2 -1
- package/dist/ai/providerRuntime.js +5 -2
- package/dist/ai/tokenUsage.d.ts +38 -0
- package/dist/ai/tokenUsage.js +106 -0
- package/dist/components/Icons.d.ts +1 -0
- package/dist/components/Icons.js +15 -0
- package/dist/components/action-toast/ActionToast.d.ts +5 -1
- package/dist/components/action-toast/ActionToast.js +57 -21
- package/dist/components/ai-input/AIInput.d.ts +4 -1
- package/dist/components/ai-input/AIInput.js +165 -50
- package/dist/components/ai-input/AIInput.module.css +40 -2
- package/dist/components/audit-log-list/AuditLogList.js +9 -2
- package/dist/components/dashboard/Dashboard.js +3 -1
- package/dist/components/hooks/useAIChatStream.d.ts +12 -1
- package/dist/components/hooks/useAIChatStream.js +13 -4
- package/dist/components/hooks/useAISettings.d.ts +6 -4
- package/dist/components/hooks/useAISettings.js +62 -22
- package/dist/components/hooks/usePluginConfig.d.ts +8 -20
- package/dist/components/hooks/usePluginConfig.js +9 -2
- package/dist/components/text-shimmer/TextShimmer.d.ts +9 -0
- package/dist/components/text-shimmer/TextShimmer.js +34 -0
- package/dist/components/text-shimmer/TextShimmer.module.css +19 -0
- package/dist/exports/client.d.ts +2 -0
- package/dist/exports/client.js +2 -0
- package/dist/handlers/chatHandler.d.ts +4 -1
- package/dist/handlers/chatHandler.js +198 -17
- package/dist/index.d.ts +6 -1
- package/dist/index.js +92 -5
- package/package.json +18 -16
|
@@ -15,16 +15,27 @@ export type MediaAttachment = {
|
|
|
15
15
|
type: "media";
|
|
16
16
|
url?: string;
|
|
17
17
|
};
|
|
18
|
-
export
|
|
18
|
+
export type DocumentScope = {
|
|
19
|
+
collection: string;
|
|
20
|
+
id?: string;
|
|
21
|
+
type: "collection";
|
|
22
|
+
} | {
|
|
23
|
+
slug: string;
|
|
24
|
+
type: "global";
|
|
25
|
+
};
|
|
26
|
+
export declare const useAIChatStream: ({ apiRoute, clearInput, documentScope, mentionsRef, prompt, selectedModel, selectedProvider, }: {
|
|
19
27
|
apiRoute: string;
|
|
20
28
|
clearInput: () => void;
|
|
29
|
+
documentScope?: DocumentScope;
|
|
21
30
|
mentionsRef: RefObject<Mention[]>;
|
|
22
31
|
prompt: string;
|
|
23
32
|
selectedModel: string;
|
|
33
|
+
selectedProvider: string | null;
|
|
24
34
|
}) => {
|
|
25
35
|
dismissChat: () => void;
|
|
26
36
|
error: string;
|
|
27
37
|
isLoading: boolean;
|
|
38
|
+
setIsLoading: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
28
39
|
proposals: ActionProposal[];
|
|
29
40
|
resetChatState: () => void;
|
|
30
41
|
response: string;
|
|
@@ -54,7 +54,7 @@ const getChatDebugMessage = (debugInfo)=>{
|
|
|
54
54
|
}
|
|
55
55
|
return getDebugReasonLabel(debugInfo.reason);
|
|
56
56
|
};
|
|
57
|
-
export const useAIChatStream = ({ apiRoute, clearInput, mentionsRef, prompt, selectedModel })=>{
|
|
57
|
+
export const useAIChatStream = ({ apiRoute, clearInput, documentScope, mentionsRef, prompt, selectedModel, selectedProvider })=>{
|
|
58
58
|
const [response, setResponse] = useState("");
|
|
59
59
|
const [tokenUsage, setTokenUsage] = useState(null);
|
|
60
60
|
const [error, setError] = useState("");
|
|
@@ -76,7 +76,6 @@ export const useAIChatStream = ({ apiRoute, clearInput, mentionsRef, prompt, sel
|
|
|
76
76
|
const submit = useCallback(async ({ attachments = [] } = {})=>{
|
|
77
77
|
const trimmedPrompt = prompt.trim();
|
|
78
78
|
if (!trimmedPrompt) return;
|
|
79
|
-
setIsLoading(true);
|
|
80
79
|
resetChatState();
|
|
81
80
|
try {
|
|
82
81
|
const res = await fetch(formatAdminURL({
|
|
@@ -87,15 +86,22 @@ export const useAIChatStream = ({ apiRoute, clearInput, mentionsRef, prompt, sel
|
|
|
87
86
|
...attachments.length > 0 ? {
|
|
88
87
|
attachments
|
|
89
88
|
} : {},
|
|
89
|
+
...documentScope ? {
|
|
90
|
+
documentScope
|
|
91
|
+
} : {},
|
|
90
92
|
mentions: mentionsRef.current,
|
|
91
93
|
model: selectedModel,
|
|
92
|
-
prompt: trimmedPrompt
|
|
94
|
+
prompt: trimmedPrompt,
|
|
95
|
+
...selectedProvider ? {
|
|
96
|
+
provider: selectedProvider
|
|
97
|
+
} : {}
|
|
93
98
|
}),
|
|
94
99
|
headers: {
|
|
95
100
|
"Content-Type": "application/json"
|
|
96
101
|
},
|
|
97
102
|
method: "POST"
|
|
98
103
|
});
|
|
104
|
+
setIsLoading(false);
|
|
99
105
|
if (!res.ok) {
|
|
100
106
|
const result = await res.json().catch(()=>null);
|
|
101
107
|
throw new Error(result?.error || "AI request failed");
|
|
@@ -195,15 +201,18 @@ export const useAIChatStream = ({ apiRoute, clearInput, mentionsRef, prompt, sel
|
|
|
195
201
|
}, [
|
|
196
202
|
apiRoute,
|
|
197
203
|
clearInput,
|
|
204
|
+
documentScope,
|
|
198
205
|
mentionsRef,
|
|
199
206
|
prompt,
|
|
200
207
|
resetChatState,
|
|
201
|
-
selectedModel
|
|
208
|
+
selectedModel,
|
|
209
|
+
selectedProvider
|
|
202
210
|
]);
|
|
203
211
|
return {
|
|
204
212
|
dismissChat,
|
|
205
213
|
error,
|
|
206
214
|
isLoading,
|
|
215
|
+
setIsLoading,
|
|
207
216
|
proposals,
|
|
208
217
|
resetChatState,
|
|
209
218
|
response,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AIProviderProfile } from "../../ai/providerOptions.js";
|
|
2
2
|
interface UseAISettingsOptions {
|
|
3
3
|
adminUserSlug?: string;
|
|
4
4
|
apiRoute: string;
|
|
5
|
-
|
|
5
|
+
managedProviders: boolean;
|
|
6
|
+
providerProfiles: AIProviderProfile[];
|
|
6
7
|
}
|
|
7
|
-
export declare const useAISettings: ({ adminUserSlug, apiRoute,
|
|
8
|
+
export declare const useAISettings: ({ adminUserSlug, apiRoute, managedProviders, providerProfiles }: UseAISettingsOptions) => {
|
|
8
9
|
selectedModel: string;
|
|
9
10
|
setSelectedModel: (model: string) => void;
|
|
10
|
-
|
|
11
|
+
setSelectedProviderModel: (provider: string, model: string) => void;
|
|
12
|
+
settingsProvider: string | null;
|
|
11
13
|
};
|
|
12
14
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { formatAdminURL } from "payload/shared";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
import { isAIProvider } from "../../ai/providerOptions.js";
|
|
5
4
|
import { isAbortError } from "../../payload/shared.js";
|
|
5
|
+
const managedSelectionKey = "payload-ai:selected-managed-model";
|
|
6
6
|
const getStoredModelKey = (provider)=>`payload-ai:selected-model:${provider}`;
|
|
7
7
|
const getStoredModel = (provider)=>{
|
|
8
8
|
if (typeof window === "undefined") return null;
|
|
@@ -12,6 +12,19 @@ const storeModel = (provider, model)=>{
|
|
|
12
12
|
if (typeof window === "undefined") return;
|
|
13
13
|
window.localStorage.setItem(getStoredModelKey(provider), model);
|
|
14
14
|
};
|
|
15
|
+
const getStoredManagedSelection = ()=>{
|
|
16
|
+
if (typeof window === "undefined") return null;
|
|
17
|
+
try {
|
|
18
|
+
const value = JSON.parse(window.localStorage.getItem(managedSelectionKey) || "null");
|
|
19
|
+
return value && typeof value.provider === "string" && typeof value.model === "string" ? value : null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const storeManagedSelection = (selection)=>{
|
|
25
|
+
if (typeof window === "undefined") return;
|
|
26
|
+
window.localStorage.setItem(managedSelectionKey, JSON.stringify(selection));
|
|
27
|
+
};
|
|
15
28
|
const fetchCurrentUserProvider = async ({ adminUserSlug, apiRoute, signal })=>{
|
|
16
29
|
const res = await fetch(formatAdminURL({
|
|
17
30
|
apiRoute,
|
|
@@ -21,20 +34,44 @@ const fetchCurrentUserProvider = async ({ adminUserSlug, apiRoute, signal })=>{
|
|
|
21
34
|
});
|
|
22
35
|
if (!res.ok) return null;
|
|
23
36
|
const result = await res.json();
|
|
24
|
-
|
|
25
|
-
return provider && isAIProvider(provider) ? provider : null;
|
|
37
|
+
return result.user?.aiProvider || null;
|
|
26
38
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const [
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
const hasModel = (profile, model)=>profile.models.some((option)=>option.value === model);
|
|
40
|
+
export const useAISettings = ({ adminUserSlug, apiRoute, managedProviders, providerProfiles })=>{
|
|
41
|
+
const [settingsProvider, setSettingsProvider] = useState(null);
|
|
42
|
+
const [selectedModel, setSelectedModelState] = useState("");
|
|
43
|
+
const setSelectedProviderModel = (provider, model)=>{
|
|
44
|
+
const profile = providerProfiles.find((candidate)=>candidate.id === provider);
|
|
45
|
+
if (!profile || !hasModel(profile, model)) return;
|
|
46
|
+
setSettingsProvider(provider);
|
|
47
|
+
setSelectedModelState(model);
|
|
48
|
+
if (managedProviders) {
|
|
49
|
+
storeManagedSelection({
|
|
50
|
+
model,
|
|
51
|
+
provider
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
storeModel(provider, model);
|
|
34
55
|
}
|
|
35
56
|
};
|
|
57
|
+
const setSelectedModel = (model)=>{
|
|
58
|
+
if (settingsProvider) setSelectedProviderModel(settingsProvider, model);
|
|
59
|
+
};
|
|
36
60
|
useEffect(()=>{
|
|
37
|
-
if (
|
|
61
|
+
if (managedProviders) {
|
|
62
|
+
const storedSelection = getStoredManagedSelection();
|
|
63
|
+
const storedProfile = storedSelection ? providerProfiles.find((profile)=>profile.id === storedSelection.provider && hasModel(profile, storedSelection.model)) : null;
|
|
64
|
+
const profile = storedProfile || providerProfiles[0];
|
|
65
|
+
const model = storedProfile && storedSelection ? storedSelection.model : profile?.defaultModel;
|
|
66
|
+
setSettingsProvider(profile?.id || null);
|
|
67
|
+
setSelectedModelState(model || "");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!adminUserSlug) {
|
|
71
|
+
setSettingsProvider(null);
|
|
72
|
+
setSelectedModelState("");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
38
75
|
const abortController = new AbortController();
|
|
39
76
|
const loadCurrentUserProvider = async ()=>{
|
|
40
77
|
try {
|
|
@@ -43,17 +80,19 @@ export const useAISettings = ({ adminUserSlug, apiRoute, defaultModels })=>{
|
|
|
43
80
|
apiRoute,
|
|
44
81
|
signal: abortController.signal
|
|
45
82
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
83
|
+
const profile = providerProfiles.find((candidate)=>candidate.id === provider);
|
|
84
|
+
if (!profile) {
|
|
85
|
+
setSettingsProvider(null);
|
|
86
|
+
setSelectedModelState("");
|
|
49
87
|
return;
|
|
50
88
|
}
|
|
51
|
-
|
|
52
|
-
|
|
89
|
+
const storedModel = getStoredModel(profile.id);
|
|
90
|
+
setSettingsProvider(profile.id);
|
|
91
|
+
setSelectedModelState(storedModel && hasModel(profile, storedModel) ? storedModel : profile.defaultModel);
|
|
53
92
|
} catch (err) {
|
|
54
93
|
if (isAbortError(err)) return;
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
setSettingsProvider(null);
|
|
95
|
+
setSelectedModelState("");
|
|
57
96
|
}
|
|
58
97
|
};
|
|
59
98
|
void loadCurrentUserProvider();
|
|
@@ -61,12 +100,13 @@ export const useAISettings = ({ adminUserSlug, apiRoute, defaultModels })=>{
|
|
|
61
100
|
}, [
|
|
62
101
|
adminUserSlug,
|
|
63
102
|
apiRoute,
|
|
64
|
-
|
|
103
|
+
managedProviders,
|
|
104
|
+
providerProfiles
|
|
65
105
|
]);
|
|
66
|
-
const settingsProvider = adminUserSlug ? fetchedProvider : null;
|
|
67
106
|
return {
|
|
68
|
-
selectedModel
|
|
69
|
-
setSelectedModel
|
|
107
|
+
selectedModel,
|
|
108
|
+
setSelectedModel,
|
|
109
|
+
setSelectedProviderModel,
|
|
70
110
|
settingsProvider
|
|
71
111
|
};
|
|
72
112
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AIProviderProfile } from "../../ai/providerOptions.js";
|
|
1
2
|
type MediaConfig = {
|
|
2
3
|
acceptedMimeTypes?: string[];
|
|
3
4
|
collectionSlug: string;
|
|
@@ -21,32 +22,19 @@ export declare const usePluginConfig: (config: {
|
|
|
21
22
|
aiModelConfig: {
|
|
22
23
|
defaults: Record<"claude" | "google" | "mistral" | "openai" | "openrouter", string>;
|
|
23
24
|
providers: {
|
|
24
|
-
claude:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
label: string;
|
|
30
|
-
value: string;
|
|
31
|
-
}[];
|
|
32
|
-
mistral: {
|
|
33
|
-
label: string;
|
|
34
|
-
value: string;
|
|
35
|
-
}[];
|
|
36
|
-
openai: {
|
|
37
|
-
label: string;
|
|
38
|
-
value: string;
|
|
39
|
-
}[];
|
|
40
|
-
openrouter: {
|
|
41
|
-
label: string;
|
|
42
|
-
value: string;
|
|
43
|
-
}[];
|
|
25
|
+
claude: import("../../ai/providerOptions.js").AIProviderModelOption[];
|
|
26
|
+
google: import("../../ai/providerOptions.js").AIProviderModelOption[];
|
|
27
|
+
mistral: import("../../ai/providerOptions.js").AIProviderModelOption[];
|
|
28
|
+
openai: import("../../ai/providerOptions.js").AIProviderModelOption[];
|
|
29
|
+
openrouter: import("../../ai/providerOptions.js").AIProviderModelOption[];
|
|
44
30
|
};
|
|
45
31
|
};
|
|
46
32
|
defaultLocale: string | undefined;
|
|
47
33
|
enabledCollectionSlugSet: Set<string> | null;
|
|
48
34
|
isCollectionMentionEnabled: (slug: string) => boolean;
|
|
49
35
|
locales: LocaleConfig[];
|
|
36
|
+
managedProviders: boolean;
|
|
50
37
|
media: MediaConfig | undefined;
|
|
38
|
+
providerProfiles: AIProviderProfile[];
|
|
51
39
|
};
|
|
52
40
|
export {};
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import { getResolvedAIModelConfig } from "../../ai/providerOptions.js";
|
|
2
|
+
import { getLegacyAIProviderProfiles, getResolvedAIModelConfig } from "../../ai/providerOptions.js";
|
|
3
3
|
export const usePluginConfig = (config)=>{
|
|
4
4
|
const pluginConfig = config.admin?.custom?.payloadAiPlugin;
|
|
5
5
|
const aiModelConfig = useMemo(()=>getResolvedAIModelConfig(pluginConfig?.models), [
|
|
6
6
|
pluginConfig?.models
|
|
7
7
|
]);
|
|
8
|
+
const providerProfiles = useMemo(()=>pluginConfig?.managedProviders ? pluginConfig.providers || [] : getLegacyAIProviderProfiles(pluginConfig?.models), [
|
|
9
|
+
pluginConfig?.managedProviders,
|
|
10
|
+
pluginConfig?.models,
|
|
11
|
+
pluginConfig?.providers
|
|
12
|
+
]);
|
|
8
13
|
const enabledCollectionSlugSet = useMemo(()=>pluginConfig?.collectionSlugs ? new Set(pluginConfig.collectionSlugs) : null, [
|
|
9
14
|
pluginConfig?.collectionSlugs
|
|
10
15
|
]);
|
|
@@ -15,6 +20,8 @@ export const usePluginConfig = (config)=>{
|
|
|
15
20
|
enabledCollectionSlugSet,
|
|
16
21
|
isCollectionMentionEnabled: (slug)=>!enabledCollectionSlugSet || enabledCollectionSlugSet.has(slug),
|
|
17
22
|
locales: localization?.locales ?? [],
|
|
18
|
-
|
|
23
|
+
managedProviders: Boolean(pluginConfig?.managedProviders),
|
|
24
|
+
media: pluginConfig?.media,
|
|
25
|
+
providerProfiles
|
|
19
26
|
};
|
|
20
27
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type TextShimmerProps = {
|
|
3
|
+
children: string;
|
|
4
|
+
as?: React.ElementType;
|
|
5
|
+
className?: string;
|
|
6
|
+
duration?: number;
|
|
7
|
+
spread?: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function TextShimmer({ children, as: Component, className, duration, spread }: TextShimmerProps): React.JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import React, { useMemo } from "react";
|
|
4
|
+
import styles from "./TextShimmer.module.css";
|
|
5
|
+
export function TextShimmer({ children, as: Component = "p", className, duration = 1, spread = 2 }) {
|
|
6
|
+
const dynamicSpread = useMemo(()=>{
|
|
7
|
+
return children.length * spread;
|
|
8
|
+
}, [
|
|
9
|
+
children,
|
|
10
|
+
spread
|
|
11
|
+
]);
|
|
12
|
+
return /*#__PURE__*/ _jsx(Component, {
|
|
13
|
+
className: `${className ?? ""} ${styles.textShimmer}`,
|
|
14
|
+
style: {
|
|
15
|
+
position: "relative",
|
|
16
|
+
display: "inline-block",
|
|
17
|
+
backgroundImage: `
|
|
18
|
+
linear-gradient(
|
|
19
|
+
90deg,
|
|
20
|
+
transparent calc(50% - ${dynamicSpread}px),
|
|
21
|
+
var(--shimmer-color, #000),
|
|
22
|
+
transparent calc(50% + ${dynamicSpread}px)
|
|
23
|
+
),
|
|
24
|
+
linear-gradient(var(--base-color, #a1a1aa), var(--base-color, #a1a1aa))
|
|
25
|
+
`,
|
|
26
|
+
backgroundSize: "250% 100%, auto",
|
|
27
|
+
backgroundRepeat: "no-repeat",
|
|
28
|
+
backgroundClip: "text",
|
|
29
|
+
WebkitBackgroundClip: "text",
|
|
30
|
+
color: "transparent"
|
|
31
|
+
},
|
|
32
|
+
children: children
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.textShimmer {
|
|
2
|
+
animation: textShimmer 2s linear infinite;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
@keyframes textShimmer {
|
|
6
|
+
0% {
|
|
7
|
+
background-position: 250% 0;
|
|
8
|
+
}
|
|
9
|
+
100% {
|
|
10
|
+
background-position: -250% 0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
:root {
|
|
16
|
+
--base-color: #71717a;
|
|
17
|
+
--shimmer-color: #ffffff;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/exports/client.d.ts
CHANGED
package/dist/exports/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PayloadHandler } from "payload";
|
|
2
2
|
import { type AIActionSignature } from "../ai/proposalSigning.js";
|
|
3
|
-
import { type AIModelConfig } from "../ai/providerOptions.js";
|
|
3
|
+
import { type AIModelConfig, type ResolvedAIProviderConfig } from "../ai/providerOptions.js";
|
|
4
|
+
import { type ResolvedMaxTokenUsageOptions } from "../ai/tokenUsage.js";
|
|
4
5
|
import { type ResolvedCollectionPermissionMap } from "../payload/collectionPermissions.js";
|
|
5
6
|
type LocalizedDataInput = Record<string, Record<string, unknown>>;
|
|
6
7
|
type ProposalWritePayload = {
|
|
@@ -36,7 +37,9 @@ type ChatOptions = {
|
|
|
36
37
|
allowUserApiKeys?: boolean;
|
|
37
38
|
collections?: ResolvedCollectionPermissionMap;
|
|
38
39
|
maxOutputTokens?: number;
|
|
40
|
+
maxTokenUsage?: ResolvedMaxTokenUsageOptions;
|
|
39
41
|
models?: AIModelConfig;
|
|
42
|
+
providers?: ResolvedAIProviderConfig[];
|
|
40
43
|
};
|
|
41
44
|
export declare const createChatHandler: (options?: ChatOptions) => PayloadHandler;
|
|
42
45
|
export {};
|