@open-mercato/ui 0.6.1-develop.3291.1.6fad645fd0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -0
  3. package/dist/ai/AiAssistantLauncher.js +8 -8
  4. package/dist/ai/AiAssistantLauncher.js.map +2 -2
  5. package/dist/ai/AiChat.js +92 -93
  6. package/dist/ai/AiChat.js.map +2 -2
  7. package/dist/ai/AiIcon.js +17 -0
  8. package/dist/ai/AiIcon.js.map +7 -0
  9. package/dist/ai/LoopDisabledBanner.js +28 -0
  10. package/dist/ai/LoopDisabledBanner.js.map +7 -0
  11. package/dist/ai/LoopTracePanel.js +123 -0
  12. package/dist/ai/LoopTracePanel.js.map +7 -0
  13. package/dist/ai/ModelPicker.js.map +1 -1
  14. package/dist/ai/index.js +8 -0
  15. package/dist/ai/index.js.map +2 -2
  16. package/dist/ai/parts/ConfirmationCard.js +1 -5
  17. package/dist/ai/parts/ConfirmationCard.js.map +2 -2
  18. package/dist/ai/parts/FieldDiffCard.js +8 -15
  19. package/dist/ai/parts/FieldDiffCard.js.map +2 -2
  20. package/dist/ai/parts/MutationResultCard.js +1 -4
  21. package/dist/ai/parts/MutationResultCard.js.map +2 -2
  22. package/dist/ai/ui-parts/pending-phase3-placeholder.js +0 -2
  23. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +2 -2
  24. package/dist/ai/useAiChat.js +18 -4
  25. package/dist/ai/useAiChat.js.map +2 -2
  26. package/dist/backend/CrudForm.js +24 -2
  27. package/dist/backend/CrudForm.js.map +2 -2
  28. package/dist/backend/DataTable.js +2 -2
  29. package/dist/backend/DataTable.js.map +2 -2
  30. package/dist/backend/confirm-dialog/ConfirmDialog.js +102 -96
  31. package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
  32. package/dist/backend/detail/CustomDataSection.js +22 -3
  33. package/dist/backend/detail/CustomDataSection.js.map +2 -2
  34. package/dist/backend/filters/AdvancedFilterBuilder.js +2 -2
  35. package/dist/backend/filters/AdvancedFilterBuilder.js.map +2 -2
  36. package/dist/backend/injection/ComponentOverrideProvider.js +1 -1
  37. package/dist/backend/injection/ComponentOverrideProvider.js.map +2 -2
  38. package/dist/backend/inputs/ComboboxInput.js +25 -7
  39. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  40. package/dist/backend/progress/useProgressPoll.js +58 -3
  41. package/dist/backend/progress/useProgressPoll.js.map +2 -2
  42. package/dist/backend/progress/useProgressSse.js +15 -2
  43. package/dist/backend/progress/useProgressSse.js.map +2 -2
  44. package/dist/backend/utils/bulkDelete.js +76 -0
  45. package/dist/backend/utils/bulkDelete.js.map +2 -2
  46. package/package.json +11 -6
  47. package/src/ai/AiAssistantLauncher.tsx +8 -8
  48. package/src/ai/AiChat.tsx +44 -38
  49. package/src/ai/AiIcon.tsx +12 -0
  50. package/src/ai/LoopDisabledBanner.tsx +38 -0
  51. package/src/ai/LoopTracePanel.tsx +184 -0
  52. package/src/ai/ModelPicker.tsx +1 -1
  53. package/src/ai/__tests__/AiChat.test.tsx +174 -5
  54. package/src/ai/index.ts +8 -0
  55. package/src/ai/parts/ConfirmationCard.tsx +1 -5
  56. package/src/ai/parts/FieldDiffCard.tsx +0 -3
  57. package/src/ai/parts/MutationResultCard.tsx +1 -4
  58. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +5 -0
  59. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +0 -2
  60. package/src/ai/useAiChat.ts +35 -5
  61. package/src/backend/CrudForm.tsx +30 -3
  62. package/src/backend/DataTable.tsx +2 -2
  63. package/src/backend/__tests__/CrudForm.custom-fields.test.tsx +72 -0
  64. package/src/backend/__tests__/CustomDataSection.test.tsx +76 -0
  65. package/src/backend/__tests__/DataTable.render.test.tsx +36 -0
  66. package/src/backend/__tests__/component-replacement.test.tsx +44 -0
  67. package/src/backend/confirm-dialog/ConfirmDialog.tsx +101 -91
  68. package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +19 -0
  69. package/src/backend/detail/CustomDataSection.tsx +26 -3
  70. package/src/backend/filters/AdvancedFilterBuilder.tsx +6 -6
  71. package/src/backend/injection/ComponentOverrideProvider.tsx +2 -1
  72. package/src/backend/inputs/ComboboxInput.tsx +29 -7
  73. package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +33 -2
  74. package/src/backend/progress/useProgressPoll.ts +68 -3
  75. package/src/backend/progress/useProgressSse.ts +16 -2
  76. package/src/backend/utils/__tests__/bulkDelete.test.ts +42 -0
  77. package/src/backend/utils/bulkDelete.ts +93 -0
