@tuturuuu/ui 0.3.1 → 0.3.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.2](https://github.com/tutur3u/platform/compare/ui-v0.3.1...ui-v0.3.2) (2026-06-10)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **web:** harden task search and command launcher ([e4f8fd2](https://github.com/tutur3u/platform/commit/e4f8fd28bd78eabb0aa38182af2a32b85b5bf3e0))
9
+
3
10
  ## [0.3.1](https://github.com/tutur3u/platform/compare/ui-v0.3.0...ui-v0.3.1) (2026-06-09)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuturuuu/ui",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -83,13 +83,13 @@
83
83
  "@tiptap/react": "3.26.0",
84
84
  "@tiptap/starter-kit": "3.26.0",
85
85
  "@tuturuuu/ai": "0.1.0",
86
- "@tuturuuu/apis": "0.0.12",
86
+ "@tuturuuu/apis": "0.1.0",
87
87
  "@tuturuuu/hooks": "0.0.1",
88
88
  "@tuturuuu/icons": "0.0.5",
89
- "@tuturuuu/internal-api": "0.2.1",
90
- "@tuturuuu/supabase": "0.3.1",
89
+ "@tuturuuu/internal-api": "0.3.0",
90
+ "@tuturuuu/supabase": "0.3.2",
91
91
  "@tuturuuu/trigger": "0.2.0",
92
- "@tuturuuu/utils": "0.3.1",
92
+ "@tuturuuu/utils": "0.4.0",
93
93
  "@types/debug": "^4.1.13",
94
94
  "browser-image-compression": "^2.0.2",
95
95
  "class-variance-authority": "^0.7.1",
@@ -148,7 +148,7 @@
148
148
  "@tanstack/react-table": "^8.21.3",
149
149
  "@testing-library/jest-dom": "^6.9.1",
150
150
  "@testing-library/react": "^16.3.2",
151
- "@tuturuuu/types": "0.4.1",
151
+ "@tuturuuu/types": "0.5.0",
152
152
  "@tuturuuu/typescript-config": "0.1.1",
153
153
  "@types/html2canvas": "^1.0.0",
154
154
  "@types/lodash": "^4.17.24",
@@ -1,11 +1,48 @@
1
- import { render, screen } from '@testing-library/react';
2
- import { describe, expect, it, vi } from 'vitest';
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
4
  import { AgentOperationsPanel } from './chat-agent-details-operations-panel';
4
5
 
6
+ const mocks = vi.hoisted(() => ({
7
+ abortAiAgentZaloPersonalQrLogin: vi.fn(),
8
+ getAiAgentZaloPersonalQrLoginStatus: vi.fn(),
9
+ getAiAgentZaloPersonalStatus: vi.fn(),
10
+ runAiAgentZaloPersonalAction: vi.fn(),
11
+ startAiAgentZaloPersonalQrLogin: vi.fn(),
12
+ toastWarning: vi.fn(),
13
+ }));
14
+
5
15
  vi.mock('next-intl', () => ({
6
16
  useTranslations: () => (key: string) => key,
7
17
  }));
8
18
 
19
+ vi.mock('@tuturuuu/internal-api/infrastructure', () => ({
20
+ abortAiAgentZaloPersonalQrLogin: (
21
+ ...args: Parameters<typeof mocks.abortAiAgentZaloPersonalQrLogin>
22
+ ) => mocks.abortAiAgentZaloPersonalQrLogin(...args),
23
+ getAiAgentZaloPersonalQrLoginStatus: (
24
+ ...args: Parameters<typeof mocks.getAiAgentZaloPersonalQrLoginStatus>
25
+ ) => mocks.getAiAgentZaloPersonalQrLoginStatus(...args),
26
+ getAiAgentZaloPersonalStatus: (
27
+ ...args: Parameters<typeof mocks.getAiAgentZaloPersonalStatus>
28
+ ) => mocks.getAiAgentZaloPersonalStatus(...args),
29
+ runAiAgentZaloPersonalAction: (
30
+ ...args: Parameters<typeof mocks.runAiAgentZaloPersonalAction>
31
+ ) => mocks.runAiAgentZaloPersonalAction(...args),
32
+ startAiAgentZaloPersonalQrLogin: (
33
+ ...args: Parameters<typeof mocks.startAiAgentZaloPersonalQrLogin>
34
+ ) => mocks.startAiAgentZaloPersonalQrLogin(...args),
35
+ }));
36
+
37
+ vi.mock('../sonner', () => ({
38
+ toast: {
39
+ error: vi.fn(),
40
+ success: vi.fn(),
41
+ warning: (...args: Parameters<typeof mocks.toastWarning>) =>
42
+ mocks.toastWarning(...args),
43
+ },
44
+ }));
45
+
9
46
  const channel = {
10
47
  adapter: 'discord' as const,
11
48
  autoRespond: true,
@@ -26,14 +63,371 @@ const channel = {
26
63
  };
27
64
 
28
65
  describe('AgentOperationsPanel', () => {
66
+ beforeEach(() => {
67
+ vi.clearAllMocks();
68
+ mocks.runAiAgentZaloPersonalAction.mockResolvedValue({
69
+ status: {
70
+ channelId: 'channel-1',
71
+ connected: true,
72
+ enabled: true,
73
+ lastError: null,
74
+ lastEventAt: null,
75
+ mode: 'personal',
76
+ ownId: 'own-1',
77
+ running: true,
78
+ startedAt: '2026-06-02T00:00:00.000Z',
79
+ },
80
+ sync: {
81
+ exhausted: true,
82
+ failedGroupHistories: 0,
83
+ groupMessages: 0,
84
+ groupsScanned: 0,
85
+ pageCount: 1,
86
+ synced: 2,
87
+ threads: 1,
88
+ timedOut: false,
89
+ userMessages: 2,
90
+ },
91
+ });
92
+ });
93
+
94
+ it('renders personal Zalo QR controls instead of webhook secret rotation', () => {
95
+ mocks.getAiAgentZaloPersonalStatus.mockResolvedValue({
96
+ status: {
97
+ channelId: 'channel-1',
98
+ connected: false,
99
+ enabled: true,
100
+ lastError: null,
101
+ lastEventAt: null,
102
+ mode: 'personal',
103
+ ownId: 'own-1',
104
+ running: false,
105
+ startedAt: null,
106
+ },
107
+ });
108
+ const queryClient = new QueryClient({
109
+ defaultOptions: {
110
+ queries: { retry: false },
111
+ },
112
+ });
113
+
114
+ render(
115
+ <QueryClientProvider client={queryClient}>
116
+ <AgentOperationsPanel
117
+ agentId="agent-1"
118
+ channel={{
119
+ ...channel,
120
+ adapter: 'zalo',
121
+ displayName: 'Personal Zalo',
122
+ webhookUrl: null,
123
+ zaloAccountMode: 'personal',
124
+ zaloPersonalOwnId: 'own-1',
125
+ }}
126
+ isPending={false}
127
+ onCopySecret={vi.fn()}
128
+ onDeploy={vi.fn()}
129
+ onPause={vi.fn()}
130
+ onRefresh={vi.fn()}
131
+ onRotateSecret={vi.fn()}
132
+ onTest={vi.fn()}
133
+ secretPreview={null}
134
+ testResult={null}
135
+ />
136
+ </QueryClientProvider>
137
+ );
138
+
139
+ expect(
140
+ screen.getByText('agent_zalo_personal_qr_title')
141
+ ).toBeInTheDocument();
142
+ expect(screen.queryByText('agent_rotate_secret')).not.toBeInTheDocument();
143
+ });
144
+
145
+ it('runs personal Zalo historical sync from the operations panel', async () => {
146
+ mocks.getAiAgentZaloPersonalStatus.mockResolvedValue({
147
+ status: {
148
+ channelId: 'channel-1',
149
+ connected: true,
150
+ enabled: true,
151
+ lastError: null,
152
+ lastEventAt: null,
153
+ mode: 'personal',
154
+ ownId: 'own-1',
155
+ running: true,
156
+ startedAt: '2026-06-02T00:00:00.000Z',
157
+ },
158
+ });
159
+ const queryClient = new QueryClient({
160
+ defaultOptions: {
161
+ mutations: { retry: false },
162
+ queries: { retry: false },
163
+ },
164
+ });
165
+
166
+ render(
167
+ <QueryClientProvider client={queryClient}>
168
+ <AgentOperationsPanel
169
+ agentId="agent-1"
170
+ channel={{
171
+ ...channel,
172
+ adapter: 'zalo',
173
+ displayName: 'Personal Zalo',
174
+ webhookUrl: null,
175
+ zaloAccountMode: 'personal',
176
+ zaloPersonalOwnId: 'own-1',
177
+ }}
178
+ isPending={false}
179
+ onCopySecret={vi.fn()}
180
+ onDeploy={vi.fn()}
181
+ onPause={vi.fn()}
182
+ onRefresh={vi.fn()}
183
+ onRotateSecret={vi.fn()}
184
+ onTest={vi.fn()}
185
+ secretPreview={null}
186
+ testResult={null}
187
+ />
188
+ </QueryClientProvider>
189
+ );
190
+
191
+ fireEvent.click(screen.getByText('agent_zalo_personal_sync_history'));
192
+
193
+ await waitFor(() => {
194
+ expect(mocks.runAiAgentZaloPersonalAction).toHaveBeenCalledWith(
195
+ 'agent-1',
196
+ 'channel-1',
197
+ 'sync-history'
198
+ );
199
+ });
200
+ });
201
+
202
+ it('runs personal Zalo phone-approved transfer sync from the operations panel', async () => {
203
+ mocks.getAiAgentZaloPersonalStatus.mockResolvedValue({
204
+ status: {
205
+ channelId: 'channel-1',
206
+ connected: true,
207
+ enabled: true,
208
+ lastError: null,
209
+ lastEventAt: null,
210
+ mode: 'personal',
211
+ ownId: 'own-1',
212
+ running: true,
213
+ startedAt: '2026-06-02T00:00:00.000Z',
214
+ },
215
+ });
216
+ mocks.runAiAgentZaloPersonalAction.mockResolvedValueOnce({
217
+ status: {
218
+ channelId: 'channel-1',
219
+ connected: true,
220
+ enabled: true,
221
+ lastError: null,
222
+ lastEventAt: null,
223
+ mode: 'personal',
224
+ ownId: 'own-1',
225
+ running: true,
226
+ startedAt: '2026-06-02T00:00:00.000Z',
227
+ },
228
+ sync: {
229
+ approvalRequested: true,
230
+ cleaned: true,
231
+ error: null,
232
+ groupMessages: 1,
233
+ pullAttempts: 1,
234
+ requestAccepted: true,
235
+ requestHttpError: null,
236
+ requestViaHttp: true,
237
+ requestViaWebSocket: true,
238
+ status: 'completed',
239
+ synced: 3,
240
+ threads: 2,
241
+ userMessages: 2,
242
+ },
243
+ });
244
+ const queryClient = new QueryClient({
245
+ defaultOptions: {
246
+ mutations: { retry: false },
247
+ queries: { retry: false },
248
+ },
249
+ });
250
+
251
+ render(
252
+ <QueryClientProvider client={queryClient}>
253
+ <AgentOperationsPanel
254
+ agentId="agent-1"
255
+ channel={{
256
+ ...channel,
257
+ adapter: 'zalo',
258
+ displayName: 'Personal Zalo',
259
+ webhookUrl: null,
260
+ zaloAccountMode: 'personal',
261
+ zaloPersonalOwnId: 'own-1',
262
+ }}
263
+ isPending={false}
264
+ onCopySecret={vi.fn()}
265
+ onDeploy={vi.fn()}
266
+ onPause={vi.fn()}
267
+ onRefresh={vi.fn()}
268
+ onRotateSecret={vi.fn()}
269
+ onTest={vi.fn()}
270
+ secretPreview={null}
271
+ testResult={null}
272
+ />
273
+ </QueryClientProvider>
274
+ );
275
+
276
+ fireEvent.click(screen.getByText('agent_zalo_personal_sync_phone'));
277
+
278
+ await waitFor(() => {
279
+ expect(mocks.runAiAgentZaloPersonalAction).toHaveBeenCalledWith(
280
+ 'agent-1',
281
+ 'channel-1',
282
+ 'sync-phone'
283
+ );
284
+ });
285
+ });
286
+
287
+ it('warns when personal Zalo phone transfer is approved but returns no payload', async () => {
288
+ mocks.getAiAgentZaloPersonalStatus.mockResolvedValue({
289
+ status: {
290
+ channelId: 'channel-1',
291
+ connected: true,
292
+ enabled: true,
293
+ lastError: null,
294
+ lastEventAt: null,
295
+ mode: 'personal',
296
+ ownId: 'own-1',
297
+ running: true,
298
+ startedAt: '2026-06-02T00:00:00.000Z',
299
+ },
300
+ });
301
+ mocks.runAiAgentZaloPersonalAction.mockResolvedValueOnce({
302
+ status: {
303
+ channelId: 'channel-1',
304
+ connected: true,
305
+ enabled: true,
306
+ lastError: 'zalo_personal_phone_sync_no_payload',
307
+ lastEventAt: null,
308
+ mode: 'personal',
309
+ ownId: 'own-1',
310
+ running: true,
311
+ startedAt: '2026-06-02T00:00:00.000Z',
312
+ },
313
+ sync: {
314
+ approvalRequested: true,
315
+ cleaned: false,
316
+ error: null,
317
+ groupMessages: 0,
318
+ pullAttempts: 90,
319
+ requestAccepted: true,
320
+ requestHttpError: 'Request failed with status code 404',
321
+ requestViaHttp: false,
322
+ requestViaWebSocket: true,
323
+ status: 'completed_no_payload',
324
+ synced: 0,
325
+ threads: 0,
326
+ userMessages: 0,
327
+ },
328
+ });
329
+ const queryClient = new QueryClient({
330
+ defaultOptions: {
331
+ mutations: { retry: false },
332
+ queries: { retry: false },
333
+ },
334
+ });
335
+
336
+ render(
337
+ <QueryClientProvider client={queryClient}>
338
+ <AgentOperationsPanel
339
+ agentId="agent-1"
340
+ channel={{
341
+ ...channel,
342
+ adapter: 'zalo',
343
+ displayName: 'Personal Zalo',
344
+ webhookUrl: null,
345
+ zaloAccountMode: 'personal',
346
+ zaloPersonalOwnId: 'own-1',
347
+ }}
348
+ isPending={false}
349
+ onCopySecret={vi.fn()}
350
+ onDeploy={vi.fn()}
351
+ onPause={vi.fn()}
352
+ onRefresh={vi.fn()}
353
+ onRotateSecret={vi.fn()}
354
+ onTest={vi.fn()}
355
+ secretPreview={null}
356
+ testResult={null}
357
+ />
358
+ </QueryClientProvider>
359
+ );
360
+
361
+ fireEvent.click(screen.getByText('agent_zalo_personal_sync_phone'));
362
+
363
+ await waitFor(() => {
364
+ expect(mocks.toastWarning).toHaveBeenCalledWith(
365
+ 'agent_zalo_personal_sync_phone_no_payload'
366
+ );
367
+ });
368
+ });
369
+
370
+ it('formats personal Zalo channel error codes for display', () => {
371
+ mocks.getAiAgentZaloPersonalStatus.mockResolvedValue({
372
+ status: {
373
+ channelId: 'channel-1',
374
+ connected: false,
375
+ enabled: true,
376
+ lastError: null,
377
+ lastEventAt: null,
378
+ mode: 'personal',
379
+ ownId: null,
380
+ running: false,
381
+ startedAt: null,
382
+ },
383
+ });
384
+ const queryClient = new QueryClient({
385
+ defaultOptions: {
386
+ queries: { retry: false },
387
+ },
388
+ });
389
+
390
+ render(
391
+ <QueryClientProvider client={queryClient}>
392
+ <AgentOperationsPanel
393
+ agentId="agent-1"
394
+ channel={{
395
+ ...channel,
396
+ adapter: 'zalo',
397
+ displayName: 'Personal Zalo',
398
+ lastError: 'zalo_personal_qr_expired',
399
+ webhookUrl: null,
400
+ zaloAccountMode: 'personal',
401
+ }}
402
+ isPending={false}
403
+ onCopySecret={vi.fn()}
404
+ onDeploy={vi.fn()}
405
+ onPause={vi.fn()}
406
+ onRefresh={vi.fn()}
407
+ onRotateSecret={vi.fn()}
408
+ onTest={vi.fn()}
409
+ secretPreview={null}
410
+ testResult={null}
411
+ />
412
+ </QueryClientProvider>
413
+ );
414
+
415
+ expect(
416
+ screen.getByText('agent_zalo_personal_qr_expired')
417
+ ).toBeInTheDocument();
418
+ expect(screen.queryByText('zalo_personal_qr_expired')).toBeNull();
419
+ });
420
+
29
421
  it('renders structured diagnostics after a channel test', () => {
30
422
  render(
31
423
  <AgentOperationsPanel
424
+ agentId="agent-1"
32
425
  channel={channel}
33
426
  isPending={false}
34
427
  onCopySecret={vi.fn()}
35
428
  onDeploy={vi.fn()}
36
429
  onPause={vi.fn()}
430
+ onRefresh={vi.fn()}
37
431
  onRotateSecret={vi.fn()}
38
432
  onTest={vi.fn()}
39
433
  secretPreview={null}
@@ -21,9 +21,12 @@ import { Button } from '../button';
21
21
  import { Input } from '../input';
22
22
  import {
23
23
  copyToClipboard,
24
+ formatZaloPersonalError,
25
+ isPersonalZaloChannel,
24
26
  KeyValue,
25
27
  PanelSection,
26
28
  } from './chat-agent-details-utils';
29
+ import { AgentZaloPersonalPanel } from './chat-agent-details-zalo-personal-panel';
27
30
 
28
31
  const DIAGNOSTIC_LABEL_KEYS = {
29
32
  adapter_account: 'agent_diagnostic_adapter_account',
@@ -32,27 +35,33 @@ const DIAGNOSTIC_LABEL_KEYS = {
32
35
  channel_enabled: 'agent_diagnostic_channel_enabled',
33
36
  last_error: 'agent_diagnostic_last_error',
34
37
  last_event: 'agent_diagnostic_last_event',
38
+ listener_lifecycle: 'agent_diagnostic_listener_lifecycle',
35
39
  required_secrets: 'agent_diagnostic_required_secrets',
36
40
  webhook_url: 'agent_diagnostic_webhook_url',
37
41
  workspace_mapping: 'agent_diagnostic_workspace_mapping',
42
+ zalo_personal_feature_flag: 'agent_diagnostic_zalo_personal_feature_flag',
38
43
  } as const;
39
44
 
40
45
  export function AgentOperationsPanel({
46
+ agentId,
41
47
  channel,
42
48
  isPending,
43
49
  onCopySecret,
44
50
  onDeploy,
45
51
  onPause,
52
+ onRefresh,
46
53
  onRotateSecret,
47
54
  onTest,
48
55
  secretPreview,
49
56
  testResult,
50
57
  }: {
58
+ agentId: string;
51
59
  channel: AiAgentChannelConfig;
52
60
  isPending: boolean;
53
61
  onCopySecret: () => void;
54
62
  onDeploy: () => void;
55
63
  onPause: () => void;
64
+ onRefresh: () => void;
56
65
  onRotateSecret: () => void;
57
66
  onTest: (prompt?: string) => void;
58
67
  secretPreview: { label: string; value: string } | null;
@@ -61,6 +70,14 @@ export function AgentOperationsPanel({
61
70
  const t = useTranslations('chat');
62
71
  const [testPrompt, setTestPrompt] = useState('');
63
72
  const webhookUrl = channel.webhookUrl;
73
+ const isPersonalZalo = isPersonalZaloChannel(channel);
74
+ const lastError =
75
+ isPersonalZalo &&
76
+ channel.lastError === 'zalo_personal_phone_sync_waiting_for_phone'
77
+ ? null
78
+ : isPersonalZalo
79
+ ? formatZaloPersonalError(channel.lastError, t)
80
+ : channel.lastError;
64
81
  const diagnosticLabel = (
65
82
  check: NonNullable<AiAgentTestResponse['checks']>[number]
66
83
  ) => {
@@ -71,12 +88,12 @@ export function AgentOperationsPanel({
71
88
  };
72
89
 
73
90
  return (
74
- <div className="space-y-4">
91
+ <div className="min-w-0 space-y-4 overflow-hidden">
75
92
  <PanelSection
76
93
  icon={<Webhook className="size-4" />}
77
94
  title={t('agent_channel')}
78
95
  >
79
- <div className="space-y-2">
96
+ <div className="min-w-0 space-y-2 overflow-hidden">
80
97
  <KeyValue label={t('agent_channel_id')} value={channel.id} />
81
98
  <KeyValue label={t('agent_adapter')} value={channel.adapter} />
82
99
  <KeyValue
@@ -105,15 +122,15 @@ export function AgentOperationsPanel({
105
122
  </Button>
106
123
  </div>
107
124
  ) : null}
108
- {channel.lastError ? (
109
- <p className="rounded-md border border-dynamic-red/20 bg-dynamic-red/5 p-2 text-dynamic-red text-xs">
110
- {channel.lastError}
125
+ {lastError ? (
126
+ <p className="break-words rounded-md border border-dynamic-red/20 bg-dynamic-red/5 p-2 text-dynamic-red text-xs">
127
+ {lastError}
111
128
  </p>
112
129
  ) : null}
113
130
  </div>
114
131
  </PanelSection>
115
132
 
116
- <div className="grid grid-cols-2 gap-2">
133
+ <div className="grid min-w-0 grid-cols-2 gap-2">
117
134
  <Button disabled={isPending} onClick={onDeploy} size="sm" type="button">
118
135
  <Play className="size-4" />
119
136
  {t('agent_deploy')}
@@ -130,6 +147,15 @@ export function AgentOperationsPanel({
130
147
  </Button>
131
148
  </div>
132
149
 
150
+ {isPersonalZalo ? (
151
+ <AgentZaloPersonalPanel
152
+ agentId={agentId}
153
+ channel={channel}
154
+ isPending={isPending}
155
+ onRefresh={onRefresh}
156
+ />
157
+ ) : null}
158
+
133
159
  <PanelSection
134
160
  icon={<FlaskConical className="size-4" />}
135
161
  title={t('agent_test')}
@@ -157,7 +183,9 @@ export function AgentOperationsPanel({
157
183
  <p className="text-muted-foreground text-xs leading-5">
158
184
  {channel.adapter === 'discord'
159
185
  ? t('agent_discord_live_test_hint')
160
- : t('agent_live_test_hint')}
186
+ : isPersonalZalo
187
+ ? t('agent_zalo_personal_live_test_hint')
188
+ : t('agent_live_test_hint')}
161
189
  </p>
162
190
  </div>
163
191
  </PanelSection>
@@ -194,7 +222,7 @@ export function AgentOperationsPanel({
194
222
  </PanelSection>
195
223
  ) : null}
196
224
 
197
- {channel.adapter === 'zalo' ? (
225
+ {channel.adapter === 'zalo' && !isPersonalZalo ? (
198
226
  <PanelSection
199
227
  icon={<RotateCw className="size-4" />}
200
228
  title={t('agent_rotate_secret')}
@@ -27,6 +27,7 @@ import { Textarea } from '../textarea';
27
27
  import {
28
28
  buildAgentPayload,
29
29
  Field,
30
+ isPersonalZaloChannel,
30
31
  PanelSection,
31
32
  secretNamesForChannel,
32
33
  } from './chat-agent-details-utils';
@@ -192,6 +193,19 @@ export function AgentSetupForm({
192
193
  />
193
194
  </Field>
194
195
  </>
196
+ ) : isPersonalZaloChannel(channel) ? (
197
+ <Field
198
+ id="agent-zalo-personal-own-id"
199
+ label={t('agent_zalo_personal_own_id')}
200
+ >
201
+ <Input
202
+ className="font-mono"
203
+ defaultValue={channel.zaloPersonalOwnId ?? ''}
204
+ id="agent-zalo-personal-own-id"
205
+ name="zaloPersonalOwnId"
206
+ readOnly
207
+ />
208
+ </Field>
195
209
  ) : (
196
210
  <Field id="agent-zalo-oa" label={t('agent_zalo_oa_id')}>
197
211
  <Input
@@ -21,11 +21,16 @@ vi.mock('next-intl', () => ({
21
21
  }));
22
22
 
23
23
  vi.mock('@tuturuuu/internal-api/infrastructure', () => ({
24
+ abortAiAgentZaloPersonalQrLogin: vi.fn(),
24
25
  deployAiAgentChannel: vi.fn(),
26
+ getAiAgentZaloPersonalQrLoginStatus: vi.fn(),
27
+ getAiAgentZaloPersonalStatus: vi.fn(),
25
28
  listAiAgents: (...args: unknown[]) => mocks.listAiAgents(...args),
26
29
  pauseAiAgentChannel: vi.fn(),
27
30
  rotateAiAgentChannelSecret: vi.fn(),
31
+ runAiAgentZaloPersonalAction: vi.fn(),
28
32
  saveAiAgent: vi.fn(),
33
+ startAiAgentZaloPersonalQrLogin: vi.fn(),
29
34
  testAiAgentChannel: vi.fn(),
30
35
  }));
31
36