@open-mercato/ui 0.6.7 → 0.6.8-canary.320.1.bf889f49e4
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +4 -2
- package/agentic/standalone-guide.md +1 -1
- package/build.mjs +2 -54
- package/dist/ai/AiChat.js +73 -18
- package/dist/ai/AiChat.js.map +2 -2
- package/dist/ai/AiChatSessions.js +11 -2
- package/dist/ai/AiChatSessions.js.map +2 -2
- package/dist/ai/LoopDisabledBanner.js +1 -1
- package/dist/ai/LoopDisabledBanner.js.map +2 -2
- package/dist/ai/parts/ConfirmationCard.js +4 -4
- package/dist/ai/parts/ConfirmationCard.js.map +2 -2
- package/dist/ai/parts/FieldDiffCard.js +2 -2
- package/dist/ai/parts/FieldDiffCard.js.map +2 -2
- package/dist/ai/parts/MutationResultCard.js +3 -3
- package/dist/ai/parts/MutationResultCard.js.map +2 -2
- package/dist/ai/parts/useAiPendingActionPolling.js +12 -2
- package/dist/ai/parts/useAiPendingActionPolling.js.map +2 -2
- package/dist/ai/ui-parts/pending-phase3-placeholder.js +1 -1
- package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +2 -2
- package/dist/backend/AppShell.js +43 -4
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/AuthSessionGuard.js +14 -0
- package/dist/backend/AuthSessionGuard.js.map +2 -2
- package/dist/backend/BackendChromeProvider.js +6 -1
- package/dist/backend/BackendChromeProvider.js.map +2 -2
- package/dist/backend/CrudForm.js +6 -4
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/DataTable.js +20 -18
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/JsonBuilder.js +3 -3
- package/dist/backend/JsonBuilder.js.map +2 -2
- package/dist/backend/NextStepCallout.js +4 -4
- package/dist/backend/NextStepCallout.js.map +1 -1
- package/dist/backend/PerspectiveSidebar.js +1 -1
- package/dist/backend/PerspectiveSidebar.js.map +2 -2
- package/dist/backend/ProfileDropdown.js +2 -1
- package/dist/backend/ProfileDropdown.js.map +2 -2
- package/dist/backend/RowActions.js +2 -2
- package/dist/backend/RowActions.js.map +2 -2
- package/dist/backend/confirm-dialog/ConfirmDialog.js +13 -1
- package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
- package/dist/backend/crud/useGroupOrder.js +14 -21
- package/dist/backend/crud/useGroupOrder.js.map +2 -2
- package/dist/backend/custom-fields/FieldDefinitionsManager.js +1 -1
- package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +2 -2
- package/dist/backend/dashboard/DashboardScreen.js +22 -8
- package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
- package/dist/backend/detail/AddressTiles.js +4 -5
- package/dist/backend/detail/AddressTiles.js.map +2 -2
- package/dist/backend/detail/AttachmentDeleteDialog.js +1 -1
- package/dist/backend/detail/AttachmentDeleteDialog.js.map +2 -2
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +1 -1
- package/dist/backend/detail/AttachmentsSection.js +1 -1
- package/dist/backend/detail/AttachmentsSection.js.map +2 -2
- package/dist/backend/detail/NotesSection.js +1 -1
- package/dist/backend/detail/NotesSection.js.map +2 -2
- package/dist/backend/detail/TagsSection.js +1 -1
- package/dist/backend/detail/TagsSection.js.map +2 -2
- package/dist/backend/filters/AdvancedFilterPanel.js +1 -1
- package/dist/backend/filters/AdvancedFilterPanel.js.map +2 -2
- package/dist/backend/forms/FormHeader.js +1 -2
- package/dist/backend/forms/FormHeader.js.map +2 -2
- package/dist/backend/icons/lucideRegistry.generated.js +1 -25
- package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
- package/dist/backend/icons/lucideRegistry.js +3 -3
- package/dist/backend/icons/lucideRegistry.js.map +2 -2
- package/dist/backend/icons/lucideRegistryRuntime.js +28 -0
- package/dist/backend/icons/lucideRegistryRuntime.js.map +7 -0
- package/dist/backend/injection/ComponentOverrideProvider.js +7 -1
- package/dist/backend/injection/ComponentOverrideProvider.js.map +2 -2
- package/dist/backend/injection/spotIds.js +23 -18
- package/dist/backend/injection/spotIds.js.map +2 -2
- package/dist/backend/injection/useRegisteredComponent.js +3 -2
- package/dist/backend/injection/useRegisteredComponent.js.map +2 -2
- package/dist/backend/inputs/ComboboxInput.js.map +1 -1
- package/dist/backend/messages/EmailThreadsPanel.js +1 -1
- package/dist/backend/messages/EmailThreadsPanel.js.map +2 -2
- package/dist/backend/perspectiveState.js +34 -0
- package/dist/backend/perspectiveState.js.map +7 -0
- package/dist/backend/schedule/ScheduleAgenda.js +3 -3
- package/dist/backend/schedule/ScheduleAgenda.js.map +1 -1
- package/dist/backend/schedule/ScheduleGrid.js +3 -3
- package/dist/backend/schedule/ScheduleGrid.js.map +1 -1
- package/dist/backend/schedule/ScheduleToolbar.js +87 -70
- package/dist/backend/schedule/ScheduleToolbar.js.map +2 -2
- package/dist/backend/sidebar/SidebarCustomizationEditor.js +30 -2
- package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +2 -2
- package/dist/backend/sidebar/customization-helpers.js +30 -0
- package/dist/backend/sidebar/customization-helpers.js.map +2 -2
- package/dist/backend/utils/apiCall.js +21 -1
- package/dist/backend/utils/apiCall.js.map +2 -2
- package/dist/backend/utils/customFieldDefs.js.map +2 -2
- package/dist/backend/utils/customFieldForms.js +10 -9
- package/dist/backend/utils/customFieldForms.js.map +2 -2
- package/dist/backend/utils/fetchAllPages.js +57 -0
- package/dist/backend/utils/fetchAllPages.js.map +7 -0
- package/dist/backend/utils/nav.js +5 -2
- package/dist/backend/utils/nav.js.map +2 -2
- package/dist/backend/version-history/VersionHistoryPanel.js +1 -1
- package/dist/backend/version-history/VersionHistoryPanel.js.map +2 -2
- package/dist/frontend/LanguageSwitcher.js +2 -1
- package/dist/frontend/LanguageSwitcher.js.map +2 -2
- package/dist/portal/PortalContext.js +1 -1
- package/dist/portal/PortalContext.js.map +2 -2
- package/dist/portal/hooks/portalBridgeStatus.js +25 -0
- package/dist/portal/hooks/portalBridgeStatus.js.map +7 -0
- package/dist/portal/hooks/usePortalEventBridge.js +6 -0
- package/dist/portal/hooks/usePortalEventBridge.js.map +2 -2
- package/dist/portal/hooks/usePortalNotifications.js +42 -4
- package/dist/portal/hooks/usePortalNotifications.js.map +2 -2
- package/dist/portal/utils/nav.js +2 -3
- package/dist/portal/utils/nav.js.map +2 -2
- package/dist/primitives/ErrorNotice.js +1 -1
- package/dist/primitives/ErrorNotice.js.map +2 -2
- package/dist/primitives/Notice.js +1 -1
- package/dist/primitives/Notice.js.map +1 -1
- package/dist/primitives/button.js +9 -4
- package/dist/primitives/button.js.map +2 -2
- package/dist/theme/ThemeProvider.js +1 -1
- package/dist/theme/ThemeProvider.js.map +2 -2
- package/dist/theme/index.js +3 -0
- package/dist/theme/index.js.map +2 -2
- package/dist/theme/theme-init-script.js +17 -0
- package/dist/theme/theme-init-script.js.map +7 -0
- package/figma/alert.figma.tsx +35 -0
- package/figma/badge.figma.tsx +33 -0
- package/figma/button.figma.tsx +33 -0
- package/figma/checkbox.figma.tsx +28 -0
- package/figma/drawer.figma.tsx +40 -0
- package/figma/input-variants.figma.tsx +58 -0
- package/figma/radio.figma.tsx +16 -0
- package/figma/select.figma.tsx +24 -0
- package/figma/status-badge.figma.tsx +24 -0
- package/figma/switch.figma.tsx +28 -0
- package/figma/tabs.figma.tsx +22 -0
- package/figma/tag.figma.tsx +27 -0
- package/figma.config.json +6 -0
- package/jest.config.cjs +20 -10
- package/package.json +21 -18
- package/scripts/lucideRegistrySource.cjs +133 -0
- package/src/ai/AiChat.tsx +111 -34
- package/src/ai/AiChatSessions.tsx +16 -4
- package/src/ai/LoopDisabledBanner.tsx +1 -1
- package/src/ai/__tests__/AiChat.test.tsx +222 -2
- package/src/ai/__tests__/AiChatSessions.test.tsx +62 -0
- package/src/ai/parts/ConfirmationCard.tsx +4 -4
- package/src/ai/parts/FieldDiffCard.tsx +2 -2
- package/src/ai/parts/MutationResultCard.tsx +3 -3
- package/src/ai/parts/__tests__/useAiPendingActionPolling.test.tsx +61 -0
- package/src/ai/parts/useAiPendingActionPolling.ts +21 -6
- package/src/ai/ui-parts/pending-phase3-placeholder.tsx +2 -2
- package/src/backend/AppShell.tsx +55 -4
- package/src/backend/AuthSessionGuard.tsx +29 -0
- package/src/backend/BackendChromeProvider.tsx +19 -1
- package/src/backend/CrudForm.tsx +18 -4
- package/src/backend/DataTable.tsx +36 -19
- package/src/backend/JsonBuilder.tsx +3 -3
- package/src/backend/NextStepCallout.tsx +4 -4
- package/src/backend/PerspectiveSidebar.tsx +1 -1
- package/src/backend/ProfileDropdown.tsx +1 -0
- package/src/backend/RowActions.tsx +2 -2
- package/src/backend/__tests__/AppShell.test.tsx +65 -7
- package/src/backend/__tests__/AuthSessionGuard.test.tsx +52 -0
- package/src/backend/__tests__/BackendChromeProvider.test.tsx +98 -2
- package/src/backend/__tests__/CrudForm.fieldsetBinding.test.tsx +158 -0
- package/src/backend/__tests__/CrudForm.groupInjectionColumn.test.tsx +98 -0
- package/src/backend/__tests__/DataTable.metaHiddenLateColumns.test.tsx +105 -0
- package/src/backend/__tests__/DataTable.perspectiveStorage.test.ts +62 -1
- package/src/backend/__tests__/nav-utils.test.ts +139 -0
- package/src/backend/confirm-dialog/ConfirmDialog.tsx +16 -1
- package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +68 -1
- package/src/backend/crud/__tests__/useGroupOrder.test.ts +39 -1
- package/src/backend/crud/useGroupOrder.ts +30 -25
- package/src/backend/custom-fields/FieldDefinitionsManager.tsx +1 -1
- package/src/backend/dashboard/DashboardScreen.tsx +29 -8
- package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +92 -1
- package/src/backend/detail/AddressTiles.tsx +3 -6
- package/src/backend/detail/AttachmentDeleteDialog.tsx +1 -1
- package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
- package/src/backend/detail/AttachmentsSection.tsx +1 -1
- package/src/backend/detail/NotesSection.tsx +1 -1
- package/src/backend/detail/TagsSection.tsx +1 -1
- package/src/backend/detail/__tests__/AttachmentAssignmentsEditor.test.tsx +21 -0
- package/src/backend/filters/AdvancedFilterPanel.tsx +1 -1
- package/src/backend/forms/FormHeader.tsx +1 -2
- package/src/backend/icons/__tests__/lucideRegistryGenerator.test.ts +240 -0
- package/src/backend/icons/lucideRegistry.generated.tsx +3 -37
- package/src/backend/icons/lucideRegistry.ts +2 -2
- package/src/backend/icons/lucideRegistryRuntime.tsx +36 -0
- package/src/backend/injection/ComponentOverrideProvider.tsx +11 -3
- package/src/backend/injection/__tests__/spotIds.extension-points.test.ts +38 -0
- package/src/backend/injection/spotIds.ts +23 -18
- package/src/backend/injection/useRegisteredComponent.tsx +3 -2
- package/src/backend/inputs/ComboboxInput.tsx +1 -1
- package/src/backend/messages/EmailThreadsPanel.tsx +1 -1
- package/src/backend/perspectiveState.ts +44 -0
- package/src/backend/schedule/ScheduleAgenda.tsx +3 -3
- package/src/backend/schedule/ScheduleGrid.tsx +3 -3
- package/src/backend/schedule/ScheduleToolbar.tsx +89 -68
- package/src/backend/sidebar/SidebarCustomizationEditor.tsx +42 -3
- package/src/backend/sidebar/__tests__/group-visibility.test.ts +131 -0
- package/src/backend/sidebar/customization-helpers.ts +47 -0
- package/src/backend/utils/__tests__/apiCall.test.ts +61 -0
- package/src/backend/utils/__tests__/customFieldForms.optionsPaging.test.ts +95 -0
- package/src/backend/utils/__tests__/fetchAllPages.test.ts +103 -0
- package/src/backend/utils/apiCall.ts +29 -1
- package/src/backend/utils/customFieldDefs.ts +1 -0
- package/src/backend/utils/customFieldForms.ts +15 -11
- package/src/backend/utils/fetchAllPages.ts +93 -0
- package/src/backend/utils/nav.ts +22 -2
- package/src/backend/version-history/VersionHistoryPanel.tsx +1 -1
- package/src/frontend/LanguageSwitcher.tsx +1 -0
- package/src/portal/PortalContext.tsx +1 -1
- package/src/portal/hooks/__tests__/usePortalEventBridge.test.tsx +66 -0
- package/src/portal/hooks/__tests__/usePortalNotifications.test.tsx +155 -0
- package/src/portal/hooks/portalBridgeStatus.ts +29 -0
- package/src/portal/hooks/usePortalEventBridge.ts +6 -0
- package/src/portal/hooks/usePortalNotifications.ts +55 -12
- package/src/portal/utils/__tests__/nav.test.ts +2 -2
- package/src/portal/utils/nav.ts +3 -4
- package/src/primitives/ErrorNotice.tsx +1 -1
- package/src/primitives/Notice.tsx +5 -5
- package/src/primitives/__tests__/button.test.tsx +70 -0
- package/src/primitives/button.tsx +10 -4
- package/src/theme/ThemeProvider.tsx +1 -2
- package/src/theme/__tests__/theme-init-script.test.ts +70 -0
- package/src/theme/index.ts +1 -0
- package/src/theme/theme-init-script.ts +23 -0
package/.turbo/turbo-build.log
CHANGED
package/AGENTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# UI Package - Agent Guidelines
|
|
2
2
|
|
|
3
|
-
UI
|
|
3
|
+
UI defaults for `packages/ui` and its consumers.
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> References: [DS rules](../../.ai/ds-rules.md), [component contracts](../../.ai/ui-components.md), and [backend component families](../../.ai/ui-backend-components.md).
|
|
6
6
|
|
|
7
7
|
## Always
|
|
8
8
|
|
|
@@ -14,6 +14,7 @@ UI usage patterns based on customers, sales, and staff modules. Use these defaul
|
|
|
14
14
|
- Use i18n keys and `useT()` for user-facing copy.
|
|
15
15
|
- Keep UMES spot IDs, replacement handles, field/group IDs, and portal page metadata stable.
|
|
16
16
|
- Follow `.ai/ds-rules.md` and `.ai/ui-components.md` for tokens, primitives, and component contracts.
|
|
17
|
+
- Keep documented primitive additions/renames and their `figma/*.figma.tsx` mappings in the same PR; run `yarn ds:code-connect:check`.
|
|
17
18
|
|
|
18
19
|
## Ask First
|
|
19
20
|
|
|
@@ -36,6 +37,7 @@ UI usage patterns based on customers, sales, and staff modules. Use these defaul
|
|
|
36
37
|
```bash
|
|
37
38
|
yarn workspace @open-mercato/ui test
|
|
38
39
|
yarn workspace @open-mercato/ui build
|
|
40
|
+
yarn ds:code-connect:check
|
|
39
41
|
yarn i18n:check
|
|
40
42
|
```
|
|
41
43
|
|
|
@@ -189,7 +189,7 @@ import {
|
|
|
189
189
|
| `usePortalAppEvent(pattern, handler, deps?)` | Listen for portal SSE events by glob pattern (e.g., `'sales.order.*'`) |
|
|
190
190
|
| `usePortalEventBridge()` | Establish singleton SSE connection — mount once in shell/layout |
|
|
191
191
|
| `usePortalInjectedMenuItems(surfaceId)` | Load feature-gated menu items for portal nav surfaces: `{ items, isLoading }` |
|
|
192
|
-
| `usePortalNotifications()` |
|
|
192
|
+
| `usePortalNotifications()` | Load portal notifications with SSE-first refresh and an 8-second fallback until the portal event bridge reports healthy: `{ notifications, unreadCount, hasNew, isLoading, refresh, markAsRead, dismiss, markAllRead }` |
|
|
193
193
|
| `usePortalDashboardWidgets(spotId)` | Load UI injection widgets (with `Widget` component) for a portal spot: `{ widgets, isLoading, error }` |
|
|
194
194
|
|
|
195
195
|
### Portal Components
|
package/build.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, writeFileSync } from 'node:fs'
|
|
|
3
3
|
import { dirname, join } from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import { buildPackage } from '../../scripts/build-package.mjs'
|
|
6
|
+
import { buildLucideRegistrySource } from './scripts/lucideRegistrySource.cjs'
|
|
6
7
|
|
|
7
8
|
const packageDir = dirname(fileURLToPath(import.meta.url))
|
|
8
9
|
|
|
@@ -88,61 +89,8 @@ async function generateLucideRegistry() {
|
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
resolved.sort((a, b) => a.kebab.localeCompare(b.kebab))
|
|
92
|
-
const uniqueExports = [...new Set(resolved.map((r) => r.exportName))].sort((a, b) => a.localeCompare(b))
|
|
93
|
-
|
|
94
|
-
const importSection = uniqueExports.length
|
|
95
|
-
? `import {\n${uniqueExports.map((n) => ` ${n},`).join('\n')}\n} from 'lucide-react'\n`
|
|
96
|
-
: ``
|
|
97
|
-
|
|
98
|
-
const registryEntries = resolved.map((r) => ` '${r.kebab}': ${r.exportName},`).join('\n')
|
|
99
|
-
|
|
100
92
|
const outPath = join(packageDir, 'src/backend/icons/lucideRegistry.generated.tsx')
|
|
101
|
-
|
|
102
|
-
// AUTO-GENERATED by @open-mercato/ui build. Do not edit by hand.
|
|
103
|
-
// Generated from discovered icon string usages across the repo.
|
|
104
|
-
|
|
105
|
-
import type * as React from 'react'
|
|
106
|
-
import type { LucideIcon } from 'lucide-react'
|
|
107
|
-
${importSection}
|
|
108
|
-
export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
109
|
-
${registryEntries ? `${registryEntries}\n` : ''}}
|
|
110
|
-
|
|
111
|
-
function normalizeKebabIconName(input: string): string {
|
|
112
|
-
const trimmed = input.trim()
|
|
113
|
-
if (!trimmed) return ''
|
|
114
|
-
const withoutPrefix = trimmed.startsWith('lucide:') ? trimmed.slice('lucide:'.length) : trimmed
|
|
115
|
-
if (!withoutPrefix) return ''
|
|
116
|
-
if (!withoutPrefix.includes('-') && !withoutPrefix.includes('_') && !withoutPrefix.includes(' ') && /[A-Z]/.test(withoutPrefix)) {
|
|
117
|
-
return withoutPrefix
|
|
118
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
119
|
-
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
120
|
-
.toLowerCase()
|
|
121
|
-
}
|
|
122
|
-
return withoutPrefix
|
|
123
|
-
.replace(/[_\\s]+/g, '-')
|
|
124
|
-
.replace(/-+/g, '-')
|
|
125
|
-
.toLowerCase()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function resolveRegisteredLucideIcon(name: string | undefined): LucideIcon | null {
|
|
129
|
-
if (!name) return null
|
|
130
|
-
const normalized = normalizeKebabIconName(name)
|
|
131
|
-
if (!normalized) return null
|
|
132
|
-
return (LUCIDE_ICON_REGISTRY as Record<string, LucideIcon | undefined>)[normalized] ?? null
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function resolveRegisteredLucideIconNode(
|
|
136
|
-
name: string | undefined,
|
|
137
|
-
className: string
|
|
138
|
-
): React.ReactNode | null {
|
|
139
|
-
const Icon = resolveRegisteredLucideIcon(name)
|
|
140
|
-
if (!Icon) return null
|
|
141
|
-
return <Icon className={className} />
|
|
142
|
-
}
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
writeFileSync(outPath, file)
|
|
93
|
+
writeFileSync(outPath, buildLucideRegistrySource(resolved))
|
|
146
94
|
console.log(`Generated lucide registry with ${resolved.length} icons -> ${outPath}`)
|
|
147
95
|
}
|
|
148
96
|
|
package/dist/ai/AiChat.js
CHANGED
|
@@ -72,35 +72,68 @@ import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
|
72
72
|
const logger = createLogger("ui").child({ component: "AiChat" });
|
|
73
73
|
const PREVIEW_DATA_URL_MAX_BYTES = 2 * 1024 * 1024;
|
|
74
74
|
const COMPACT_FOOTER_MAX_WIDTH = 640;
|
|
75
|
+
function readErrorStatus(error) {
|
|
76
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
77
|
+
const { status } = error;
|
|
78
|
+
return typeof status === "number" ? status : void 0;
|
|
79
|
+
}
|
|
80
|
+
function logModelsFailure(status, details) {
|
|
81
|
+
const message = "Failed to load AI agent models";
|
|
82
|
+
if (status === 401 || status === 403) {
|
|
83
|
+
logger.warn(message, details);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
logger.error(message, details);
|
|
87
|
+
}
|
|
75
88
|
function useAgentModels(agent) {
|
|
76
89
|
const [providers, setProviders] = React.useState([]);
|
|
77
90
|
const [allowRuntimeOverride, setAllowRuntimeOverride] = React.useState(false);
|
|
78
91
|
const [defaultLabel, setDefaultLabel] = React.useState(null);
|
|
79
|
-
const [
|
|
92
|
+
const [status, setStatus] = React.useState("loading");
|
|
93
|
+
const [degraded, setDegraded] = React.useState(false);
|
|
80
94
|
React.useEffect(() => {
|
|
95
|
+
let cancelled = false;
|
|
81
96
|
const modelsUrl = `/api/ai_assistant/ai/agents/${encodeURIComponent(agent)}/models`;
|
|
82
|
-
|
|
97
|
+
setStatus("loading");
|
|
83
98
|
setDefaultLabel(null);
|
|
84
|
-
|
|
99
|
+
setDegraded(false);
|
|
100
|
+
apiCall(modelsUrl).then((result) => {
|
|
101
|
+
if (cancelled) return;
|
|
85
102
|
if (!result.ok || !result.result) {
|
|
86
|
-
|
|
103
|
+
logModelsFailure(result.status, { agent, status: result.status });
|
|
104
|
+
setStatus("failed");
|
|
87
105
|
return;
|
|
88
106
|
}
|
|
89
107
|
const effectiveAllowRuntimeOverride = result.result.allowRuntimeOverride ?? result.result.allowRuntimeModelOverride ?? false;
|
|
90
108
|
setAllowRuntimeOverride(effectiveAllowRuntimeOverride);
|
|
91
109
|
setProviders(result.result.providers);
|
|
110
|
+
if (result.result.degraded === true) {
|
|
111
|
+
setDegraded(true);
|
|
112
|
+
logger.warn("AI agent models response is degraded", {
|
|
113
|
+
agent,
|
|
114
|
+
degradedReason: result.result.degradedReason ?? null
|
|
115
|
+
});
|
|
116
|
+
}
|
|
92
117
|
setDefaultLabel(
|
|
93
118
|
result.result.defaultProviderName && result.result.defaultModelName ? `${result.result.defaultProviderName} / ${result.result.defaultModelName}` : result.result.defaultProviderId && result.result.defaultModelId ? `${result.result.defaultProviderId} / ${result.result.defaultModelId}` : null
|
|
94
119
|
);
|
|
95
|
-
|
|
120
|
+
setStatus("ready");
|
|
121
|
+
}).catch((err) => {
|
|
122
|
+
if (cancelled) return;
|
|
123
|
+
logModelsFailure(readErrorStatus(err), { agent, err });
|
|
124
|
+
setStatus("failed");
|
|
96
125
|
});
|
|
126
|
+
return () => {
|
|
127
|
+
cancelled = true;
|
|
128
|
+
};
|
|
97
129
|
}, [agent]);
|
|
98
130
|
return {
|
|
99
131
|
providers,
|
|
100
132
|
allowRuntimeOverride,
|
|
101
133
|
allowRuntimeModelOverride: allowRuntimeOverride,
|
|
102
134
|
defaultLabel,
|
|
103
|
-
|
|
135
|
+
status,
|
|
136
|
+
degraded
|
|
104
137
|
};
|
|
105
138
|
}
|
|
106
139
|
function isModelPickerValueAvailable(value, providers) {
|
|
@@ -122,14 +155,33 @@ async function readFileAsDataUrl(file) {
|
|
|
122
155
|
}
|
|
123
156
|
});
|
|
124
157
|
}
|
|
125
|
-
function
|
|
126
|
-
if (!code) return "
|
|
158
|
+
function mapErrorCodeToStatus(code) {
|
|
159
|
+
if (!code) return "error";
|
|
127
160
|
const warningCodes = /* @__PURE__ */ new Set([
|
|
128
161
|
"tool_not_whitelisted",
|
|
129
162
|
"tool_features_denied",
|
|
130
|
-
"attachment_type_not_accepted"
|
|
163
|
+
"attachment_type_not_accepted",
|
|
164
|
+
// Content-safety rejections are soft: the user can rephrase and retry.
|
|
165
|
+
"moderation_blocked",
|
|
166
|
+
"moderation_unavailable"
|
|
131
167
|
]);
|
|
132
|
-
return warningCodes.has(code) ? "warning" : "
|
|
168
|
+
return warningCodes.has(code) ? "warning" : "error";
|
|
169
|
+
}
|
|
170
|
+
function resolveChatErrorMessage(code, fallback, translate) {
|
|
171
|
+
switch (code) {
|
|
172
|
+
case "moderation_blocked":
|
|
173
|
+
return translate(
|
|
174
|
+
"ai_assistant.errors.moderationBlocked",
|
|
175
|
+
"Your message was blocked by the content safety filter. Please rephrase and try again."
|
|
176
|
+
);
|
|
177
|
+
case "moderation_unavailable":
|
|
178
|
+
return translate(
|
|
179
|
+
"ai_assistant.errors.moderationUnavailable",
|
|
180
|
+
"The content safety check is temporarily unavailable. Please try again in a moment."
|
|
181
|
+
);
|
|
182
|
+
default:
|
|
183
|
+
return fallback;
|
|
184
|
+
}
|
|
133
185
|
}
|
|
134
186
|
const MARKDOWN_TYPOGRAPHY_CLASS = cn(
|
|
135
187
|
"[&_p]:my-1 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0",
|
|
@@ -835,13 +887,14 @@ function AiChat({
|
|
|
835
887
|
allowRuntimeOverride,
|
|
836
888
|
allowRuntimeModelOverride,
|
|
837
889
|
defaultLabel: modelDefaultLabel,
|
|
838
|
-
|
|
890
|
+
status: modelProvidersStatus,
|
|
891
|
+
degraded: modelProvidersDegraded
|
|
839
892
|
} = useAgentModels(agent);
|
|
840
893
|
const [modelPickerValue, setModelPickerValue] = React.useState(
|
|
841
894
|
() => readModelPickerValue(agent)
|
|
842
895
|
);
|
|
843
896
|
const effectiveModelPickerValue = React.useMemo(() => {
|
|
844
|
-
if (
|
|
897
|
+
if (modelProvidersStatus !== "ready" || !allowRuntimeOverride || modelProviders.length === 0) {
|
|
845
898
|
return null;
|
|
846
899
|
}
|
|
847
900
|
if (modelPickerValue && isModelPickerValueAvailable(modelPickerValue, modelProviders)) {
|
|
@@ -852,13 +905,14 @@ function AiChat({
|
|
|
852
905
|
allowRuntimeOverride,
|
|
853
906
|
modelPickerValue,
|
|
854
907
|
modelProviders,
|
|
855
|
-
|
|
908
|
+
modelProvidersStatus
|
|
856
909
|
]);
|
|
857
910
|
React.useEffect(() => {
|
|
858
911
|
setModelPickerValue(readModelPickerValue(agent));
|
|
859
912
|
}, [agent]);
|
|
860
913
|
React.useEffect(() => {
|
|
861
|
-
if (
|
|
914
|
+
if (modelProvidersStatus !== "ready") return;
|
|
915
|
+
if (modelProvidersDegraded) return;
|
|
862
916
|
if (!allowRuntimeModelOverride || modelProviders.length === 0) {
|
|
863
917
|
if (modelPickerValue !== null) {
|
|
864
918
|
setModelPickerValue(null);
|
|
@@ -875,7 +929,8 @@ function AiChat({
|
|
|
875
929
|
allowRuntimeModelOverride,
|
|
876
930
|
modelPickerValue,
|
|
877
931
|
modelProviders,
|
|
878
|
-
|
|
932
|
+
modelProvidersDegraded,
|
|
933
|
+
modelProvidersStatus
|
|
879
934
|
]);
|
|
880
935
|
const handleModelPickerChange = React.useCallback(
|
|
881
936
|
(value) => {
|
|
@@ -1092,7 +1147,7 @@ function AiChat({
|
|
|
1092
1147
|
setTimeout(() => textareaRef.current?.focus(), 0);
|
|
1093
1148
|
}, [chat, upload]);
|
|
1094
1149
|
const resolvedPlaceholder = placeholder ?? t("ai_assistant.chat.composerPlaceholder", "Message the AI agent...");
|
|
1095
|
-
const
|
|
1150
|
+
const errorStatus = mapErrorCodeToStatus(chat.error?.code);
|
|
1096
1151
|
return /* @__PURE__ */ jsxs(
|
|
1097
1152
|
"section",
|
|
1098
1153
|
{
|
|
@@ -1172,11 +1227,11 @@ function AiChat({
|
|
|
1172
1227
|
]
|
|
1173
1228
|
}
|
|
1174
1229
|
),
|
|
1175
|
-
chat.error ? /* @__PURE__ */ jsxs(Alert, {
|
|
1230
|
+
chat.error ? /* @__PURE__ */ jsxs(Alert, { status: errorStatus, "data-ai-chat-error": chat.error.code ?? "unknown", children: [
|
|
1176
1231
|
/* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.chat.errorTitle", "Agent dispatch failed") }),
|
|
1177
1232
|
/* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1178
1233
|
chat.error.code ? /* @__PURE__ */ jsx("span", { className: "mr-2 font-mono text-xs", children: chat.error.code }) : null,
|
|
1179
|
-
chat.error.message
|
|
1234
|
+
resolveChatErrorMessage(chat.error.code, chat.error.message, t)
|
|
1180
1235
|
] })
|
|
1181
1236
|
] }) : null,
|
|
1182
1237
|
chat.isOwner === false ? /* @__PURE__ */ jsx(
|