package/dist/ai/AiChat.js CHANGED
@@ -41,6 +41,7 @@ import {
41
41
  } from "./useAiChat.js";
42
42
  import { useAiChatUpload } from "./useAiChatUpload.js";
43
43
  import { useAiShortcuts } from "./useAiShortcuts.js";
44
+ import { LoopTracePanel } from "./LoopTracePanel.js";
44
45
  const PREVIEW_DATA_URL_MAX_BYTES = 2 * 1024 * 1024;
45
46
  const COMPACT_FOOTER_MAX_WIDTH = 640;
46
47
  const MODEL_PICKER_STORAGE_PREFIX = "om-ai-model-picker:";
@@ -73,7 +74,7 @@ function writeModelPickerValue(agentId, value) {
73
74
  }
74
75
  function useAgentModels(agent) {
75
76
  const [providers, setProviders] = React.useState([]);
76
- const [allowRuntimeModelOverride, setAllowRuntimeModelOverride] = React.useState(false);
77
+ const [allowRuntimeOverride, setAllowRuntimeOverride] = React.useState(false);
77
78
  const [defaultLabel, setDefaultLabel] = React.useState(null);
78
79
  const [loaded, setLoaded] = React.useState(false);
79
80
  React.useEffect(() => {
@@ -85,7 +86,8 @@ function useAgentModels(agent) {
85
86
  setLoaded(true);
86
87
  return;
87
88
  }
88
- setAllowRuntimeModelOverride(result.result.allowRuntimeModelOverride);
89
+ const effectiveAllowRuntimeOverride = result.result.allowRuntimeOverride ?? result.result.allowRuntimeModelOverride ?? false;
90
+ setAllowRuntimeOverride(effectiveAllowRuntimeOverride);
89
91
  setProviders(result.result.providers);
90
92
  setDefaultLabel(
91
93
  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
@@ -93,16 +95,13 @@ function useAgentModels(agent) {
93
95
  setLoaded(true);
94
96
  });
95
97
  }, [agent]);
96
- return { providers, allowRuntimeModelOverride, defaultLabel, loaded };
97
- }
98
- function firstAvailableModelPickerValue(providers) {
99
- for (const provider of providers) {
100
- const model = provider.models[0];
101
- if (model) {
102
- return { providerId: provider.id, modelId: model.id };
103
- }
104
- }
105
- return null;
98
+ return {
99
+ providers,
100
+ allowRuntimeOverride,
101
+ allowRuntimeModelOverride: allowRuntimeOverride,
102
+ defaultLabel,
103
+ loaded
104
+ };
106
105
  }
