@open-mercato/core 0.4.5-develop-811deeb983 → 0.4.5-develop-3d8e759e45

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 (76) hide show
  1. package/dist/modules/catalog/inbox-actions.js +51 -0
  2. package/dist/modules/catalog/inbox-actions.js.map +7 -0
  3. package/dist/modules/customers/inbox-actions.js +230 -0
  4. package/dist/modules/customers/inbox-actions.js.map +7 -0
  5. package/dist/modules/inbox_ops/api/emails/[id]/route.js +40 -1
  6. package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
  7. package/dist/modules/inbox_ops/api/extract/route.js +87 -0
  8. package/dist/modules/inbox_ops/api/extract/route.js.map +7 -0
  9. package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js +6 -1
  10. package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js.map +2 -2
  11. package/dist/modules/inbox_ops/api/proposals/counts/route.js.map +2 -2
  12. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +40 -14
  13. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
  14. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js +2 -2
  15. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js.map +2 -2
  16. package/dist/modules/inbox_ops/backend/inbox-ops/page.js +161 -79
  17. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
  18. package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js +2 -2
  19. package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js.map +2 -2
  20. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js +109 -62
  21. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js.map +3 -3
  22. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js +2 -2
  23. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js.map +2 -2
  24. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +36 -14
  25. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  26. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js +2 -2
  27. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js.map +2 -2
  28. package/dist/modules/inbox_ops/components/proposals/ActionCard.js +65 -10
  29. package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
  30. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +58 -10
  31. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  32. package/dist/modules/inbox_ops/lib/constants.js.map +2 -2
  33. package/dist/modules/inbox_ops/lib/contactValidation.js +40 -0
  34. package/dist/modules/inbox_ops/lib/contactValidation.js.map +7 -0
  35. package/dist/modules/inbox_ops/lib/executionEngine.js +31 -826
  36. package/dist/modules/inbox_ops/lib/executionEngine.js.map +3 -3
  37. package/dist/modules/inbox_ops/lib/executionHelpers.js +368 -0
  38. package/dist/modules/inbox_ops/lib/executionHelpers.js.map +7 -0
  39. package/dist/modules/inbox_ops/lib/extractionPrompt.js +28 -35
  40. package/dist/modules/inbox_ops/lib/extractionPrompt.js.map +3 -3
  41. package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js +1 -0
  42. package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js.map +7 -0
  43. package/dist/modules/inbox_ops/lib/translationProvider.js +15 -10
  44. package/dist/modules/inbox_ops/lib/translationProvider.js.map +2 -2
  45. package/dist/modules/inbox_ops/subscribers/extractionWorker.js +16 -16
  46. package/dist/modules/inbox_ops/subscribers/extractionWorker.js.map +2 -2
  47. package/dist/modules/sales/inbox-actions.js +278 -0
  48. package/dist/modules/sales/inbox-actions.js.map +7 -0
  49. package/jest.config.cjs +1 -0
  50. package/jest.mocks/inbox-actions.generated.js +5 -0
  51. package/package.json +2 -2
  52. package/src/modules/catalog/inbox-actions.ts +60 -0
  53. package/src/modules/customers/inbox-actions.ts +285 -0
  54. package/src/modules/inbox_ops/api/emails/[id]/route.ts +44 -0
  55. package/src/modules/inbox_ops/api/extract/route.ts +94 -0
  56. package/src/modules/inbox_ops/api/proposals/[id]/translate/route.ts +6 -1
  57. package/src/modules/inbox_ops/api/proposals/counts/route.ts +2 -0
  58. package/src/modules/inbox_ops/backend/inbox-ops/log/page.meta.ts +2 -2
  59. package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +43 -13
  60. package/src/modules/inbox_ops/backend/inbox-ops/page.meta.ts +2 -2
  61. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +176 -81
  62. package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.ts +2 -2
  63. package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.tsx +122 -68
  64. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.meta.ts +2 -2
  65. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +36 -14
  66. package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +91 -7
  67. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +64 -12
  68. package/src/modules/inbox_ops/lib/constants.ts +9 -0
  69. package/src/modules/inbox_ops/lib/contactValidation.ts +54 -0
  70. package/src/modules/inbox_ops/lib/executionEngine.ts +47 -1060
  71. package/src/modules/inbox_ops/lib/executionHelpers.ts +527 -0
  72. package/src/modules/inbox_ops/lib/extractionPrompt.ts +45 -34
  73. package/src/modules/inbox_ops/lib/inbox-actions-generated.d.ts +11 -0
  74. package/src/modules/inbox_ops/lib/translationProvider.ts +16 -10
  75. package/src/modules/inbox_ops/subscribers/extractionWorker.ts +16 -18
  76. package/src/modules/sales/inbox-actions.ts +359 -0
