From 396b457b2b2f572678199bca711d9e518bda17c0 Mon Sep 17 00:00:00 2001 From: monster Date: Mon, 16 Mar 2026 12:10:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E6=89=A7=E8=A1=8C=E6=AD=A5=E9=AA=A4?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E5=9C=A8=E8=81=8A=E5=A4=A9=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E9=A1=B6=E9=83=A8=EF=BC=8C=E4=B8=8D=E9=9A=8F=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=BB=9A=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 抽取 PlanStepsBlock 组件 - 新增 planPosition 属性支持 inline/sticky 模式 - task-detail 和 task-dev 使用 sticky 模式,执行步骤固定在顶部 Made-with: Cursor --- .../components/console/task/chat-panel.tsx | 147 ++++++++++-------- .../pages/console/user/task/task-detail.tsx | 8 +- .../src/pages/console/user/task/task-dev.tsx | 48 +++--- 3 files changed, 113 insertions(+), 90 deletions(-) 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 && ( + + )}
+ {plan && plan.entries.length > 0 && ( +
+ +
+ )}
}
+ {plan && plan.entries.length > 0 && ( +
+
+ +
+
+ )}
- +
From 82db1a1513545029f65a6d0e7d4e02843354104e Mon Sep 17 00:00:00 2001 From: monster Date: Mon, 16 Mar 2026 12:11:16 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=E4=BB=BB=E5=8A=A1=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=9B=BA=E5=AE=9A=E4=BD=BF=E7=94=A8=20opencode?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=E5=BC=80=E5=8F=91=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- .../components/console/task/task-input.tsx | 38 ++----------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/console/task/task-input.tsx b/frontend/src/components/console/task/task-input.tsx index 7712288b..02a3982d 100644 --- a/frontend/src/components/console/task/task-input.tsx +++ b/frontend/src/components/console/task/task-input.tsx @@ -18,7 +18,7 @@ import { Spinner } from "@/components/ui/spinner"; import { cn } from "@/lib/utils"; import { getBrandFromModelName, getGitPlatformIcon, getHostBadges, getImageShortName, getInterfaceTypeBadge, getModelHealthBadge, getOSFromImageName, getOwnerTypeBadge, getRepoIcon, getRepoNameFromUrl, getSkillTagIcon, selectHost, selectImage, selectModel } from "@/utils/common"; import { apiRequest } from "@/utils/requestUtils"; -import { IconBug, IconLink, IconPuzzle, IconSend, IconSourceCode, IconSquareRoundedLetterOFilled, IconTerminal2, IconUpload, IconUser, IconVocabulary, IconXboxX } from "@tabler/icons-react"; +import { IconBug, IconLink, IconPuzzle, IconSend, IconSourceCode, IconTerminal2, IconUpload, IconUser, IconVocabulary, IconXboxX } from "@tabler/icons-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useSettingsDialog } from "@/pages/console/user/page"; @@ -102,8 +102,7 @@ export function TaskInput({ repos, onTaskCreated }: TaskInputProps) { const [selectedSkill, setSelectedSkill] = useState(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) { 任务参数 - - 开发工具 - - - - 大模型