diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 3f553f8..01cfefe 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -260,7 +260,7 @@ export function App(){ // The bot is parked in waiting_takeover: nothing moves (including queued // messages) until the human releases the screen, so say so loudly. const desktopInteractive=computer.screenAvailable&&!viewOnlyFor(computer.controlHolder,computer.sharedInput); - const pausedForUser=computer.takeoverRequested&&workingMembers.length===0&&(!computer.waitingSessionId||computer.waitingSessionId===activeSessionId||Boolean(activeRoom)); + const pausedForUser=computer.takeoverRequested&&(!computer.waitingSessionId||computer.waitingSessionId===activeSessionId||Boolean(activeRoom)); // Only the newest assistant reply can still be "continued": an older pause // belongs to a run that has already moved on. const lastAssistantSeq=messages.reduce((max,message)=>message.role==="assistant"?Math.max(max,message.seq??0):max,0); @@ -423,8 +423,9 @@ export function App(){ if(!beforeTakeover.current)beforeTakeover.current={part:rightPart,collapsed:rightCollapsed,open:computerOpen}; setRightPart("computer");setRightCollapsed(false);setComputerOpen(true); await action(async()=>{try{await api(`/api/computer/${id}/boot`,{method:"POST",body:"{}"})}catch{/* already up */}}); - // Opening the shared screen never pauses a running task. - if(!computer.sharedInput)await setControl("user",id); + // Pause the bot so the human can use the screen without fighting it, + // including on shared CUA desktops that previously hid this control. + await setControl("user",id); } async function reloadSchedules(){if(!paneBotId)return;setSchedules(await api(`/api/bots/${paneBotId}/schedules`))} async function saveScheduleDraft(){ @@ -738,9 +739,9 @@ export function App(){
{activeRoom?<>member.id)}/>{activeRoom.name}event.stopPropagation()}>{hostMenuOpen&&
{t("hostMenuLabel")}{activeRoom.members.map(member=>)}
}
{setSessionMenuOpen(false);setSessionToDelete(id)}} onClear={()=>{setSessionMenuOpen(false);setClearOpen(true)}}/>{topTools}:active?<>{active.name}{setSessionMenuOpen(false);setSessionToDelete(id)}} onClear={()=>{setSessionMenuOpen(false);setClearOpen(true)}}/>{topTools}:<>{t("chooseBot")}{topTools}}
- {callOpen&&voiceSettings?.enabled&&active&&activeSessionId&&!activeRoomId?setCallOpen(false)} onTakeOver={()=>{setRightPart("computer");setRightCollapsed(false);setComputerOpen(true);if(active&&!computer.sharedInput)void setControl("user",active.id)}}/>:null} + {callOpen&&voiceSettings?.enabled&&active&&activeSessionId&&!activeRoomId?setCallOpen(false)} onTakeOver={()=>void openLoginScreen()}/>:null} void openLoginScreen()}/> -
{(activeRoom||active)&&messages.length===0?
{activeRoom?:}

{activeRoom?t("startRoomDiscussion",{name:activeRoom.name}):t("startBotWork",{name:active!.name})}

{activeRoom?t("roomWillReply",{names:listJoin(activeRoom.members.map(member=>member.name))}):active!.description||t("botWelcome")}

:messages.map((message,index)=>{const spoken=message.role!=="user"&&Boolean(activeRoom);const speakerName=message.speakerName||(spoken?paneBot?.name:undefined);const speakerShape=(message.speakerShape||paneBot?.avatarShape||"blob") as AvatarShape;const files=messageFiles(message.blocks);const chips=chipBlocks(message.blocks);const hideBody=isAutoAttachCaption(message.body,files)||isAutoScheduleCaption(message.body,chips);const quoted=replyQuote(message.blocks);const quoteChip=quoted?{quoted.name||t("you")}:null;const previous=index>0?messages[index-1]:null;const newDay=!previous||!sameDay(previous.createdAt,message.createdAt);return {newDay&&}
{spoken&&}{spoken&&{speakerName}}{files.length>0&&
{files.map(file=>setPreviewFile({botId:file.botId||message.speakerBotId||paneBot.id,path:file.path!,name:file.name}):undefined}/>)}
}{chips.map((chip,index)=>chip.kind==="error"?
{errorTitle(chip.code)}
{errorActions(chip.code).map(next=>next==="retry"?:next==="screen"?:)}
:chip.kind==="resume"?
{resumeTitle(chip.reason)}
{(chip.limit||0)>0?
{t("resumeProgress",{turns:chip.turns||0,limit:chip.limit||0})}
:null}{resumedChips[message.id]||(message.seq??0)
}
:chip.kind==="login"?
{t(chip.intervention==="verification"?"verificationNeedsYou":chip.intervention==="authorization"?"authorizationNeedsYou":"loginNeedsYou")}
{chip.reason&&
{chip.reason}
}
{chip.site||message.body}
{chip.why?
{t("loginWhy",{why:chip.why})}
:null}
:chip.kind==="schedule"?
{t("scheduleChip")}
{chip.name}{scheduleWhen(chip.cron,chip.human)}
:
{t("scheduleRunChip")}
{chip.name}{scheduleWhen(chip.cron,chip.human)}
)}{!hideBody&&(message.role==="assistant"?
{quoteChip}{shownReplyText(message,replyDrafts)}
:{quoteChip})}
})}{pausedForUser&&paneBot&&
{computer.sharedInput?t("sharedNeedsUser",{name:paneBot.name}):computer.controlHolder==="user"?t("pausedUserControl",{name:paneBot.name}):t("pausedNeedsUser",{name:paneBot.name})}{(computer.queuedRuns||0)>0&&<> {t("queuedMessages",{count:computer.queuedRuns||0})}}{computer.sharedInput||computer.controlHolder==="user"?:}
}{teaching&&active&&
{t("teachingLive",{goal:teaching.goal})}{t("teachingHint")}{teaching.eventCount>0&&<> · {t("teachingCaptured",{count:teaching.eventCount})}}
}{drafting&&
{t("distilling",{goal:drafting.goal})}
}{skillDraft&&active&&void saveSkill(skillDraft,name,playbook)} onTest={(name,playbook)=>void testSkill(skillDraft,name,playbook)} onDiscard={()=>void discardSkill(skillDraft)} onEdit={()=>setEditingSkillId(skillDraft.id)} onExport={(name,playbook)=>downloadSkill(name,skillDraft.goal,playbook)}/>}{visibleReplyDrafts(replyDrafts,messages).map(reply=>{const speaker=bots.find(bot=>bot.id===reply.botId);return
{activeRoom&&speaker&&<>{speaker.name}}
{reply.text}
})} +
{(activeRoom||active)&&messages.length===0?
{activeRoom?:}

{activeRoom?t("startRoomDiscussion",{name:activeRoom.name}):t("startBotWork",{name:active!.name})}

{activeRoom?t("roomWillReply",{names:listJoin(activeRoom.members.map(member=>member.name))}):active!.description||t("botWelcome")}

:messages.map((message,index)=>{const spoken=message.role!=="user"&&Boolean(activeRoom);const speakerName=message.speakerName||(spoken?paneBot?.name:undefined);const speakerShape=(message.speakerShape||paneBot?.avatarShape||"blob") as AvatarShape;const files=messageFiles(message.blocks);const chips=chipBlocks(message.blocks);const hideBody=isAutoAttachCaption(message.body,files)||isAutoScheduleCaption(message.body,chips);const quoted=replyQuote(message.blocks);const quoteChip=quoted?{quoted.name||t("you")}:null;const previous=index>0?messages[index-1]:null;const newDay=!previous||!sameDay(previous.createdAt,message.createdAt);return {newDay&&}
{spoken&&}{spoken&&{speakerName}}{files.length>0&&
{files.map(file=>setPreviewFile({botId:file.botId||message.speakerBotId||paneBot.id,path:file.path!,name:file.name}):undefined}/>)}
}{chips.map((chip,index)=>chip.kind==="error"?
{errorTitle(chip.code)}
{errorActions(chip.code).map(next=>next==="retry"?:next==="screen"?:)}
:chip.kind==="resume"?
{resumeTitle(chip.reason)}
{(chip.limit||0)>0?
{t("resumeProgress",{turns:chip.turns||0,limit:chip.limit||0})}
:null}{resumedChips[message.id]||(message.seq??0)
}
:chip.kind==="login"?
{t(chip.intervention==="verification"?"verificationNeedsYou":chip.intervention==="authorization"?"authorizationNeedsYou":"loginNeedsYou")}
{chip.reason&&
{chip.reason}
}
{chip.site||message.body}
{chip.why?
{t("loginWhy",{why:chip.why})}
:null}
:chip.kind==="schedule"?
{t("scheduleChip")}
{chip.name}{scheduleWhen(chip.cron,chip.human)}
:
{t("scheduleRunChip")}
{chip.name}{scheduleWhen(chip.cron,chip.human)}
)}{!hideBody&&(message.role==="assistant"?
{quoteChip}{shownReplyText(message,replyDrafts)}
:{quoteChip})}
})}{pausedForUser&&paneBot&&
{computer.sharedInput?t("sharedNeedsUser",{name:paneBot.name}):computer.controlHolder==="user"?t("pausedUserControl",{name:paneBot.name}):t("pausedNeedsUser",{name:paneBot.name})}{(computer.queuedRuns||0)>0&&<> {t("queuedMessages",{count:computer.queuedRuns||0})}}
{(computer.sharedInput||computer.controlHolder==="user")&&}
}{teaching&&active&&
{t("teachingLive",{goal:teaching.goal})}{t("teachingHint")}{teaching.eventCount>0&&<> · {t("teachingCaptured",{count:teaching.eventCount})}}
}{drafting&&
{t("distilling",{goal:drafting.goal})}
}{skillDraft&&active&&void saveSkill(skillDraft,name,playbook)} onTest={(name,playbook)=>void testSkill(skillDraft,name,playbook)} onDiscard={()=>void discardSkill(skillDraft)} onEdit={()=>setEditingSkillId(skillDraft.id)} onExport={(name,playbook)=>downloadSkill(name,skillDraft.goal,playbook)}/>}{visibleReplyDrafts(replyDrafts,messages).map(reply=>{const speaker=bots.find(bot=>bot.id===reply.botId);return
{activeRoom&&speaker&&<>{speaker.name}}
{reply.text}
})} {Object.values(replyDrafts).filter(item=>item.status&&!item.messageId).map(item=>
{item.status}
)}
{error&&
{error}
} @@ -761,7 +762,7 @@ export function App(){
{paneBot?{t("botComputer",{name:paneBot.name})}:{t("computer")}}{computer.state==="booting"?:}{stateLabel(computer.state)}
{computerOpen?:frame}{!computerOpen&&hud}
- {paneBot&&<>
{t("dedicatedScreen")}{meetingMode?null:}
{teaching?
:void setControl("user")} onRelease={()=>void setControl("none")}/>}

{t("computerLoginHint")}

{clipboardStatus}

{paneBot&&setPreviewFile({botId:paneBot.id,path,name})}/>}{scheduleDraft?setScheduleDraft(current=>current?{...current,...next}:next)} onBack={()=>setScheduleDraft(null)} onSave={()=>void saveScheduleDraft()} onDelete={scheduleDraft.id?()=>void action(async()=>{await api(`/api/schedules/${scheduleDraft.id}`,{method:"DELETE"});setScheduleDraft(null);await reloadSchedules()}):undefined}/>:setScheduleDraft({name:"",instructions:"",enabled:true,preset:defaultCronPreset()})} onOpen={item=>setScheduleDraft({id:item.id,timezone:item.timezone,threadId:item.threadId,name:item.name,instructions:item.instructions,enabled:item.enabled,preset:presetFromCron(item.cron)})} onRun={item=>void action(async()=>{setRunningScheduleId(item.id);try{await api(`/api/schedules/${item.id}/run`,{method:"POST",body:"{}"});await loadSessions()}finally{setRunningScheduleId(null)}})}/>}
} + {paneBot&&<>
{t("dedicatedScreen")}{meetingMode?null:}
{teaching?
:void openLoginScreen()} onRelease={()=>void setControl("none")}/>}

