Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions apps/electron/src/renderer/atoms/agent-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,15 +515,6 @@ export const agentStartedAtAtom = atom<number | undefined>((get) => {
return get(agentStreamingStatesAtom).get(currentId)?.startedAt
})

export const agentRunningSessionIdsAtom = atom<Set<string>>((get) => {
const states = get(agentStreamingStatesAtom)
const ids = new Set<string>()
for (const [id, state] of states) {
if (state.running) ids.add(id)
}
return ids
})

/** 侧边栏会话指示点状态 */
export type SessionIndicatorStatus = 'idle' | 'running' | 'blocked' | 'completed'

Expand Down Expand Up @@ -555,6 +546,10 @@ export const dockBadgeCountAtom = atom<number>((get) => {
/**
* 每个会话的指示点状态(只包含非 idle 的会话)
* 优先级:blocked > running > completed > idle
*
* backgroundWaiting 状态(后台任务运行中、主循环已空闲)也映射为 'running',
* 与真运行态同态显示蓝色脉冲——侧边栏与 Tab 不区分两者;
* AgentView 输入区通过独立的 backgroundWaiting 徽章做差异化提示。
*/
export const agentSessionIndicatorMapAtom = atom<Map<string, SessionIndicatorStatus>>((get) => {
const streamStates = get(agentStreamingStatesAtom)
Expand All @@ -566,7 +561,7 @@ export const agentSessionIndicatorMapAtom = atom<Map<string, SessionIndicatorSta
const map = new Map<string, SessionIndicatorStatus>()

for (const [id, state] of streamStates) {
if (!state.running) continue
if (!state.running && !state.backgroundWaiting) continue
const hasBlock = (pendingPerms.get(id)?.length ?? 0) > 0
|| (pendingAskUser.get(id)?.length ?? 0) > 0
|| (pendingExitPlan.get(id)?.length ?? 0) > 0
Expand Down
6 changes: 3 additions & 3 deletions apps/electron/src/renderer/atoms/tab-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
streamingConversationIdsAtom,
} from './chat-atoms'
import {
agentRunningSessionIdsAtom,
agentSessionIndicatorMapAtom,
unviewedCompletedSessionIdsAtom,
} from './agent-atoms'
Expand Down Expand Up @@ -141,14 +140,15 @@ export const activeSessionIdAtom = atom<string | null>((get) => {
export const tabStreamingMapAtom = atom<Map<string, boolean>>((get) => {
const tabs = get(tabsAtom)
const chatStreaming = get(streamingConversationIdsAtom)
const agentRunning = get(agentRunningSessionIdsAtom)
const agentIndicator = get(agentSessionIndicatorMapAtom)
const map = new Map<string, boolean>()
for (const tab of tabs) {
if (tab.type === 'scratch') continue
if (tab.type === 'chat') {
map.set(tab.id, chatStreaming.has(tab.sessionId))
} else if (tab.type === 'agent') {
map.set(tab.id, agentRunning.has(tab.sessionId))
const status = agentIndicator.get(tab.sessionId)
map.set(tab.id, status === 'running' || status === 'blocked')
}
}
return map
Expand Down
17 changes: 16 additions & 1 deletion apps/electron/src/renderer/components/agent/AgentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import * as React from 'react'
import { unstable_batchedUpdates } from 'react-dom'
import { useAtom, useAtomValue, useSetAtom, useStore } from 'jotai'
import { toast } from 'sonner'
import { Bot, CornerDownLeft, Square, Settings, Paperclip, FolderPlus, X, Copy, Check, Brain, Sparkles, Eye } from 'lucide-react'
import { Bot, CornerDownLeft, Square, Settings, Paperclip, FolderPlus, X, Copy, Check, Brain, Sparkles, Eye, Loader2 } from 'lucide-react'
import { AgentMessages } from './AgentMessages'
import { AgentHeader } from './AgentHeader'
import { ContextUsageBadge } from './ContextUsageBadge'
Expand Down Expand Up @@ -2051,6 +2051,9 @@ export function AgentView({ sessionId }: { sessionId: string }): React.ReactElem
setProcessGroupsKeepExpanded,
])

// 后台任务运行中且无输入文本时显示状态标签;用户开始输入后让出位置给 Send 按钮,
// 避免用户在后台任务运行期间无法发送新消息(backgroundWaiting 状态支持注入式发送)
const showBackgroundWaitingBadge = backgroundWaiting && !hasTextInput
const inputTrailingNode = streaming && !hasTextInput ? (
<Tooltip>
<TooltipTrigger asChild>
Expand All @@ -2068,6 +2071,18 @@ export function AgentView({ sessionId }: { sessionId: string }): React.ReactElem
<p>停止 Agent ({getAcceleratorDisplay(getActiveAccelerator('stop-generation'))})</p>
</TooltipContent>
</Tooltip>
) : showBackgroundWaitingBadge ? (
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1.5 px-2 h-[36px] rounded-full bg-blue-500/10 text-blue-500/80">
<Loader2 className="size-4 animate-spin" />
<span className="text-xs font-medium whitespace-nowrap">后台任务运行中</span>
</div>
</TooltipTrigger>
<TooltipContent side="top">
<p>Agent 正在后台执行任务,您可以继续输入消息</p>
</TooltipContent>
</Tooltip>
) : (
<Button
type="button"
Expand Down