|
|
@@ -191,7 +191,10 @@ let cursor = 0,
|
|
|
const selected = computed(() => (typeof route.params.id === 'string' ? route.params.id : undefined))
|
|
|
const selectedTask = computed(() => typeof route.query.taskId === 'string' ? route.query.taskId : '')
|
|
|
const shell=ref<InstanceType<typeof WorkspaceShell>>(),vmWorkspace=ref<InstanceType<typeof LocalVmWorkspace>>(),desktopViewer=ref<InstanceType<typeof ComputerPanel>>()
|
|
|
-const computerOpen=ref(false),computerDockOpen=ref(false),twoDesktops=ref(false),desktopAgent=ref<Agent>()
|
|
|
+const computerOpen=ref(false),dockView=ref<'computer'|'inspector'>(),twoDesktops=ref(false),desktopAgent=ref<Agent>()
|
|
|
+const creatingTask=ref(false)
|
|
|
+const CREATE_TASK_VALUE='__create_task__'
|
|
|
+function toggleDock(view:'computer'|'inspector'){dockView.value=dockView.value===view?undefined:view}
|
|
|
async function openComputer(agent:Agent){
|
|
|
if(window.yaoyaoDesktop?.openComputer){try{await window.yaoyaoDesktop.openComputer(agent.id)}catch(cause){error.value=cause instanceof Error?cause.message:'无法打开电脑窗口'}return}
|
|
|
desktopAgent.value=agent;computerOpen.value=true
|
|
|
@@ -320,6 +323,20 @@ async function markRead() {
|
|
|
})
|
|
|
read.readSeq = read.lastSeq
|
|
|
}
|
|
|
+async function selectTask(event:Event){
|
|
|
+ const select=event.currentTarget as HTMLSelectElement,value=select.value
|
|
|
+ if(value!==CREATE_TASK_VALUE){await router.push({query:{...route.query,taskId:value}});return}
|
|
|
+ select.value=activeTask.value?.id??''
|
|
|
+ if(!active.value||active.value.kind!=='group'||creatingTask.value)return
|
|
|
+ const conversationId=active.value.id
|
|
|
+ creatingTask.value=true;error.value=''
|
|
|
+ try{
|
|
|
+ const result=await apiRequest<{task:WorkspaceTask}>(`/api/app/conversations/${conversationId}/tasks`,{method:'POST',body:{}})
|
|
|
+ if(active.value?.id!==conversationId)return
|
|
|
+ tasks.value=[result.task,...tasks.value.filter(task=>task.id!==result.task.id)]
|
|
|
+ await router.push({query:{...route.query,taskId:result.task.id}})
|
|
|
+ }catch(cause){error.value=cause instanceof Error?cause.message:'新建任务失败'}finally{creatingTask.value=false}
|
|
|
+}
|
|
|
async function stopTask() {
|
|
|
if (!active.value || !activeTask.value) return
|
|
|
try {
|
|
|
@@ -653,6 +670,7 @@ watch([selected,selectedTask], () => {
|
|
|
pendingRequestId = undefined;uploadedSources = [];uploadedReferences = []
|
|
|
void load()
|
|
|
})
|
|
|
+watch(selected,()=>{dockView.value=undefined})
|
|
|
onMounted(async () => {
|
|
|
void auth.refreshProfileAvatars().catch(() => undefined)
|
|
|
try {
|
|
|
@@ -719,11 +737,16 @@ onBeforeUnmount(() => {
|
|
|
<template #header-leading><button v-if="active" class="workspace-list-back icon-button" aria-label="返回 Bot 列表" @click="router.push('/conversations')"><AppIcon name="chevron-left" /></button></template>
|
|
|
<template #header-actions>
|
|
|
<div v-if="active" class="header-actions">
|
|
|
- <button type="button" class="icon-button" :aria-label="active.kind==='group'?'Inspector':'电脑与定时任务'" :title="active.kind==='group'?'Inspector':'电脑与定时任务'" @click="computerDockOpen=!computerDockOpen"><AppIcon :name="active.kind==='group'?'tools':'monitor'"/></button>
|
|
|
- <select v-if="active.kind === 'group' && tasks.length" class="task-picker" aria-label="当前任务" :value="activeTask?.id" @change="router.push({query:{...route.query,taskId:($event.target as HTMLSelectElement).value}})">
|
|
|
- <option v-for="task in tasks" :key="task.id" :value="task.id">{{ task.title }}</option>
|
|
|
- </select>
|
|
|
+ <button v-if="active.kind==='direct'" type="button" class="icon-button" aria-label="电脑与定时任务" title="电脑与定时任务" :aria-pressed="dockView==='computer'" @click="toggleDock('computer')"><AppIcon name="monitor"/></button>
|
|
|
+ <span v-if="active.kind === 'group' && tasks.length" class="task-picker-shell">
|
|
|
+ <select class="task-picker" aria-label="当前任务" :aria-busy="creatingTask" :disabled="creatingTask" :value="activeTask?.id" @change="selectTask">
|
|
|
+ <option v-for="task in tasks" :key="task.id" :value="task.id">{{ task.title }}</option>
|
|
|
+ <option :value="CREATE_TASK_VALUE">新建任务</option>
|
|
|
+ </select>
|
|
|
+ <AppIcon name="chevron-down" :size="14"/>
|
|
|
+ </span>
|
|
|
<button class="icon-button" aria-label="聊天设置" title="聊天设置" @click="openDialog(active.kind === 'direct' ? 'editAgent' : 'editGroup')"><AppIcon name="settings" /></button>
|
|
|
+ <button type="button" class="icon-button" aria-label="Inspector" title="Inspector" :aria-pressed="dockView==='inspector'" @click="toggleDock('inspector')"><AppIcon name="bug"/></button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</MessageTimeline>
|
|
|
@@ -736,7 +759,7 @@ onBeforeUnmount(() => {
|
|
|
:mention-options="active.kind === 'group' ? members.map(a => ({id:a.id,label:a.name,insertText:`@${a.name} `})) : []"
|
|
|
@send="sendFromComposer" @stop="control('stop')" @tool-trace-toggle="showThinking = !showThinking" @clear-reference="quoted = null" @error="error = $event" />
|
|
|
</section>
|
|
|
- <WorkspaceBotPanel v-if="computerDockOpen&&active" :conversation-id="active.id" :task-id="activeTask?.id" :group="active.kind==='group'" :active="!twoDesktops" :agents="computerCandidates" :is-admin="auth.user?.role === 'admin'" @close="computerDockOpen=false" @changed="refresh()" @settings="shell?.openLocalVm()" @desktop="openComputer" @workspace="desktopAgent=$event;twoDesktops=true" />
|
|
|
+ <WorkspaceBotPanel v-if="dockView&&active" :conversation-id="active.id" :task-id="activeTask?.id" :mode="dockView" :active="!twoDesktops" :agents="computerCandidates" :is-admin="auth.user?.role === 'admin'" @close="dockView=undefined" @changed="refresh()" @settings="shell?.openLocalVm()" @desktop="openComputer" @workspace="desktopAgent=$event;twoDesktops=true" />
|
|
|
</div>
|
|
|
<ComputerPanel ref="desktopViewer" v-if="computerOpen&&desktopAgent" :agents="[desktopAgent]" auto-take @changed="load()" @close="computerOpen=false" />
|
|
|
<LocalVmWorkspace ref="vmWorkspace" v-if="twoDesktops&&desktopAgent" :agents="agents.filter(a=>a.execution==='computer'&&!a.archived&&!a.computerEnvironmentId)" :primary="desktopAgent.id" @close="twoDesktops=false" />
|
|
|
@@ -915,9 +938,9 @@ onBeforeUnmount(() => {
|
|
|
margin: 15px 0;
|
|
|
font-size: 13px;
|
|
|
}
|
|
|
-.task-picker { max-width:180px;min-height:36px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--text-primary);padding:4px 8px;font:inherit }
|
|
|
+.task-picker-shell{position:relative;display:inline-flex;align-items:center;max-width:210px;min-width:0}.task-picker-shell>.app-icon{position:absolute;right:11px;pointer-events:none;color:var(--text-muted)}.task-picker{width:100%;min-width:0;max-width:210px;min-height:38px;appearance:none;border:1px solid var(--line);border-radius:999px;background:var(--surface);color:var(--text-primary);padding:6px 34px 6px 14px;font:inherit;font-size:12px;font-weight:520;text-overflow:ellipsis;cursor:pointer;box-shadow:0 1px 2px color-mix(in srgb,var(--text-primary) 5%,transparent);transition:border-color 140ms ease,background-color 140ms ease,box-shadow 140ms ease}.task-picker:hover{border-color:var(--line-strong);background:var(--surface-hover)}.task-picker:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.task-picker:disabled{cursor:wait;opacity:.55}
|
|
|
.task-queue-status{margin:0 20px 8px;font-size:13px;color:var(--text-muted)}
|
|
|
-@media(max-width:720px){.task-picker{max-width:100px}}
|
|
|
+@media(max-width:720px){.task-picker-shell,.task-picker{max-width:118px}.task-picker{min-height:44px;padding-left:12px}}
|
|
|
.team-management-permission > span {
|
|
|
display: flex;
|
|
|
align-items: center;
|