@@ -12,10 +12,10 @@ export const metadata = {
12
12
  requireFeatures: ['inbox_ops.proposals.view'],
13
13
  pageTitle: 'Proposals',
14
14
  pageTitleKey: 'inbox_ops.nav.proposals',
15
- pageGroup: 'InboxOps',
15
+ pageGroup: 'AI Inbox Actions',
16
16
  pageGroupKey: 'inbox_ops.nav.group',
17
17
  pagePriority: 45,
18
18
  pageOrder: 100,
19
19
  icon: inboxIcon,
20
- breadcrumb: [{ label: 'InboxOps', labelKey: 'inbox_ops.nav.group' }],
20
+ breadcrumb: [{ label: 'AI Inbox Actions', labelKey: 'inbox_ops.nav.group' }],
21
21
  } as const
@@ -5,12 +5,17 @@ import Link from 'next/link'
5
5
  import { useRouter } from 'next/navigation'
6
6
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
7
7
  import { DataTable } from '@open-mercato/ui/backend/DataTable'
8
+ import { RowActions } from '@open-mercato/ui/backend/RowActions'
8
9
  import type { ColumnDef } from '@tanstack/react-table'
10
+ import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
9
11
  import { Button } from '@open-mercato/ui/primitives/button'
10
12
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
13
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
11
14
  import { useT } from '@open-mercato/shared/lib/i18n/context'
12
15
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
13
- import { Badge } from '@open-mercato/ui/primitives/badge'
16
+ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
17
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
18
+ import { ErrorMessage } from '@open-mercato/ui/backend/detail'
14
19
  import { Settings, Inbox, Copy } from 'lucide-react'
15
20
 
16
21
  type ProposalRow = {
@@ -75,33 +80,48 @@ export default function InboxOpsProposalsPage() {
75
80
  const t = useT()
76
81
  const router = useRouter()
77
82
  const scopeVersion = useOrganizationScopeVersion()
83
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
84
+ const { runMutation } = useGuardedMutation<Record<string, unknown>>({
85
+ contextId: 'inbox-ops-proposals',
86
+ })
78
87
 
79
88
  const [items, setItems] = React.useState<ProposalRow[]>([])
80
89
  const [total, setTotal] = React.useState(0)
81
90
  const [page, setPage] = React.useState(1)
82
91
  const [pageSize] = React.useState(25)
83
- const [statusFilter, setStatusFilter] = React.useState<string | undefined>()
92
+ const [filterValues, setFilterValues] = React.useState<FilterValues>({})
84
93
  const [search, setSearch] = React.useState('')
85
94
  const [isLoading, setIsLoading] = React.useState(true)
95
+ const [error, setError] = React.useState<string | null>(null)
96
+ const [initialLoadComplete, setInitialLoadComplete] = React.useState(false)
86
97
  const [counts, setCounts] = React.useState<StatusCounts>({ pending: 0, partial: 0, accepted: 0, rejected: 0 })
87
98
  const [settings, setSettings] = React.useState<{ inboxAddress?: string } | null>(null)
88
99
  const [copied, setCopied] = React.useState(false)
89
100
 
101
+ const statusFilter = typeof filterValues.status === 'string' ? filterValues.status : undefined
102
+
90
103
  const loadProposals = React.useCallback(async () => {
91
104
  setIsLoading(true)
105
+ setError(null)
92
106
  const params = new URLSearchParams()
93
107
  params.set('page', String(page))
94
108
  params.set('pageSize', String(pageSize))
95
109
  if (statusFilter) params.set('status', statusFilter)
96
- if (search) params.set('search', search)
110
+ if (search.trim()) params.set('search', search.trim())
97
111
 
98
- const result = await apiCall<ProposalListResponse>(`/api/inbox_ops/proposals?${params}`)
99
- if (result?.ok && result.result?.items) {
100
- setItems(result.result.items)
101
- setTotal(result.result.total || 0)
112
+ try {
113
+ const result = await apiCall<ProposalListResponse>(`/api/inbox_ops/proposals?${params}`)
114
+ if (result?.ok && result.result?.items) {
115
+ setItems(result.result.items)
116
+ setTotal(result.result.total || 0)
117
+ } else {
118
+ setError(t('inbox_ops.flash.load_failed', 'Failed to load proposals'))
119
+ }
120
+ } catch {
121
+ setError(t('inbox_ops.flash.load_failed', 'Failed to load proposals'))
102
122
  }
103
123
  setIsLoading(false)
104
- }, [page, pageSize, statusFilter, search, scopeVersion])
124
+ }, [page, pageSize, statusFilter, search, scopeVersion, t])
105
125
 
106
126
  const loadCounts = React.useCallback(async () => {
107
127
  const result = await apiCall<StatusCounts>('/api/inbox_ops/proposals/counts')
@@ -114,10 +134,14 @@ export default function InboxOpsProposalsPage() {
114
134
  }, [scopeVersion])
115
135
 
116
136
  React.useEffect(() => {
117
- loadProposals()
118
- loadCounts()
119
- loadSettings()
120
- }, [loadProposals, loadCounts, loadSettings])
137
+ Promise.all([loadProposals(), loadCounts(), loadSettings()]).then(() => {
138
+ setInitialLoadComplete(true)
139
+ })
140
+ }, []) // eslint-disable-line react-hooks/exhaustive-deps
141
+
142
+ React.useEffect(() => {
143
+ if (initialLoadComplete) loadProposals()
144
+ }, [page, statusFilter, search, scopeVersion]) // eslint-disable-line react-hooks/exhaustive-deps
121
145
 
