feat(deps): implement dependency auto-completion and interactive installation CLI (#147)

This commit is contained in:
duorameng
2026-07-10 08:45:26 +08:00
parent f0967abc6f
commit 3354f3d5ed
11 changed files with 442 additions and 7 deletions
+2 -1
View File
@@ -360,7 +360,8 @@ export const api = {
const query = new URLSearchParams({ language })
if (lang_version) query.set('lang_version', lang_version)
return request<Dependency[]>(`/deps/installed?${query}`)
}
},
getInstallSuggestCmd: (logID: string) => request<{ command: string }>(`/deps/install-suggest-cmd?log_id=${logID}`)
},
agents: {
list: () => request<Agent[]>('/agents'),
+1 -1
View File
@@ -318,7 +318,7 @@ async function save() {
<template>
<Dialog :open="open" @update:open="emit('update:open', $event)">
<DialogContent class="max-w-[95vw] sm:max-w-[700px] xl:max-w-[950px] p-0 overflow-hidden border-none bg-background shadow-2xl transition-all duration-300" style="text-rendering: optimizeLegibility;" @openAutoFocus.prevent>
<DialogContent class="max-w-[95vw] sm:max-w-[700px] xl:max-w-[950px] p-0 overflow-hidden border-none bg-background shadow-2xl transition-all duration-300" style="text-rendering: optimizeLegibility;" @openAutoFocus.prevent @pointerDownOutside.prevent>
<div class="flex flex-col max-h-[85vh]">
<DialogHeader class="px-5 sm:px-6 pr-20 pt-6 pb-2 shrink-0">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-2">
+11 -4
View File
@@ -9,7 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { ScrollArea } from '@/components/ui/scroll-area'
import DirTreeSelect from '@/components/DirTreeSelect.vue'
import { Plus, X, ChevronDown, Search, AlertCircle, Terminal, Zap, Lock, Variable } from 'lucide-vue-next'
import { Plus, X, ChevronDown, Search, AlertCircle, Terminal, Zap, Lock, Variable, Wrench } from 'lucide-vue-next'
import { Badge } from '@/components/ui/badge'
import { cn } from '@/lib/utils'
import { api, type Task, type EnvVar, type Agent } from '@/api'
@@ -283,7 +283,7 @@ async function save() {
<template>
<Dialog :open="open" @update:open="emit('update:open', $event)">
<DialogContent class="max-w-[95vw] sm:max-w-[600px] xl:max-w-[850px] p-0 overflow-hidden border-none bg-background shadow-2xl transition-all duration-300" style="text-rendering: optimizeLegibility;" @openAutoFocus.prevent>
<DialogContent class="max-w-[95vw] sm:max-w-[600px] xl:max-w-[850px] p-0 overflow-hidden border-none bg-background shadow-2xl transition-all duration-300" style="text-rendering: optimizeLegibility;" @openAutoFocus.prevent @pointerDownOutside.prevent>
<div class="flex flex-col max-h-[85vh]">
<DialogHeader class="px-6 pr-12 pt-6 pb-2 shrink-0 border-b border-muted/50">
<DialogTitle class="text-xl font-bold py-2">
@@ -354,8 +354,15 @@ async function save() {
<div class="grid grid-cols-1 sm:grid-cols-4 items-start gap-3">
<div class="sm:col-span-1" />
<div class="sm:col-span-3">
<div class="flex items-center gap-2.5 p-3 rounded-xl bg-amber-500/5 border border-amber-500/10 text-amber-600 dark:text-amber-400 text-[11px] leading-relaxed font-medium">
<AlertCircle class="h-4 w-4 shrink-0 text-amber-500" /><p>请先在<b>语言依赖</b>中安装所需的运行时执行脚本时将自动注入该环境</p>
<div class="flex flex-col gap-2 p-3 rounded-xl bg-amber-500/5 border border-amber-500/10 text-amber-600 dark:text-amber-400 text-[11px] leading-relaxed font-medium">
<div class="flex items-center gap-2.5">
<AlertCircle class="h-4 w-4 shrink-0 text-amber-500" />
<p>请先在<b>语言依赖</b>中安装所需的运行时执行脚本时将自动注入该环境</p>
</div>
<div class="flex items-center gap-2.5 mt-1 border-t border-amber-500/10 pt-1.5">
<Wrench class="h-4 w-4 shrink-0 text-amber-500" />
<p><b>依赖自动补全</b>若执行报错提示缺失包 requests 可在任务列表的<b>操作菜单</b>中点击<b>补全依赖</b>进行一键安装</p>
</div>
</div>
</div>
</div>
+51 -1
View File
@@ -2,15 +2,17 @@
import { ref, onMounted, computed, watch, onUnmounted } from 'vue'
import { Button } from '@/components/ui/button'
import BaihuDialog from '@/components/ui/BaihuDialog.vue'
import { Dialog, DialogContent } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import Pagination from '@/components/Pagination.vue'
import TaskDialog from './TaskDialog.vue'
import RepoDialog from './RepoDialog.vue'
import LogViewer from '@/views/history/LogViewer.vue'
import { Plus, Play, Pencil, Trash2, Search, ScrollText, GitBranch, Terminal, Server, Monitor, X, Loader2, RefreshCw, Wifi, WifiOff, Zap, ZapOff, Copy, Tag, ChevronDown, Pin, PinOff, MoreHorizontal, CalendarClock } from 'lucide-vue-next'
import { Plus, Play, Pencil, Trash2, Search, ScrollText, GitBranch, Terminal, Server, Monitor, X, Loader2, RefreshCw, Wifi, WifiOff, Zap, ZapOff, Copy, Tag, ChevronDown, Pin, PinOff, MoreHorizontal, CalendarClock, Wrench } from 'lucide-vue-next'
import TagInput from '@/components/TagInput.vue'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import XTerminal from '@/components/XTerminal.vue'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
DropdownMenu,
@@ -416,6 +418,31 @@ async function viewLogs(taskId: string) {
}
}
const showTerminalDialog = ref(false)
const terminalCmd = ref('')
async function handleTaskDepInstall(task: Task) {
try {
const res = await api.logs.list({ task_id: task.id, page: 1, page_size: 1 })
if (res.data && res.data.length > 0) {
const latestLog = res.data[0]
if (latestLog) {
const cmdRes = await api.deps.getInstallSuggestCmd(latestLog.id)
if (cmdRes && cmdRes.command) {
terminalCmd.value = cmdRes.command
showTerminalDialog.value = true
}
} else {
toast.error('未找到该任务的运行日志,无法分析依赖')
}
} else {
toast.error('该任务尚未运行,无日志可分析')
}
} catch (err: any) {
toast.error(err.message || '获取依赖安装命令失败')
}
}
// 视图管理
const taskViews = ref<any[]>([])
const newViewName = ref('')
@@ -797,6 +824,10 @@ watch(() => route.query.agent_id, (newVal: any) => {
<PinOff v-else class="h-3.5 w-3.5 mr-2 text-primary" />
<span>{{ task.pin_type === 'top' ? '取消置顶' : '置顶任务' }}</span>
</DropdownMenuItem>
<DropdownMenuItem @click="handleTaskDepInstall(task)">
<Wrench class="h-3.5 w-3.5 mr-2 text-amber-500" />
<span>补全依赖</span>
</DropdownMenuItem>
<DropdownMenuItem @click="duplicateTask(task)">
<Copy class="h-3.5 w-3.5 mr-2" />
<span>复制任务</span>
@@ -881,6 +912,10 @@ watch(() => route.query.agent_id, (newVal: any) => {
<PinOff v-else class="h-3.5 w-3.5 mr-2 text-primary" />
<span>{{ task.pin_type === 'top' ? '取消置顶' : '置顶任务' }}</span>
</DropdownMenuItem>
<DropdownMenuItem @click="handleTaskDepInstall(task)">
<Wrench class="h-3.5 w-3.5 mr-2 text-amber-500" />
<span>补全依赖</span>
</DropdownMenuItem>
<DropdownMenuItem @click="duplicateTask(task)">
<Copy class="h-3.5 w-3.5 mr-2" />
<span>复制任务</span>
@@ -1070,5 +1105,20 @@ watch(() => route.query.agent_id, (newVal: any) => {
</Button>
</template>
</BaihuDialog>
<!-- 依赖补全终端 -->
<Dialog v-model:open="showTerminalDialog">
<DialogContent class="w-[calc(100%-1rem)] sm:max-w-[90vw] lg:max-w-4xl h-[60vh] sm:h-[80vh] flex flex-col p-0 overflow-hidden bg-[#1e1e1e] border-none shadow-2xl [&>button]:hidden">
<div class="flex items-center justify-between px-3 py-2 border-b border-[#3c3c3c]">
<span class="text-xs sm:text-sm font-medium text-gray-300">依赖补全终端</span>
<Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white" @click="showTerminalDialog = false">
<X class="h-4 w-4" />
</Button>
</div>
<div class="flex-1 overflow-hidden">
<XTerminal v-if="showTerminalDialog" :initial-command="terminalCmd" />
</div>
</DialogContent>
</Dialog>
</div>
</template>