107
106
  function isModelPickerValueAvailable(value, providers) {
108
107
  return providers.some(
@@ -197,70 +196,76 @@ function ToolCallList({ toolCalls }) {
197
196
  const t = useT();
198
197
  const [openId, setOpenId] = React.useState(null);
199
198
  if (!toolCalls || toolCalls.length === 0) return null;
200
- return /* @__PURE__ */ jsx("div", { className: "space-y-1", "data-ai-chat-tool-calls": "", children: toolCalls.map((call) => {
201
- const isOpen = openId === call.id;
202
- const isError = call.state === "error";
203
- const isPending = call.state === "pending";
204
- const isComplete = call.state === "complete";
205
- const statusLabel = isError ? t("ai_assistant.chat.toolError", "failed") : isPending ? t("ai_assistant.chat.toolRunning", "running\u2026") : t("ai_assistant.chat.toolDone", "done");
206
- return /* @__PURE__ */ jsxs(
207
- "div",
208
- {
209
- className: cn(
210
- "rounded-md border border-border bg-muted/30",
211
- isError ? "border-destructive/40 bg-destructive/5" : ""
212
- ),
213
- "data-ai-chat-tool-call": call.toolName,
214
- "data-ai-chat-tool-state": call.state,
215
- children: [
216
- /* @__PURE__ */ jsxs(
217
- "button",
218
- {
219
- type: "button",
220
- onClick: () => setOpenId(isOpen ? null : call.id),
221
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs font-medium hover:bg-muted/60",
222
- children: [
223
- isOpen ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5 text-muted-foreground", "aria-hidden": true }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5 text-muted-foreground", "aria-hidden": true }),
224
- isPending ? /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin text-muted-foreground", "aria-hidden": true }) : /* @__PURE__ */ jsx(
225
- Wrench,
226
- {
227
- className: cn(
228
- "size-3.5",
229
- isError ? "text-destructive" : "text-muted-foreground"
230
- ),
231
- "aria-hidden": true
232
- }
233
- ),
234
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: call.toolName }),
235
- /* @__PURE__ */ jsx(
236
- "span",
237
- {
238
- className: cn(
239
- "ml-auto text-[10px] uppercase tracking-wide",
240
- isError ? "text-destructive" : isComplete ? "text-status-success-text" : "text-muted-foreground"
241
- ),
242
- children: statusLabel
243
- }
244
- )
245
- ]
246
- }
199
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", "data-ai-chat-tool-calls": "", children: [
200
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-0.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: [
201
+ /* @__PURE__ */ jsx("span", { children: t("ai_assistant.chat.agentTasksTitle", "Agent tasks") }),
202
+ /* @__PURE__ */ jsx("span", { children: toolCalls.length })
203
+ ] }),
204
+ toolCalls.map((call) => {
205
+ const isOpen = openId === call.id;
206
+ const isError = call.state === "error";
207
+ const isPending = call.state === "pending";
208
+ const isComplete = call.state === "complete";
209
+ const statusLabel = isError ? t("ai_assistant.chat.toolError", "failed") : isPending ? t("ai_assistant.chat.toolRunning", "running\u2026") : t("ai_assistant.chat.toolDone", "done");
210
+ return /* @__PURE__ */ jsxs(
211
+ "div",
212
+ {
213
+ className: cn(
214
+ "rounded-md border border-border bg-muted/30",
215
+ isError ? "border-destructive/40 bg-destructive/5" : ""
247
216
  ),
248
- isOpen ? /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 px-2 py-1.5 text-xs", children: [
249
- call.input !== void 0 ? /* @__PURE__ */ jsxs("div", { children: [
250
- /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: t("ai_assistant.chat.toolInput", "Input") }),
251
- /* @__PURE__ */ jsx("pre", { className: "mt-0.5 max-h-32 overflow-auto rounded bg-background p-1.5 font-mono text-[11px]", children: safeStringify(call.input) })
252
- ] }) : null,
253
- call.output !== void 0 && !isError ? /* @__PURE__ */ jsxs("div", { children: [
254
- /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: t("ai_assistant.chat.toolOutput", "Output") }),
255
- /* @__PURE__ */ jsx("pre", { className: "mt-0.5 max-h-32 overflow-auto rounded bg-background p-1.5 font-mono text-[11px]", children: safeStringify(call.output) })
256
- ] }) : null,
257
- call.errorMessage ? /* @__PURE__ */ jsx("div", { className: "text-destructive", children: call.errorMessage }) : null
258
- ] }) : null
259
- ]
260
- },
261
- call.id
262
- );
263
- }) });
217
+ "data-ai-chat-tool-call": call.toolName,
218
+ "data-ai-chat-tool-state": call.state,
219
+ children: [
220
+ /* @__PURE__ */ jsxs(
221
+ "button",
222
+ {
223
+ type: "button",
224
+ onClick: () => setOpenId(isOpen ? null : call.id),
225
+ className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs font-medium hover:bg-muted/60",
226
+ children: [
227
+ isOpen ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5 text-muted-foreground", "aria-hidden": true }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5 text-muted-foreground", "aria-hidden": true }),
228
+ isPending ? /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin text-muted-foreground", "aria-hidden": true }) : /* @__PURE__ */ jsx(
229
+ Wrench,
230
+ {
231
+ className: cn(
232
+ "size-3.5",
233
+ isError ? "text-destructive" : "text-muted-foreground"
234
+ ),
235
+ "aria-hidden": true
236
+ }
237
+ ),
238
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: call.toolName }),
239
+ /* @__PURE__ */ jsx(
240
+ "span",
241
+ {
242
+ className: cn(
243
+ "ml-auto text-[10px] uppercase tracking-wide",
244
+ isError ? "text-destructive" : isComplete ? "text-status-success-text" : "text-muted-foreground"
245
+ ),
246
+ children: statusLabel
247
+ }
248
+ )
249
+ ]
250
+ }
251
+ ),
252
+ isOpen ? /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border/60 px-2 py-1.5 text-xs", children: [
253
+ call.input !== void 0 ? /* @__PURE__ */ jsxs("div", { children: [
254
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: t("ai_assistant.chat.toolInput", "Input") }),
255
+ /* @__PURE__ */ jsx("pre", { className: "mt-0.5 max-h-32 overflow-auto rounded bg-background p-1.5 font-mono text-[11px]", children: safeStringify(call.input) })
256
+ ] }) : null,
257
+ call.output !== void 0 && !isError ? /* @__PURE__ */ jsxs("div", { children: [
258
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: t("ai_assistant.chat.toolOutput", "Output") }),
259
+ /* @__PURE__ */ jsx("pre", { className: "mt-0.5 max-h-32 overflow-auto rounded bg-background p-1.5 font-mono text-[11px]", children: safeStringify(call.output) })
260
+ ] }) : null,
261
+ call.errorMessage ? /* @__PURE__ */ jsx("div", { className: "text-destructive", children: call.errorMessage }) : null
262
+ ] }) : null
263
+ ]
264
+ },
265
+ call.id
266
+ );
267
+ })
268
+ ] });
264
269
  }
