feat(desktop): add cancel-summon button in SummoningModal

Add Cancel button in SummoningModal after 60s threshold. Call POST
/v1/agents/{id}/cancel-summon via getApiClient(). Update agent status
to summon_failed. Add i18n strings for en/vi/zh in desktop locale.
This commit is contained in:
viettranx committed 2026-04-17 19:02:53 +07:00
1 parent 2a7a8f9a84
commit f2325f6d98
8 files changed
+56 -6

No files matched your search

@@ -11,7 +11,7 @@ import type { AgentData, AgentInput } from '../../types/agent'
export function AgentList() {
const { t } = useTranslation(['agents', 'common'])
const { agents, loading, atLimit, createAgent, updateAgent, deleteAgent, resummonAgent, fetchAgents } = useAgentCrud()
const { agents, loading, atLimit, createAgent, updateAgent, deleteAgent, resummonAgent, cancelSummonAgent, fetchAgents } = useAgentCrud()
const setStoreAgents = useAgentStore((s) => s.setAgents)
const [formOpen, setFormOpen] = useState(false)
@@ -140,6 +140,7 @@ export function AgentList() {
agentId={summoningAgent.id}
agentName={summoningAgent.name}
onContinue={handleSummoningComplete}
onCancel={cancelSummonAgent}
/>
)}
</>
@@ -1,6 +1,7 @@
import { useState, useMemo, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { getApiClient } from '../../lib/api'
import { agentService } from '../../services/agent-service'
import { SummoningModal } from './SummoningModal'
import type { ProviderData } from '../../types/provider'
@@ -109,6 +110,7 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp
agentId={createdAgent.id}
agentName={createdAgent.name}
onContinue={onComplete}
onCancel={(id) => agentService.cancelSummon(id)}
/>
)
}
@@ -11,14 +11,18 @@ interface SummoningModalProps {
agentId: string
agentName: string
onContinue: () => void
onCancel?: (agentId: string) => Promise<void>
}
export function SummoningModal({ agentId, agentName, onContinue }: SummoningModalProps) {
const CANCEL_THRESHOLD_SEC = 60
export function SummoningModal({ agentId, agentName, onContinue, onCancel }: SummoningModalProps) {
const { t } = useTranslation(['desktop', 'common'])
const [generatedFiles, setGeneratedFiles] = useState<string[]>([])
const [status, setStatus] = useState<'summoning' | 'completed' | 'failed'>('summoning')
const [errorMsg, setErrorMsg] = useState('')
const [retrying, setRetrying] = useState(false)
const [cancelling, setCancelling] = useState(false)
const [elapsed, setElapsed] = useState(0)
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
@@ -84,6 +88,19 @@ export function SummoningModal({ agentId, agentName, onContinue }: SummoningModa
}
}
const handleCancel = async () => {
if (!onCancel) return
setCancelling(true)
try {
await onCancel(agentId)
// BE emits WS type=failed → listener above switches to failed state
} catch {
// keep modal open
} finally {
setCancelling(false)
}
}
return (
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="bg-surface-secondary border border-border rounded-2xl shadow-xl max-w-md w-full mx-4">
@@ -164,6 +181,19 @@ export function SummoningModal({ agentId, agentName, onContinue }: SummoningModa
</p>
)}
{status === 'summoning' && elapsed >= CANCEL_THRESHOLD_SEC && onCancel && (
<div className="flex flex-col items-center gap-1">
<p className="text-xs text-text-muted">{t('desktop:summoning.takingTooLong')}</p>
<button
onClick={handleCancel}
disabled={cancelling}
className="px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-tertiary rounded-md transition-colors disabled:opacity-50"
>
{cancelling ? t('desktop:summoning.cancelling') : t('desktop:summoning.cancel')}
</button>
</div>
)}
{status === 'completed' && (
<button
onClick={onContinue}
@@ -63,7 +63,11 @@ export function useAgentCrud() {
setAgents((prev) => prev.map((a) => a.id === id ? { ...a, status: 'summoning' } : a))
}, [])
const cancelSummonAgent = useCallback(async (id: string) => {
await agentService.cancelSummon(id)
}, [])
const atLimit = agents.length >= MAX_AGENTS_LITE
return { agents, loading, atLimit, fetchAgents, createAgent, updateAgent, deleteAgent, resummonAgent }
return { agents, loading, atLimit, fetchAgents, createAgent, updateAgent, deleteAgent, resummonAgent, cancelSummonAgent }
}
@@ -26,7 +26,10 @@
"fileLabelSOUL": "Core personality",
"fileLabelIDENTITY": "Identity & knowledge",
"agentReady": "{{name}} is ready!",
"retrying": "Retrying..."
"retrying": "Retrying...",
"takingTooLong": "Taking too long?",
"cancel": "Cancel",
"cancelling": "Cancelling..."
},
"settings": {
"title": "Settings",
@@ -26,7 +26,10 @@
"fileLabelSOUL": "Tính cách cốt lõi",
"fileLabelIDENTITY": "Danh tính & kiến thức",
"agentReady": "{{name}} đã sẵn sàng!",
"retrying": "Đang thử lại..."
"retrying": "Đang thử lại...",
"takingTooLong": "Mất quá nhiều thời gian?",
"cancel": "Huỷ",
"cancelling": "Đang huỷ..."
},
"settings": {
"title": "Settings",
@@ -26,7 +26,10 @@
"fileLabelSOUL": "核心个性",
"fileLabelIDENTITY": "身份与知识",
"agentReady": "{{name}} 已就绪!",
"retrying": "重试中..."
"retrying": "重试中...",
"takingTooLong": "耗时过长?",
"cancel": "取消",
"cancelling": "正在取消..."
},
"settings": {
"title": "Settings",
@@ -38,6 +38,10 @@ export const agentService = {
return getApiClient().post<void>(`/v1/agents/${id}/resummon`, {})
},
cancelSummon(id: string): Promise<void> {
return getApiClient().post<void>(`/v1/agents/${id}/cancel-summon`, {})
},
resummonWs(agentId: string): Promise<unknown> {
return getWsClient().call('agents.resummon', { agentId })
},