@purposeinplay/payload-ai-translate 0.1.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/LICENSE +21 -0
- package/README.md +714 -0
- package/dist/alerts-collection.d.ts +21 -0
- package/dist/alerts-collection.js +159 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +918 -0
- package/dist/bulk-translate-batches-collection.d.ts +29 -0
- package/dist/bulk-translate-batches-collection.js +404 -0
- package/dist/bulk-translate-units-collection.d.ts +35 -0
- package/dist/bulk-translate-units-collection.js +310 -0
- package/dist/client/estimated-cost-cell.d.ts +6 -0
- package/dist/client/estimated-cost-cell.js +12 -0
- package/dist/client/excluded-fields-field.d.ts +45 -0
- package/dist/client/excluded-fields-field.js +553 -0
- package/dist/client/field-translate-button.d.ts +6 -0
- package/dist/client/field-translate-button.js +199 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +6 -0
- package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
- package/dist/client/lib/use-global-kill-switches.js +58 -0
- package/dist/client/translate-button.d.ts +2 -0
- package/dist/client/translate-button.js +228 -0
- package/dist/client/translate-modal.d.ts +16 -0
- package/dist/client/translate-modal.js +549 -0
- package/dist/client/translation-progress.d.ts +10 -0
- package/dist/client/translation-progress.js +297 -0
- package/dist/components/TranslationNavGroup.d.ts +45 -0
- package/dist/components/TranslationNavGroup.js +104 -0
- package/dist/defaults.d.ts +11 -0
- package/dist/defaults.js +16 -0
- package/dist/endpoints/client-config.d.ts +44 -0
- package/dist/endpoints/client-config.js +145 -0
- package/dist/endpoints/estimate.d.ts +5 -0
- package/dist/endpoints/estimate.js +237 -0
- package/dist/endpoints/progress.d.ts +2 -0
- package/dist/endpoints/progress.js +314 -0
- package/dist/endpoints/translate.d.ts +11 -0
- package/dist/endpoints/translate.js +376 -0
- package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
- package/dist/endpoints/translation-hub/_helpers.js +297 -0
- package/dist/endpoints/translation-hub/active.d.ts +21 -0
- package/dist/endpoints/translation-hub/active.js +220 -0
- package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
- package/dist/endpoints/translation-hub/cancel.js +233 -0
- package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
- package/dist/endpoints/translation-hub/enqueue.js +529 -0
- package/dist/endpoints/translation-hub/failures.d.ts +12 -0
- package/dist/endpoints/translation-hub/failures.js +67 -0
- package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
- package/dist/endpoints/translation-hub/force-reset.js +144 -0
- package/dist/endpoints/translation-hub/index.d.ts +21 -0
- package/dist/endpoints/translation-hub/index.js +20 -0
- package/dist/endpoints/translation-hub/list.d.ts +40 -0
- package/dist/endpoints/translation-hub/list.js +182 -0
- package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
- package/dist/endpoints/translation-hub/preflight.js +141 -0
- package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
- package/dist/endpoints/translation-hub/retry-failed.js +235 -0
- package/dist/endpoints/translation-hub/revert.d.ts +88 -0
- package/dist/endpoints/translation-hub/revert.js +405 -0
- package/dist/endpoints/translation-hub/status.d.ts +45 -0
- package/dist/endpoints/translation-hub/status.js +391 -0
- package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
- package/dist/endpoints/translation-hub/usage-summary.js +481 -0
- package/dist/exports/client.d.ts +6 -0
- package/dist/exports/client.js +6 -0
- package/dist/exports/components.d.ts +6 -0
- package/dist/exports/components.js +5 -0
- package/dist/exports/index.d.ts +8 -0
- package/dist/exports/index.js +7 -0
- package/dist/exports/providers.d.ts +9 -0
- package/dist/exports/providers.js +5 -0
- package/dist/exports/views-client.d.ts +23 -0
- package/dist/exports/views-client.js +22 -0
- package/dist/exports/views.d.ts +30 -0
- package/dist/exports/views.js +29 -0
- package/dist/hooks/after-change-global.d.ts +4 -0
- package/dist/hooks/after-change-global.js +109 -0
- package/dist/hooks/after-change.d.ts +16 -0
- package/dist/hooks/after-change.js +205 -0
- package/dist/hooks/after-delete.d.ts +30 -0
- package/dist/hooks/after-delete.js +95 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/jobs-collection.d.ts +17 -0
- package/dist/jobs-collection.js +139 -0
- package/dist/lexical/classifier.d.ts +3 -0
- package/dist/lexical/classifier.js +108 -0
- package/dist/lexical/deserializer.d.ts +4 -0
- package/dist/lexical/deserializer.js +263 -0
- package/dist/lexical/placeholder-integrity.d.ts +6 -0
- package/dist/lexical/placeholder-integrity.js +21 -0
- package/dist/lexical/placeholders.d.ts +21 -0
- package/dist/lexical/placeholders.js +117 -0
- package/dist/lexical/serializer.d.ts +21 -0
- package/dist/lexical/serializer.js +233 -0
- package/dist/lexical/types.d.ts +32 -0
- package/dist/lexical/types.js +1 -0
- package/dist/lib/auth-diagnostics.d.ts +14 -0
- package/dist/lib/auth-diagnostics.js +19 -0
- package/dist/lib/batch-counts.d.ts +58 -0
- package/dist/lib/batch-counts.js +105 -0
- package/dist/lib/bulk-translate-migrations.d.ts +92 -0
- package/dist/lib/bulk-translate-migrations.js +153 -0
- package/dist/lib/coalescing-queue.d.ts +38 -0
- package/dist/lib/coalescing-queue.js +69 -0
- package/dist/lib/content-extractor.d.ts +16 -0
- package/dist/lib/content-extractor.js +410 -0
- package/dist/lib/content-hash.d.ts +1 -0
- package/dist/lib/content-hash.js +19 -0
- package/dist/lib/content-patcher.d.ts +15 -0
- package/dist/lib/content-patcher.js +293 -0
- package/dist/lib/cost-guards.d.ts +2 -0
- package/dist/lib/cost-guards.js +18 -0
- package/dist/lib/daily-spend-cap.d.ts +58 -0
- package/dist/lib/daily-spend-cap.js +233 -0
- package/dist/lib/effective-locales.d.ts +181 -0
- package/dist/lib/effective-locales.js +302 -0
- package/dist/lib/error-messages.d.ts +245 -0
- package/dist/lib/error-messages.js +626 -0
- package/dist/lib/events.d.ts +39 -0
- package/dist/lib/events.js +146 -0
- package/dist/lib/exclude-fields.d.ts +3 -0
- package/dist/lib/exclude-fields.js +64 -0
- package/dist/lib/field-breadcrumb.d.ts +31 -0
- package/dist/lib/field-breadcrumb.js +227 -0
- package/dist/lib/field-diff.d.ts +1 -0
- package/dist/lib/field-diff.js +25 -0
- package/dist/lib/field-empty.d.ts +2 -0
- package/dist/lib/field-empty.js +68 -0
- package/dist/lib/field-resolver.d.ts +3 -0
- package/dist/lib/field-resolver.js +164 -0
- package/dist/lib/group-soft-skips.d.ts +39 -0
- package/dist/lib/group-soft-skips.js +45 -0
- package/dist/lib/locale-merge.d.ts +44 -0
- package/dist/lib/locale-merge.js +357 -0
- package/dist/lib/locale-row-check.d.ts +30 -0
- package/dist/lib/locale-row-check.js +64 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +97 -0
- package/dist/lib/manual-edit-guard.d.ts +128 -0
- package/dist/lib/manual-edit-guard.js +393 -0
- package/dist/lib/output-validation.d.ts +48 -0
- package/dist/lib/output-validation.js +148 -0
- package/dist/lib/payload-read.d.ts +16 -0
- package/dist/lib/payload-read.js +51 -0
- package/dist/lib/per-doc-claim.d.ts +90 -0
- package/dist/lib/per-doc-claim.js +140 -0
- package/dist/lib/per-doc-lock.d.ts +94 -0
- package/dist/lib/per-doc-lock.js +119 -0
- package/dist/lib/persist-usage.d.ts +91 -0
- package/dist/lib/persist-usage.js +116 -0
- package/dist/lib/progress-store.d.ts +103 -0
- package/dist/lib/progress-store.js +314 -0
- package/dist/lib/rate-limiter.d.ts +3 -0
- package/dist/lib/rate-limiter.js +53 -0
- package/dist/lib/snapshot-select.d.ts +43 -0
- package/dist/lib/snapshot-select.js +108 -0
- package/dist/lib/translate-prompt.d.ts +31 -0
- package/dist/lib/translate-prompt.js +66 -0
- package/dist/lib/translation-token-bucket.d.ts +57 -0
- package/dist/lib/translation-token-bucket.js +365 -0
- package/dist/lib/truncate-source-value.d.ts +1 -0
- package/dist/lib/truncate-source-value.js +27 -0
- package/dist/manual-edit-collection.d.ts +22 -0
- package/dist/manual-edit-collection.js +124 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +934 -0
- package/dist/providers/ai-sdk-adapter.d.ts +35 -0
- package/dist/providers/ai-sdk-adapter.js +100 -0
- package/dist/providers/anthropic.d.ts +31 -0
- package/dist/providers/anthropic.js +66 -0
- package/dist/providers/custom.d.ts +36 -0
- package/dist/providers/custom.js +24 -0
- package/dist/providers/gemini.d.ts +20 -0
- package/dist/providers/gemini.js +48 -0
- package/dist/providers/mock.d.ts +2 -0
- package/dist/providers/mock.js +29 -0
- package/dist/providers/openai.d.ts +28 -0
- package/dist/providers/openai.js +69 -0
- package/dist/settings-global.d.ts +74 -0
- package/dist/settings-global.js +216 -0
- package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
- package/dist/tasks/bulk-translate-coordinator.js +708 -0
- package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
- package/dist/tasks/bulk-translate-doc-task.js +1000 -0
- package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
- package/dist/tasks/bulk-translate-janitor.js +311 -0
- package/dist/tasks/translate-job-task.d.ts +51 -0
- package/dist/tasks/translate-job-task.js +154 -0
- package/dist/translate.d.ts +113 -0
- package/dist/translate.js +911 -0
- package/dist/translation-daily-spend-collection.d.ts +24 -0
- package/dist/translation-daily-spend-collection.js +133 -0
- package/dist/translation-rate-limits-collection.d.ts +30 -0
- package/dist/translation-rate-limits-collection.js +144 -0
- package/dist/types.d.ts +672 -0
- package/dist/types.js +1 -0
- package/dist/usage-collection.d.ts +14 -0
- package/dist/usage-collection.js +377 -0
- package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
- package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
- package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
- package/dist/views/BulkRunsHub/BucketRow.js +982 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
- package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
- package/dist/views/BulkRunsHub/EmptyState.js +64 -0
- package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
- package/dist/views/BulkRunsHub/FilterBar.js +284 -0
- package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
- package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
- package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
- package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
- package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
- package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
- package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
- package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
- package/dist/views/BulkRunsHub/index.d.ts +17 -0
- package/dist/views/BulkRunsHub/index.js +80 -0
- package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
- package/dist/views/BulkRunsHub/urlFilters.js +50 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
- package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
- package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
- package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
- package/dist/views/TranslationHub/ActiveJobs.js +320 -0
- package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
- package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
- package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
- package/dist/views/TranslationHub/AlertBanner.js +568 -0
- package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
- package/dist/views/TranslationHub/AuditPanel.js +1367 -0
- package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
- package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
- package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
- package/dist/views/TranslationHub/Hub.client.js +357 -0
- package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
- package/dist/views/TranslationHub/ModelCombobox.js +415 -0
- package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
- package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
- package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
- package/dist/views/TranslationHub/SettingsRail.js +382 -0
- package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
- package/dist/views/TranslationHub/StatusStrip.js +451 -0
- package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
- package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
- package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
- package/dist/views/TranslationHub/UsageTable.js +1240 -0
- package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
- package/dist/views/TranslationHub/alertGrouping.js +99 -0
- package/dist/views/TranslationHub/index.d.ts +20 -0
- package/dist/views/TranslationHub/index.js +109 -0
- package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
- package/dist/views/TranslationHub/tabNavigation.js +74 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
- package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
- package/dist/views/TranslationHub/useFocusTrap.js +81 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
- package/dist/views/shared/EditorError.d.ts +97 -0
- package/dist/views/shared/EditorError.js +205 -0
- package/dist/views/shared/ModelCell.d.ts +18 -0
- package/dist/views/shared/ModelCell.js +31 -0
- package/dist/views/shared/docHref.d.ts +16 -0
- package/dist/views/shared/docHref.js +26 -0
- package/dist/views/shared/fetch-error-body.d.ts +25 -0
- package/dist/views/shared/fetch-error-body.js +42 -0
- package/dist/views/shared/filterPillStyle.d.ts +35 -0
- package/dist/views/shared/filterPillStyle.js +40 -0
- package/dist/views/shared/format.d.ts +75 -0
- package/dist/views/shared/format.js +131 -0
- package/package.json +141 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { readResponseError } from '../shared/fetch-error-body.js';
|
|
5
|
+
const VENDOR_LABEL = {
|
|
6
|
+
openai: 'OpenAI',
|
|
7
|
+
anthropic: 'Anthropic',
|
|
8
|
+
google: 'Google',
|
|
9
|
+
meta: 'Meta',
|
|
10
|
+
other: 'Other'
|
|
11
|
+
};
|
|
12
|
+
const VENDOR_ORDER = [
|
|
13
|
+
'openai',
|
|
14
|
+
'anthropic',
|
|
15
|
+
'google',
|
|
16
|
+
'meta',
|
|
17
|
+
'other'
|
|
18
|
+
];
|
|
19
|
+
function filter(models, q) {
|
|
20
|
+
const query = q.trim().toLowerCase();
|
|
21
|
+
if (!query) {
|
|
22
|
+
return models;
|
|
23
|
+
}
|
|
24
|
+
return models.filter((m)=>m.id.toLowerCase().includes(query) || m.name.toLowerCase().includes(query));
|
|
25
|
+
}
|
|
26
|
+
function group(models) {
|
|
27
|
+
const buckets = new Map();
|
|
28
|
+
for (const m of models){
|
|
29
|
+
let bucket = buckets.get(m.vendor);
|
|
30
|
+
if (!bucket) {
|
|
31
|
+
bucket = [];
|
|
32
|
+
buckets.set(m.vendor, bucket);
|
|
33
|
+
}
|
|
34
|
+
bucket.push(m);
|
|
35
|
+
}
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const v of VENDOR_ORDER){
|
|
38
|
+
const bucket = buckets.get(v);
|
|
39
|
+
if (bucket) {
|
|
40
|
+
out.push({
|
|
41
|
+
vendor: v,
|
|
42
|
+
models: [
|
|
43
|
+
...bucket
|
|
44
|
+
].sort((a, b)=>a.id.localeCompare(b.id))
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
// ROUND2-3: canonical slug pattern. Display value MUST match this so
|
|
51
|
+
// `:invalid` styling doesn't trip on the loaded-correct field. Free-text
|
|
52
|
+
// typed values that don't match show an inline `role="alert"` message
|
|
53
|
+
// and `aria-invalid="true"`.
|
|
54
|
+
const CANONICAL_SLUG_PATTERN = '^$|^[a-z0-9.\\-]+/[a-z0-9.\\-]+$';
|
|
55
|
+
const CANONICAL_SLUG_REGEX = /^[a-z0-9.-]+\/[a-z0-9.-]+$/;
|
|
56
|
+
/**
|
|
57
|
+
* Compact combobox for the Translation Hub's settings rail. Same data
|
|
58
|
+
* source as the standalone OpenRouter Settings page (`/api/openrouter/models`)
|
|
59
|
+
* but slimmer styling — fits the 320px rail.
|
|
60
|
+
*/ export const ModelCombobox = ({ basePath, value, disabled, onChange })=>{
|
|
61
|
+
const [models, setModels] = useState(null);
|
|
62
|
+
const [error, setError] = useState(null);
|
|
63
|
+
const [fromFallback, setFromFallback] = useState(false);
|
|
64
|
+
const [query, setQuery] = useState('');
|
|
65
|
+
const [open, setOpen] = useState(false);
|
|
66
|
+
const [highlightIdx, setHighlightIdx] = useState(0);
|
|
67
|
+
// ROUND2-3: track whether the current query string (free-text typed
|
|
68
|
+
// by the user while the popover is open) parses to a canonical slug.
|
|
69
|
+
// Drives the inline alert + aria-invalid. Distinct from the
|
|
70
|
+
// `savedMissing` warning, which fires when the *saved* value isn't
|
|
71
|
+
// in the catalog.
|
|
72
|
+
const [showInvalidAlert, setShowInvalidAlert] = useState(false);
|
|
73
|
+
const ref = useRef(null);
|
|
74
|
+
const inputRef = useRef(null);
|
|
75
|
+
// ROUND3-1: the last canonical slug we know is good — either the
|
|
76
|
+
// initially-mounted `value` prop or whatever was most-recently picked
|
|
77
|
+
// from the listbox. `handleBlur` restores this into the displayed
|
|
78
|
+
// input string on soft-revert instead of clearing to empty. The
|
|
79
|
+
// empty-input-with-populated-caption gap that editors saw post-blur
|
|
80
|
+
// came from `setQuery('')` paired with the conditional
|
|
81
|
+
// `value={open ? query : currentSlug}` — when the input loses focus
|
|
82
|
+
// we want the *displayed* string to be the canonical slug, not the
|
|
83
|
+
// last typed garbage and not an empty string.
|
|
84
|
+
const lastPickedSlugRef = useRef(value);
|
|
85
|
+
useEffect(()=>{
|
|
86
|
+
let cancelled = false;
|
|
87
|
+
fetch(`${basePath}/api/openrouter/models`, {
|
|
88
|
+
credentials: 'include'
|
|
89
|
+
}).then(async (r)=>{
|
|
90
|
+
if (!r.ok) {
|
|
91
|
+
throw new Error(await readResponseError(r));
|
|
92
|
+
}
|
|
93
|
+
return await r.json();
|
|
94
|
+
}).then((d)=>{
|
|
95
|
+
if (cancelled) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
setModels(d.models);
|
|
99
|
+
setFromFallback(d.fromFallback);
|
|
100
|
+
}).catch((e)=>{
|
|
101
|
+
if (cancelled) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
105
|
+
});
|
|
106
|
+
return ()=>{
|
|
107
|
+
cancelled = true;
|
|
108
|
+
};
|
|
109
|
+
}, [
|
|
110
|
+
basePath
|
|
111
|
+
]);
|
|
112
|
+
useEffect(()=>{
|
|
113
|
+
if (!open) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
function close(e) {
|
|
117
|
+
if (!ref.current?.contains(e.target)) {
|
|
118
|
+
setOpen(false);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
document.addEventListener('mousedown', close);
|
|
122
|
+
return ()=>document.removeEventListener('mousedown', close);
|
|
123
|
+
}, [
|
|
124
|
+
open
|
|
125
|
+
]);
|
|
126
|
+
const filtered = useMemo(()=>filter(models ?? [], query), [
|
|
127
|
+
models,
|
|
128
|
+
query
|
|
129
|
+
]);
|
|
130
|
+
const grouped = useMemo(()=>group(filtered), [
|
|
131
|
+
filtered
|
|
132
|
+
]);
|
|
133
|
+
const flat = useMemo(()=>grouped.flatMap((g)=>g.models), [
|
|
134
|
+
grouped
|
|
135
|
+
]);
|
|
136
|
+
const savedMissing = Boolean(value && models && !models.some((m)=>m.id === value));
|
|
137
|
+
// ROUND2-3: the displayed input value is the canonical slug only —
|
|
138
|
+
// "google/gemini-2.5-flash", not "Google: Gemini 2.5 Flash
|
|
139
|
+
// (google/gemini-2.5-flash)". The pretty form failed the `pattern=`
|
|
140
|
+
// regex on its own display value, which tripped `:invalid` styling
|
|
141
|
+
// even when the field was correct. Human label moves to the caption
|
|
142
|
+
// below (aria-describedby).
|
|
143
|
+
const currentSlug = value;
|
|
144
|
+
// ROUND3-1: keep the soft-revert anchor in sync with the
|
|
145
|
+
// controlling parent — when the prop changes (e.g. after a server
|
|
146
|
+
// save), the next blur restores the new server value, not the
|
|
147
|
+
// pre-save one.
|
|
148
|
+
useEffect(()=>{
|
|
149
|
+
if (value) {
|
|
150
|
+
lastPickedSlugRef.current = value;
|
|
151
|
+
}
|
|
152
|
+
}, [
|
|
153
|
+
value
|
|
154
|
+
]);
|
|
155
|
+
const humanLabel = useMemo(()=>{
|
|
156
|
+
if (!value) {
|
|
157
|
+
return '';
|
|
158
|
+
}
|
|
159
|
+
const hit = models?.find((m)=>m.id === value);
|
|
160
|
+
return hit ? hit.name : '';
|
|
161
|
+
}, [
|
|
162
|
+
value,
|
|
163
|
+
models
|
|
164
|
+
]);
|
|
165
|
+
useEffect(()=>setHighlightIdx(0), []);
|
|
166
|
+
function pick(id) {
|
|
167
|
+
lastPickedSlugRef.current = id;
|
|
168
|
+
onChange(id);
|
|
169
|
+
setOpen(false);
|
|
170
|
+
setQuery('');
|
|
171
|
+
setShowInvalidAlert(false);
|
|
172
|
+
inputRef.current?.blur();
|
|
173
|
+
}
|
|
174
|
+
// ROUND2-3 / ROUND3-1: soft-revert on blur. If the user typed a
|
|
175
|
+
// non-canonical value (something that didn't match a listbox option),
|
|
176
|
+
// restore the last-known canonical slug into the visible input rather
|
|
177
|
+
// than leaving it empty. ROUND2-3 cleared `query` which — combined
|
|
178
|
+
// with `value={open ? query : currentSlug}` — looked correct on
|
|
179
|
+
// paper, but `setOpen(false)` runs concurrently with the input losing
|
|
180
|
+
// focus, so editors saw the input flash empty before the next render
|
|
181
|
+
// settled. Round-3 verification surfaced this as the "empty input
|
|
182
|
+
// next to populated caption" UX gap. Closing the popover here and
|
|
183
|
+
// explicitly tracking the last-picked slug fixes both: the input
|
|
184
|
+
// reads the ref's canonical value on the next render and the editor
|
|
185
|
+
// sees their typo dismissed in favour of the previously-saved model.
|
|
186
|
+
function handleBlur() {
|
|
187
|
+
setShowInvalidAlert(false);
|
|
188
|
+
setQuery('');
|
|
189
|
+
setOpen(false);
|
|
190
|
+
}
|
|
191
|
+
function keyDown(e) {
|
|
192
|
+
if (e.key === 'Escape') {
|
|
193
|
+
setOpen(false);
|
|
194
|
+
setQuery('');
|
|
195
|
+
} else if (e.key === 'ArrowDown') {
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
if (!open) {
|
|
198
|
+
setOpen(true);
|
|
199
|
+
}
|
|
200
|
+
setHighlightIdx((i)=>Math.min(i + 1, flat.length - 1));
|
|
201
|
+
} else if (e.key === 'ArrowUp') {
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
setHighlightIdx((i)=>Math.max(i - 1, 0));
|
|
204
|
+
} else if (e.key === 'Enter') {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
const t = flat[highlightIdx];
|
|
207
|
+
if (t) {
|
|
208
|
+
pick(t.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
213
|
+
ref: ref,
|
|
214
|
+
style: {
|
|
215
|
+
position: 'relative'
|
|
216
|
+
},
|
|
217
|
+
children: [
|
|
218
|
+
fromFallback && /*#__PURE__*/ _jsx("p", {
|
|
219
|
+
style: {
|
|
220
|
+
margin: '0 0 0.5rem',
|
|
221
|
+
color: 'var(--theme-warning-500, #b45309)',
|
|
222
|
+
fontSize: '0.75rem'
|
|
223
|
+
},
|
|
224
|
+
children: "Live catalog unreachable — showing fallback."
|
|
225
|
+
}),
|
|
226
|
+
savedMissing && /*#__PURE__*/ _jsx("p", {
|
|
227
|
+
style: {
|
|
228
|
+
margin: '0 0 0.5rem',
|
|
229
|
+
color: 'var(--theme-warning-500, #b45309)',
|
|
230
|
+
fontSize: '0.75rem'
|
|
231
|
+
},
|
|
232
|
+
children: "Saved model not in current catalog — translation falls back to default."
|
|
233
|
+
}),
|
|
234
|
+
error && /*#__PURE__*/ _jsx("p", {
|
|
235
|
+
style: {
|
|
236
|
+
margin: '0 0 0.5rem',
|
|
237
|
+
color: 'var(--theme-error-500, #b91c1c)',
|
|
238
|
+
fontSize: '0.75rem'
|
|
239
|
+
},
|
|
240
|
+
children: error
|
|
241
|
+
}),
|
|
242
|
+
/*#__PURE__*/ _jsx("input", {
|
|
243
|
+
"aria-autocomplete": "list",
|
|
244
|
+
"aria-controls": open ? 'model-combobox-listbox' : undefined,
|
|
245
|
+
"aria-describedby": humanLabel || showInvalidAlert ? 'model-combobox-caption' : undefined,
|
|
246
|
+
"aria-expanded": open,
|
|
247
|
+
"aria-haspopup": "listbox",
|
|
248
|
+
// ROUND2-3: reflect `validity.patternMismatch` so AT users hear
|
|
249
|
+
// "invalid entry" instead of silently typing into a field that
|
|
250
|
+
// ignores them. Drives the inline alert too.
|
|
251
|
+
"aria-invalid": showInvalidAlert || undefined,
|
|
252
|
+
autoComplete: "off",
|
|
253
|
+
disabled: disabled || models === null && !error,
|
|
254
|
+
// Permissive pattern — accepts a typed-in vendor/slug id with
|
|
255
|
+
// the OpenRouter canonical shape; downstream picker still
|
|
256
|
+
// validates against the live `models` catalog. Empty allowed
|
|
257
|
+
// so the user can clear the field. ROUND2-3: the *displayed*
|
|
258
|
+
// value is now the canonical slug, so this pattern no longer
|
|
259
|
+
// trips on its own loaded value.
|
|
260
|
+
list: "model-combobox-known-models",
|
|
261
|
+
onBlur: handleBlur,
|
|
262
|
+
onChange: (e)=>{
|
|
263
|
+
const next = e.target.value;
|
|
264
|
+
setQuery(next);
|
|
265
|
+
if (!open) {
|
|
266
|
+
setOpen(true);
|
|
267
|
+
}
|
|
268
|
+
// ROUND2-3: visible feedback for non-canonical free-text
|
|
269
|
+
// typing. Empty is allowed (clears the field); anything else
|
|
270
|
+
// must match the canonical slug regex. The save-side guard
|
|
271
|
+
// already gates `onChange` on listbox-pick only — this is
|
|
272
|
+
// purely UI feedback.
|
|
273
|
+
setShowInvalidAlert(next.length > 0 && !CANONICAL_SLUG_REGEX.test(next));
|
|
274
|
+
},
|
|
275
|
+
onFocus: ()=>setOpen(true),
|
|
276
|
+
onKeyDown: keyDown,
|
|
277
|
+
pattern: CANONICAL_SLUG_PATTERN,
|
|
278
|
+
placeholder: models === null && !error ? 'Loading…' : 'Search models…',
|
|
279
|
+
ref: inputRef,
|
|
280
|
+
role: "combobox",
|
|
281
|
+
style: {
|
|
282
|
+
width: '100%',
|
|
283
|
+
padding: '0.5rem 0.75rem',
|
|
284
|
+
background: 'var(--theme-elevation-50)',
|
|
285
|
+
border: showInvalidAlert ? '1px solid var(--theme-error-500, #b91c1c)' : '1px solid var(--theme-elevation-150)',
|
|
286
|
+
borderRadius: '4px',
|
|
287
|
+
color: 'var(--theme-elevation-1000)',
|
|
288
|
+
font: 'inherit'
|
|
289
|
+
},
|
|
290
|
+
type: "text",
|
|
291
|
+
value: open ? query : currentSlug
|
|
292
|
+
}),
|
|
293
|
+
showInvalidAlert ? /*#__PURE__*/ _jsxs("span", {
|
|
294
|
+
id: "model-combobox-caption",
|
|
295
|
+
role: "alert",
|
|
296
|
+
style: {
|
|
297
|
+
display: 'block',
|
|
298
|
+
marginTop: '0.25rem',
|
|
299
|
+
fontSize: '0.7rem',
|
|
300
|
+
color: 'var(--theme-error-500, #b91c1c)'
|
|
301
|
+
},
|
|
302
|
+
children: [
|
|
303
|
+
"Use the format ",
|
|
304
|
+
/*#__PURE__*/ _jsx("code", {
|
|
305
|
+
children: "vendor/model"
|
|
306
|
+
}),
|
|
307
|
+
" — e.g. ",
|
|
308
|
+
/*#__PURE__*/ _jsx("code", {
|
|
309
|
+
children: "google/gemini-2.5-flash"
|
|
310
|
+
}),
|
|
311
|
+
"."
|
|
312
|
+
]
|
|
313
|
+
}) : humanLabel ? /*#__PURE__*/ _jsx("span", {
|
|
314
|
+
id: "model-combobox-caption",
|
|
315
|
+
style: {
|
|
316
|
+
display: 'block',
|
|
317
|
+
marginTop: '0.25rem',
|
|
318
|
+
fontSize: '0.7rem',
|
|
319
|
+
color: 'var(--theme-elevation-500)'
|
|
320
|
+
},
|
|
321
|
+
children: humanLabel
|
|
322
|
+
}) : null,
|
|
323
|
+
models && /*#__PURE__*/ _jsx("datalist", {
|
|
324
|
+
id: "model-combobox-known-models",
|
|
325
|
+
children: models.map((m)=>/*#__PURE__*/ _jsx("option", {
|
|
326
|
+
value: m.id,
|
|
327
|
+
children: m.name
|
|
328
|
+
}, m.id))
|
|
329
|
+
}),
|
|
330
|
+
open && models && /*#__PURE__*/ _jsx("div", {
|
|
331
|
+
id: "model-combobox-listbox",
|
|
332
|
+
role: "listbox",
|
|
333
|
+
style: {
|
|
334
|
+
position: 'absolute',
|
|
335
|
+
top: '100%',
|
|
336
|
+
left: 0,
|
|
337
|
+
right: 0,
|
|
338
|
+
zIndex: 10,
|
|
339
|
+
marginTop: '4px',
|
|
340
|
+
maxHeight: '280px',
|
|
341
|
+
overflowY: 'auto',
|
|
342
|
+
background: 'var(--theme-elevation-50)',
|
|
343
|
+
border: '1px solid var(--theme-elevation-150)',
|
|
344
|
+
borderRadius: '4px',
|
|
345
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
|
346
|
+
fontSize: '0.875rem'
|
|
347
|
+
},
|
|
348
|
+
children: grouped.length === 0 ? /*#__PURE__*/ _jsxs("div", {
|
|
349
|
+
style: {
|
|
350
|
+
padding: '0.4rem 0.75rem',
|
|
351
|
+
color: 'var(--theme-elevation-500)'
|
|
352
|
+
},
|
|
353
|
+
children: [
|
|
354
|
+
'No models match "',
|
|
355
|
+
query,
|
|
356
|
+
'"'
|
|
357
|
+
]
|
|
358
|
+
}) : grouped.map(({ vendor, models: mods })=>/*#__PURE__*/ _jsxs(React.Fragment, {
|
|
359
|
+
children: [
|
|
360
|
+
/*#__PURE__*/ _jsx("div", {
|
|
361
|
+
style: {
|
|
362
|
+
padding: '0.3rem 0.75rem',
|
|
363
|
+
background: 'var(--theme-elevation-100)',
|
|
364
|
+
fontSize: '0.7rem',
|
|
365
|
+
fontWeight: 600,
|
|
366
|
+
textTransform: 'uppercase',
|
|
367
|
+
color: 'var(--theme-elevation-500)',
|
|
368
|
+
position: 'sticky',
|
|
369
|
+
top: 0
|
|
370
|
+
},
|
|
371
|
+
children: VENDOR_LABEL[vendor]
|
|
372
|
+
}),
|
|
373
|
+
mods.map((m)=>{
|
|
374
|
+
const i = flat.indexOf(m);
|
|
375
|
+
const hl = i === highlightIdx;
|
|
376
|
+
const sel = m.id === value;
|
|
377
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
378
|
+
"aria-selected": sel,
|
|
379
|
+
onMouseDown: (e)=>{
|
|
380
|
+
e.preventDefault();
|
|
381
|
+
pick(m.id);
|
|
382
|
+
},
|
|
383
|
+
onMouseEnter: ()=>setHighlightIdx(i),
|
|
384
|
+
role: "option",
|
|
385
|
+
style: {
|
|
386
|
+
padding: '0.4rem 0.75rem',
|
|
387
|
+
cursor: 'pointer',
|
|
388
|
+
background: hl ? 'var(--theme-elevation-100)' : 'transparent',
|
|
389
|
+
fontWeight: sel ? 600 : 400,
|
|
390
|
+
color: 'var(--theme-elevation-1000)'
|
|
391
|
+
},
|
|
392
|
+
tabIndex: -1,
|
|
393
|
+
children: [
|
|
394
|
+
m.name,
|
|
395
|
+
' ',
|
|
396
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
397
|
+
style: {
|
|
398
|
+
color: 'var(--theme-elevation-500)',
|
|
399
|
+
fontSize: '0.75rem'
|
|
400
|
+
},
|
|
401
|
+
children: [
|
|
402
|
+
"(",
|
|
403
|
+
m.id,
|
|
404
|
+
")"
|
|
405
|
+
]
|
|
406
|
+
})
|
|
407
|
+
]
|
|
408
|
+
}, m.id);
|
|
409
|
+
})
|
|
410
|
+
]
|
|
411
|
+
}, vendor))
|
|
412
|
+
})
|
|
413
|
+
]
|
|
414
|
+
});
|
|
415
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { LocaleConfig, TranslationSettings } from './Hub.client.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
basePath: string;
|
|
5
|
+
locales: LocaleConfig;
|
|
6
|
+
onSettingsChange: (next: TranslationSettings) => void;
|
|
7
|
+
translationSettings: TranslationSettings | null;
|
|
8
|
+
}
|
|
9
|
+
export declare const PerCollectionConfig: React.FC<Props>;
|
|
10
|
+
export default PerCollectionConfig;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports the pure `summarize` helper from PerCollectionConfig.tsx so it
|
|
3
|
+
* can be unit-tested without importing the full React component.
|
|
4
|
+
*
|
|
5
|
+
* Keeping the logic here ensures a single source of truth — the component
|
|
6
|
+
* imports from this file rather than defining the function inline.
|
|
7
|
+
*/
|
|
8
|
+
export type PerCollectionEntry = {
|
|
9
|
+
slug: string;
|
|
10
|
+
enabled?: boolean | null;
|
|
11
|
+
autoOnPublish?: boolean | null;
|
|
12
|
+
targetLocalesOverride?: string[] | null;
|
|
13
|
+
translateSlug?: boolean | null;
|
|
14
|
+
excludedFieldPaths?: string[] | null;
|
|
15
|
+
};
|
|
16
|
+
export declare function summarize(row: PerCollectionEntry, fieldCount: number | null): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports the pure `summarize` helper from PerCollectionConfig.tsx so it
|
|
3
|
+
* can be unit-tested without importing the full React component.
|
|
4
|
+
*
|
|
5
|
+
* Keeping the logic here ensures a single source of truth — the component
|
|
6
|
+
* imports from this file rather than defining the function inline.
|
|
7
|
+
*/ export function summarize(row, fieldCount) {
|
|
8
|
+
const bits = [];
|
|
9
|
+
if (row.enabled === false) bits.push('DISABLED');
|
|
10
|
+
if (row.autoOnPublish === false) bits.push('manual only');
|
|
11
|
+
if (row.targetLocalesOverride?.length) {
|
|
12
|
+
bits.push(`locales: ${row.targetLocalesOverride.join('/')}`);
|
|
13
|
+
}
|
|
14
|
+
if (row.translateSlug) bits.push('slug translated');
|
|
15
|
+
if (row.excludedFieldPaths?.length) {
|
|
16
|
+
bits.push(fieldCount != null ? `${row.excludedFieldPaths.length}/${fieldCount} fields excluded` : `${row.excludedFieldPaths.length} fields excluded`);
|
|
17
|
+
}
|
|
18
|
+
return bits.length === 0 ? 'inherits site-wide defaults' : bits.join(' · ');
|
|
19
|
+
}
|