{t("computerLoginHint")}

{clipboardStatus}

{paneBot&&setPreviewFile({botId:paneBot.id,path,name})}/>}{scheduleDraft?setScheduleDraft(current=>current?{...current,...next}:next)} onBack={()=>setScheduleDraft(null)} onSave={()=>void saveScheduleDraft()} onDelete={scheduleDraft.id?()=>void action(async()=>{await api(`/api/schedules/${scheduleDraft.id}`,{method:"DELETE"});setScheduleDraft(null);await reloadSchedules()}):undefined}/>:setScheduleDraft({name:"",instructions:"",enabled:true,preset:defaultCronPreset()})} onOpen={item=>setScheduleDraft({id:item.id,timezone:item.timezone,threadId:item.threadId,name:item.name,instructions:item.instructions,enabled:item.enabled,preset:presetFromCron(item.cron)})} onRun={item=>void action(async()=>{setRunningScheduleId(item.id);try{await api(`/api/schedules/${item.id}/run`,{method:"POST",body:"{}"});await loadSessions()}finally{setRunningScheduleId(null)}})}/>}
}
@@ -786,7 +787,7 @@ export function App(){ } - {computerOpen&&paneBot&&createPortal(
{modeLabel(computer.mode)}{teaching?t("teachingBadge"):computer.sharedInput?t("sharedDesktop"):computer.controlHolder==="user"?t("userControlling"):computer.usingComputer?t("aiReadOnly"):t("readOnly")}
{teaching?
:void setControl("user")} onRelease={()=>void setControl("none")}/>}
{frame}{hud}
{error&&
{error}
}
,document.body)} + {computerOpen&&paneBot&&createPortal(
{modeLabel(computer.mode)}{teaching?t("teachingBadge"):computer.sharedInput?t("sharedDesktop"):computer.controlHolder==="user"?t("userControlling"):computer.usingComputer?t("aiReadOnly"):t("readOnly")}
{teaching?
:void openLoginScreen()} onRelease={()=>void setControl("none")}/>}
{frame}{hud}
{error&&
{error}
}
,document.body)} {teachOpen&&active&&setTeachOpen(false)} start={goal=>void startTeaching(goal)}/>} {editingSkill&&setEditingSkillId(null)} save={(name,playbook)=>void updateSkill(editingSkill,name,playbook)} test={(name,playbook)=>void testSkill(editingSkill,name,playbook)} remove={()=>void deleteSkill(editingSkill)} exportFile={(name,playbook)=>downloadSkill(name,editingSkill.goal,playbook)}/>} {clipboardOpen&&setClipboardOpen(false)} paste={text=>{pasteText(text);setClipboardOpen(false)}}/>} @@ -1174,9 +1175,8 @@ function ControlButtons({computer,busy,pending,action,active,sessionId,onBoot,on const run=(operation:"boot"|"restart"|"stop",handler?:()=>Promise)=>{setMenuOpen(false);void action(handler||(()=>api(`/api/computer/${active.id}/${operation}`,{method:"POST",body:"{}"})))}; return
{!running? - :computer.sharedInput?(computer.takeoverRequested||computer.controlHolder==="user"?:null) - :computer.controlHolder==="user"? - :} + :computer.takeoverRequested||computer.controlHolder==="user"? + :} {running&&working&&} {computer.state!=="stopped"&&
diff --git a/apps/web/src/call.tsx b/apps/web/src/call.tsx index 78a0e6a..eaa09e2 100644 --- a/apps/web/src/call.tsx +++ b/apps/web/src/call.tsx @@ -175,7 +175,7 @@ export function CallOverlay({
{showTakeover ? ( - + ) : null}

{t("callShortcuts")}

diff --git a/apps/web/src/computer.css b/apps/web/src/computer.css index 276f0f4..f9e48da 100644 --- a/apps/web/src/computer.css +++ b/apps/web/src/computer.css @@ -10,8 +10,8 @@ .computer-caption{display:flex;align-items:center;justify-content:space-between;padding:14px 0;color:var(--muted)} .control-bar{display:flex;align-items:center;justify-content:flex-end;gap:8px;border-top:1px solid var(--hairline);padding:16px 0} .computer-overlay{position:fixed;inset:0;z-index:50;display:flex;flex-direction:column;background:rgba(4,4,5,.98)} -.computer-overlay>header{height:64px;display:flex;align-items:center;justify-content:space-between;padding:0 18px;border-bottom:1px solid var(--line)} -.computer-overlay>header>div{display:flex;align-items:center;gap:10px} +.computer-overlay>header{min-height:64px;height:auto;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;padding:8px 18px;border-bottom:1px solid var(--line);overflow:visible} +.computer-overlay>header>div{display:flex;align-items:center;flex-wrap:wrap;gap:10px;min-width:0} .computer-overlay .avatar{width:30px;height:30px} .control-badge{padding:5px 10px;border-radius:999px;background:rgba(48,162,75,.14);color:var(--success);font-size:13px} .overlay-screen{flex:1;min-height:0;padding:20px;display:flex;justify-content:center} diff --git a/apps/web/src/locales/en.ts b/apps/web/src/locales/en.ts index a2fbca1..6507ace 100644 --- a/apps/web/src/locales/en.ts +++ b/apps/web/src/locales/en.ts @@ -29,7 +29,7 @@ export const en: { [K in keyof typeof zhTW]: string } = { results: "Results", advancedSettings: "Advanced settings", sharedDesktop: "Shared control", - sharedNeedsUser: "{name} is waiting for you to finish the steps on screen. Then press “Done, continue”.", + sharedNeedsUser: "{name} is waiting for you to finish the steps on screen. Press “Take over” to open it, then “Done, continue”.", doneContinue: "Done, continue", search: "Search", sharedComputer: "Shared computer", diff --git a/apps/web/src/locales/zh-TW.ts b/apps/web/src/locales/zh-TW.ts index f68d16e..4d2105f 100644 --- a/apps/web/src/locales/zh-TW.ts +++ b/apps/web/src/locales/zh-TW.ts @@ -27,7 +27,7 @@ export const zhTW = { results: "成果", advancedSettings: "進階設定", sharedDesktop: "共同操作", - sharedNeedsUser: "{name} 已暫停等你完成畫面上的步驟。完成後按「完成,繼續」。", + sharedNeedsUser: "{name} 已暫停等你完成畫面上的步驟。按「接手操作」打開畫面,完成後按「完成,繼續」。", doneContinue: "完成,繼續", search: "搜尋", sharedComputer: "共用電腦", privateComputer: "私人電腦", stopped: "已關閉", booting: "啟動中", suspended: "休眠中", error: "發生錯誤", diff --git a/apps/web/src/refinements.css b/apps/web/src/refinements.css index 0efa33c..ee6605b 100644 --- a/apps/web/src/refinements.css +++ b/apps/web/src/refinements.css @@ -468,7 +468,8 @@ .error-banner{z-index:8;bottom:118px} -.pause-banner{display:flex;align-items:center;gap:14px;width:min(760px,100%);margin:4px auto 18px;padding:12px 14px;border:1px solid var(--warn-border);border-radius:12px;background:var(--warn-bg);color:var(--warn-fg);font-size:13px;line-height:1.5} +.pause-banner{display:flex;align-items:center;flex-wrap:wrap;gap:14px;width:min(760px,100%);margin:4px auto 18px;padding:12px 14px;border:1px solid var(--warn-border);border-radius:12px;background:var(--warn-bg);color:var(--warn-fg);font-size:13px;line-height:1.5} +.pause-banner-actions{display:flex;flex-wrap:wrap;gap:8px;flex:0 0 auto} .pause-banner span{flex:1} diff --git a/apps/web/src/schedule.css b/apps/web/src/schedule.css index 6f4309b..e9b91a3 100644 --- a/apps/web/src/schedule.css +++ b/apps/web/src/schedule.css @@ -34,5 +34,6 @@ .sched-when>span{display:flex;flex-wrap:wrap;gap:6px;overflow-wrap:anywhere} .sched-editor .dialog-actions{flex-wrap:wrap} .login-chip,.sched-chip{width:100%;overflow-wrap:anywhere} -.message:has(>.sched-chip),.message:has(>.login-chip){flex-direction:column;gap:8px} +.login-chip>button{justify-self:start} +.message:has(>.sched-chip),.message:has(>.login-chip){flex-direction:column;gap:8px;width:100%} .message.spoken>.sched-chip,.message.spoken>.login-chip{grid-column:2;max-width:100%;min-width:0} diff --git a/apps/web/src/task-status.tsx b/apps/web/src/task-status.tsx index b31af7e..891ac07 100644 --- a/apps/web/src/task-status.tsx +++ b/apps/web/src/task-status.tsx @@ -32,7 +32,7 @@ export function TaskStatus({tasks,onTakeover}:{tasks:TaskSnapshot[];onTakeover:( const state=taskState(task,Date.now());const report=task.report; return