feat(frontend): integrate with the API to create a sub-conversation for the planning agent (#11730)

This commit is contained in:
Hiep Le 2025-11-15 09:43:21 +07:00 committed by GitHub
parent 833aae1833
commit d6fab190bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 84 additions and 12 deletions

View File

@ -60,6 +60,8 @@ class V1ConversationService {
selected_branch?: string,
conversationInstructions?: string,
trigger?: ConversationTrigger,
parent_conversation_id?: string,
agent_type?: "default" | "plan",
): Promise<V1AppConversationStartTask> {
const body: V1AppConversationStartRequest = {
selected_repository: selectedRepository,
@ -67,6 +69,8 @@ class V1ConversationService {
selected_branch,
title: conversationInstructions,
trigger,
parent_conversation_id: parent_conversation_id || null,
agent_type,
};
// Add initial message if provided

View File

@ -30,6 +30,8 @@ export interface V1AppConversationStartRequest {
title?: string | null;
trigger?: ConversationTrigger | null;
pr_number?: number[];
parent_conversation_id?: string | null;
agent_type?: "default" | "plan";
}
export type V1AppConversationStartTaskStatus =

View File

@ -77,6 +77,7 @@ export interface Conversation {
session_api_key: string | null;
pr_number?: number[] | null;
conversation_version?: "V0" | "V1";
sub_conversation_ids?: string[];
}
export interface ResultSet<T> {

View File

@ -1,4 +1,4 @@
import React, { useMemo, useEffect } from "react";
import React, { useMemo, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Typography } from "#/ui/typography";
import { I18nKey } from "#/i18n/declaration";
@ -11,10 +11,12 @@ import { cn } from "#/utils/utils";
import { USE_PLANNING_AGENT } from "#/utils/feature-flags";
import { useAgentState } from "#/hooks/use-agent-state";
import { AgentState } from "#/types/agent-state";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { useCreateConversation } from "#/hooks/mutation/use-create-conversation";
import { displaySuccessToast } from "#/utils/custom-toast-handlers";
export function ChangeAgentButton() {
const { t } = useTranslation();
const [contextMenuOpen, setContextMenuOpen] = React.useState(false);
const [contextMenuOpen, setContextMenuOpen] = useState<boolean>(false);
const conversationMode = useConversationStore(
(state) => state.conversationMode,
@ -28,8 +30,14 @@ export function ChangeAgentButton() {
const { curAgentState } = useAgentState();
const { t } = useTranslation();
const isAgentRunning = curAgentState === AgentState.RUNNING;
const { data: conversation } = useActiveConversation();
const { mutate: createConversation, isPending: isCreatingConversation } =
useCreateConversation();
// Close context menu when agent starts running
useEffect(() => {
if (isAgentRunning && contextMenuOpen) {
@ -37,6 +45,40 @@ export function ChangeAgentButton() {
}
}, [isAgentRunning, contextMenuOpen]);
const handlePlanClick = (
event: React.MouseEvent<HTMLButtonElement> | KeyboardEvent,
) => {
event.preventDefault();
event.stopPropagation();
// Set conversation mode to "plan" immediately
setConversationMode("plan");
// Check if sub_conversation_ids is not empty
if (
(conversation?.sub_conversation_ids &&
conversation.sub_conversation_ids.length > 0) ||
!conversation?.conversation_id
) {
// Do nothing if both conditions are true
return;
}
// Create a new sub-conversation if we have a current conversation ID
createConversation(
{
parentConversationId: conversation.conversation_id,
agentType: "plan",
},
{
onSuccess: () =>
displaySuccessToast(
t(I18nKey.PLANNING_AGENTT$PLANNING_AGENT_INITIALIZED),
),
},
);
};
// Handle Shift + Tab keyboard shortcut to cycle through modes
useEffect(() => {
if (!shouldUsePlanningAgent || isAgentRunning) {
@ -52,7 +94,11 @@ export function ChangeAgentButton() {
// Cycle between modes: code -> plan -> code
const nextMode = conversationMode === "code" ? "plan" : "code";
setConversationMode(nextMode);
if (nextMode === "plan") {
handlePlanClick(event);
} else {
setConversationMode(nextMode);
}
}
};
@ -80,12 +126,6 @@ export function ChangeAgentButton() {
setConversationMode("code");
};
const handlePlanClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
setConversationMode("plan");
};
const isExecutionAgent = conversationMode === "code";
const buttonLabel = useMemo(() => {
@ -102,6 +142,8 @@ export function ChangeAgentButton() {
return <LessonPlanIcon width={18} height={18} color="#ffffff" />;
}, [isExecutionAgent]);
const isButtonDisabled = isAgentRunning || isCreatingConversation;
if (!shouldUsePlanningAgent) {
return null;
}
@ -111,11 +153,11 @@ export function ChangeAgentButton() {
<button
type="button"
onClick={handleButtonClick}
disabled={isAgentRunning}
disabled={isButtonDisabled}
className={cn(
"flex items-center border border-[#4B505F] rounded-[100px] transition-opacity",
!isExecutionAgent && "border-[#597FF4] bg-[#4A67BD]",
isAgentRunning
isButtonDisabled
? "opacity-50 cursor-not-allowed"
: "cursor-pointer hover:opacity-80",
)}

View File

@ -17,6 +17,8 @@ interface CreateConversationVariables {
suggestedTask?: SuggestedTask;
conversationInstructions?: string;
createMicroagent?: CreateMicroagent;
parentConversationId?: string;
agentType?: "default" | "plan";
}
// Response type that combines both V1 and legacy responses
@ -44,6 +46,8 @@ export const useCreateConversation = () => {
suggestedTask,
conversationInstructions,
createMicroagent,
parentConversationId,
agentType,
} = variables;
const useV1 = USE_V1_CONVERSATION_API() && !createMicroagent;
@ -57,6 +61,8 @@ export const useCreateConversation = () => {
repository?.branch,
conversationInstructions,
undefined, // trigger - will be set by backend
parentConversationId,
agentType,
);
// Return a special task ID that the frontend will recognize

View File

@ -946,4 +946,5 @@ export enum I18nKey {
COMMON$LET_S_WORK_ON_A_PLAN = "COMMON$LET_S_WORK_ON_A_PLAN",
COMMON$CODE_AGENT_DESCRIPTION = "COMMON$CODE_AGENT_DESCRIPTION",
COMMON$PLAN_AGENT_DESCRIPTION = "COMMON$PLAN_AGENT_DESCRIPTION",
PLANNING_AGENTT$PLANNING_AGENT_INITIALIZED = "PLANNING_AGENTT$PLANNING_AGENT_INITIALIZED",
}

View File

@ -15134,5 +15134,21 @@
"tr": "Hedefleri belirtin, görevleri yapılandırın ve sonraki adımlarınızı belirleyin.",
"de": "Umreißen Sie Ziele, strukturieren Sie Aufgaben und planen Sie Ihre nächsten Schritte.",
"uk": "Окресліть цілі, структуруйте завдання та сплануйте наступні кроки."
},
"PLANNING_AGENTT$PLANNING_AGENT_INITIALIZED": {
"en": "Planning agent initialized",
"ja": "プランニングエージェントが初期化されました",
"zh-CN": "规划代理已初始化",
"zh-TW": "規劃代理已初始化",
"ko-KR": "계획 에이전트가 초기화되었습니다",
"no": "Planleggingsagent er initialisert",
"it": "Agente di pianificazione inizializzato",
"pt": "Agente de planejamento inicializado",
"es": "Agente de planificación inicializado",
"ar": "تم تهيئة وكيل التخطيط",
"fr": "Agent de planification initialisé",
"tr": "Planlama ajanı başlatıldı",
"de": "Planungsagent wurde initialisiert",
"uk": "Агент планування ініціалізовано"
}
}