mirror of
https://github.com/symgraph/IDAssist
synced 2026-08-09 13:11:45 +00:00
Add ReAct plan approval step.
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
"plugin": {
|
||||
"name": "IDAssist",
|
||||
"entryPoint": "idassist_plugin.py",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"description": "AI-powered reverse engineering plugin with LLM analysis, semantic knowledge graphs, RAG search, and SymGraph collaboration",
|
||||
"license": "MIT",
|
||||
"urls": {
|
||||
|
||||
@@ -31,6 +31,7 @@ from src.services.transcript_renderer import TranscriptRenderer
|
||||
from src.services.tool_approval_service import ToolApprovalService
|
||||
from src.controllers.react_thread import ReActOrchestratorThread
|
||||
from src.services.models.react_models import ReActConfig, ReActResult, ReActStatus
|
||||
from src.services.react.react_prompts import ReActPrompts
|
||||
|
||||
|
||||
class LLMQueryThread(QThread):
|
||||
@@ -488,6 +489,45 @@ class ToolExecutorThread(QThread):
|
||||
_STREAM_ARG_UNSET = object()
|
||||
|
||||
|
||||
class ReActPlanningThread(QThread):
|
||||
plan_complete = Signal(str)
|
||||
plan_error = Signal(str)
|
||||
|
||||
def __init__(self, prompt: str, provider_config: dict, llm_factory, parent=None):
|
||||
super().__init__(parent)
|
||||
self.prompt = prompt
|
||||
self.provider_config = provider_config
|
||||
self.llm_factory = llm_factory
|
||||
self.cancelled = False
|
||||
|
||||
def cancel(self):
|
||||
self.cancelled = True
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
asyncio.run(self._run_plan())
|
||||
except Exception as e:
|
||||
if not self.cancelled:
|
||||
self.plan_error.emit(str(e))
|
||||
|
||||
async def _run_plan(self):
|
||||
if self.cancelled:
|
||||
return
|
||||
from src.services.models.llm_models import ChatMessage, ChatRequest, MessageRole
|
||||
|
||||
provider = self.llm_factory.create_provider(self.provider_config)
|
||||
request = ChatRequest(
|
||||
messages=[ChatMessage(role=MessageRole.USER, content=self.prompt)],
|
||||
model=getattr(provider, "model", ""),
|
||||
max_tokens=getattr(provider, "max_tokens", 4096),
|
||||
temperature=0.3,
|
||||
stream=False,
|
||||
)
|
||||
response = await provider.chat_completion(request)
|
||||
if not self.cancelled:
|
||||
self.plan_complete.emit(response.content or "")
|
||||
|
||||
|
||||
class QueryController:
|
||||
CHAT_TYPE_METADATA_KEY = "chat_type"
|
||||
DOCUMENT_CHAT_METADATA_KEY = "is_document_chat"
|
||||
@@ -563,13 +603,22 @@ class QueryController:
|
||||
|
||||
# ReAct (agentic mode) state
|
||||
self._react_thread: Optional[ReActOrchestratorThread] = None
|
||||
self._react_planning_thread: Optional[ReActPlanningThread] = None
|
||||
self._react_active = False
|
||||
self._react_plan_pending = False
|
||||
self._react_streaming_final_answer = False
|
||||
self._last_todo_snapshot = ""
|
||||
self._last_react_tasks: List[Dict[str, str]] = []
|
||||
self._current_react_run_id: Optional[str] = None
|
||||
self._current_react_objective: Optional[str] = None
|
||||
self._current_react_final_status: Optional[str] = None
|
||||
self._pending_react_plan: Optional[str] = None
|
||||
self._pending_react_initial_context: str = ""
|
||||
self._pending_react_provider_config: Optional[Dict[str, Any]] = None
|
||||
self._pending_react_provider_type: str = "anthropic_platform"
|
||||
self._pending_react_mcp_tools: List[Dict[str, Any]] = []
|
||||
self._pending_react_config: Optional[ReActConfig] = None
|
||||
self._pending_react_refinements: List[str] = []
|
||||
|
||||
# Connect view signals
|
||||
self._connect_signals()
|
||||
@@ -606,6 +655,8 @@ class QueryController:
|
||||
self.view.agentic_enabled_changed.connect(self.on_agentic_enabled_changed)
|
||||
self.view.accept_all_tools_changed.connect(self.on_accept_all_tools_changed)
|
||||
self.view.approval_decision_requested.connect(self.on_approval_decision_requested)
|
||||
self.view.plan_approved_requested.connect(self.on_react_plan_approved)
|
||||
self.view.plan_cancel_requested.connect(self.on_react_plan_cancelled)
|
||||
self.view.transcript_link_clicked.connect(self.on_transcript_link_clicked)
|
||||
self.view.rlhf_feedback_requested.connect(self.handle_rlhf_feedback)
|
||||
|
||||
@@ -1249,6 +1300,9 @@ class QueryController:
|
||||
|
||||
# Route to agentic mode if enabled
|
||||
if self.view.is_agentic_enabled():
|
||||
if self._react_plan_pending:
|
||||
self._refine_react_plan(query_text)
|
||||
return
|
||||
self._submit_agentic_query(query_text)
|
||||
return
|
||||
|
||||
@@ -1350,6 +1404,12 @@ class QueryController:
|
||||
self._reasoning_filter.reset()
|
||||
self._streaming_renderer.reset()
|
||||
|
||||
if self._react_planning_thread and self._react_planning_thread.isRunning():
|
||||
self._react_planning_thread.cancel()
|
||||
self._react_planning_thread.wait(1000)
|
||||
self.view.set_query_running(False)
|
||||
return
|
||||
|
||||
# Cancel ReAct if active
|
||||
if self._react_active and self._react_thread:
|
||||
self._react_thread.cancel()
|
||||
@@ -3604,6 +3664,123 @@ Tool Usage Guidelines:
|
||||
# ReAct (Agentic Mode) Methods
|
||||
# =========================================================================
|
||||
|
||||
def _build_react_planning_prompt(self, objective: str, initial_context: str,
|
||||
previous_plan: Optional[str] = None,
|
||||
refinement: Optional[str] = None) -> str:
|
||||
base = ReActPrompts.get_planning_prompt(objective, initial_context)
|
||||
if previous_plan:
|
||||
base += f"\n\n## Previous Proposed Plan\n{previous_plan}\n"
|
||||
if refinement:
|
||||
base += (
|
||||
f"\n\n## User Refinement Request\n{refinement}\n\n"
|
||||
"Revise the plan to incorporate this feedback. Return only the updated markdown checklist."
|
||||
)
|
||||
else:
|
||||
base += "\n\nReturn only the proposed markdown checklist and any brief assumptions needed to execute it."
|
||||
return base
|
||||
|
||||
def _start_react_planning(self, objective: str, initial_context: str,
|
||||
provider_config: Dict[str, Any],
|
||||
previous_plan: Optional[str] = None,
|
||||
refinement: Optional[str] = None):
|
||||
if self._react_planning_thread and self._react_planning_thread.isRunning():
|
||||
self._react_planning_thread.cancel()
|
||||
self._react_planning_thread.wait(1000)
|
||||
|
||||
prompt = self._build_react_planning_prompt(objective, initial_context, previous_plan, refinement)
|
||||
self._react_planning_thread = ReActPlanningThread(prompt, provider_config, self.llm_factory)
|
||||
self._react_planning_thread.plan_complete.connect(self._on_react_plan_ready)
|
||||
self._react_planning_thread.plan_error.connect(self._on_react_plan_error)
|
||||
self.view.set_query_running(True)
|
||||
self._react_planning_thread.start()
|
||||
|
||||
def _on_react_plan_ready(self, plan_text: str):
|
||||
self._pending_react_plan = plan_text.strip()
|
||||
self._react_plan_pending = True
|
||||
self.view.set_query_running(False)
|
||||
self.view.set_pending_plan(self._pending_react_plan)
|
||||
if self.current_chat_id and self._current_query_binary_hash and self._pending_react_plan:
|
||||
self.transcript_service.append_todo_snapshot(
|
||||
self._current_query_binary_hash,
|
||||
str(self.current_chat_id),
|
||||
self._pending_react_plan,
|
||||
[],
|
||||
metadata_extra={
|
||||
"react_run_id": self._current_react_run_id,
|
||||
"react_objective": self._current_react_objective,
|
||||
"agent_phase": "plan_pending",
|
||||
},
|
||||
)
|
||||
self._refresh_transcript_view("")
|
||||
|
||||
def _on_react_plan_error(self, error: str):
|
||||
log.log_error(f"ReAct planning error: {error}")
|
||||
self.view.set_query_running(False)
|
||||
self._react_plan_pending = False
|
||||
self.view.set_pending_plan(None)
|
||||
self._add_message_to_chat(self.current_chat_id, "error", f"**Agentic Planning Error:** {error}")
|
||||
self._current_query_binary_hash = None
|
||||
|
||||
def _refine_react_plan(self, refinement_text: str):
|
||||
if not self._react_plan_pending or not self._pending_react_provider_config:
|
||||
return
|
||||
self._pending_react_refinements.append(refinement_text)
|
||||
if self.current_chat_id and self._current_query_binary_hash:
|
||||
self.transcript_service.append_event(
|
||||
self._current_query_binary_hash,
|
||||
str(self.current_chat_id),
|
||||
TranscriptEventKind.USER_MESSAGE,
|
||||
"user",
|
||||
refinement_text,
|
||||
metadata={"agent_phase": "plan_refinement", "react_run_id": self._current_react_run_id},
|
||||
)
|
||||
self._start_react_planning(
|
||||
self._current_react_objective or refinement_text,
|
||||
self._pending_react_initial_context,
|
||||
self._pending_react_provider_config,
|
||||
previous_plan=self._pending_react_plan,
|
||||
refinement=refinement_text,
|
||||
)
|
||||
|
||||
def on_react_plan_cancelled(self):
|
||||
if self._react_planning_thread and self._react_planning_thread.isRunning():
|
||||
self._react_planning_thread.cancel()
|
||||
self._react_plan_pending = False
|
||||
self._pending_react_plan = None
|
||||
self.view.set_pending_plan(None)
|
||||
self.view.set_query_running(False)
|
||||
self._current_react_run_id = None
|
||||
self._current_react_objective = None
|
||||
self._current_query_binary_hash = None
|
||||
|
||||
def on_react_plan_approved(self):
|
||||
if not self._react_plan_pending or not self._pending_react_plan:
|
||||
return
|
||||
self._react_plan_pending = False
|
||||
self.view.set_pending_plan(None)
|
||||
if self.current_chat_id and self._current_query_binary_hash:
|
||||
self.transcript_service.append_iteration_notice(
|
||||
self._current_query_binary_hash,
|
||||
str(self.current_chat_id),
|
||||
"Approved agentic investigation plan",
|
||||
metadata={"category": "plan_approved"},
|
||||
metadata_extra={
|
||||
"react_run_id": self._current_react_run_id,
|
||||
"react_objective": self._current_react_objective,
|
||||
"agent_phase": "plan_approved",
|
||||
},
|
||||
)
|
||||
|
||||
provider = self.llm_factory.create_provider(self._pending_react_provider_config)
|
||||
self._start_react_analysis(
|
||||
self._current_react_objective or "",
|
||||
self._pending_react_initial_context,
|
||||
provider,
|
||||
self._pending_react_mcp_tools,
|
||||
self._pending_react_config or ReActConfig(max_iterations=15, reflection_enabled=True),
|
||||
approved_plan=self._pending_react_plan,
|
||||
)
|
||||
|
||||
def _submit_agentic_query(self, query_text: str):
|
||||
"""Handle agentic (ReAct) query submission"""
|
||||
log.log_info(f"Agentic query submitted: {query_text[:50]}...")
|
||||
@@ -3625,12 +3802,11 @@ Tool Usage Guidelines:
|
||||
if self.current_chat_id is None:
|
||||
self.new_chat()
|
||||
|
||||
# Set active state
|
||||
self._react_active = True
|
||||
# Set planning state. Tools are not initialized/executed until plan approval.
|
||||
self._react_plan_pending = False
|
||||
self._current_react_run_id = react_run_id
|
||||
self._current_react_objective = query_text
|
||||
self._current_query_binary_hash = self._get_current_binary_hash()
|
||||
self.view.set_query_running(True)
|
||||
self._active_stream_markdown = ""
|
||||
self._stream_base_html = ""
|
||||
self._context_snapshot = self._build_context_snapshot()
|
||||
@@ -3678,22 +3854,21 @@ Tool Usage Guidelines:
|
||||
+ initial_context
|
||||
)
|
||||
|
||||
# MCP tools are required for agentic mode
|
||||
self.mcp_connection_manager.ensure_connections()
|
||||
mcp_tools = self.mcp_connection_manager.get_available_tools_for_llm()
|
||||
log.log_info(f"Agentic mode with {len(mcp_tools)} tools available")
|
||||
|
||||
# Create provider instance
|
||||
provider = self.llm_factory.create_provider(active_provider)
|
||||
|
||||
# Create ReAct config
|
||||
config = ReActConfig(
|
||||
max_iterations=15,
|
||||
reflection_enabled=True
|
||||
)
|
||||
|
||||
# Start ReAct thread
|
||||
self._start_react_analysis(query_text, initial_context, provider, mcp_tools, config)
|
||||
self._pending_react_initial_context = initial_context
|
||||
self._pending_react_provider_config = active_provider
|
||||
self._pending_react_provider_type = provider_type
|
||||
self._pending_react_mcp_tools = []
|
||||
self._pending_react_config = config
|
||||
self._pending_react_refinements = []
|
||||
self._pending_react_plan = None
|
||||
|
||||
self._start_react_planning(query_text, initial_context, active_provider)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Exception in submit_agentic_query: {str(e)}"
|
||||
@@ -3716,9 +3891,17 @@ Current Offset: {context.get('offset_hex', 'N/A')}"""
|
||||
|
||||
def _start_react_analysis(self, objective: str, initial_context: str,
|
||||
provider, mcp_tools: List[Dict[str, Any]],
|
||||
config: ReActConfig):
|
||||
config: ReActConfig,
|
||||
approved_plan: Optional[str] = None):
|
||||
"""Start the ReAct analysis thread"""
|
||||
log.log_info("Starting ReAct analysis thread")
|
||||
self._react_active = True
|
||||
self.view.set_query_running(True)
|
||||
|
||||
if not mcp_tools:
|
||||
self.mcp_connection_manager.ensure_connections()
|
||||
mcp_tools = self.mcp_connection_manager.get_available_tools_for_llm()
|
||||
log.log_info(f"Agentic mode with {len(mcp_tools)} tools available")
|
||||
|
||||
# Reset streaming state for ReAct analysis
|
||||
self._reasoning_filter.reset()
|
||||
@@ -3738,7 +3921,8 @@ Current Offset: {context.get('offset_hex', 'N/A')}"""
|
||||
llm_provider=provider,
|
||||
mcp_orchestrator=self.mcp_orchestrator,
|
||||
mcp_tools=mcp_tools,
|
||||
config=config
|
||||
config=config,
|
||||
approved_plan=approved_plan
|
||||
)
|
||||
|
||||
# Connect signals
|
||||
@@ -3941,6 +4125,8 @@ Current Offset: {context.get('offset_hex', 'N/A')}"""
|
||||
self._current_react_run_id = None
|
||||
self._current_react_objective = None
|
||||
self._current_react_final_status = None
|
||||
self._pending_react_plan = None
|
||||
self._pending_react_provider_config = None
|
||||
self._current_query_binary_hash = None
|
||||
self.view.set_query_running(False)
|
||||
|
||||
@@ -3970,6 +4156,8 @@ Current Offset: {context.get('offset_hex', 'N/A')}"""
|
||||
self._current_react_run_id = None
|
||||
self._current_react_objective = None
|
||||
self._current_react_final_status = None
|
||||
self._pending_react_plan = None
|
||||
self._pending_react_provider_config = None
|
||||
self._current_query_binary_hash = None
|
||||
self.view.set_query_running(False)
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ class ReActOrchestratorThread(QThread):
|
||||
mcp_orchestrator,
|
||||
mcp_tools: List[Dict[str, Any]],
|
||||
config: ReActConfig = None,
|
||||
approved_plan: Optional[str] = None,
|
||||
parent=None):
|
||||
"""
|
||||
Initialize the ReAct orchestrator thread.
|
||||
@@ -64,6 +65,7 @@ class ReActOrchestratorThread(QThread):
|
||||
self.mcp_orchestrator = mcp_orchestrator
|
||||
self.mcp_tools = mcp_tools
|
||||
self.config = config or ReActConfig()
|
||||
self.approved_plan = approved_plan
|
||||
self.cancelled = False
|
||||
self._orchestrator: Optional[ReActOrchestrator] = None
|
||||
|
||||
@@ -123,7 +125,8 @@ class ReActOrchestratorThread(QThread):
|
||||
# Run analysis
|
||||
result = await self._orchestrator.analyze(
|
||||
self.objective,
|
||||
self.initial_context
|
||||
self.initial_context,
|
||||
approved_plan=self.approved_plan,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@@ -116,7 +116,8 @@ class ReActOrchestrator:
|
||||
|
||||
self.conversation_history = cleaned_history
|
||||
|
||||
async def analyze(self, objective: str, initial_context: str = "") -> ReActResult:
|
||||
async def analyze(self, objective: str, initial_context: str = "",
|
||||
approved_plan: Optional[str] = None) -> ReActResult:
|
||||
"""
|
||||
Run ReAct analysis for the given objective.
|
||||
|
||||
@@ -144,11 +145,16 @@ class ReActOrchestrator:
|
||||
log.log_info(f"ReActOrchestrator: Starting analysis for: {objective[:50]}...")
|
||||
|
||||
try:
|
||||
# Phase 1: Planning
|
||||
if self.on_progress:
|
||||
self.on_progress("Planning investigation...", 0)
|
||||
|
||||
await self._run_planning(objective, initial_context)
|
||||
# Phase 1: Planning. If the controller already got explicit user approval,
|
||||
# seed the todo manager from that plan and skip a second planning call.
|
||||
if approved_plan:
|
||||
self.todo_manager.initialize_from_llm_response(approved_plan)
|
||||
if self.on_todos_updated:
|
||||
self.on_todos_updated(self.todo_manager.to_transcript_snapshot())
|
||||
else:
|
||||
if self.on_progress:
|
||||
self.on_progress("Planning investigation...", 0)
|
||||
await self._run_planning(objective, initial_context)
|
||||
|
||||
if self.cancelled:
|
||||
return ReActResult.cancelled()
|
||||
|
||||
+73
-17
@@ -81,6 +81,8 @@ class QueryTabView(QWidget):
|
||||
agentic_enabled_changed = Signal(bool)
|
||||
accept_all_tools_changed = Signal(bool)
|
||||
approval_decision_requested = Signal(str)
|
||||
plan_approved_requested = Signal()
|
||||
plan_cancel_requested = Signal()
|
||||
transcript_link_clicked = Signal(str)
|
||||
history_entry_upsert_requested = Signal(int, str, str, str)
|
||||
# RLHF feedback signals
|
||||
@@ -111,6 +113,7 @@ class QueryTabView(QWidget):
|
||||
# Main text widget - HTML browser/Markdown editor
|
||||
self.create_main_text_widget()
|
||||
|
||||
self.create_plan_panel()
|
||||
self.create_approval_panel()
|
||||
|
||||
# History table
|
||||
@@ -122,12 +125,13 @@ class QueryTabView(QWidget):
|
||||
# Add widgets to splitter
|
||||
self.splitter.addWidget(self.query_browser)
|
||||
self.splitter.addWidget(self.query_editor)
|
||||
self.splitter.addWidget(self.plan_panel)
|
||||
self.splitter.addWidget(self.approval_panel)
|
||||
self.splitter.addWidget(self.history_table)
|
||||
self.splitter.addWidget(self.input_widget)
|
||||
|
||||
# Set initial splitter sizes (give more space to main text and input)
|
||||
self.splitter.setSizes([400, 400, 0, 80, 100])
|
||||
self.splitter.setSizes([400, 400, 0, 0, 80, 100])
|
||||
|
||||
layout.addWidget(self.splitter)
|
||||
|
||||
@@ -343,9 +347,10 @@ class QueryTabView(QWidget):
|
||||
|
||||
# Save history and input panel sizes before the swap
|
||||
old_sizes = self.splitter.sizes()
|
||||
approval_size = old_sizes[2] if len(old_sizes) >= 3 else 0
|
||||
history_size = old_sizes[3] if len(old_sizes) >= 4 else 80
|
||||
input_size = old_sizes[4] if len(old_sizes) >= 5 else 100
|
||||
plan_size = old_sizes[2] if len(old_sizes) >= 3 else 0
|
||||
approval_size = old_sizes[3] if len(old_sizes) >= 4 else 0
|
||||
history_size = old_sizes[4] if len(old_sizes) >= 5 else 80
|
||||
input_size = old_sizes[5] if len(old_sizes) >= 6 else 100
|
||||
|
||||
if self.is_edit_mode:
|
||||
self.query_browser.hide()
|
||||
@@ -364,11 +369,11 @@ class QueryTabView(QWidget):
|
||||
|
||||
# Restore: give all remaining space to the active text widget
|
||||
total = sum(self.splitter.sizes())
|
||||
active_size = total - approval_size - history_size - input_size
|
||||
active_size = total - plan_size - approval_size - history_size - input_size
|
||||
if self.is_edit_mode:
|
||||
self.splitter.setSizes([0, active_size, approval_size, history_size, input_size])
|
||||
self.splitter.setSizes([0, active_size, plan_size, approval_size, history_size, input_size])
|
||||
else:
|
||||
self.splitter.setSizes([active_size, 0, approval_size, history_size, input_size])
|
||||
self.splitter.setSizes([active_size, 0, plan_size, approval_size, history_size, input_size])
|
||||
|
||||
self.edit_mode_changed.emit(self.is_edit_mode)
|
||||
|
||||
@@ -378,15 +383,16 @@ class QueryTabView(QWidget):
|
||||
return
|
||||
self.is_edit_mode = False
|
||||
old_sizes = self.splitter.sizes()
|
||||
approval_size = old_sizes[2] if len(old_sizes) >= 3 else 0
|
||||
history_size = old_sizes[3] if len(old_sizes) >= 4 else 80
|
||||
input_size = old_sizes[4] if len(old_sizes) >= 5 else 100
|
||||
plan_size = old_sizes[2] if len(old_sizes) >= 3 else 0
|
||||
approval_size = old_sizes[3] if len(old_sizes) >= 4 else 0
|
||||
history_size = old_sizes[4] if len(old_sizes) >= 5 else 80
|
||||
input_size = old_sizes[5] if len(old_sizes) >= 6 else 100
|
||||
self.query_editor.hide()
|
||||
self.query_browser.show()
|
||||
self.edit_save_button.setText("Edit")
|
||||
total = sum(self.splitter.sizes())
|
||||
active_size = total - approval_size - history_size - input_size
|
||||
self.splitter.setSizes([active_size, 0, approval_size, history_size, input_size])
|
||||
active_size = total - plan_size - approval_size - history_size - input_size
|
||||
self.splitter.setSizes([active_size, 0, plan_size, approval_size, history_size, input_size])
|
||||
|
||||
def on_submit_clicked(self):
|
||||
"""Handle submit button click - toggles between submit and stop"""
|
||||
@@ -696,17 +702,28 @@ class QueryTabView(QWidget):
|
||||
self.approval_panel.show()
|
||||
self._restore_splitter_after_approval_change(approval_visible=True)
|
||||
|
||||
def set_pending_plan(self, plan_text: str = None):
|
||||
if not plan_text:
|
||||
self.plan_panel.hide()
|
||||
self.plan_preview.setPlainText("")
|
||||
self._restore_splitter_after_approval_change()
|
||||
return
|
||||
self.plan_preview.setPlainText(plan_text)
|
||||
self.plan_panel.show()
|
||||
self._restore_splitter_after_approval_change()
|
||||
|
||||
def _restore_splitter_after_approval_change(self, approval_visible: bool = False):
|
||||
sizes = self.splitter.sizes()
|
||||
if len(sizes) < 5:
|
||||
if len(sizes) < 6:
|
||||
return
|
||||
plan_size = 170 if self.plan_panel.isVisible() else 0
|
||||
approval_size = 96 if approval_visible else 0
|
||||
remaining = sum(sizes) - approval_size
|
||||
remaining = sum(sizes) - plan_size - approval_size
|
||||
active_index = 1 if self.is_edit_mode else 0
|
||||
history_size = sizes[3]
|
||||
input_size = sizes[4]
|
||||
history_size = sizes[4]
|
||||
input_size = sizes[5]
|
||||
active_size = max(0, remaining - history_size - input_size)
|
||||
updated = [0, 0, approval_size, history_size, input_size]
|
||||
updated = [0, 0, plan_size, approval_size, history_size, input_size]
|
||||
updated[active_index] = active_size
|
||||
self.splitter.setSizes(updated)
|
||||
|
||||
@@ -843,3 +860,42 @@ class QueryTabView(QWidget):
|
||||
self.rlhf_feedback_requested.emit(False)
|
||||
elif ":" in url_str:
|
||||
self.transcript_link_clicked.emit(url_str)
|
||||
def create_plan_panel(self):
|
||||
self.plan_panel = QWidget()
|
||||
self.plan_panel.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
||||
self.plan_panel.setStyleSheet(
|
||||
"QWidget { border: 1px solid #4f8cc9; background: #303842; }"
|
||||
"QLabel { border: none; color: #d8dee9; }"
|
||||
"QPlainTextEdit { border: none; background: #2f3439; color: #d8dee9; padding: 4px; }"
|
||||
"QPushButton { margin: 3px; padding: 3px 8px; }"
|
||||
)
|
||||
panel_layout = QVBoxLayout()
|
||||
panel_layout.setContentsMargins(6, 5, 6, 5)
|
||||
panel_layout.setSpacing(4)
|
||||
|
||||
self.plan_summary_label = QLabel("Review the proposed investigation plan. Submit refinement details below, or approve to run.")
|
||||
self.plan_summary_label.setWordWrap(True)
|
||||
self.plan_summary_label.setStyleSheet("font-size: 11px;")
|
||||
panel_layout.addWidget(self.plan_summary_label)
|
||||
|
||||
self.plan_preview = QPlainTextEdit()
|
||||
self.plan_preview.setReadOnly(True)
|
||||
self.plan_preview.setMaximumBlockCount(300)
|
||||
self.plan_preview.setMaximumHeight(130)
|
||||
self.plan_preview.setStyleSheet("font-family: monospace; font-size: 11px;")
|
||||
panel_layout.addWidget(self.plan_preview)
|
||||
|
||||
button_row = QHBoxLayout()
|
||||
button_row.setContentsMargins(0, 0, 0, 0)
|
||||
button_row.setSpacing(4)
|
||||
self.approve_plan_button = QPushButton("Approve Plan")
|
||||
self.cancel_plan_button = QPushButton("Cancel")
|
||||
self.approve_plan_button.clicked.connect(self.plan_approved_requested.emit)
|
||||
self.cancel_plan_button.clicked.connect(self.plan_cancel_requested.emit)
|
||||
button_row.addWidget(self.approve_plan_button)
|
||||
button_row.addWidget(self.cancel_plan_button)
|
||||
button_row.addStretch()
|
||||
panel_layout.addLayout(button_row)
|
||||
|
||||
self.plan_panel.setLayout(panel_layout)
|
||||
self.plan_panel.hide()
|
||||
|
||||
Reference in New Issue
Block a user