122
146
  const handleCopyAddress = React.useCallback(() => {
123
147
  if (settings?.inboxAddress) {
@@ -127,6 +151,58 @@ export default function InboxOpsProposalsPage() {
127
151
  }
128
152
  }, [settings])
129
153
 
154
+ const handleRefresh = React.useCallback(() => {
155
+ loadProposals()
156
+ loadCounts()
157
+ }, [loadProposals, loadCounts])
158
+
159
+ const handleFiltersApply = React.useCallback((values: FilterValues) => {
160
+ setFilterValues(values)
161
+ setPage(1)
162
+ }, [])
163
+
164
+ const handleFiltersClear = React.useCallback(() => {
165
+ setFilterValues({})
166
+ setPage(1)
167
+ }, [])
168
+
169
+ const handleRejectProposal = React.useCallback(async (proposalId: string) => {
170
+ const confirmed = await confirm({
171
+ title: t('inbox_ops.action.reject_all', 'Reject Proposal'),
172
+ text: t('inbox_ops.action.reject_all_confirm', 'Reject all pending actions in this proposal?'),
173
+ })
174
+ if (!confirmed) return
175
+
176
+ const result = await runMutation({
177
+ operation: () => apiCall<{ ok: boolean }>(
178
+ `/api/inbox_ops/proposals/${proposalId}/reject`,
179
+ { method: 'POST' },
180
+ ),
181
+ context: {},
182
+ })
183
+ if (result?.ok && result.result?.ok) {
184
+ flash(t('inbox_ops.action.proposal_rejected', 'Proposal rejected'), 'success')
185
+ loadProposals()
186
+ loadCounts()
187
+ } else {
188
+ flash(t('inbox_ops.flash.action_reject_failed', 'Failed to reject'), 'error')
189
+ }
190
+ }, [confirm, t, loadProposals, loadCounts, runMutation])
191
+
192
+ const filters = React.useMemo<FilterDef[]>(() => [
193
+ {
194
+ id: 'status',
195
+ label: t('inbox_ops.list.filters.status', 'Status'),
196
+ type: 'select',
197
+ options: [
198
+ { value: 'pending', label: `${t('inbox_ops.status.pending', 'Pending')} (${counts.pending})` },
199
+ { value: 'partial', label: `${t('inbox_ops.status.partial', 'Partial')} (${counts.partial})` },
200
+ { value: 'accepted', label: `${t('inbox_ops.status.accepted', 'Accepted')} (${counts.accepted})` },
201
+ { value: 'rejected', label: `${t('inbox_ops.status.rejected', 'Rejected')} (${counts.rejected})` },
202
+ ],
203
+ },
204
+ ], [t, counts])
205
+
130
206
  const columns: ColumnDef<ProposalRow>[] = React.useMemo(() => [
131
207
  {
132
208
  accessorKey: 'summary',
@@ -152,7 +228,7 @@ export default function InboxOpsProposalsPage() {
152
228
  },
153
229
  {
154
230
  id: 'actions_count',
155
- header: t('inbox_ops.actions_count', 'Actions'),
231
+ header: t('inbox_ops.list.progress', 'Progress'),
156
232
  cell: ({ row }) => {
157
233
  const pending = row.original.pendingActionCount ?? 0
158
234
  const total = row.original.actionCount ?? 0
@@ -183,83 +259,102 @@ export default function InboxOpsProposalsPage() {
183
259
  ], [t])
184
260
 
185
261
  const totalCount = counts.pending + counts.partial + counts.accepted + counts.rejected
186
- const isEmpty = totalCount === 0 && !isLoading
187
262
 
188
- const tabs = [
189
- { label: `${t('common.all', 'All')} (${totalCount})`, value: undefined },
190
- { label: `${t('inbox_ops.status.pending', 'Pending')} (${counts.pending})`, value: 'pending' },
191
- { label: `${t('inbox_ops.status.partial', 'Partial')} (${counts.partial})`, value: 'partial' },
192
- { label: `${t('inbox_ops.status.accepted', 'Accepted')} (${counts.accepted})`, value: 'accepted' },
193
- { label: `${t('inbox_ops.status.rejected', 'Rejected')} (${counts.rejected})`, value: 'rejected' },
194
- ]
263
+ const emptyStateContent = initialLoadComplete && totalCount === 0 ? (
264
+ <div className="flex flex-col items-center justify-center py-16 text-center">
265
+ <Inbox className="h-12 w-12 text-muted-foreground mb-4" />
266
+ <h2 className="text-lg font-semibold mb-2">{t('inbox_ops.empty.title', 'Forward emails to start')}</h2>
267
+ {settings?.inboxAddress && (
268
+ <div className="mt-4 flex items-center gap-2 bg-muted rounded-lg px-4 py-3">
269
+ <code className="text-sm font-mono">{settings.inboxAddress}</code>
270
+ <Button type="button" variant="outline" size="sm" onClick={handleCopyAddress}>
271
+ <Copy className="h-4 w-4" />
272
+ {copied ? t('inbox_ops.settings.copied', 'Copied') : t('inbox_ops.settings.copy', 'Copy')}
273
+ </Button>
274
+ </div>
275
+ )}
276
+ <ol className="mt-6 text-sm text-muted-foreground text-left space-y-2">
277
+ <li>1. {t('inbox_ops.empty.step1', 'Forward any email thread to this address')}</li>
278
+ <li>2. {t('inbox_ops.empty.step2', "We'll analyze it and propose actions")}</li>
279
+ <li>3. {t('inbox_ops.empty.step3', 'Review and accept with one click')}</li>
280
+ </ol>
281
+ </div>
282
+ ) : undefined
283
+
284
+ if (error && !initialLoadComplete) {
285
+ return (
286
+ <Page>
287
+ <PageBody>
288
+ <ErrorMessage label={error} />
289
+ </PageBody>
290
+ </Page>
291
+ )
292
+ }
195
293
 
196
294
  return (
197
295
  <Page>
198
- <div className="flex items-center justify-between px-3 py-3 md:px-6 md:py-4">
199
- <h1 className="text-lg font-semibold">{t('inbox_ops.title', 'InboxOps')}</h1>
200
- <Link href="/backend/inbox-ops/settings">
201
- <Button variant="outline" size="sm">
202
- <Settings className="h-4 w-4" />
203
- <span className="hidden md:inline ml-1">{t('inbox_ops.settings.title', 'Settings')}</span>
204
- </Button>
205
- </Link>
206
- </div>
207
-
208
296
  <PageBody>
209
- {isEmpty ? (
210
- <div className="flex flex-col items-center justify-center py-16 text-center">
211
- <Inbox className="h-12 w-12 text-muted-foreground mb-4" />
212
- <h2 className="text-lg font-semibold mb-2">{t('inbox_ops.empty.title', 'Forward emails to start')}</h2>
213
- {settings?.inboxAddress && (
214
- <div className="mt-4 flex items-center gap-2 bg-muted rounded-lg px-4 py-3">
215
- <code className="text-sm font-mono">{settings.inboxAddress}</code>
216
- <Button variant="outline" size="sm" onClick={handleCopyAddress}>
297
+ <DataTable<ProposalRow>
298
+ title={t('inbox_ops.title', 'AI Inbox Actions')}
299
+ refreshButton={{
300
+ label: t('inbox_ops.list.actions.refresh', 'Refresh'),
301
+ onRefresh: handleRefresh,
302
+ }}
303
+ actions={(
304
+ <div className="flex items-center gap-2">
305
+ {settings?.inboxAddress && (
306
+ <Button type="button" variant="outline" size="sm" onClick={handleCopyAddress}>
217
307
  <Copy className="h-4 w-4" />
218
- {copied ? t('inbox_ops.settings.copied', 'Copied') : t('inbox_ops.settings.copy', 'Copy')}
308
+ <span className="hidden md:inline ml-1">
309
+ {copied ? t('inbox_ops.settings.copied', 'Copied') : t('inbox_ops.settings.copy', 'Copy')}
310
+ </span>
219
311
  </Button>
220
- </div>
221
- )}
222
- <ol className="mt-6 text-sm text-muted-foreground text-left space-y-2">
223
- <li>1. {t('inbox_ops.empty.step1', 'Forward any email thread to this address')}</li>
224
- <li>2. {t('inbox_ops.empty.step2', "We'll analyze it and propose actions")}</li>
225
- <li>3. {t('inbox_ops.empty.step3', 'Review and accept with one click')}</li>
226
- </ol>
227
- </div>
228
- ) : (
229
- <>
230
- <div className="flex items-center gap-2 px-3 py-2 md:px-0 overflow-x-auto">
231
- {tabs.map((tab) => (
232
- <Button
233
- key={tab.value ?? 'all'}
234
- variant={statusFilter === tab.value ? 'default' : 'outline'}
235
- size="sm"
236
- onClick={() => { setStatusFilter(tab.value); setPage(1) }}
237
- >
238
- {tab.label}
239
- </Button>
240
- ))}
312
+ )}
313
+ <Button variant="outline" size="sm" asChild>
314
+ <Link href="/backend/inbox-ops/settings">
315
+ <Settings className="h-4 w-4" />
316
+ <span className="hidden md:inline ml-1">{t('inbox_ops.list.actions.settings', 'Settings')}</span>
317
+ </Link>
318
+ </Button>
241
319
  </div>
242
-
243
- <div className="overflow-auto">
244
- <div className="min-w-[640px]">
245
- <DataTable
246
- columns={columns}
247
- data={items}
248
- isLoading={isLoading}
249
- onRowClick={(row) => router.push(`/backend/inbox-ops/proposals/${row.id}`)}
250
- pagination={{
251
- page,
252
- pageSize,
253
- total,
254
- totalPages: Math.ceil(total / pageSize),
255
- onPageChange: setPage,
256
- }}
257
- />
258
- </div>
259
- </div>
260
- </>
261
- )}
320
+ )}
321
+ columns={columns}
322
+ data={items}
323
+ searchValue={search}
324
+ onSearchChange={(value) => { setSearch(value); setPage(1) }}
325
+ searchPlaceholder={t('inbox_ops.list.searchPlaceholder', 'Search proposals...')}
326
+ filters={filters}
327
+ filterValues={filterValues}
328
+ onFiltersApply={handleFiltersApply}
329
+ onFiltersClear={handleFiltersClear}
330
+ onRowClick={(row) => router.push(`/backend/inbox-ops/proposals/${row.id}`)}
331
+ rowActions={(row) => (
332
+ <RowActions items={[
333
+ {
334
+ id: 'view',
335
+ label: t('inbox_ops.list.actions.view', 'View'),
336
+ onSelect: () => router.push(`/backend/inbox-ops/proposals/${row.id}`),
337
+ },
338
+ ...(row.status === 'pending' || row.status === 'partial' ? [{
339
+ id: 'reject',
340
+ label: t('inbox_ops.list.actions.reject', 'Reject'),
341
+ destructive: true,
342
+ onSelect: () => handleRejectProposal(row.id),
343
+ }] : []),
344
+ ]} />
345
+ )}
346
+ pagination={{
347
+ page,
348
+ pageSize,
349
+ total,
350
+ totalPages: Math.ceil(total / pageSize),
351
+ onPageChange: setPage,
352
+ }}
353
+ isLoading={isLoading}
354
+ emptyState={emptyStateContent}
355
+ />
262
356
  </PageBody>
357
+ {ConfirmDialogElement}
263
358
  </Page>
264
359
  )
265
360
  }
@@ -3,11 +3,11 @@ export const metadata = {
3
3
  requireFeatures: ['inbox_ops.proposals.view'],
4
4
  pageTitle: 'Proposal',
5
5
  pageTitleKey: 'inbox_ops.nav.proposal_detail',
6
- pageGroup: 'InboxOps',
6
+ pageGroup: 'AI Inbox Actions',
7
7
  pageGroupKey: 'inbox_ops.nav.group',
8
8
  navHidden: true,
9
9
  breadcrumb: [
10
- { label: 'InboxOps', labelKey: 'inbox_ops.nav.group', href: '/backend/inbox-ops' },
10
+ { label: 'AI Inbox Actions', labelKey: 'inbox_ops.nav.group', href: '/backend/inbox-ops' },
11
11
  { label: 'Proposal', labelKey: 'inbox_ops.nav.proposal_detail' },
12
12
  ],
13
13
  }