@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
package/README.md
CHANGED
|
@@ -39,19 +39,21 @@ export default buildConfig({
|
|
|
39
39
|
});
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Without centrally configured providers, the plugin adds two fields to the configured Payload admin user collection:
|
|
43
43
|
|
|
44
44
|
- `aiProvider`
|
|
45
45
|
- `aiApiKey`
|
|
46
46
|
|
|
47
47
|
Users can select their provider and optionally store their own API key in account settings. If no account-level key is set, the chat endpoint uses provider environment variables.
|
|
48
48
|
|
|
49
|
+
When `providers` is configured, provider selection and API keys are managed centrally. The plugin does not add either field to the user collection. All configured provider models are grouped by provider in the AI input.
|
|
50
|
+
|
|
49
51
|
## Options
|
|
50
52
|
|
|
51
53
|
```ts
|
|
52
|
-
import type {
|
|
54
|
+
import type { PayloadAIPluginOptions } from "@mvriu5/payload-ai";
|
|
53
55
|
|
|
54
|
-
const options:
|
|
56
|
+
const options: PayloadAIPluginOptions = {
|
|
55
57
|
allowUserApiKeys: false,
|
|
56
58
|
collections: {
|
|
57
59
|
media: {
|
|
@@ -72,22 +74,41 @@ const options: PayloadAiPluginOptions = {
|
|
|
72
74
|
acceptedMimeTypes: ["image/*"],
|
|
73
75
|
maxFileSize: 10 * 1024 * 1024,
|
|
74
76
|
},
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
providers: [
|
|
78
|
+
{
|
|
79
|
+
id: "company-openai",
|
|
80
|
+
label: "Company OpenAI",
|
|
81
|
+
provider: "openai",
|
|
82
|
+
apiKey: process.env.COMPANY_OPENAI_API_KEY,
|
|
83
|
+
models: [
|
|
82
84
|
{ label: "GPT-4.1 Mini", value: "gpt-4.1-mini" },
|
|
83
85
|
{ label: "GPT-4.1", value: "gpt-4.1" },
|
|
84
86
|
],
|
|
87
|
+
defaultModel: "gpt-4.1-mini",
|
|
85
88
|
},
|
|
86
|
-
|
|
89
|
+
{
|
|
90
|
+
id: "ollama",
|
|
91
|
+
label: "Local Ollama",
|
|
92
|
+
provider: "openai",
|
|
93
|
+
baseURL: "http://localhost:11434/v1",
|
|
94
|
+
apiKey: "ollama",
|
|
95
|
+
models: [
|
|
96
|
+
{ label: "Llama 3.3", value: "llama3.3" },
|
|
97
|
+
{ label: "Qwen 3", value: "qwen3" },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
87
101
|
maxOutputTokens: 1200,
|
|
102
|
+
maxTokenUsage: {
|
|
103
|
+
type: "user",
|
|
104
|
+
perDay: 50_000,
|
|
105
|
+
perWeek: 250_000,
|
|
106
|
+
},
|
|
88
107
|
};
|
|
89
108
|
```
|
|
90
109
|
|
|
110
|
+
`models` configures model choices for the user-selected provider mode. Use `providers` instead when provider selection, credentials, and endpoints should be managed centrally.
|
|
111
|
+
|
|
91
112
|
### `collections`
|
|
92
113
|
|
|
93
114
|
Restricts AI read and write proposals to enabled collection slugs. If omitted, all non-internal Payload collections are available.
|
|
@@ -180,6 +201,27 @@ OpenRouter includes these built-in model options:
|
|
|
180
201
|
- `anthropic/claude-3.5-sonnet`
|
|
181
202
|
- `google/gemini-2.0-flash-001`
|
|
182
203
|
|
|
204
|
+
### `providers`
|
|
205
|
+
|
|
206
|
+
Enables centrally managed provider profiles. Each profile supports:
|
|
207
|
+
|
|
208
|
+
- `id`: unique provider profile identifier
|
|
209
|
+
- `label`: provider group label shown in the model select
|
|
210
|
+
- `provider`: SDK adapter (`openai`, `openrouter`, `claude`, `mistral`, or `google`)
|
|
211
|
+
- `models`: allowed model list
|
|
212
|
+
- `defaultModel`: optional default; otherwise the first configured model is used
|
|
213
|
+
- `baseURL`: optional custom provider URL
|
|
214
|
+
- `apiKey`: optional server-side API key
|
|
215
|
+
|
|
216
|
+
When at least one profile is configured:
|
|
217
|
+
|
|
218
|
+
- `aiProvider` and `aiApiKey` are not added to the admin user collection.
|
|
219
|
+
- Models from every configured profile are displayed in provider groups.
|
|
220
|
+
- The chat endpoint validates both provider IDs and model IDs against this configuration.
|
|
221
|
+
- User-level provider and API key values are ignored.
|
|
222
|
+
|
|
223
|
+
`apiKey` and `baseURL` remain server-side and are not included in Payload's public admin configuration. Custom endpoints must implement the protocol expected by their selected SDK adapter. For Ollama, vLLM, and similar APIs, use the `openai` adapter with an OpenAI-compatible `/v1` endpoint.
|
|
224
|
+
|
|
183
225
|
### `maxOutputTokens`
|
|
184
226
|
|
|
185
227
|
Controls the maximum number of output tokens the chat endpoint may generate per request. If omitted, the plugin uses `700`.
|
|
@@ -190,6 +232,24 @@ payloadAiPlugin({
|
|
|
190
232
|
})
|
|
191
233
|
```
|
|
192
234
|
|
|
235
|
+
### `maxTokenUsage`
|
|
236
|
+
|
|
237
|
+
Limits total AI tokens across rolling 24-hour and 7-day windows.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
payloadAiPlugin({
|
|
241
|
+
maxTokenUsage: {
|
|
242
|
+
type: "user",
|
|
243
|
+
perDay: 50_000,
|
|
244
|
+
perWeek: 250_000,
|
|
245
|
+
},
|
|
246
|
+
})
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Use `type: "user"` to enforce separate budgets per authenticated user, or `type: "site"` to share one budget across the entire Payload installation. `perDay` and `perWeek` are optional individually, but at least one must be configured.
|
|
250
|
+
|
|
251
|
+
Completed model usage is stored in the hidden `payload-ai-usage` collection. Requests made after a limit is reached return HTTP `429`. Because providers report token usage after completion, the request that crosses a limit is allowed to finish and subsequent requests are blocked.
|
|
252
|
+
|
|
193
253
|
### `allowUserApiKeys`
|
|
194
254
|
|
|
195
255
|
Controls whether the plugin adds an `aiApiKey` field to the admin user collection.
|
|
@@ -202,6 +262,8 @@ payloadAiPlugin({
|
|
|
202
262
|
|
|
203
263
|
When disabled, users can still select an AI provider, but API keys must come from environment variables.
|
|
204
264
|
|
|
265
|
+
This option only applies when `providers` is not configured. Managed provider mode never adds user-level AI settings.
|
|
266
|
+
|
|
205
267
|
### `disabled`
|
|
206
268
|
|
|
207
269
|
Disables endpoint and UI registration while keeping the plugin call in your config.
|
|
@@ -213,8 +275,9 @@ The package lazy-loads provider SDKs at runtime. If a user selects `claude`, `go
|
|
|
213
275
|
|
|
214
276
|
API key priority is:
|
|
215
277
|
|
|
216
|
-
1.
|
|
217
|
-
2.
|
|
278
|
+
1. managed provider `apiKey`, when `providers` is configured
|
|
279
|
+
2. account-level API key, unless `allowUserApiKeys: false`
|
|
280
|
+
3. provider environment variables
|
|
218
281
|
|
|
219
282
|
- `OPENAI_API_KEY`, `OPENAI_MODEL`
|
|
220
283
|
- `OPENROUTER_API_KEY`, `OPENROUTER_MODEL`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type AIProviderModelOption = {
|
|
1
|
+
export type AIProviderModelOption = {
|
|
2
2
|
label: string;
|
|
3
3
|
value: string;
|
|
4
4
|
};
|
|
@@ -71,6 +71,26 @@ declare const aiProviderModels: {
|
|
|
71
71
|
};
|
|
72
72
|
export type AIProvider = keyof typeof aiProviderModels;
|
|
73
73
|
type AIProviderModels = Record<AIProvider, AIProviderModelOption[]>;
|
|
74
|
+
export type AIProviderConfig = {
|
|
75
|
+
apiKey?: string;
|
|
76
|
+
baseURL?: string;
|
|
77
|
+
defaultModel?: string;
|
|
78
|
+
id: string;
|
|
79
|
+
label: string;
|
|
80
|
+
models: AIProviderModelOption[];
|
|
81
|
+
provider: AIProvider;
|
|
82
|
+
};
|
|
83
|
+
export type AIProviderProfile = {
|
|
84
|
+
defaultModel: string;
|
|
85
|
+
id: string;
|
|
86
|
+
label: string;
|
|
87
|
+
models: AIProviderModelOption[];
|
|
88
|
+
provider: AIProvider;
|
|
89
|
+
};
|
|
90
|
+
export type ResolvedAIProviderConfig = AIProviderProfile & {
|
|
91
|
+
apiKey?: string;
|
|
92
|
+
baseURL?: string;
|
|
93
|
+
};
|
|
74
94
|
export declare const aiProviders: {
|
|
75
95
|
label: string;
|
|
76
96
|
value: AIProvider;
|
|
@@ -85,4 +105,7 @@ export declare const getResolvedAIModelConfig: (modelConfig?: AIModelConfig) =>
|
|
|
85
105
|
providers: AIProviderModels;
|
|
86
106
|
};
|
|
87
107
|
export declare const isAIProvider: (provider: string) => provider is AIProvider;
|
|
108
|
+
export declare const getLegacyAIProviderProfiles: (modelConfig?: AIModelConfig) => AIProviderProfile[];
|
|
109
|
+
export declare const resolveAIProviderConfigs: (providers?: AIProviderConfig[]) => ResolvedAIProviderConfig[];
|
|
110
|
+
export declare const toClientAIProviderProfiles: (providers: ResolvedAIProviderConfig[]) => AIProviderProfile[];
|
|
88
111
|
export {};
|
|
@@ -144,3 +144,84 @@ export const getResolvedAIModelConfig = (modelConfig)=>{
|
|
|
144
144
|
};
|
|
145
145
|
};
|
|
146
146
|
export const isAIProvider = (provider)=>provider in aiProviderModels;
|
|
147
|
+
export const getLegacyAIProviderProfiles = (modelConfig)=>{
|
|
148
|
+
const resolvedModels = getResolvedAIModelConfig(modelConfig);
|
|
149
|
+
return aiProviders.map(({ label, value })=>({
|
|
150
|
+
defaultModel: resolvedModels.defaults[value],
|
|
151
|
+
id: value,
|
|
152
|
+
label,
|
|
153
|
+
models: resolvedModels.providers[value],
|
|
154
|
+
provider: value
|
|
155
|
+
}));
|
|
156
|
+
};
|
|
157
|
+
export const resolveAIProviderConfigs = (providers)=>{
|
|
158
|
+
if (!providers?.length) return [];
|
|
159
|
+
const providerIDs = new Set();
|
|
160
|
+
return providers.map((providerConfig, index)=>{
|
|
161
|
+
const path = `providers[${index}]`;
|
|
162
|
+
const id = providerConfig.id.trim();
|
|
163
|
+
const label = providerConfig.label.trim();
|
|
164
|
+
if (!id || !/^[a-z0-9][a-z0-9_-]*$/i.test(id)) {
|
|
165
|
+
throw new Error(`${path}.id must contain only letters, numbers, hyphens, or underscores.`);
|
|
166
|
+
}
|
|
167
|
+
if (providerIDs.has(id)) throw new Error(`Duplicate AI provider id: ${id}`);
|
|
168
|
+
providerIDs.add(id);
|
|
169
|
+
if (!label) throw new Error(`${path}.label is required.`);
|
|
170
|
+
if (!isAIProvider(providerConfig.provider)) {
|
|
171
|
+
throw new Error(`${path}.provider is unsupported: ${String(providerConfig.provider)}`);
|
|
172
|
+
}
|
|
173
|
+
if (!providerConfig.models.length) throw new Error(`${path}.models must contain at least one model.`);
|
|
174
|
+
const modelValues = new Set();
|
|
175
|
+
const models = providerConfig.models.map((model, modelIndex)=>{
|
|
176
|
+
const modelPath = `${path}.models[${modelIndex}]`;
|
|
177
|
+
const modelLabel = model.label.trim();
|
|
178
|
+
const value = model.value.trim();
|
|
179
|
+
if (!modelLabel) throw new Error(`${modelPath}.label is required.`);
|
|
180
|
+
if (!value) throw new Error(`${modelPath}.value is required.`);
|
|
181
|
+
if (modelValues.has(value)) throw new Error(`Duplicate model value "${value}" in AI provider "${id}".`);
|
|
182
|
+
modelValues.add(value);
|
|
183
|
+
return {
|
|
184
|
+
label: modelLabel,
|
|
185
|
+
value
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
const defaultModel = providerConfig.defaultModel?.trim() || models[0].value;
|
|
189
|
+
if (!modelValues.has(defaultModel)) {
|
|
190
|
+
throw new Error(`${path}.defaultModel must match a configured model value.`);
|
|
191
|
+
}
|
|
192
|
+
if (providerConfig.baseURL) {
|
|
193
|
+
let parsedURL;
|
|
194
|
+
try {
|
|
195
|
+
parsedURL = new URL(providerConfig.baseURL);
|
|
196
|
+
} catch {
|
|
197
|
+
throw new Error(`${path}.baseURL must be a valid URL.`);
|
|
198
|
+
}
|
|
199
|
+
if (![
|
|
200
|
+
"http:",
|
|
201
|
+
"https:"
|
|
202
|
+
].includes(parsedURL.protocol)) {
|
|
203
|
+
throw new Error(`${path}.baseURL must use http or https.`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
...providerConfig.apiKey ? {
|
|
208
|
+
apiKey: providerConfig.apiKey
|
|
209
|
+
} : {},
|
|
210
|
+
...providerConfig.baseURL ? {
|
|
211
|
+
baseURL: providerConfig.baseURL
|
|
212
|
+
} : {},
|
|
213
|
+
defaultModel,
|
|
214
|
+
id,
|
|
215
|
+
label,
|
|
216
|
+
models,
|
|
217
|
+
provider: providerConfig.provider
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
export const toClientAIProviderProfiles = (providers)=>providers.map(({ defaultModel, id, label, models, provider })=>({
|
|
222
|
+
defaultModel,
|
|
223
|
+
id,
|
|
224
|
+
label,
|
|
225
|
+
models,
|
|
226
|
+
provider
|
|
227
|
+
}));
|
|
@@ -8,6 +8,7 @@ type ProviderConfig = {
|
|
|
8
8
|
};
|
|
9
9
|
type ModelConfig = {
|
|
10
10
|
apiKey: string;
|
|
11
|
+
baseURL?: string;
|
|
11
12
|
model: string;
|
|
12
13
|
provider: AIProvider;
|
|
13
14
|
};
|
|
@@ -15,5 +16,5 @@ export declare const getProviderConfig: ({ apiKey, defaultModels, model, provide
|
|
|
15
16
|
apiKey: string | undefined;
|
|
16
17
|
modelID: string;
|
|
17
18
|
};
|
|
18
|
-
export declare const getModel: ({ apiKey, model, provider }: ModelConfig) => Promise<LanguageModel>;
|
|
19
|
+
export declare const getModel: ({ apiKey, baseURL, model, provider }: ModelConfig) => Promise<LanguageModel>;
|
|
19
20
|
export {};
|
|
@@ -33,9 +33,12 @@ export const getProviderConfig = ({ apiKey, defaultModels, model, provider })=>{
|
|
|
33
33
|
const getMissingProviderDependencyError = (packageName, provider)=>{
|
|
34
34
|
return new Error(`Missing optional dependency ${packageName}. Install it to use the ${provider} provider.`);
|
|
35
35
|
};
|
|
36
|
-
export const getModel = async ({ apiKey, model, provider })=>{
|
|
36
|
+
export const getModel = async ({ apiKey, baseURL, model, provider })=>{
|
|
37
37
|
const providerOptions = {
|
|
38
|
-
apiKey
|
|
38
|
+
apiKey,
|
|
39
|
+
...baseURL ? {
|
|
40
|
+
baseURL
|
|
41
|
+
} : {}
|
|
39
42
|
};
|
|
40
43
|
if (provider === "claude") {
|
|
41
44
|
try {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { PayloadHandler } from "payload";
|
|
2
|
+
export type MaxTokenUsageOptions = {
|
|
3
|
+
perDay?: number;
|
|
4
|
+
perWeek?: number;
|
|
5
|
+
type: "site" | "user";
|
|
6
|
+
};
|
|
7
|
+
export type ResolvedMaxTokenUsageOptions = {
|
|
8
|
+
perDay?: number;
|
|
9
|
+
perWeek?: number;
|
|
10
|
+
type: "site" | "user";
|
|
11
|
+
};
|
|
12
|
+
export type TokenUsageData = {
|
|
13
|
+
inputTokens?: number;
|
|
14
|
+
outputTokens?: number;
|
|
15
|
+
totalTokens?: number;
|
|
16
|
+
};
|
|
17
|
+
type TokenUsageLimit = {
|
|
18
|
+
limit: number;
|
|
19
|
+
period: "day" | "week";
|
|
20
|
+
used: number;
|
|
21
|
+
};
|
|
22
|
+
export declare const tokenUsageCollectionSlug = "payload-ai-usage";
|
|
23
|
+
export declare const resolveMaxTokenUsageOptions: (options?: MaxTokenUsageOptions) => ResolvedMaxTokenUsageOptions | undefined;
|
|
24
|
+
export declare const getExceededTokenUsageLimit: ({ maxTokenUsage, now, req, userID, }: {
|
|
25
|
+
maxTokenUsage?: ResolvedMaxTokenUsageOptions;
|
|
26
|
+
now?: Date;
|
|
27
|
+
req: Parameters<PayloadHandler>[0];
|
|
28
|
+
userID: number | string;
|
|
29
|
+
}) => Promise<TokenUsageLimit | null>;
|
|
30
|
+
export declare const recordTokenUsage: ({ model, now, provider, req, usage, userID, }: {
|
|
31
|
+
model: string;
|
|
32
|
+
now?: Date;
|
|
33
|
+
provider: string;
|
|
34
|
+
req: Parameters<PayloadHandler>[0];
|
|
35
|
+
usage: TokenUsageData;
|
|
36
|
+
userID: number | string;
|
|
37
|
+
}) => Promise<void>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export const tokenUsageCollectionSlug = "payload-ai-usage";
|
|
2
|
+
const dayInMilliseconds = 24 * 60 * 60 * 1000;
|
|
3
|
+
const weekInMilliseconds = 7 * dayInMilliseconds;
|
|
4
|
+
const resolvePositiveInteger = (value, path)=>{
|
|
5
|
+
if (value === undefined) return undefined;
|
|
6
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`${path} must be a positive number.`);
|
|
7
|
+
return Math.floor(value);
|
|
8
|
+
};
|
|
9
|
+
export const resolveMaxTokenUsageOptions = (options)=>{
|
|
10
|
+
if (!options) return undefined;
|
|
11
|
+
if (![
|
|
12
|
+
"site",
|
|
13
|
+
"user"
|
|
14
|
+
].includes(options.type)) throw new Error('maxTokenUsage.type must be either "user" or "site".');
|
|
15
|
+
const perDay = resolvePositiveInteger(options.perDay, "maxTokenUsage.perDay");
|
|
16
|
+
const perWeek = resolvePositiveInteger(options.perWeek, "maxTokenUsage.perWeek");
|
|
17
|
+
if (!perDay && !perWeek) throw new Error("maxTokenUsage must configure perDay, perWeek, or both.");
|
|
18
|
+
return {
|
|
19
|
+
...perDay ? {
|
|
20
|
+
perDay
|
|
21
|
+
} : {},
|
|
22
|
+
...perWeek ? {
|
|
23
|
+
perWeek
|
|
24
|
+
} : {},
|
|
25
|
+
type: options.type
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const normalizeTokenCount = (value)=>typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
29
|
+
const getTokenCount = (usage)=>typeof usage.totalTokens === "number" ? normalizeTokenCount(usage.totalTokens) : normalizeTokenCount(usage.inputTokens) + normalizeTokenCount(usage.outputTokens);
|
|
30
|
+
export const getExceededTokenUsageLimit = async ({ maxTokenUsage, now = new Date(), req, userID })=>{
|
|
31
|
+
if (!maxTokenUsage) return null;
|
|
32
|
+
const dayStart = new Date(now.getTime() - dayInMilliseconds);
|
|
33
|
+
const weekStart = new Date(now.getTime() - weekInMilliseconds);
|
|
34
|
+
const queryStart = maxTokenUsage.perWeek ? weekStart : dayStart;
|
|
35
|
+
let page = 1;
|
|
36
|
+
let usagePerDay = 0;
|
|
37
|
+
let usagePerWeek = 0;
|
|
38
|
+
while(true){
|
|
39
|
+
const result = await req.payload.find({
|
|
40
|
+
collection: tokenUsageCollectionSlug,
|
|
41
|
+
depth: 0,
|
|
42
|
+
limit: 500,
|
|
43
|
+
overrideAccess: true,
|
|
44
|
+
page,
|
|
45
|
+
req,
|
|
46
|
+
where: {
|
|
47
|
+
and: [
|
|
48
|
+
{
|
|
49
|
+
recordedAt: {
|
|
50
|
+
greater_than_equal: queryStart.toISOString()
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
...maxTokenUsage.type === "user" ? [
|
|
54
|
+
{
|
|
55
|
+
userID: {
|
|
56
|
+
equals: String(userID)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
] : []
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
for (const document of result.docs || []){
|
|
64
|
+
const tokenCount = getTokenCount(document);
|
|
65
|
+
usagePerWeek += tokenCount;
|
|
66
|
+
if (document.recordedAt && new Date(document.recordedAt).getTime() >= dayStart.getTime()) {
|
|
67
|
+
usagePerDay += tokenCount;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!result.hasNextPage || !result.nextPage) break;
|
|
71
|
+
page = result.nextPage;
|
|
72
|
+
}
|
|
73
|
+
if (maxTokenUsage.perDay && usagePerDay >= maxTokenUsage.perDay) {
|
|
74
|
+
return {
|
|
75
|
+
limit: maxTokenUsage.perDay,
|
|
76
|
+
period: "day",
|
|
77
|
+
used: usagePerDay
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (maxTokenUsage.perWeek && usagePerWeek >= maxTokenUsage.perWeek) {
|
|
81
|
+
return {
|
|
82
|
+
limit: maxTokenUsage.perWeek,
|
|
83
|
+
period: "week",
|
|
84
|
+
used: usagePerWeek
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
};
|
|
89
|
+
export const recordTokenUsage = async ({ model, now = new Date(), provider, req, usage, userID })=>{
|
|
90
|
+
const totalTokens = getTokenCount(usage);
|
|
91
|
+
if (!totalTokens) return;
|
|
92
|
+
await req.payload.create({
|
|
93
|
+
collection: tokenUsageCollectionSlug,
|
|
94
|
+
data: {
|
|
95
|
+
inputTokens: typeof usage.inputTokens === "number" ? normalizeTokenCount(usage.inputTokens) : undefined,
|
|
96
|
+
model,
|
|
97
|
+
outputTokens: typeof usage.outputTokens === "number" ? normalizeTokenCount(usage.outputTokens) : undefined,
|
|
98
|
+
provider,
|
|
99
|
+
recordedAt: now.toISOString(),
|
|
100
|
+
totalTokens,
|
|
101
|
+
userID: String(userID)
|
|
102
|
+
},
|
|
103
|
+
overrideAccess: true,
|
|
104
|
+
req
|
|
105
|
+
});
|
|
106
|
+
};
|
|
@@ -4,3 +4,4 @@ export declare function OpenaiIcon(props: SVGProps<SVGSVGElement>): import("reac
|
|
|
4
4
|
export declare function ClaudeIcon(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
|
5
5
|
export declare function GoogleGeminiIcon(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
|
6
6
|
export declare function OpenrouterIcon(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
|
7
|
+
export declare function PaperclipIcon(props: SVGProps<SVGSVGElement>): import("react").JSX.Element;
|
package/dist/components/Icons.js
CHANGED
|
@@ -274,3 +274,18 @@ export function OpenrouterIcon(props) {
|
|
|
274
274
|
})
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
|
+
export function PaperclipIcon(props) {
|
|
278
|
+
return /*#__PURE__*/ _jsx("svg", {
|
|
279
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
280
|
+
viewBox: "0 0 24 24",
|
|
281
|
+
fill: "none",
|
|
282
|
+
stroke: "currentColor",
|
|
283
|
+
strokeWidth: "2",
|
|
284
|
+
strokeLinecap: "round",
|
|
285
|
+
strokeLinejoin: "round",
|
|
286
|
+
...props,
|
|
287
|
+
children: /*#__PURE__*/ _jsx("path", {
|
|
288
|
+
d: "m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
}
|
|
@@ -5,8 +5,11 @@ export type ActionProposal = {
|
|
|
5
5
|
};
|
|
6
6
|
action: "create" | "delete" | "update" | "updateGlobal";
|
|
7
7
|
collection?: string;
|
|
8
|
+
data?: Record<string, unknown>;
|
|
8
9
|
id?: string;
|
|
9
10
|
label: string;
|
|
11
|
+
locale?: string;
|
|
12
|
+
localizedData?: Record<string, Record<string, unknown>>;
|
|
10
13
|
slug?: string;
|
|
11
14
|
};
|
|
12
15
|
type ActionToastProps = {
|
|
@@ -15,6 +18,7 @@ type ActionToastProps = {
|
|
|
15
18
|
error?: string;
|
|
16
19
|
getViewURL?: (proposal: ActionProposal) => string | null;
|
|
17
20
|
isApplying: boolean;
|
|
21
|
+
isLoading: boolean;
|
|
18
22
|
onDismiss?: () => void;
|
|
19
23
|
onDismissError?: () => void;
|
|
20
24
|
onApply: (proposal: ActionProposal, index: number) => void;
|
|
@@ -26,5 +30,5 @@ type ActionToastProps = {
|
|
|
26
30
|
totalTokens?: number;
|
|
27
31
|
} | null;
|
|
28
32
|
};
|
|
29
|
-
export declare const ActionToast: ({ apiRoute, description, error, getViewURL, isApplying, onDismiss, onDismissError, onApply, prompt, proposals, tokenUsage, }: ActionToastProps) => import("react").JSX.Element | null;
|
|
33
|
+
export declare const ActionToast: ({ apiRoute, description, error, getViewURL, isApplying, isLoading, onDismiss, onDismissError, onApply, prompt, proposals, tokenUsage, }: ActionToastProps) => import("react").JSX.Element | null;
|
|
30
34
|
export {};
|