@open-mercato/ai-assistant 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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +30 -4
- package/dist/frontend/components/AiChatButton.js +3 -2
- package/dist/frontend/components/AiChatButton.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js +364 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +7 -7
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js +182 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js +316 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +8 -7
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +43 -20
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/route.js +4 -3
- package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/daily/route.js +111 -0
- package/dist/modules/ai_assistant/api/usage/daily/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js +108 -0
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/usage/sessions/route.js +153 -0
- package/dist/modules/ai_assistant/api/usage/sessions/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +335 -38
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +2 -7
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +44 -35
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js +282 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +12 -0
- package/dist/modules/ai_assistant/cli.js.map +2 -2
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +1 -1
- package/dist/modules/ai_assistant/data/entities.js +177 -1
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +104 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js +168 -0
- package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js.map +7 -0
- package/dist/modules/ai_assistant/events.js +8 -0
- package/dist/modules/ai_assistant/events.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +74 -1
- package/dist/modules/ai_assistant/i18n/en.json +74 -1
- package/dist/modules/ai_assistant/i18n/es.json +75 -2
- package/dist/modules/ai_assistant/i18n/pl.json +74 -1
- package/dist/modules/ai_assistant/lib/agent-policy.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +588 -23
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +3 -3
- package/dist/modules/ai_assistant/lib/agent-tools.js +6 -1
- package/dist/modules/ai_assistant/lib/agent-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-factory.js +63 -22
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js +78 -0
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js.map +7 -0
- package/dist/modules/ai_assistant/lib/usage-serialization.js +33 -0
- package/dist/modules/ai_assistant/lib/usage-serialization.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js +25 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js +88 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js.map +7 -0
- package/dist/modules/ai_assistant/setup.js +34 -0
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js +114 -0
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +7 -0
- package/generated/entities/ai_token_usage_daily/index.ts +16 -0
- package/generated/entities/ai_token_usage_event/index.ts +19 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +47 -1
- package/package.json +15 -7
- package/src/frontend/components/AiChatButton.tsx +3 -2
- package/src/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.ts +521 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +8 -8
- package/src/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.ts +231 -0
- package/src/modules/ai_assistant/__tests__/events.test.ts +4 -3
- package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +5 -5
- package/src/modules/ai_assistant/__tests__/token-usage-recorder.test.ts +109 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.ts +388 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +5 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +8 -7
- package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +102 -5
- package/src/modules/ai_assistant/api/ai/chat/route.ts +55 -18
- package/src/modules/ai_assistant/api/settings/route.ts +5 -3
- package/src/modules/ai_assistant/api/usage/daily/__tests__/route.test.ts +159 -0
- package/src/modules/ai_assistant/api/usage/daily/route.ts +126 -0
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/__tests__/route.test.ts +143 -0
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/route.ts +130 -0
- package/src/modules/ai_assistant/api/usage/sessions/__tests__/route.test.ts +123 -0
- package/src/modules/ai_assistant/api/usage/sessions/route.ts +184 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +372 -16
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +1 -4
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +26 -9
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.tsx +469 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +18 -0
- package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +1 -1
- package/src/modules/ai_assistant/data/entities.ts +237 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +135 -3
- package/src/modules/ai_assistant/data/repositories/AiTokenUsageRepository.ts +213 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +223 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiTokenUsageRepository.test.ts +58 -0
- package/src/modules/ai_assistant/events.ts +8 -0
- package/src/modules/ai_assistant/i18n/de.json +74 -1
- package/src/modules/ai_assistant/i18n/en.json +74 -1
- package/src/modules/ai_assistant/i18n/es.json +75 -2
- package/src/modules/ai_assistant/i18n/pl.json +74 -1
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase0.test.ts +439 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase1.test.ts +243 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase2.test.ts +388 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase3.test.ts +359 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +2 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +2 -1
- package/src/modules/ai_assistant/lib/__tests__/max-steps-budget.integration.test.ts +12 -13
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +77 -14
- package/src/modules/ai_assistant/lib/agent-policy.ts +9 -0
- package/src/modules/ai_assistant/lib/agent-runtime.ts +1148 -43
- package/src/modules/ai_assistant/lib/agent-tools.ts +5 -1
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +289 -2
- package/src/modules/ai_assistant/lib/model-factory.ts +128 -43
- package/src/modules/ai_assistant/lib/token-usage-recorder.ts +122 -0
- package/src/modules/ai_assistant/lib/usage-serialization.ts +29 -0
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +791 -0
- package/src/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.ts +25 -0
- package/src/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.ts +89 -0
- package/src/modules/ai_assistant/setup.ts +49 -0
- package/src/modules/ai_assistant/workers/__tests__/ai-token-usage-prune.test.ts +144 -0
- package/src/modules/ai_assistant/workers/ai-token-usage-prune.ts +188 -0
package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx
CHANGED
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
5
5
|
import {
|
|
6
|
-
AlertCircle,
|
|
7
6
|
Bot,
|
|
8
7
|
BookOpen,
|
|
9
|
-
CheckCircle2,
|
|
10
8
|
History,
|
|
11
9
|
Image as ImageIcon,
|
|
12
10
|
FileText,
|
|
@@ -16,6 +14,7 @@ import {
|
|
|
16
14
|
RefreshCcw,
|
|
17
15
|
Save,
|
|
18
16
|
ShieldAlert,
|
|
17
|
+
ShieldOff,
|
|
19
18
|
Trash2,
|
|
20
19
|
Wand2,
|
|
21
20
|
Wrench,
|
|
@@ -26,6 +25,7 @@ import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
|
26
25
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
27
26
|
import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
|
|
28
27
|
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
28
|
+
import { Input } from '@open-mercato/ui/primitives/input'
|
|
29
29
|
import { Label } from '@open-mercato/ui/primitives/label'
|
|
30
30
|
import { Radio, RadioGroup } from '@open-mercato/ui/primitives/radio'
|
|
31
31
|
import {
|
|
@@ -704,8 +704,11 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
704
704
|
</div>
|
|
705
705
|
</div>
|
|
706
706
|
|
|
707
|
-
<Alert
|
|
708
|
-
|
|
707
|
+
<Alert
|
|
708
|
+
variant="info"
|
|
709
|
+
icon={<ShieldAlert aria-hidden="true" />}
|
|
710
|
+
data-ai-agent-mutation-policy-notice
|
|
711
|
+
>
|
|
709
712
|
<AlertTitle>
|
|
710
713
|
{t(
|
|
711
714
|
'ai_assistant.agents.mutation_policy.noticeTitle',
|
|
@@ -729,7 +732,6 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
729
732
|
/>
|
|
730
733
|
) : query.isError ? (
|
|
731
734
|
<Alert variant="destructive" data-ai-agent-mutation-policy-load-error>
|
|
732
|
-
<AlertCircle className="size-4" aria-hidden />
|
|
733
735
|
<AlertTitle>
|
|
734
736
|
{t(
|
|
735
737
|
'ai_assistant.agents.mutation_policy.loadErrorTitle',
|
|
@@ -818,7 +820,6 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
818
820
|
|
|
819
821
|
{state.kind === 'success' ? (
|
|
820
822
|
<Alert variant="success" data-ai-agent-mutation-policy-state="success">
|
|
821
|
-
<CheckCircle2 className="size-4" aria-hidden />
|
|
822
823
|
<AlertTitle>
|
|
823
824
|
{t('ai_assistant.agents.mutation_policy.savedTitle', 'Mutation policy updated')}
|
|
824
825
|
</AlertTitle>
|
|
@@ -827,7 +828,6 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
827
828
|
) : null}
|
|
828
829
|
{state.kind === 'error' ? (
|
|
829
830
|
<Alert variant="destructive" data-ai-agent-mutation-policy-state="error">
|
|
830
|
-
<AlertCircle className="size-4" aria-hidden />
|
|
831
831
|
<AlertTitle>
|
|
832
832
|
{t(
|
|
833
833
|
'ai_assistant.agents.mutation_policy.errorTitle',
|
|
@@ -1190,7 +1190,6 @@ function AgentModelOverrideSection({ agent }: { agent: AgentSettings }) {
|
|
|
1190
1190
|
/>
|
|
1191
1191
|
) : settingsQuery.isError ? (
|
|
1192
1192
|
<Alert variant="destructive" data-ai-agent-model-override-load-error>
|
|
1193
|
-
<AlertCircle className="size-4" aria-hidden />
|
|
1194
1193
|
<AlertTitle>
|
|
1195
1194
|
{t(
|
|
1196
1195
|
'ai_assistant.agents.model_override.loadErrorTitle',
|
|
@@ -1401,13 +1400,11 @@ function AgentModelOverrideSection({ agent }: { agent: AgentSettings }) {
|
|
|
1401
1400
|
|
|
1402
1401
|
{state.kind === 'success' ? (
|
|
1403
1402
|
<Alert variant="success" data-ai-agent-model-override-state="success">
|
|
1404
|
-
<CheckCircle2 className="size-4" aria-hidden />
|
|
1405
1403
|
<AlertDescription>{state.message}</AlertDescription>
|
|
1406
1404
|
</Alert>
|
|
1407
1405
|
) : null}
|
|
1408
1406
|
{state.kind === 'error' ? (
|
|
1409
1407
|
<Alert variant="destructive" data-ai-agent-model-override-state="error">
|
|
1410
|
-
<AlertCircle className="size-4" aria-hidden />
|
|
1411
1408
|
<AlertDescription>{state.message}</AlertDescription>
|
|
1412
1409
|
</Alert>
|
|
1413
1410
|
) : null}
|
|
@@ -1448,6 +1445,364 @@ function AgentModelOverrideSection({ agent }: { agent: AgentSettings }) {
|
|
|
1448
1445
|
)
|
|
1449
1446
|
}
|
|
1450
1447
|
|
|
1448
|
+
type LoopOverrideRow = {
|
|
1449
|
+
id: string
|
|
1450
|
+
agentId: string | null
|
|
1451
|
+
loopDisabled: boolean | null
|
|
1452
|
+
loopMaxSteps: number | null
|
|
1453
|
+
loopMaxToolCalls: number | null
|
|
1454
|
+
loopMaxWallClockMs: number | null
|
|
1455
|
+
loopMaxTokens: number | null
|
|
1456
|
+
loopStopWhenJson: unknown[] | null
|
|
1457
|
+
loopActiveToolsJson: string[] | null
|
|
1458
|
+
updatedAt: string
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
type LoopOverrideResponse = {
|
|
1462
|
+
agentId: string
|
|
1463
|
+
override: LoopOverrideRow | null
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
async function fetchLoopOverride(agentId: string): Promise<LoopOverrideResponse> {
|
|
1467
|
+
const { result, status } = await apiCallOrThrow<LoopOverrideResponse>(
|
|
1468
|
+
`/api/ai_assistant/ai/agents/${encodeURIComponent(agentId)}/loop-override`,
|
|
1469
|
+
{ method: 'GET', credentials: 'include' },
|
|
1470
|
+
{ errorMessage: 'Failed to load loop override' },
|
|
1471
|
+
)
|
|
1472
|
+
if (!result) throw new Error(`Failed to load loop override (${status})`)
|
|
1473
|
+
return result
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function LoopPolicySection({ agent }: { agent: AgentSettings }) {
|
|
1477
|
+
const t = useT()
|
|
1478
|
+
const queryClient = useQueryClient()
|
|
1479
|
+
|
|
1480
|
+
const query = useQuery<LoopOverrideResponse>({
|
|
1481
|
+
queryKey: ['ai_assistant', 'agent_settings', 'loop_override', agent.id],
|
|
1482
|
+
queryFn: () => fetchLoopOverride(agent.id),
|
|
1483
|
+
retry: false,
|
|
1484
|
+
})
|
|
1485
|
+
|
|
1486
|
+
const currentOverride = query.data?.override ?? null
|
|
1487
|
+
|
|
1488
|
+
const [loopDisabled, setLoopDisabled] = React.useState<boolean>(false)
|
|
1489
|
+
const [maxSteps, setMaxSteps] = React.useState<string>('')
|
|
1490
|
+
const [maxToolCalls, setMaxToolCalls] = React.useState<string>('')
|
|
1491
|
+
const [maxWallClockMs, setMaxWallClockMs] = React.useState<string>('')
|
|
1492
|
+
const [maxTokens, setMaxTokens] = React.useState<string>('')
|
|
1493
|
+
const [isSaving, setIsSaving] = React.useState(false)
|
|
1494
|
+
const [isClearing, setIsClearing] = React.useState(false)
|
|
1495
|
+
const [state, setState] = React.useState<
|
|
1496
|
+
| { kind: 'idle' }
|
|
1497
|
+
| { kind: 'success'; message: string }
|
|
1498
|
+
| { kind: 'error'; message: string }
|
|
1499
|
+
>({ kind: 'idle' })
|
|
1500
|
+
|
|
1501
|
+
React.useEffect(() => {
|
|
1502
|
+
setLoopDisabled(currentOverride?.loopDisabled ?? false)
|
|
1503
|
+
setMaxSteps(currentOverride?.loopMaxSteps != null ? String(currentOverride.loopMaxSteps) : '')
|
|
1504
|
+
setMaxToolCalls(
|
|
1505
|
+
currentOverride?.loopMaxToolCalls != null ? String(currentOverride.loopMaxToolCalls) : '',
|
|
1506
|
+
)
|
|
1507
|
+
setMaxWallClockMs(
|
|
1508
|
+
currentOverride?.loopMaxWallClockMs != null
|
|
1509
|
+
? String(currentOverride.loopMaxWallClockMs)
|
|
1510
|
+
: '',
|
|
1511
|
+
)
|
|
1512
|
+
setMaxTokens(
|
|
1513
|
+
currentOverride?.loopMaxTokens != null ? String(currentOverride.loopMaxTokens) : '',
|
|
1514
|
+
)
|
|
1515
|
+
setState({ kind: 'idle' })
|
|
1516
|
+
}, [
|
|
1517
|
+
agent.id,
|
|
1518
|
+
currentOverride?.loopDisabled,
|
|
1519
|
+
currentOverride?.loopMaxSteps,
|
|
1520
|
+
currentOverride?.loopMaxToolCalls,
|
|
1521
|
+
currentOverride?.loopMaxWallClockMs,
|
|
1522
|
+
currentOverride?.loopMaxTokens,
|
|
1523
|
+
])
|
|
1524
|
+
|
|
1525
|
+
const toNullableInt = (value: string): number | null => {
|
|
1526
|
+
const trimmed = value.trim()
|
|
1527
|
+
if (trimmed === '') return null
|
|
1528
|
+
const parsed = parseInt(trimmed, 10)
|
|
1529
|
+
return isNaN(parsed) ? null : parsed
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
const save = React.useCallback(async () => {
|
|
1533
|
+
if (isSaving) return
|
|
1534
|
+
setIsSaving(true)
|
|
1535
|
+
setState({ kind: 'idle' })
|
|
1536
|
+
try {
|
|
1537
|
+
const { ok, status, result } = await apiCall<{
|
|
1538
|
+
ok?: boolean
|
|
1539
|
+
error?: string
|
|
1540
|
+
code?: string
|
|
1541
|
+
}>(
|
|
1542
|
+
`/api/ai_assistant/ai/agents/${encodeURIComponent(agent.id)}/loop-override`,
|
|
1543
|
+
{
|
|
1544
|
+
method: 'PUT',
|
|
1545
|
+
headers: { 'content-type': 'application/json' },
|
|
1546
|
+
credentials: 'include',
|
|
1547
|
+
body: JSON.stringify({
|
|
1548
|
+
loopDisabled: loopDisabled || null,
|
|
1549
|
+
loopMaxSteps: toNullableInt(maxSteps),
|
|
1550
|
+
loopMaxToolCalls: toNullableInt(maxToolCalls),
|
|
1551
|
+
loopMaxWallClockMs: toNullableInt(maxWallClockMs),
|
|
1552
|
+
loopMaxTokens: toNullableInt(maxTokens),
|
|
1553
|
+
}),
|
|
1554
|
+
},
|
|
1555
|
+
)
|
|
1556
|
+
const payload = result ?? {}
|
|
1557
|
+
if (!ok) {
|
|
1558
|
+
setState({
|
|
1559
|
+
kind: 'error',
|
|
1560
|
+
message: payload.error ?? `Failed to save loop policy (${status}).`,
|
|
1561
|
+
})
|
|
1562
|
+
return
|
|
1563
|
+
}
|
|
1564
|
+
setState({
|
|
1565
|
+
kind: 'success',
|
|
1566
|
+
message: t('ai_assistant.agents.loop_policy.savedMessage', 'Loop policy override saved.'),
|
|
1567
|
+
})
|
|
1568
|
+
await queryClient.invalidateQueries({
|
|
1569
|
+
queryKey: ['ai_assistant', 'agent_settings', 'loop_override', agent.id],
|
|
1570
|
+
})
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
setState({
|
|
1573
|
+
kind: 'error',
|
|
1574
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1575
|
+
})
|
|
1576
|
+
} finally {
|
|
1577
|
+
setIsSaving(false)
|
|
1578
|
+
}
|
|
1579
|
+
}, [agent.id, isSaving, loopDisabled, maxSteps, maxToolCalls, maxWallClockMs, maxTokens, queryClient, t])
|
|
1580
|
+
|
|
1581
|
+
const clear = React.useCallback(async () => {
|
|
1582
|
+
if (isClearing) return
|
|
1583
|
+
setIsClearing(true)
|
|
1584
|
+
setState({ kind: 'idle' })
|
|
1585
|
+
try {
|
|
1586
|
+
const { ok, status, result } = await apiCall<{ ok?: boolean; error?: string }>(
|
|
1587
|
+
`/api/ai_assistant/ai/agents/${encodeURIComponent(agent.id)}/loop-override`,
|
|
1588
|
+
{ method: 'DELETE', credentials: 'include' },
|
|
1589
|
+
)
|
|
1590
|
+
const payload = result ?? {}
|
|
1591
|
+
if (!ok) {
|
|
1592
|
+
setState({
|
|
1593
|
+
kind: 'error',
|
|
1594
|
+
message: payload.error ?? `Failed to clear loop override (${status}).`,
|
|
1595
|
+
})
|
|
1596
|
+
return
|
|
1597
|
+
}
|
|
1598
|
+
setState({
|
|
1599
|
+
kind: 'success',
|
|
1600
|
+
message: t(
|
|
1601
|
+
'ai_assistant.agents.loop_policy.clearedMessage',
|
|
1602
|
+
'Loop policy override cleared; agent is using its declared defaults.',
|
|
1603
|
+
),
|
|
1604
|
+
})
|
|
1605
|
+
await queryClient.invalidateQueries({
|
|
1606
|
+
queryKey: ['ai_assistant', 'agent_settings', 'loop_override', agent.id],
|
|
1607
|
+
})
|
|
1608
|
+
} catch (err) {
|
|
1609
|
+
setState({
|
|
1610
|
+
kind: 'error',
|
|
1611
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1612
|
+
})
|
|
1613
|
+
} finally {
|
|
1614
|
+
setIsClearing(false)
|
|
1615
|
+
}
|
|
1616
|
+
}, [agent.id, isClearing, queryClient, t])
|
|
1617
|
+
|
|
1618
|
+
return (
|
|
1619
|
+
<section
|
|
1620
|
+
className="rounded-lg border border-border bg-background p-4"
|
|
1621
|
+
data-ai-agent-loop-policy={agent.id}
|
|
1622
|
+
>
|
|
1623
|
+
<header className="flex items-center justify-between gap-3 border-b border-border pb-3">
|
|
1624
|
+
<div className="flex items-center gap-2">
|
|
1625
|
+
<ShieldOff className="size-4 text-muted-foreground" aria-hidden />
|
|
1626
|
+
<div>
|
|
1627
|
+
<h3 className="text-sm font-semibold">
|
|
1628
|
+
{t('ai_assistant.agents.loop_policy.title', 'Loop policy')}
|
|
1629
|
+
</h3>
|
|
1630
|
+
<p className="text-xs text-muted-foreground">
|
|
1631
|
+
{t(
|
|
1632
|
+
'ai_assistant.agents.loop_policy.subtitle',
|
|
1633
|
+
'Set per-tenant budget limits or disable the agentic loop for this agent.',
|
|
1634
|
+
)}
|
|
1635
|
+
</p>
|
|
1636
|
+
</div>
|
|
1637
|
+
</div>
|
|
1638
|
+
{currentOverride?.loopDisabled ? (
|
|
1639
|
+
<Badge variant="destructive" data-ai-agent-loop-disabled-badge>
|
|
1640
|
+
{t('ai_assistant.agents.loop_policy.disabledBadge', 'Loop disabled')}
|
|
1641
|
+
</Badge>
|
|
1642
|
+
) : null}
|
|
1643
|
+
</header>
|
|
1644
|
+
|
|
1645
|
+
<div className="mt-3 flex flex-col gap-4">
|
|
1646
|
+
{query.isLoading ? (
|
|
1647
|
+
<SettingsLoading
|
|
1648
|
+
message={t('ai_assistant.agents.loop_policy.loading', 'Loading loop policy...')}
|
|
1649
|
+
/>
|
|
1650
|
+
) : query.isError ? (
|
|
1651
|
+
<Alert variant="destructive" data-ai-agent-loop-policy-load-error>
|
|
1652
|
+
<AlertTitle>
|
|
1653
|
+
{t('ai_assistant.agents.loop_policy.loadErrorTitle', 'Failed to load loop policy')}
|
|
1654
|
+
</AlertTitle>
|
|
1655
|
+
<AlertDescription>
|
|
1656
|
+
{query.error instanceof Error ? query.error.message : String(query.error)}
|
|
1657
|
+
</AlertDescription>
|
|
1658
|
+
</Alert>
|
|
1659
|
+
) : (
|
|
1660
|
+
<>
|
|
1661
|
+
<div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2">
|
|
1662
|
+
<div>
|
|
1663
|
+
<span className="text-sm font-medium">
|
|
1664
|
+
{t('ai_assistant.agents.loop_policy.killSwitchLabel', 'Kill switch')}
|
|
1665
|
+
</span>
|
|
1666
|
+
<p className="text-xs text-muted-foreground">
|
|
1667
|
+
{t(
|
|
1668
|
+
'ai_assistant.agents.loop_policy.killSwitchDescription',
|
|
1669
|
+
'When enabled, the agent runs as a single model call with no tool loop.',
|
|
1670
|
+
)}
|
|
1671
|
+
</p>
|
|
1672
|
+
</div>
|
|
1673
|
+
<Switch
|
|
1674
|
+
checked={loopDisabled}
|
|
1675
|
+
onCheckedChange={(next: boolean) => setLoopDisabled(next)}
|
|
1676
|
+
aria-label={t('ai_assistant.agents.loop_policy.killSwitchLabel', 'Kill switch')}
|
|
1677
|
+
data-ai-agent-loop-kill-switch
|
|
1678
|
+
/>
|
|
1679
|
+
</div>
|
|
1680
|
+
|
|
1681
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
1682
|
+
<div className="flex flex-col gap-1">
|
|
1683
|
+
<Label htmlFor={`loop-max-steps-${agent.id}`} className="text-xs">
|
|
1684
|
+
{t('ai_assistant.agents.loop_policy.maxStepsLabel', 'Max steps')}
|
|
1685
|
+
</Label>
|
|
1686
|
+
<Input
|
|
1687
|
+
id={`loop-max-steps-${agent.id}`}
|
|
1688
|
+
type="number"
|
|
1689
|
+
min={1}
|
|
1690
|
+
max={1000}
|
|
1691
|
+
value={maxSteps}
|
|
1692
|
+
onChange={(event) => setMaxSteps(event.target.value)}
|
|
1693
|
+
placeholder={t('ai_assistant.agents.loop_policy.noOverridePlaceholder', 'No override')}
|
|
1694
|
+
className="h-8 text-sm"
|
|
1695
|
+
data-ai-agent-loop-max-steps
|
|
1696
|
+
/>
|
|
1697
|
+
</div>
|
|
1698
|
+
<div className="flex flex-col gap-1">
|
|
1699
|
+
<Label htmlFor={`loop-max-tool-calls-${agent.id}`} className="text-xs">
|
|
1700
|
+
{t('ai_assistant.agents.loop_policy.maxToolCallsLabel', 'Max tool calls')}
|
|
1701
|
+
</Label>
|
|
1702
|
+
<Input
|
|
1703
|
+
id={`loop-max-tool-calls-${agent.id}`}
|
|
1704
|
+
type="number"
|
|
1705
|
+
min={1}
|
|
1706
|
+
max={10000}
|
|
1707
|
+
value={maxToolCalls}
|
|
1708
|
+
onChange={(event) => setMaxToolCalls(event.target.value)}
|
|
1709
|
+
placeholder={t('ai_assistant.agents.loop_policy.noOverridePlaceholder', 'No override')}
|
|
1710
|
+
className="h-8 text-sm"
|
|
1711
|
+
data-ai-agent-loop-max-tool-calls
|
|
1712
|
+
/>
|
|
1713
|
+
</div>
|
|
1714
|
+
<div className="flex flex-col gap-1">
|
|
1715
|
+
<Label htmlFor={`loop-max-wall-clock-${agent.id}`} className="text-xs">
|
|
1716
|
+
{t('ai_assistant.agents.loop_policy.maxWallClockMsLabel', 'Max wall-clock (ms)')}
|
|
1717
|
+
</Label>
|
|
1718
|
+
<Input
|
|
1719
|
+
id={`loop-max-wall-clock-${agent.id}`}
|
|
1720
|
+
type="number"
|
|
1721
|
+
min={100}
|
|
1722
|
+
max={3600000}
|
|
1723
|
+
value={maxWallClockMs}
|
|
1724
|
+
onChange={(event) => setMaxWallClockMs(event.target.value)}
|
|
1725
|
+
placeholder={t('ai_assistant.agents.loop_policy.noOverridePlaceholder', 'No override')}
|
|
1726
|
+
className="h-8 text-sm"
|
|
1727
|
+
data-ai-agent-loop-max-wall-clock-ms
|
|
1728
|
+
/>
|
|
1729
|
+
</div>
|
|
1730
|
+
<div className="flex flex-col gap-1">
|
|
1731
|
+
<Label htmlFor={`loop-max-tokens-${agent.id}`} className="text-xs">
|
|
1732
|
+
{t('ai_assistant.agents.loop_policy.maxTokensLabel', 'Max tokens')}
|
|
1733
|
+
</Label>
|
|
1734
|
+
<Input
|
|
1735
|
+
id={`loop-max-tokens-${agent.id}`}
|
|
1736
|
+
type="number"
|
|
1737
|
+
min={1}
|
|
1738
|
+
max={10000000}
|
|
1739
|
+
value={maxTokens}
|
|
1740
|
+
onChange={(event) => setMaxTokens(event.target.value)}
|
|
1741
|
+
placeholder={t('ai_assistant.agents.loop_policy.noOverridePlaceholder', 'No override')}
|
|
1742
|
+
className="h-8 text-sm"
|
|
1743
|
+
data-ai-agent-loop-max-tokens
|
|
1744
|
+
/>
|
|
1745
|
+
</div>
|
|
1746
|
+
</div>
|
|
1747
|
+
|
|
1748
|
+
{state.kind === 'success' ? (
|
|
1749
|
+
<Alert variant="success" data-ai-agent-loop-policy-state="success">
|
|
1750
|
+
<AlertTitle>
|
|
1751
|
+
{t('ai_assistant.agents.loop_policy.savedTitle', 'Loop policy updated')}
|
|
1752
|
+
</AlertTitle>
|
|
1753
|
+
<AlertDescription>{state.message}</AlertDescription>
|
|
1754
|
+
</Alert>
|
|
1755
|
+
) : null}
|
|
1756
|
+
{state.kind === 'error' ? (
|
|
1757
|
+
<Alert variant="destructive" data-ai-agent-loop-policy-state="error">
|
|
1758
|
+
<AlertTitle>
|
|
1759
|
+
{t(
|
|
1760
|
+
'ai_assistant.agents.loop_policy.errorTitle',
|
|
1761
|
+
'Failed to update loop policy',
|
|
1762
|
+
)}
|
|
1763
|
+
</AlertTitle>
|
|
1764
|
+
<AlertDescription>{state.message}</AlertDescription>
|
|
1765
|
+
</Alert>
|
|
1766
|
+
) : null}
|
|
1767
|
+
|
|
1768
|
+
<div className="flex items-center justify-end gap-2">
|
|
1769
|
+
<Button
|
|
1770
|
+
type="button"
|
|
1771
|
+
size="sm"
|
|
1772
|
+
variant="outline"
|
|
1773
|
+
onClick={() => void clear()}
|
|
1774
|
+
disabled={isClearing || isSaving || !currentOverride}
|
|
1775
|
+
data-ai-agent-loop-policy-clear
|
|
1776
|
+
>
|
|
1777
|
+
{isClearing ? (
|
|
1778
|
+
<Loader2 className="size-4 animate-spin" aria-hidden />
|
|
1779
|
+
) : (
|
|
1780
|
+
<Trash2 className="size-4" aria-hidden />
|
|
1781
|
+
)}
|
|
1782
|
+
<span>{t('ai_assistant.agents.loop_policy.clear', 'Clear override')}</span>
|
|
1783
|
+
</Button>
|
|
1784
|
+
<Button
|
|
1785
|
+
type="button"
|
|
1786
|
+
size="sm"
|
|
1787
|
+
onClick={() => void save()}
|
|
1788
|
+
disabled={isSaving || isClearing}
|
|
1789
|
+
data-ai-agent-loop-policy-save
|
|
1790
|
+
>
|
|
1791
|
+
{isSaving ? (
|
|
1792
|
+
<Loader2 className="size-4 animate-spin" aria-hidden />
|
|
1793
|
+
) : (
|
|
1794
|
+
<Save className="size-4" aria-hidden />
|
|
1795
|
+
)}
|
|
1796
|
+
<span>{t('ai_assistant.agents.loop_policy.save', 'Save override')}</span>
|
|
1797
|
+
</Button>
|
|
1798
|
+
</div>
|
|
1799
|
+
</>
|
|
1800
|
+
)}
|
|
1801
|
+
</div>
|
|
1802
|
+
</section>
|
|
1803
|
+
)
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1451
1806
|
function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
1452
1807
|
const t = useT()
|
|
1453
1808
|
const queryClient = useQueryClient()
|
|
@@ -1709,6 +2064,8 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
1709
2064
|
|
|
1710
2065
|
<MutationPolicySection agent={agent} />
|
|
1711
2066
|
|
|
2067
|
+
<LoopPolicySection agent={agent} />
|
|
2068
|
+
|
|
1712
2069
|
<section
|
|
1713
2070
|
className="rounded-lg border border-border bg-background p-4"
|
|
1714
2071
|
data-ai-agent-prompt-editor={agent.id}
|
|
@@ -1741,8 +2098,11 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
1741
2098
|
</Button>
|
|
1742
2099
|
</header>
|
|
1743
2100
|
<div className="mt-3">
|
|
1744
|
-
<Alert
|
|
1745
|
-
|
|
2101
|
+
<Alert
|
|
2102
|
+
variant="info"
|
|
2103
|
+
icon={<Wand2 aria-hidden="true" />}
|
|
2104
|
+
data-ai-agent-prompt-notice
|
|
2105
|
+
>
|
|
1746
2106
|
<AlertTitle>
|
|
1747
2107
|
{t('ai_assistant.agents.override.noticeTitle', 'Prompt overrides are additive')}
|
|
1748
2108
|
</AlertTitle>
|
|
@@ -1756,7 +2116,6 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
1756
2116
|
</div>
|
|
1757
2117
|
{saveState.kind === 'success' ? (
|
|
1758
2118
|
<Alert variant="success" className="mt-3" data-ai-agent-prompt-state="success">
|
|
1759
|
-
<CheckCircle2 className="size-4" aria-hidden />
|
|
1760
2119
|
<AlertTitle>
|
|
1761
2120
|
{saveState.version > 0
|
|
1762
2121
|
? t('ai_assistant.agents.override.savedTitle', 'Prompt override saved')
|
|
@@ -1771,7 +2130,6 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
1771
2130
|
) : null}
|
|
1772
2131
|
{saveState.kind === 'error' ? (
|
|
1773
2132
|
<Alert variant="destructive" className="mt-3" data-ai-agent-prompt-state="error">
|
|
1774
|
-
<AlertCircle className="size-4" aria-hidden />
|
|
1775
2133
|
<AlertTitle>
|
|
1776
2134
|
{t('ai_assistant.agents.override.errorTitle', 'Failed to save prompt override')}
|
|
1777
2135
|
</AlertTitle>
|
|
@@ -1892,7 +2250,6 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
1892
2250
|
/>
|
|
1893
2251
|
) : overrideQuery.isError ? (
|
|
1894
2252
|
<Alert variant="destructive" data-ai-agent-override-history-error>
|
|
1895
|
-
<AlertCircle className="size-4" aria-hidden />
|
|
1896
2253
|
<AlertTitle>
|
|
1897
2254
|
{t(
|
|
1898
2255
|
'ai_assistant.agents.override.history.errorTitle',
|
|
@@ -2002,7 +2359,6 @@ export function AiAgentSettingsPageClient() {
|
|
|
2002
2359
|
if (isError) {
|
|
2003
2360
|
return (
|
|
2004
2361
|
<Alert variant="destructive" data-ai-agent-settings-error>
|
|
2005
|
-
<AlertCircle className="size-4" aria-hidden />
|
|
2006
2362
|
<AlertTitle>
|
|
2007
2363
|
{t('ai_assistant.agents.loadErrorTitle', 'Failed to load AI agents')}
|
|
2008
2364
|
</AlertTitle>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
5
|
-
import { Loader2, Save, Shield, Trash2,
|
|
5
|
+
import { Loader2, Save, Shield, Trash2, ShieldCheck } from 'lucide-react'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
|
|
8
8
|
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
@@ -130,7 +130,6 @@ export function AiTenantAllowlistPageClient(): React.JSX.Element {
|
|
|
130
130
|
<div className="flex max-w-3xl flex-col gap-4">
|
|
131
131
|
{pageHeader}
|
|
132
132
|
<Alert variant="destructive">
|
|
133
|
-
<AlertCircle className="size-4" />
|
|
134
133
|
<AlertTitle>{t('ai_assistant.allowlist.loadError.title', 'Failed to load allowlist')}</AlertTitle>
|
|
135
134
|
<AlertDescription>
|
|
136
135
|
{settingsQuery.error instanceof Error
|
|
@@ -286,7 +285,6 @@ export function AiTenantAllowlistPageClient(): React.JSX.Element {
|
|
|
286
285
|
? null
|
|
287
286
|
: (
|
|
288
287
|
<Alert>
|
|
289
|
-
<Info className="h-4 w-4" />
|
|
290
288
|
<AlertTitle>{t('ai_assistant.allowlist.envBanner.title', 'Env allowlist is in effect')}</AlertTitle>
|
|
291
289
|
<AlertDescription className="space-y-1">
|
|
292
290
|
{envAllowedProviders ? (
|
|
@@ -314,7 +312,6 @@ export function AiTenantAllowlistPageClient(): React.JSX.Element {
|
|
|
314
312
|
|
|
315
313
|
{feedback ? (
|
|
316
314
|
<Alert variant={feedback.kind === 'error' ? 'destructive' : undefined}>
|
|
317
|
-
{feedback.kind === 'error' ? <AlertCircle className="h-4 w-4" /> : <Info className="h-4 w-4" />}
|
|
318
315
|
<AlertDescription>{feedback.text}</AlertDescription>
|
|
319
316
|
</Alert>
|
|
320
317
|
) : null}
|
package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useQuery } from '@tanstack/react-query'
|
|
5
|
-
import {
|
|
5
|
+
import { Bot, BookOpen, Loader2, Play, RefreshCcw } from 'lucide-react'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
|
|
8
8
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
@@ -13,7 +13,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@open-mercato/ui/primi
|
|
|
13
13
|
import { Textarea } from '@open-mercato/ui/primitives/textarea'
|
|
14
14
|
import { EmptyState } from '@open-mercato/ui/backend/EmptyState'
|
|
15
15
|
import { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
16
|
-
import { AiChat, createAiUiPartRegistry, useAiShortcuts } from '@open-mercato/ui/ai'
|
|
16
|
+
import { AiChat, createAiUiPartRegistry, LoopDisabledBanner, useAiShortcuts } from '@open-mercato/ui/ai'
|
|
17
17
|
import type { AiChatDebugPromptSection, AiChatDebugTool } from '@open-mercato/ui/ai'
|
|
18
18
|
|
|
19
19
|
type PlaygroundAgentTool = {
|
|
@@ -110,10 +110,7 @@ function PlaygroundNoAgents() {
|
|
|
110
110
|
function AgentDetails({ agent }: { agent: PlaygroundAgent }) {
|
|
111
111
|
const t = useT()
|
|
112
112
|
return (
|
|
113
|
-
<div
|
|
114
|
-
className="rounded-md border border-border bg-muted/30 p-3 text-sm"
|
|
115
|
-
data-ai-playground-agent={agent.id}
|
|
116
|
-
>
|
|
113
|
+
<div className="rounded-md border border-border bg-muted/30 p-3 text-sm">
|
|
117
114
|
<div className="font-semibold">{agent.label}</div>
|
|
118
115
|
<p className="mt-1 text-xs text-muted-foreground">{agent.description}</p>
|
|
119
116
|
<dl className="mt-3 grid grid-cols-2 gap-2 text-xs">
|
|
@@ -196,6 +193,27 @@ async function fetchAgentResolutions(): Promise<SettingsAgentResolutionResponse>
|
|
|
196
193
|
return { agents: result.result.agents ?? [] }
|
|
197
194
|
}
|
|
198
195
|
|
|
196
|
+
async function fetchLoopOverrideForAgent(
|
|
197
|
+
agentId: string,
|
|
198
|
+
): Promise<{ agentId: string; override: { loopDisabled?: boolean | null } | null }> {
|
|
199
|
+
const result = await apiCall<{ agentId: string; override: { loopDisabled?: boolean | null } | null }>(
|
|
200
|
+
`/api/ai_assistant/ai/agents/${encodeURIComponent(agentId)}/loop-override`,
|
|
201
|
+
{ method: 'GET', credentials: 'include' },
|
|
202
|
+
)
|
|
203
|
+
if (!result.ok || !result.result) return { agentId, override: null }
|
|
204
|
+
return result.result
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function LoopDisabledPlaygroundBanner({ agentId }: { agentId: string }) {
|
|
208
|
+
const { data } = useQuery({
|
|
209
|
+
queryKey: ['ai_assistant', 'loop_override', agentId],
|
|
210
|
+
queryFn: () => fetchLoopOverrideForAgent(agentId),
|
|
211
|
+
staleTime: 30000,
|
|
212
|
+
})
|
|
213
|
+
if (!data?.override?.loopDisabled) return null
|
|
214
|
+
return <LoopDisabledBanner agentId={agentId} />
|
|
215
|
+
}
|
|
216
|
+
|
|
199
217
|
function ModelResolutionPanel({ agentId }: { agentId: string }) {
|
|
200
218
|
const t = useT()
|
|
201
219
|
const { data } = useQuery({
|
|
@@ -211,7 +229,6 @@ function ModelResolutionPanel({ agentId }: { agentId: string }) {
|
|
|
211
229
|
<dl
|
|
212
230
|
className="grid grid-cols-2 gap-x-4 gap-y-1 rounded-md border border-border bg-muted/30 p-3 text-xs sm:grid-cols-4"
|
|
213
231
|
data-ai-playground-model-resolution={agentId}
|
|
214
|
-
data-ai-playground-resolution-panel={agentId}
|
|
215
232
|
>
|
|
216
233
|
<div>
|
|
217
234
|
<dt className="font-medium text-muted-foreground">
|
|
@@ -317,7 +334,7 @@ function ChatLane({ agent, debug }: { agent: PlaygroundAgent; debug: boolean })
|
|
|
317
334
|
}
|
|
318
335
|
|
|
319
336
|
return (
|
|
320
|
-
<div data-ai-playground-chat={agent.id}>
|
|
337
|
+
<div className="flex flex-col gap-2" data-ai-playground-chat={agent.id}>
|
|
321
338
|
<AiChat
|
|
322
339
|
key={agent.id}
|
|
323
340
|
agent={agent.id}
|
|
@@ -562,7 +579,6 @@ export function AiPlaygroundPageClient() {
|
|
|
562
579
|
if (isError) {
|
|
563
580
|
return (
|
|
564
581
|
<Alert variant="destructive" data-ai-playground-error>
|
|
565
|
-
<AlertCircle className="size-4" aria-hidden />
|
|
566
582
|
<AlertTitle>
|
|
567
583
|
{t('ai_assistant.playground.loadErrorTitle', 'Failed to load AI agents')}
|
|
568
584
|
</AlertTitle>
|
|
@@ -651,6 +667,7 @@ export function AiPlaygroundPageClient() {
|
|
|
651
667
|
</div>
|
|
652
668
|
{selectedAgent ? <AgentDetails agent={selectedAgent} /> : null}
|
|
653
669
|
{selectedAgent ? <ModelResolutionPanel agentId={selectedAgent.id} /> : null}
|
|
670
|
+
{selectedAgent ? <LoopDisabledPlaygroundBanner agentId={selectedAgent.id} /> : null}
|
|
654
671
|
</section>
|
|
655
672
|
|
|
656
673
|
{selectedAgent ? (
|