265
270
  function ReasoningPanel({ text, streaming }) {
266
271
  const t = useT();
@@ -560,6 +565,7 @@ function AiChat({
560
565
  uiParts: uiPartsProp,
561
566
  debugTools,
562
567
  debugPromptSections,
568
+ loopTrace,
563
569
  conversationId,
564
570
  suggestions,
565
571
  contextItems,
@@ -602,6 +608,7 @@ function AiChat({
602
608
  );
603
609
  const {
604
610
  providers: modelProviders,
611
+ allowRuntimeOverride,
605
612
  allowRuntimeModelOverride,
606
613
  defaultLabel: modelDefaultLabel,
607
614
  loaded: modelProvidersLoaded
@@ -610,15 +617,15 @@ function AiChat({
610
617
  () => readModelPickerValue(agent)
611
618
  );
612
619
  const effectiveModelPickerValue = React.useMemo(() => {
613
- if (!modelProvidersLoaded || !allowRuntimeModelOverride || modelProviders.length === 0) {
620
+ if (!modelProvidersLoaded || !allowRuntimeOverride || modelProviders.length === 0) {
614
621
  return null;
615
622
  }
616
623
  if (modelPickerValue && isModelPickerValueAvailable(modelPickerValue, modelProviders)) {
617
624
  return modelPickerValue;
618
625
  }
619
- return firstAvailableModelPickerValue(modelProviders);
626
+ return null;
620
627
  }, [
621
- allowRuntimeModelOverride,
628
+ allowRuntimeOverride,
622
629
  modelPickerValue,
623
630
  modelProviders,
624
631
  modelProvidersLoaded
@@ -636,9 +643,8 @@ function AiChat({
636
643
  return;
637
644
  }
638
645
  if (modelPickerValue && !isModelPickerValueAvailable(modelPickerValue, modelProviders)) {
639
- const fallback = firstAvailableModelPickerValue(modelProviders);
640
- setModelPickerValue(fallback);
641
- writeModelPickerValue(agent, fallback);
646
+ setModelPickerValue(null);
647
+ writeModelPickerValue(agent, null);
642
648
  }
643
649
  }, [
644
650
  agent,
@@ -695,26 +701,16 @@ function AiChat({
695
701
  toolSig
696
702
  ].join("#");
697
703
  }, [lastAssistant]);
698
- const lastStreamUpdateRef = React.useRef(Date.now());
699
704
  const lastSnapshotRef = React.useRef("");
700
705
  const [, setStreamTick] = React.useState(0);
701
706
  React.useEffect(() => {
702
707
  if (assistantStreamSnapshot !== lastSnapshotRef.current) {
703
708
  lastSnapshotRef.current = assistantStreamSnapshot;
704
- lastStreamUpdateRef.current = Date.now();
705
709
  setStreamTick((value) => value + 1);
706
710
  }
707
711
  }, [assistantStreamSnapshot]);
708
- React.useEffect(() => {
709
- if (!isStreaming && !isSubmitting) return;
710
- const interval = window.setInterval(() => {
711
- setStreamTick((value) => value + 1);
712
- }, 200);
713
- return () => window.clearInterval(interval);
714
- }, [isStreaming, isSubmitting]);
715
- const idleDuringStream = isStreaming && Date.now() - lastStreamUpdateRef.current >= 300;
716
712
  const showThinkingIndicator = isSubmitting || isStreaming && (!hasAnyVisibleSignal || hasPendingToolCall || // Tool just returned and the model hasn't started speaking yet.
717
- hasCompletedToolCall && !trimmedContent || idleDuringStream);
713
+ hasCompletedToolCall && !trimmedContent);
718
714
  const activeRegistry = registry ?? defaultAiUiPartRegistry;
719
715
  const uiParts = React.useMemo(
720
716
  () => (uiPartsProp ?? []).map((part) => ({
@@ -1043,7 +1039,7 @@ function AiChat({
1043
1039
  children: /* @__PURE__ */ jsx(Paperclip, { className: "size-4", "aria-hidden": true })
1044
1040
  }
1045
1041
  ),
1046
- allowRuntimeModelOverride && modelProviders.length > 0 ? /* @__PURE__ */ jsx(
1042
+ allowRuntimeOverride && modelProviders.length > 0 ? /* @__PURE__ */ jsx(
1047
1043
  ModelPicker,
1048
1044
  {
1049
1045
  agentId: agent,
@@ -1103,6 +1099,7 @@ function AiChat({
1103
1099
  {
1104
1100
  tools: debugTools,
1105
1101
  promptSections: debugPromptSections,
1102
+ loopTrace: loopTrace ?? chat.lastLoopTrace ?? void 0,
1106
1103
  lastRequestDebug: chat.lastRequestDebug,
1107
1104
  lastResponseDebug: chat.lastResponseDebug,
1108
1105
  status: chat.status,
@@ -1116,6 +1113,7 @@ function AiChat({
1116
1113
  function AiChatDebugPanel({
1117
1114
  tools,
1118
1115
  promptSections,
1116
+ loopTrace,
1119
1117
  lastRequestDebug,
1120
1118
  lastResponseDebug,
1121
1119
  status,
@@ -1129,6 +1127,7 @@ function AiChatDebugPanel({
1129
1127
  "data-ai-chat-debug": "true",
1130
1128
  children: [
1131
1129
  /* @__PURE__ */ jsx("div", { className: "font-semibold", children: t("ai_assistant.chat.debug.panelTitle", "Debug panel") }),
1130
+ loopTrace ? /* @__PURE__ */ jsx(LoopTracePanel, { trace: loopTrace }) : null,
1132
1131
  /* @__PURE__ */ jsxs("details", { className: "rounded border border-border bg-background", "data-ai-chat-debug-section": "tools", open: true, children: [
1133
1132
  /* @__PURE__ */ jsxs("summary", { className: "cursor-pointer px-2 py-1 font-semibold", children: [
1134
1133
  t("ai_assistant.chat.debug.toolsSection", "Resolved tools"),