diff --git a/frontend/src/components/console/task/chat-panel.tsx b/frontend/src/components/console/task/chat-panel.tsx index 015fa472..56ac6cd9 100644 --- a/frontend/src/components/console/task/chat-panel.tsx +++ b/frontend/src/components/console/task/chat-panel.tsx @@ -7,6 +7,77 @@ import { ChevronsDownUp, ChevronsUpDown } from "lucide-react" import { Label } from "@/components/ui/label" import { IconCircle, IconCircleCheck, IconLoader, IconPlayerStopFilled, IconSubtask } from "@tabler/icons-react" import type { AvailableCommands, PlanEntry, RepoFileChange, TaskPlan, TaskStreamStatus, TaskWebSocketManager } from "./ws-manager" + +export interface PlanStepsBlockProps { + plan: TaskPlan + streamStatus: TaskStreamStatus +} + +export function PlanStepsBlock({ plan, streamStatus }: PlanStepsBlockProps) { + const [planOpened, setPlanOpened] = React.useState(false) + + React.useEffect(() => { + if (!plan) return + setPlanOpened(plan.entries.some((entry: PlanEntry) => entry.status !== "completed")) + }, [plan]) + + if (!plan || plan.entries.length === 0) return null + + const renderPlan = () => { + if (planOpened) { + return plan.entries.map((entry: PlanEntry, index: number) => ( +
+ {entry.status === "in_progress" && streamStatus === "executing" ? ( + + ) : entry.status === "completed" ? ( + + ) : ( + + )} +
+ {entry.content} +
+
+ )) + } else { + const firstInProgress = plan.entries.find((entry: PlanEntry) => entry.status === "in_progress") + if (!firstInProgress || streamStatus !== "executing") return null + return ( +
+ {firstInProgress.status === "in_progress" ? ( + + ) : firstInProgress.status === "completed" ? ( + + ) : ( + + )} +
{firstInProgress.content}
+
+ ) + } + } + + return ( +
+
+ + +
+
{renderPlan()}
+
+ ) +} import { TaskChatInputBox } from "./chat-inputbox" import { cn } from "@/lib/utils" import { FileChangesDialog } from "./file-changes-dialog" @@ -20,6 +91,8 @@ interface TaskChatPanelProps { streamStatus: TaskStreamStatus disabled: boolean plan: TaskPlan | null + /** 当为 'sticky' 时,执行步骤由父组件渲染并固定在顶部,本组件不渲染 */ + planPosition?: "inline" | "sticky" availableCommands: AvailableCommands | null sending: boolean queueSize: number @@ -32,8 +105,7 @@ interface TaskChatPanelProps { taskManager: TaskWebSocketManager | null } -export const TaskChatPanel = ({ scrollContainerRef: externalScrollRef, inputPortalTargetRef, messages, cli, streamStatus, disabled, plan, availableCommands, sending, sendUserInput, sendCancelCommand, sendResetSession, sendReloadSession, queueSize, fileChanges, fileChangesMap, taskManager }: TaskChatPanelProps) => { - const [planOpened, setPlanOpened] = React.useState(false) +export const TaskChatPanel = ({ scrollContainerRef: externalScrollRef, inputPortalTargetRef, messages, cli, streamStatus, disabled, plan, planPosition = "inline", availableCommands, sending, sendUserInput, sendCancelCommand, sendResetSession, sendReloadSession, queueSize, fileChanges, fileChangesMap, taskManager }: TaskChatPanelProps) => { const [timeCost, setTimeCost] = React.useState(0) const internalScrollRef = React.useRef(null) const scrollContainerRef = externalScrollRef ?? internalScrollRef @@ -42,18 +114,9 @@ export const TaskChatPanel = ({ scrollContainerRef: externalScrollRef, inputPort React.useEffect(() => { - setShowSubmitButton(streamStatus === 'waiting') + setShowSubmitButton(streamStatus === "waiting") }, [streamStatus]) - React.useEffect(() => { - if (!plan) { - return - } - - setPlanOpened(plan.entries.some((entry: PlanEntry) => entry.status !== 'completed')) - - }, [plan]) - React.useEffect(() => { if (streamStatus === 'executing') { setTimeCost(0) @@ -139,65 +202,11 @@ export const TaskChatPanel = ({ scrollContainerRef: externalScrollRef, inputPort } } - const renderPlan = () => { - if (!plan || plan.entries.length === 0) { - return null - } - if (planOpened) { - return plan.entries.map((entry: PlanEntry, index: number) => ( -
- {entry.status === 'in_progress' && streamStatus === 'executing' ? ( - - ) : ( - entry.status === 'completed' ? ( - - ) : ( - - ) - )} -
- {entry.content} -
-
- )) - } else { - const firstInProgress = plan.entries.find((entry: PlanEntry) => entry.status === 'in_progress') - if (!firstInProgress || streamStatus !== 'executing') { - return null - } - return
- {firstInProgress.status === 'in_progress' ? ( - - ) : ( - firstInProgress.status === 'completed' ? ( - - ) : ( - - ) - )} -
- {firstInProgress.content} -
-
- } - } - return (
- {plan && plan.entries.length > 0 &&
-
- - -
-
- {renderPlan()} -
-
} + {planPosition === "inline" && plan && plan.entries.length > 0 && ( + + )}
(defaultSkills); const [skillList, setSkillList] = useState([]); - // 运行参数状态 - const [selectedTool, setSelectedTool] = useState(""); + // 运行参数状态(工具固定为 opencode) const [selectedModelId, setSelectedModelId] = useState(""); const [selectedHostId, setSelectedHostId] = useState(""); const [selectedImageId, setSelectedImageId] = useState(""); @@ -222,7 +221,6 @@ export function TaskInput({ repos, onTaskCreated }: TaskInputProps) { const setDefaultConfig = () => { setSelectedModelId(selectModel(modelsWithEconomy, true)) - setSelectedTool(ConstsCliName.CliNameOpencode) setSelectedHostId(selectHost(hosts, true)) if (user.role === ConstsUserRole.UserRoleSubAccount) { @@ -247,11 +245,7 @@ export function TaskInput({ repos, onTaskCreated }: TaskInputProps) { return true } - if (selectedTool === ConstsCliName.CliNameCodex) { - return model.interface_type === ConstsInterfaceType.InterfaceTypeOpenAIResponse - } else if (selectedTool === ConstsCliName.CliNameClaude) { - return model.interface_type === ConstsInterfaceType.InterfaceTypeAnthropic - } + // 固定使用 opencode,支持所有模型 return true; }; @@ -274,7 +268,7 @@ export function TaskInput({ repos, onTaskCreated }: TaskInputProps) { const executeTask = async () => { setCreatingTask(true); await apiRequest('v1UsersTasksCreate', { - cli_name: selectedTool, + cli_name: ConstsCliName.CliNameOpencode, content: taskContent, git_identity_id: selectedIdentityId || undefined, host_id: selectedHostId, @@ -752,30 +746,6 @@ export function TaskInput({ repos, onTaskCreated }: TaskInputProps) { 任务参数 - - 开发工具 - - - - 大模型 diff --git a/frontend/src/pages/console/user/task/task-detail.tsx b/frontend/src/pages/console/user/task/task-detail.tsx index 539a5d14..abd61117 100644 --- a/frontend/src/pages/console/user/task/task-detail.tsx +++ b/frontend/src/pages/console/user/task/task-detail.tsx @@ -1,6 +1,6 @@ import { ConstsTaskStatus, type DomainProjectTask } from "@/api/Api" import { useBreadcrumbTask } from "@/components/console/breadcrumb-task-context" -import { TaskChatPanel } from "@/components/console/task/chat-panel" +import { PlanStepsBlock, TaskChatPanel } from "@/components/console/task/chat-panel" import { TaskFileExplorer } from "@/components/console/task/task-file-explorer" import { TaskTerminalPanel } from "@/components/console/task/task-terminal-panel" import type { MessageType } from "@/components/console/task/message" @@ -228,6 +228,11 @@ export default function TaskDetailPage() { const chatSection = (
+ {plan && plan.entries.length > 0 && ( +
+ +
+ )}
}
+ {plan && plan.entries.length > 0 && ( +
+
+ +
+
+ )}
- +