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 && (
+
+ )}