Spaces:
Running
Running
Centralize agent type registry into single source of truth
Browse filesReplace ~17 scattered type definitions across 4 files with a unified
AGENT_REGISTRY on both backend (agents.py) and frontend (script.js).
Adding a new agent now only requires one registry entry per side.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- backend/agents.py +350 -0
- backend/command.py +8 -97
- backend/main.py +11 -140
- frontend/index.html +6 -24
- frontend/script.js +110 -82
backend/agents.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Centralized Agent Type Registry.
|
| 3 |
+
|
| 4 |
+
Adding a new agent type:
|
| 5 |
+
1. Add an entry to AGENT_REGISTRY below
|
| 6 |
+
2. Implement its streaming handler (or use "builtin:chat" for simple LLM proxy)
|
| 7 |
+
3. Add the same entry to AGENT_REGISTRY in frontend/script.js
|
| 8 |
+
|
| 9 |
+
SSE Event Protocol — every handler emits `data: {JSON}\\n\\n` lines with a `type` field.
|
| 10 |
+
|
| 11 |
+
Required events (all agents):
|
| 12 |
+
- done : stream complete
|
| 13 |
+
- error : { content: str } error message
|
| 14 |
+
|
| 15 |
+
Common optional events:
|
| 16 |
+
- thinking : { content: str } reasoning text
|
| 17 |
+
- content : { content: str } streamed response tokens
|
| 18 |
+
- result_preview: { content: str, figures: dict } inline result with optional images
|
| 19 |
+
- result : { content: str, figures: dict } final output for command center widget
|
| 20 |
+
- generating : still working between turns
|
| 21 |
+
- retry : { attempt, max_attempts, delay, message } retrying after error
|
| 22 |
+
|
| 23 |
+
Code-specific events:
|
| 24 |
+
- code_start : { code: str } before code execution
|
| 25 |
+
- code : { output, error, images } code cell result
|
| 26 |
+
- upload : { paths, output } files uploaded to sandbox
|
| 27 |
+
- download : { paths, output } files downloaded from sandbox
|
| 28 |
+
|
| 29 |
+
Research-specific events:
|
| 30 |
+
- status : { message } progress text
|
| 31 |
+
- queries : { queries: list, iteration: int } search queries generated
|
| 32 |
+
- source : { url, title, query_index, ... } source found
|
| 33 |
+
- query_stats : { query_index, relevant_count, irrelevant_count, error_count }
|
| 34 |
+
- assessment : { sufficient, missing_aspects, findings_count, reasoning }
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
# ============================================================
|
| 38 |
+
# THE REGISTRY — single source of truth for all agent types
|
| 39 |
+
# ============================================================
|
| 40 |
+
|
| 41 |
+
AGENT_REGISTRY = {
|
| 42 |
+
"command": {
|
| 43 |
+
"label": "TASKS",
|
| 44 |
+
"system_prompt": (
|
| 45 |
+
"You are a helpful AI assistant in the Productive interface command center.\n\n"
|
| 46 |
+
"{tools_section}\n\n"
|
| 47 |
+
"When a user asks you to perform a task that would benefit from a specialized notebook, you can:\n"
|
| 48 |
+
"1. Briefly acknowledge the request\n"
|
| 49 |
+
"2. Use the appropriate tool to launch a notebook with the task\n\n"
|
| 50 |
+
"You can also answer questions directly without launching a notebook if appropriate.\n\n"
|
| 51 |
+
"Examples:\n"
|
| 52 |
+
'- User: "Can you help me analyze this CSV file?"\n'
|
| 53 |
+
" You: Use launch_code_notebook tool with the task\n\n"
|
| 54 |
+
'- User: "Research the latest developments in AI"\n'
|
| 55 |
+
" You: Use launch_research_notebook tool with the topic\n\n"
|
| 56 |
+
'- User: "What was the result of the research in 2 sentences?"\n'
|
| 57 |
+
" You: Summarize the research results without using tools\n\n"
|
| 58 |
+
"Be concise and helpful. Don't duplicate effort - either answer directly OR launch a notebook, not both. "
|
| 59 |
+
"Answer questions about results directly without launching new notebooks.\n\n"
|
| 60 |
+
"IMPORTANT guidelines when delegating to notebooks:\n"
|
| 61 |
+
"- Do NOT ask notebooks to save or create files unless the user explicitly requests it or implicitly necessary to solve a task.\n"
|
| 62 |
+
"- NEVER overwrite existing files without explicit user permission.\n"
|
| 63 |
+
"- Each notebook has a task_id. If a a new task is clearly related to a existing notebook "
|
| 64 |
+
"re-use the task id to reuse the notebook. This will reuse the existing context and also the jupyter kernel for code notebooks."
|
| 65 |
+
),
|
| 66 |
+
"tool": None,
|
| 67 |
+
"tool_arg": None,
|
| 68 |
+
"has_counter": False,
|
| 69 |
+
"in_menu": False,
|
| 70 |
+
"in_launcher": False,
|
| 71 |
+
"placeholder": "Enter message...",
|
| 72 |
+
},
|
| 73 |
+
|
| 74 |
+
"agent": {
|
| 75 |
+
"label": "AGENT",
|
| 76 |
+
"system_prompt": (
|
| 77 |
+
"You are an autonomous agent assistant specialized in breaking down and executing multi-step tasks.\n\n"
|
| 78 |
+
"Your role is to:\n"
|
| 79 |
+
"- Understand complex tasks and break them down into clear steps\n"
|
| 80 |
+
"- Execute tasks methodically\n"
|
| 81 |
+
"- Keep track of progress and next steps\n"
|
| 82 |
+
"- Provide clear status updates\n\n"
|
| 83 |
+
"Focus on being proactive, organized, and thorough in completing multi-step workflows.\n"
|
| 84 |
+
),
|
| 85 |
+
"tool": {
|
| 86 |
+
"type": "function",
|
| 87 |
+
"function": {
|
| 88 |
+
"name": "launch_agent_notebook",
|
| 89 |
+
"description": "Launch an autonomous agent notebook for multi-step tasks that need planning and execution. Use this for complex workflows, task organization, or anything requiring multiple coordinated steps.",
|
| 90 |
+
"parameters": {
|
| 91 |
+
"type": "object",
|
| 92 |
+
"properties": {
|
| 93 |
+
"task": {
|
| 94 |
+
"type": "string",
|
| 95 |
+
"description": "The task or instruction for the agent. Should contain all necessary context."
|
| 96 |
+
},
|
| 97 |
+
"task_id": {
|
| 98 |
+
"type": "string",
|
| 99 |
+
"description": "A 2-3 word summary of the task, separated by dashes."
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"required": ["task", "task_id"]
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
},
|
| 106 |
+
"tool_arg": "task",
|
| 107 |
+
"has_counter": True,
|
| 108 |
+
"in_menu": False,
|
| 109 |
+
"in_launcher": True,
|
| 110 |
+
"placeholder": "Enter message...",
|
| 111 |
+
},
|
| 112 |
+
|
| 113 |
+
"code": {
|
| 114 |
+
"label": "CODE",
|
| 115 |
+
"system_prompt": (
|
| 116 |
+
"You are a coding assistant with access to a Python code execution environment.\n\n"
|
| 117 |
+
"Your role is to:\n"
|
| 118 |
+
"- Write and execute Python code to solve problems\n"
|
| 119 |
+
"- Analyze data and answer questions\n"
|
| 120 |
+
"- Debug code and explain errors\n"
|
| 121 |
+
"- Break down complex tasks into executable steps\n\n"
|
| 122 |
+
"You have access to a Jupyter kernel with these packages:\n"
|
| 123 |
+
"pandas, matplotlib, numpy, scipy, scikit-learn, seaborn, plotly, requests, beautifulsoup4, and more.\n\n"
|
| 124 |
+
"You have three tools available:\n"
|
| 125 |
+
"- execute_code: Run Python code. The execution environment is stateful - variables and imports persist between calls.\n"
|
| 126 |
+
"- upload_files: Upload files from the workspace to the execution environment for analysis. "
|
| 127 |
+
"Files will be available at /home/user/<filename>. Use this when you need to analyze data files, scripts, or other files from the project.\n"
|
| 128 |
+
"- download_files: Download files from the execution environment to the workspace. "
|
| 129 |
+
"ONLY use this when the user explicitly asks to save/download files, or when saving files is clearly part of the task "
|
| 130 |
+
'(e.g., "generate a dataset and save it"). Do NOT automatically save intermediate files, plots, or outputs unless requested.\n\n'
|
| 131 |
+
"## IMPORTANT Guidelines\n\n"
|
| 132 |
+
"**Only create artifacts when explicitly requested:**\n"
|
| 133 |
+
"- Do NOT save files unless the user explicitly asks to save/export/download\n"
|
| 134 |
+
"- NEVER overwrite existing files without explicit user permission - always ask first or use a new filename\n\n"
|
| 135 |
+
"When solving problems:\n"
|
| 136 |
+
"1. Break down the task into logical steps\n"
|
| 137 |
+
"2. Execute code incrementally\n"
|
| 138 |
+
"3. Check outputs before proceeding\n"
|
| 139 |
+
"4. Only create visualizations if explicitly requested\n\n"
|
| 140 |
+
"IMPORTANT: When you DO generate plots/figures (only when requested), they are automatically named as figure_1, figure_2, etc. "
|
| 141 |
+
'The execution output will show which figures were created (e.g., "[Generated figures: figure_1, figure_2]").\n\n'
|
| 142 |
+
"## CRITICAL: You MUST provide a <result> tag\n\n"
|
| 143 |
+
"When you have completed the task, you MUST provide a brief summary using the <result> tag. "
|
| 144 |
+
"This is REQUIRED - without it, your work will not be visible in the command center.\n\n"
|
| 145 |
+
"Keep results SHORT - 1-2 sentences max. Do NOT list implementation details, styling choices, or technical specifics. "
|
| 146 |
+
"The user can see the code and output above. Just state what was done.\n\n"
|
| 147 |
+
"To include figures, use self-closing tags like <figure_1> (NOT </figure_1> or <figure_1></figure_1>).\n\n"
|
| 148 |
+
"Example:\n"
|
| 149 |
+
"<result>\n"
|
| 150 |
+
"Here's the sine function plot:\n\n"
|
| 151 |
+
"<figure_1>\n"
|
| 152 |
+
"</result>\n"
|
| 153 |
+
),
|
| 154 |
+
"tool": {
|
| 155 |
+
"type": "function",
|
| 156 |
+
"function": {
|
| 157 |
+
"name": "launch_code_notebook",
|
| 158 |
+
"description": "Launch a code notebook with Python execution environment. Use this for data analysis, creating visualizations, running code, debugging, or anything involving programming.",
|
| 159 |
+
"parameters": {
|
| 160 |
+
"type": "object",
|
| 161 |
+
"properties": {
|
| 162 |
+
"task": {
|
| 163 |
+
"type": "string",
|
| 164 |
+
"description": "The coding task or question. Should contain all necessary context."
|
| 165 |
+
},
|
| 166 |
+
"task_id": {
|
| 167 |
+
"type": "string",
|
| 168 |
+
"description": "A 2-3 word summary of the task, separated by dashes."
|
| 169 |
+
}
|
| 170 |
+
},
|
| 171 |
+
"required": ["task", "task_id"]
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
},
|
| 175 |
+
"tool_arg": "task",
|
| 176 |
+
"has_counter": True,
|
| 177 |
+
"in_menu": True,
|
| 178 |
+
"in_launcher": True,
|
| 179 |
+
"placeholder": "Enter message...",
|
| 180 |
+
},
|
| 181 |
+
|
| 182 |
+
"research": {
|
| 183 |
+
"label": "RESEARCH",
|
| 184 |
+
"system_prompt": (
|
| 185 |
+
"You are a research assistant specialized in deep analysis and information gathering.\n\n"
|
| 186 |
+
"Your role is to:\n"
|
| 187 |
+
"- Conduct thorough research on topics\n"
|
| 188 |
+
"- Synthesize information from multiple sources\n"
|
| 189 |
+
"- Provide well-structured, evidence-based answers\n"
|
| 190 |
+
"- Identify key insights and trends\n\n"
|
| 191 |
+
"When presenting your final research report:\n"
|
| 192 |
+
"1. Be CONCISE - focus on key findings, not lengthy explanations\n"
|
| 193 |
+
"2. Use TABLES wherever possible to structure information clearly\n"
|
| 194 |
+
"3. Use markdown table syntax for comparisons, lists of facts, statistics, etc.\n"
|
| 195 |
+
"4. Example table format:\n"
|
| 196 |
+
" | Category | Details |\n"
|
| 197 |
+
" |----------|--------|\n"
|
| 198 |
+
" | Item 1 | Data |\n"
|
| 199 |
+
" | Item 2 | Data |\n\n"
|
| 200 |
+
"5. Only use prose for context and synthesis that can't be tabulated\n"
|
| 201 |
+
'6. DO NOT include a title or heading like "Research Report:" - start directly with your findings\n\n'
|
| 202 |
+
"When you have completed your research, wrap your final report in <result> tags:\n\n"
|
| 203 |
+
"<result>\n"
|
| 204 |
+
"Your concise, table-based report here (NO title/heading)\n"
|
| 205 |
+
"</result>\n\n"
|
| 206 |
+
"The report will be sent back to the main interface.\n\n"
|
| 207 |
+
"Focus on being comprehensive, analytical, and well-sourced in your research.\n"
|
| 208 |
+
),
|
| 209 |
+
"tool": {
|
| 210 |
+
"type": "function",
|
| 211 |
+
"function": {
|
| 212 |
+
"name": "launch_research_notebook",
|
| 213 |
+
"description": "Launch a research notebook for deep analysis requiring web search. Use this for researching topics, gathering information from multiple sources, or analyzing current information.",
|
| 214 |
+
"parameters": {
|
| 215 |
+
"type": "object",
|
| 216 |
+
"properties": {
|
| 217 |
+
"topic": {
|
| 218 |
+
"type": "string",
|
| 219 |
+
"description": "The research topic or question. Should be clear and specific."
|
| 220 |
+
},
|
| 221 |
+
"task_id": {
|
| 222 |
+
"type": "string",
|
| 223 |
+
"description": "A 2-3 word summary of the research topic, separated by dashes."
|
| 224 |
+
}
|
| 225 |
+
},
|
| 226 |
+
"required": ["topic", "task_id"]
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
},
|
| 230 |
+
"tool_arg": "topic",
|
| 231 |
+
"has_counter": True,
|
| 232 |
+
"in_menu": True,
|
| 233 |
+
"in_launcher": True,
|
| 234 |
+
"placeholder": "Enter message...",
|
| 235 |
+
},
|
| 236 |
+
|
| 237 |
+
"chat": {
|
| 238 |
+
"label": "CHAT",
|
| 239 |
+
"system_prompt": (
|
| 240 |
+
"You are a conversational AI assistant.\n\n"
|
| 241 |
+
"Your role is to:\n"
|
| 242 |
+
"- Engage in natural, helpful conversation\n"
|
| 243 |
+
"- Answer questions clearly and concisely\n"
|
| 244 |
+
"- Provide thoughtful responses\n"
|
| 245 |
+
"- Be friendly and approachable\n\n"
|
| 246 |
+
"Focus on being conversational, helpful, and easy to understand.\n"
|
| 247 |
+
),
|
| 248 |
+
"tool": {
|
| 249 |
+
"type": "function",
|
| 250 |
+
"function": {
|
| 251 |
+
"name": "launch_chat_notebook",
|
| 252 |
+
"description": "Launch a conversational chat notebook for extended back-and-forth discussion. Use this when the user wants to continue a conversation in a dedicated space.",
|
| 253 |
+
"parameters": {
|
| 254 |
+
"type": "object",
|
| 255 |
+
"properties": {
|
| 256 |
+
"message": {
|
| 257 |
+
"type": "string",
|
| 258 |
+
"description": "The initial message or context for the chat."
|
| 259 |
+
},
|
| 260 |
+
"task_id": {
|
| 261 |
+
"type": "string",
|
| 262 |
+
"description": "A 2-3 word summary of the conversation topic, separated by dashes."
|
| 263 |
+
}
|
| 264 |
+
},
|
| 265 |
+
"required": ["message", "task_id"]
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
},
|
| 269 |
+
"tool_arg": "message",
|
| 270 |
+
"has_counter": True,
|
| 271 |
+
"in_menu": True,
|
| 272 |
+
"in_launcher": True,
|
| 273 |
+
"placeholder": "Enter message...",
|
| 274 |
+
},
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
# ============================================================
|
| 279 |
+
# Derived helpers — replace scattered dicts across the codebase
|
| 280 |
+
# ============================================================
|
| 281 |
+
|
| 282 |
+
def get_system_prompt(agent_key: str) -> str:
|
| 283 |
+
"""Get system prompt for an agent type."""
|
| 284 |
+
agent = AGENT_REGISTRY.get(agent_key)
|
| 285 |
+
if not agent:
|
| 286 |
+
return ""
|
| 287 |
+
prompt = agent["system_prompt"]
|
| 288 |
+
# For command center, fill in the tools section dynamically
|
| 289 |
+
if "{tools_section}" in prompt:
|
| 290 |
+
prompt = prompt.replace("{tools_section}", _build_tools_section())
|
| 291 |
+
return prompt
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def get_tools() -> list:
|
| 295 |
+
"""Get tool definitions for the command center (replaces TOOLS in command.py)."""
|
| 296 |
+
return [
|
| 297 |
+
agent["tool"]
|
| 298 |
+
for agent in AGENT_REGISTRY.values()
|
| 299 |
+
if agent["tool"] is not None
|
| 300 |
+
]
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def get_notebook_type_map() -> dict:
|
| 304 |
+
"""Map tool function names to agent keys (replaces notebook_type_map in command.py)."""
|
| 305 |
+
result = {}
|
| 306 |
+
for key, agent in AGENT_REGISTRY.items():
|
| 307 |
+
if agent["tool"] is not None:
|
| 308 |
+
func_name = agent["tool"]["function"]["name"]
|
| 309 |
+
result[func_name] = key
|
| 310 |
+
return result
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def get_tool_arg(agent_key: str) -> str:
|
| 314 |
+
"""Get the argument name for extracting the initial message from tool call args."""
|
| 315 |
+
agent = AGENT_REGISTRY.get(agent_key)
|
| 316 |
+
return agent["tool_arg"] if agent else "task"
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def get_default_counters() -> dict:
|
| 320 |
+
"""Get default notebook counters (replaces hardcoded dict in get_default_workspace)."""
|
| 321 |
+
return {
|
| 322 |
+
key: 0
|
| 323 |
+
for key, agent in AGENT_REGISTRY.items()
|
| 324 |
+
if agent["has_counter"]
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def get_registry_for_frontend() -> list:
|
| 329 |
+
"""Serialize registry metadata for the frontend /api/agents endpoint."""
|
| 330 |
+
return [
|
| 331 |
+
{
|
| 332 |
+
"key": key,
|
| 333 |
+
"label": agent["label"],
|
| 334 |
+
"hasCounter": agent["has_counter"],
|
| 335 |
+
"inMenu": agent["in_menu"],
|
| 336 |
+
"inLauncher": agent["in_launcher"],
|
| 337 |
+
"placeholder": agent["placeholder"],
|
| 338 |
+
}
|
| 339 |
+
for key, agent in AGENT_REGISTRY.items()
|
| 340 |
+
]
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def _build_tools_section() -> str:
|
| 344 |
+
"""Build the 'available tools' text for the command center system prompt."""
|
| 345 |
+
lines = ["You have access to tools that can launch specialized notebooks for different types of tasks:"]
|
| 346 |
+
for key, agent in AGENT_REGISTRY.items():
|
| 347 |
+
if agent["tool"] is not None:
|
| 348 |
+
tool_func = agent["tool"]["function"]
|
| 349 |
+
lines.append(f"- {tool_func['name']}: {tool_func['description']}")
|
| 350 |
+
return "\n".join(lines)
|
backend/command.py
CHANGED
|
@@ -9,93 +9,10 @@ from typing import List, Dict
|
|
| 9 |
|
| 10 |
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
-
# Tool definitions
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
"function": {
|
| 17 |
-
"name": "launch_agent_notebook",
|
| 18 |
-
"description": "Launch an autonomous agent notebook for multi-step tasks that need planning and execution. Use this for complex workflows, task organization, or anything requiring multiple coordinated steps.",
|
| 19 |
-
"parameters": {
|
| 20 |
-
"type": "object",
|
| 21 |
-
"properties": {
|
| 22 |
-
"task": {
|
| 23 |
-
"type": "string",
|
| 24 |
-
"description": "The task or instruction for the agent. Should contain all necessary context."
|
| 25 |
-
},
|
| 26 |
-
"task_id": {
|
| 27 |
-
"type": "string",
|
| 28 |
-
"description": "A 2-3 word summary of the task, separated by dashes."
|
| 29 |
-
}
|
| 30 |
-
},
|
| 31 |
-
"required": ["task", "task_id"]
|
| 32 |
-
}
|
| 33 |
-
}
|
| 34 |
-
},
|
| 35 |
-
{
|
| 36 |
-
"type": "function",
|
| 37 |
-
"function": {
|
| 38 |
-
"name": "launch_code_notebook",
|
| 39 |
-
"description": "Launch a code notebook with Python execution environment. Use this for data analysis, creating visualizations, running code, debugging, or anything involving programming.",
|
| 40 |
-
"parameters": {
|
| 41 |
-
"type": "object",
|
| 42 |
-
"properties": {
|
| 43 |
-
"task": {
|
| 44 |
-
"type": "string",
|
| 45 |
-
"description": "The coding task or question. Should contain all necessary context."
|
| 46 |
-
},
|
| 47 |
-
"task_id": {
|
| 48 |
-
"type": "string",
|
| 49 |
-
"description": "A 2-3 word summary of the task, separated by dashes."
|
| 50 |
-
}
|
| 51 |
-
},
|
| 52 |
-
"required": ["task", "task_id"]
|
| 53 |
-
}
|
| 54 |
-
}
|
| 55 |
-
},
|
| 56 |
-
{
|
| 57 |
-
"type": "function",
|
| 58 |
-
"function": {
|
| 59 |
-
"name": "launch_research_notebook",
|
| 60 |
-
"description": "Launch a research notebook for deep analysis requiring web search. Use this for researching topics, gathering information from multiple sources, or analyzing current information.",
|
| 61 |
-
"parameters": {
|
| 62 |
-
"type": "object",
|
| 63 |
-
"properties": {
|
| 64 |
-
"topic": {
|
| 65 |
-
"type": "string",
|
| 66 |
-
"description": "The research topic or question. Should be clear and specific."
|
| 67 |
-
},
|
| 68 |
-
"task_id": {
|
| 69 |
-
"type": "string",
|
| 70 |
-
"description": "A 2-3 word summary of the research topic, separated by dashes."
|
| 71 |
-
}
|
| 72 |
-
},
|
| 73 |
-
"required": ["topic", "task_id"]
|
| 74 |
-
}
|
| 75 |
-
}
|
| 76 |
-
},
|
| 77 |
-
{
|
| 78 |
-
"type": "function",
|
| 79 |
-
"function": {
|
| 80 |
-
"name": "launch_chat_notebook",
|
| 81 |
-
"description": "Launch a conversational chat notebook for extended back-and-forth discussion. Use this when the user wants to continue a conversation in a dedicated space.",
|
| 82 |
-
"parameters": {
|
| 83 |
-
"type": "object",
|
| 84 |
-
"properties": {
|
| 85 |
-
"message": {
|
| 86 |
-
"type": "string",
|
| 87 |
-
"description": "The initial message or context for the chat."
|
| 88 |
-
},
|
| 89 |
-
"task_id": {
|
| 90 |
-
"type": "string",
|
| 91 |
-
"description": "A 2-3 word summary of the conversation topic, separated by dashes."
|
| 92 |
-
}
|
| 93 |
-
},
|
| 94 |
-
"required": ["message", "task_id"]
|
| 95 |
-
}
|
| 96 |
-
}
|
| 97 |
-
}
|
| 98 |
-
]
|
| 99 |
|
| 100 |
MAX_TURNS = 10 # Limit conversation turns in command center
|
| 101 |
MAX_RETRIES = 3 # Maximum retries for LLM calls
|
|
@@ -219,19 +136,13 @@ def stream_command_center(client, model: str, messages: List[Dict], extra_params
|
|
| 219 |
yield {"type": "error", "content": "Failed to parse tool arguments"}
|
| 220 |
return
|
| 221 |
|
| 222 |
-
# Map function names to notebook types
|
| 223 |
-
notebook_type_map =
|
| 224 |
-
"launch_agent_notebook": "agent",
|
| 225 |
-
"launch_code_notebook": "code",
|
| 226 |
-
"launch_research_notebook": "research",
|
| 227 |
-
"launch_chat_notebook": "chat"
|
| 228 |
-
}
|
| 229 |
-
|
| 230 |
notebook_type = notebook_type_map.get(function_name)
|
| 231 |
|
| 232 |
if notebook_type:
|
| 233 |
-
# Get the message
|
| 234 |
-
initial_message = args.get(
|
| 235 |
task_id = args.get("task_id", "")
|
| 236 |
|
| 237 |
# Send launch action to frontend
|
|
|
|
| 9 |
|
| 10 |
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
+
# Tool definitions derived from agent registry
|
| 13 |
+
from agents import get_tools, get_notebook_type_map, get_tool_arg
|
| 14 |
+
|
| 15 |
+
TOOLS = get_tools()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
MAX_TURNS = 10 # Limit conversation turns in command center
|
| 18 |
MAX_RETRIES = 3 # Maximum retries for LLM calls
|
|
|
|
| 136 |
yield {"type": "error", "content": "Failed to parse tool arguments"}
|
| 137 |
return
|
| 138 |
|
| 139 |
+
# Map function names to notebook types (derived from registry)
|
| 140 |
+
notebook_type_map = get_notebook_type_map()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
notebook_type = notebook_type_map.get(function_name)
|
| 142 |
|
| 143 |
if notebook_type:
|
| 144 |
+
# Get the initial message using the registered arg name for this type
|
| 145 |
+
initial_message = args.get(get_tool_arg(notebook_type)) or args.get("task") or args.get("message")
|
| 146 |
task_id = args.get("task_id", "")
|
| 147 |
|
| 148 |
# Send launch action to frontend
|
backend/main.py
CHANGED
|
@@ -13,6 +13,7 @@ import signal
|
|
| 13 |
import sys
|
| 14 |
from datetime import datetime
|
| 15 |
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
| 16 |
|
| 17 |
# Thread pool for running sync generators without blocking the event loop
|
| 18 |
# Use daemon threads so they don't block shutdown
|
|
@@ -154,139 +155,7 @@ app.add_middleware(
|
|
| 154 |
allow_headers=["*"],
|
| 155 |
)
|
| 156 |
|
| 157 |
-
#
|
| 158 |
-
SYSTEM_PROMPTS = {
|
| 159 |
-
"command": """You are a helpful AI assistant in the Productive interface command center.
|
| 160 |
-
|
| 161 |
-
You have access to tools that can launch specialized notebooks for different types of tasks:
|
| 162 |
-
- launch_agent_notebook: For multi-step tasks that need planning and execution
|
| 163 |
-
- launch_code_notebook: For data analysis, visualizations, running Python code
|
| 164 |
-
- launch_research_notebook: For researching topics using web search
|
| 165 |
-
- launch_chat_notebook: For extended conversational discussions
|
| 166 |
-
|
| 167 |
-
When a user asks you to perform a task that would benefit from a specialized notebook, you can:
|
| 168 |
-
1. Briefly acknowledge the request
|
| 169 |
-
2. Use the appropriate tool to launch a notebook with the task
|
| 170 |
-
|
| 171 |
-
You can also answer questions directly without launching a notebook if appropriate.
|
| 172 |
-
|
| 173 |
-
Examples:
|
| 174 |
-
- User: "Can you help me analyze this CSV file?"
|
| 175 |
-
You: Use launch_code_notebook tool with the task
|
| 176 |
-
|
| 177 |
-
- User: "Research the latest developments in AI"
|
| 178 |
-
You: Use launch_research_notebook tool with the topic
|
| 179 |
-
|
| 180 |
-
- User: "What was the result of the research in 2 sentences?"
|
| 181 |
-
You: Summarize the research results without using tools
|
| 182 |
-
|
| 183 |
-
Be concise and helpful. Don't duplicate effort - either answer directly OR launch a notebook, not both. Answer questions about results directly without launching new notebooks.
|
| 184 |
-
|
| 185 |
-
IMPORTANT guidelines when delegating to notebooks:
|
| 186 |
-
- Do NOT ask notebooks to save or create files unless the user explicitly requests it or implicitly necessary to solve a task.
|
| 187 |
-
- NEVER overwrite existing files without explicit user permission.
|
| 188 |
-
- Each notebook has a task_id. If a a new task is clearly related to a existing notebook re-use the task id to reuse the notebook. This will reuse the existing context and also the jupyter kernel for code notebooks.""",
|
| 189 |
-
"agent": """You are an autonomous agent assistant specialized in breaking down and executing multi-step tasks.
|
| 190 |
-
|
| 191 |
-
Your role is to:
|
| 192 |
-
- Understand complex tasks and break them down into clear steps
|
| 193 |
-
- Execute tasks methodically
|
| 194 |
-
- Keep track of progress and next steps
|
| 195 |
-
- Provide clear status updates
|
| 196 |
-
|
| 197 |
-
Focus on being proactive, organized, and thorough in completing multi-step workflows.
|
| 198 |
-
""",
|
| 199 |
-
"code": """You are a coding assistant with access to a Python code execution environment.
|
| 200 |
-
|
| 201 |
-
Your role is to:
|
| 202 |
-
- Write and execute Python code to solve problems
|
| 203 |
-
- Analyze data and answer questions
|
| 204 |
-
- Debug code and explain errors
|
| 205 |
-
- Break down complex tasks into executable steps
|
| 206 |
-
|
| 207 |
-
You have access to a Jupyter kernel with these packages:
|
| 208 |
-
pandas, matplotlib, numpy, scipy, scikit-learn, seaborn, plotly, requests, beautifulsoup4, and more.
|
| 209 |
-
|
| 210 |
-
You have three tools available:
|
| 211 |
-
- execute_code: Run Python code. The execution environment is stateful - variables and imports persist between calls.
|
| 212 |
-
- upload_files: Upload files from the workspace to the execution environment for analysis. Files will be available at /home/user/<filename>. Use this when you need to analyze data files, scripts, or other files from the project.
|
| 213 |
-
- download_files: Download files from the execution environment to the workspace. ONLY use this when the user explicitly asks to save/download files, or when saving files is clearly part of the task (e.g., "generate a dataset and save it"). Do NOT automatically save intermediate files, plots, or outputs unless requested.
|
| 214 |
-
|
| 215 |
-
## IMPORTANT Guidelines
|
| 216 |
-
|
| 217 |
-
**Only create artifacts when explicitly requested:**
|
| 218 |
-
- Do NOT save files unless the user explicitly asks to save/export/download
|
| 219 |
-
- NEVER overwrite existing files without explicit user permission - always ask first or use a new filename
|
| 220 |
-
|
| 221 |
-
When solving problems:
|
| 222 |
-
1. Break down the task into logical steps
|
| 223 |
-
2. Execute code incrementally
|
| 224 |
-
3. Check outputs before proceeding
|
| 225 |
-
4. Only create visualizations if explicitly requested
|
| 226 |
-
|
| 227 |
-
IMPORTANT: When you DO generate plots/figures (only when requested), they are automatically named as figure_1, figure_2, etc. The execution output will show which figures were created (e.g., "[Generated figures: figure_1, figure_2]").
|
| 228 |
-
|
| 229 |
-
## CRITICAL: You MUST provide a <result> tag
|
| 230 |
-
|
| 231 |
-
When you have completed the task, you MUST ALWAYS provide a summary using the <result> tag. This is REQUIRED - without it, your work will not be visible in the command center. To include figures in your result (when you've created them), use self-closing figure tags like <figure_1>, <figure_2>, <figure_3> etc.:
|
| 232 |
-
|
| 233 |
-
Example:
|
| 234 |
-
<result>
|
| 235 |
-
Created a sine and cosine plot from 0 to 2π:
|
| 236 |
-
|
| 237 |
-
<figure_1>
|
| 238 |
-
|
| 239 |
-
The plot shows both functions overlaid on the same axes.
|
| 240 |
-
</result>
|
| 241 |
-
|
| 242 |
-
IMPORTANT: Use self-closing tags like <figure_1> (NOT </figure_1> or <figure_1></figure_1>). Each tag will be replaced with the actual image.
|
| 243 |
-
|
| 244 |
-
The result will be sent back to the command center with embedded images. DO NOT forget the <result> tag - it is mandatory for every completed task.
|
| 245 |
-
|
| 246 |
-
Focus on being precise, practical, and thorough in your coding assistance.
|
| 247 |
-
""",
|
| 248 |
-
"research": """You are a research assistant specialized in deep analysis and information gathering.
|
| 249 |
-
|
| 250 |
-
Your role is to:
|
| 251 |
-
- Conduct thorough research on topics
|
| 252 |
-
- Synthesize information from multiple sources
|
| 253 |
-
- Provide well-structured, evidence-based answers
|
| 254 |
-
- Identify key insights and trends
|
| 255 |
-
|
| 256 |
-
When presenting your final research report:
|
| 257 |
-
1. Be CONCISE - focus on key findings, not lengthy explanations
|
| 258 |
-
2. Use TABLES wherever possible to structure information clearly
|
| 259 |
-
3. Use markdown table syntax for comparisons, lists of facts, statistics, etc.
|
| 260 |
-
4. Example table format:
|
| 261 |
-
| Category | Details |
|
| 262 |
-
|----------|---------|
|
| 263 |
-
| Item 1 | Data |
|
| 264 |
-
| Item 2 | Data |
|
| 265 |
-
|
| 266 |
-
5. Only use prose for context and synthesis that can't be tabulated
|
| 267 |
-
6. DO NOT include a title or heading like "Research Report:" - start directly with your findings
|
| 268 |
-
|
| 269 |
-
When you have completed your research, wrap your final report in <result> tags:
|
| 270 |
-
|
| 271 |
-
<result>
|
| 272 |
-
Your concise, table-based report here (NO title/heading)
|
| 273 |
-
</result>
|
| 274 |
-
|
| 275 |
-
The report will be sent back to the main interface.
|
| 276 |
-
|
| 277 |
-
Focus on being comprehensive, analytical, and well-sourced in your research.
|
| 278 |
-
""",
|
| 279 |
-
"chat": """You are a conversational AI assistant.
|
| 280 |
-
|
| 281 |
-
Your role is to:
|
| 282 |
-
- Engage in natural, helpful conversation
|
| 283 |
-
- Answer questions clearly and concisely
|
| 284 |
-
- Provide thoughtful responses
|
| 285 |
-
- Be friendly and approachable
|
| 286 |
-
|
| 287 |
-
Focus on being conversational, helpful, and easy to understand.
|
| 288 |
-
"""
|
| 289 |
-
}
|
| 290 |
|
| 291 |
|
| 292 |
def record_api_call(tab_id: str, messages: List[dict]):
|
|
@@ -737,6 +606,12 @@ async def api_info():
|
|
| 737 |
}
|
| 738 |
|
| 739 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
@app.post("/api/generate-title")
|
| 741 |
async def generate_title(request: TitleRequest):
|
| 742 |
"""Generate a short 2-3 word title for a user query"""
|
|
@@ -1275,12 +1150,7 @@ def get_default_workspace():
|
|
| 1275 |
"version": 1,
|
| 1276 |
"tabCounter": 1,
|
| 1277 |
"activeTabId": 0,
|
| 1278 |
-
"notebookCounters":
|
| 1279 |
-
"agent": 0,
|
| 1280 |
-
"code": 0,
|
| 1281 |
-
"research": 0,
|
| 1282 |
-
"chat": 0
|
| 1283 |
-
},
|
| 1284 |
"tabs": [
|
| 1285 |
{
|
| 1286 |
"id": 0,
|
|
@@ -1437,7 +1307,8 @@ Current theme: {name}
|
|
| 1437 |
|
| 1438 |
def get_system_prompt(notebook_type: str, frontend_context: Optional[Dict] = None) -> str:
|
| 1439 |
"""Get system prompt for a notebook type with dynamic context appended"""
|
| 1440 |
-
|
|
|
|
| 1441 |
file_tree = get_file_tree_for_prompt()
|
| 1442 |
|
| 1443 |
# Build the full prompt with context sections
|
|
|
|
| 13 |
import sys
|
| 14 |
from datetime import datetime
|
| 15 |
from concurrent.futures import ThreadPoolExecutor
|
| 16 |
+
from agents import AGENT_REGISTRY, get_default_counters, get_registry_for_frontend
|
| 17 |
|
| 18 |
# Thread pool for running sync generators without blocking the event loop
|
| 19 |
# Use daemon threads so they don't block shutdown
|
|
|
|
| 155 |
allow_headers=["*"],
|
| 156 |
)
|
| 157 |
|
| 158 |
+
# Agent type registry is in agents.py — system prompts, tools, and metadata are all defined there
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
|
| 160 |
|
| 161 |
def record_api_call(tab_id: str, messages: List[dict]):
|
|
|
|
| 606 |
}
|
| 607 |
|
| 608 |
|
| 609 |
+
@app.get("/api/agents")
|
| 610 |
+
async def get_agents():
|
| 611 |
+
"""Return agent type registry for frontend consumption."""
|
| 612 |
+
return {"agents": get_registry_for_frontend()}
|
| 613 |
+
|
| 614 |
+
|
| 615 |
@app.post("/api/generate-title")
|
| 616 |
async def generate_title(request: TitleRequest):
|
| 617 |
"""Generate a short 2-3 word title for a user query"""
|
|
|
|
| 1150 |
"version": 1,
|
| 1151 |
"tabCounter": 1,
|
| 1152 |
"activeTabId": 0,
|
| 1153 |
+
"notebookCounters": get_default_counters(),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1154 |
"tabs": [
|
| 1155 |
{
|
| 1156 |
"id": 0,
|
|
|
|
| 1307 |
|
| 1308 |
def get_system_prompt(notebook_type: str, frontend_context: Optional[Dict] = None) -> str:
|
| 1309 |
"""Get system prompt for a notebook type with dynamic context appended"""
|
| 1310 |
+
from agents import get_system_prompt as _get_agent_prompt
|
| 1311 |
+
base_prompt = _get_agent_prompt(notebook_type) or _get_agent_prompt("command")
|
| 1312 |
file_tree = get_file_tree_for_prompt()
|
| 1313 |
|
| 1314 |
# Build the full prompt with context sections
|
frontend/index.html
CHANGED
|
@@ -20,9 +20,7 @@
|
|
| 20 |
<div class="new-tab-wrapper">
|
| 21 |
<button class="new-tab-btn" id="newTabBtn">+</button>
|
| 22 |
<div class="new-tab-menu" id="newTabMenu">
|
| 23 |
-
<
|
| 24 |
-
<div class="menu-item" data-type="code">CODE</div>
|
| 25 |
-
<div class="menu-item" data-type="research">RESEARCH</div>
|
| 26 |
</div>
|
| 27 |
</div>
|
| 28 |
<div class="tab-bar-spacer"></div>
|
|
@@ -60,11 +58,8 @@
|
|
| 60 |
<div class="notebook-type">TASK CENTER</div>
|
| 61 |
<h2>Task Center</h2>
|
| 62 |
</div>
|
| 63 |
-
<div class="header-actions">
|
| 64 |
-
<
|
| 65 |
-
<button class="launcher-btn" data-type="code">CODE</button>
|
| 66 |
-
<button class="launcher-btn" data-type="research">RESEARCH</button>
|
| 67 |
-
<button class="launcher-btn" data-type="chat">CHAT</button>
|
| 68 |
<button class="debug-btn" id="debugBtn">DEBUG</button>
|
| 69 |
</div>
|
| 70 |
</div>
|
|
@@ -210,21 +205,8 @@
|
|
| 210 |
<span class="label-text">NOTEBOOK MODELS</span>
|
| 211 |
<span class="label-description">Select which model to use for each notebook type</span>
|
| 212 |
</label>
|
| 213 |
-
<div class="notebook-models-grid">
|
| 214 |
-
<
|
| 215 |
-
<select id="setting-notebook-command" class="settings-select"></select>
|
| 216 |
-
|
| 217 |
-
<label>AGENT:</label>
|
| 218 |
-
<select id="setting-notebook-agent" class="settings-select"></select>
|
| 219 |
-
|
| 220 |
-
<label>CODE:</label>
|
| 221 |
-
<select id="setting-notebook-code" class="settings-select"></select>
|
| 222 |
-
|
| 223 |
-
<label>RESEARCH:</label>
|
| 224 |
-
<select id="setting-notebook-research" class="settings-select"></select>
|
| 225 |
-
|
| 226 |
-
<label>CHAT:</label>
|
| 227 |
-
<select id="setting-notebook-chat" class="settings-select"></select>
|
| 228 |
</div>
|
| 229 |
</div>
|
| 230 |
|
|
@@ -475,6 +457,6 @@
|
|
| 475 |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 476 |
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
| 477 |
<script src="research-ui.js?v=23"></script>
|
| 478 |
-
<script src="script.js?v=
|
| 479 |
</body>
|
| 480 |
</html>
|
|
|
|
| 20 |
<div class="new-tab-wrapper">
|
| 21 |
<button class="new-tab-btn" id="newTabBtn">+</button>
|
| 22 |
<div class="new-tab-menu" id="newTabMenu">
|
| 23 |
+
<!-- Generated dynamically from AGENT_REGISTRY -->
|
|
|
|
|
|
|
| 24 |
</div>
|
| 25 |
</div>
|
| 26 |
<div class="tab-bar-spacer"></div>
|
|
|
|
| 58 |
<div class="notebook-type">TASK CENTER</div>
|
| 59 |
<h2>Task Center</h2>
|
| 60 |
</div>
|
| 61 |
+
<div class="header-actions" id="launcherButtons">
|
| 62 |
+
<!-- Launcher buttons generated dynamically from AGENT_REGISTRY -->
|
|
|
|
|
|
|
|
|
|
| 63 |
<button class="debug-btn" id="debugBtn">DEBUG</button>
|
| 64 |
</div>
|
| 65 |
</div>
|
|
|
|
| 205 |
<span class="label-text">NOTEBOOK MODELS</span>
|
| 206 |
<span class="label-description">Select which model to use for each notebook type</span>
|
| 207 |
</label>
|
| 208 |
+
<div class="notebook-models-grid" id="notebookModelsGrid">
|
| 209 |
+
<!-- Generated dynamically from AGENT_REGISTRY -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
</div>
|
| 211 |
</div>
|
| 212 |
|
|
|
|
| 457 |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 458 |
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
| 459 |
<script src="research-ui.js?v=23"></script>
|
| 460 |
+
<script src="script.js?v=57"></script>
|
| 461 |
</body>
|
| 462 |
</html>
|
frontend/script.js
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
// State management
|
| 2 |
let tabCounter = 1;
|
| 3 |
let activeTabId = 0;
|
|
@@ -22,13 +53,7 @@ let settings = {
|
|
| 22 |
// New provider/model structure
|
| 23 |
providers: {}, // providerId -> {name, endpoint, token}
|
| 24 |
models: {}, // modelId -> {name, providerId, modelId (API model string)}
|
| 25 |
-
notebooks:
|
| 26 |
-
command: '',
|
| 27 |
-
agent: '',
|
| 28 |
-
code: '',
|
| 29 |
-
research: '',
|
| 30 |
-
chat: ''
|
| 31 |
-
},
|
| 32 |
// Service API keys
|
| 33 |
e2bKey: '',
|
| 34 |
serperKey: '',
|
|
@@ -51,21 +76,8 @@ const toolCallIds = {};
|
|
| 51 |
// Track notebooks by task_id for reuse (maps task_id -> tabId)
|
| 52 |
const taskIdToTabId = {};
|
| 53 |
|
| 54 |
-
// Track notebook counters for each type
|
| 55 |
-
let notebookCounters =
|
| 56 |
-
'agent': 0,
|
| 57 |
-
'code': 0,
|
| 58 |
-
'research': 0,
|
| 59 |
-
'chat': 0
|
| 60 |
-
};
|
| 61 |
-
|
| 62 |
-
const notebookTitles = {
|
| 63 |
-
'command-center': 'Task Center',
|
| 64 |
-
'agent': 'AGENT',
|
| 65 |
-
'code': 'CODE',
|
| 66 |
-
'research': 'RESEARCH',
|
| 67 |
-
'chat': 'CHAT'
|
| 68 |
-
};
|
| 69 |
|
| 70 |
// Debounce timer for workspace saving
|
| 71 |
let saveWorkspaceTimer = null;
|
|
@@ -90,7 +102,7 @@ function resetLocalState() {
|
|
| 90 |
Object.keys(taskIdToTabId).forEach(k => delete taskIdToTabId[k]);
|
| 91 |
researchQueryTabIds = {};
|
| 92 |
showAllTurns = false;
|
| 93 |
-
notebookCounters =
|
| 94 |
|
| 95 |
// Reset timeline data
|
| 96 |
Object.keys(timelineData).forEach(k => delete timelineData[k]);
|
|
@@ -362,8 +374,7 @@ function renderTimeline() {
|
|
| 362 |
function renderNotebookTimeline(tabId, notebook, isNested = false) {
|
| 363 |
const isActive = activeTabId === tabId;
|
| 364 |
const isClosed = notebook.isClosed || false;
|
| 365 |
-
const
|
| 366 |
-
const typeLabel = typeLabels[notebook.type] || notebook.type.toUpperCase();
|
| 367 |
|
| 368 |
let html = `<div class="tl-widget${isNested ? '' : ' compact'}${isActive ? ' active' : ''}${isClosed ? ' closed' : ''}" data-tab-id="${tabId}">`;
|
| 369 |
|
|
@@ -423,7 +434,7 @@ function renderNotebookTimeline(tabId, notebook, isNested = false) {
|
|
| 423 |
if (event.childTabId !== null) {
|
| 424 |
const childNotebook = timelineData[event.childTabId];
|
| 425 |
if (childNotebook) {
|
| 426 |
-
const childTypeLabel =
|
| 427 |
const childIsGenerating = childNotebook.isGenerating;
|
| 428 |
const turnCount = childNotebook.events.length;
|
| 429 |
|
|
@@ -590,7 +601,60 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
| 590 |
showSessionSelector(sessionsData.sessions);
|
| 591 |
});
|
| 592 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
function initializeEventListeners() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
// Launcher buttons in command center
|
| 595 |
document.querySelectorAll('.launcher-btn').forEach(btn => {
|
| 596 |
btn.addEventListener('click', (e) => {
|
|
@@ -1123,7 +1187,7 @@ function createNotebookTab(type, initialMessage = null, autoSwitch = true, taskI
|
|
| 1123 |
notebookCounters[type]++;
|
| 1124 |
title = `New ${type} task`;
|
| 1125 |
} else {
|
| 1126 |
-
title =
|
| 1127 |
}
|
| 1128 |
|
| 1129 |
// Register in timeline
|
|
@@ -1205,20 +1269,6 @@ function createNotebookContent(type, tabId, title = null) {
|
|
| 1205 |
return document.querySelector('[data-content-id="0"]').innerHTML;
|
| 1206 |
}
|
| 1207 |
|
| 1208 |
-
const placeholders = {
|
| 1209 |
-
'agent': 'Enter message...',
|
| 1210 |
-
'code': 'Enter message...',
|
| 1211 |
-
'research': 'Enter message...',
|
| 1212 |
-
'chat': 'Enter message...'
|
| 1213 |
-
};
|
| 1214 |
-
|
| 1215 |
-
const typeLabels = {
|
| 1216 |
-
'agent': 'AGENT',
|
| 1217 |
-
'code': 'CODE',
|
| 1218 |
-
'research': 'RESEARCH',
|
| 1219 |
-
'chat': 'CHAT'
|
| 1220 |
-
};
|
| 1221 |
-
|
| 1222 |
// Use unique ID combining type and tabId to ensure unique container IDs
|
| 1223 |
const uniqueId = `${type}-${tabId}`;
|
| 1224 |
|
|
@@ -1229,7 +1279,7 @@ function createNotebookContent(type, tabId, title = null) {
|
|
| 1229 |
<div class="notebook-interface">
|
| 1230 |
<div class="notebook-header">
|
| 1231 |
<div>
|
| 1232 |
-
<div class="notebook-type">${
|
| 1233 |
<h2>${escapeHtml(displayTitle)}</h2>
|
| 1234 |
</div>
|
| 1235 |
</div>
|
|
@@ -1239,7 +1289,7 @@ function createNotebookContent(type, tabId, title = null) {
|
|
| 1239 |
</div>
|
| 1240 |
<div class="input-area">
|
| 1241 |
<div class="input-container">
|
| 1242 |
-
<textarea placeholder="${
|
| 1243 |
<button>SEND</button>
|
| 1244 |
</div>
|
| 1245 |
</div>
|
|
@@ -2435,12 +2485,7 @@ async function loadWorkspace() {
|
|
| 2435 |
function restoreWorkspace(workspace) {
|
| 2436 |
// Restore counters
|
| 2437 |
tabCounter = workspace.tabCounter || 1;
|
| 2438 |
-
notebookCounters = workspace.notebookCounters ||
|
| 2439 |
-
'agent': 0,
|
| 2440 |
-
'code': 0,
|
| 2441 |
-
'research': 0,
|
| 2442 |
-
'chat': 0
|
| 2443 |
-
};
|
| 2444 |
|
| 2445 |
// Restore timeline data before tabs so renderTimeline works
|
| 2446 |
if (workspace.timelineData) {
|
|
@@ -2477,7 +2522,7 @@ function restoreTab(tabData) {
|
|
| 2477 |
tab.className = 'tab';
|
| 2478 |
tab.dataset.tabId = tabData.id;
|
| 2479 |
tab.innerHTML = `
|
| 2480 |
-
<span class="tab-title">${tabData.title ||
|
| 2481 |
<span class="tab-status" style="display: none;"><span></span><span></span><span></span></span>
|
| 2482 |
<span class="tab-close">×</span>
|
| 2483 |
`;
|
|
@@ -2816,7 +2861,7 @@ function serializeTab(tabId, type) {
|
|
| 2816 |
const tabData = {
|
| 2817 |
id: tabId,
|
| 2818 |
type: type,
|
| 2819 |
-
title: tabEl?.querySelector('.tab-title')?.textContent ||
|
| 2820 |
messages: []
|
| 2821 |
};
|
| 2822 |
|
|
@@ -3045,7 +3090,7 @@ function migrateSettings(oldSettings) {
|
|
| 3045 |
|
| 3046 |
// Migrate notebook-specific models if they existed
|
| 3047 |
const oldModels = oldSettings.models || {};
|
| 3048 |
-
const notebookTypes =
|
| 3049 |
notebookTypes.forEach(type => {
|
| 3050 |
if (oldModels[type]) {
|
| 3051 |
const specificModelId = `model_${type}`;
|
|
@@ -3186,12 +3231,9 @@ function populateModelDropdowns() {
|
|
| 3186 |
const models = settings.models || {};
|
| 3187 |
const notebooks = settings.notebooks || {};
|
| 3188 |
|
|
|
|
| 3189 |
const dropdownIds = [
|
| 3190 |
-
|
| 3191 |
-
'setting-notebook-agent',
|
| 3192 |
-
'setting-notebook-code',
|
| 3193 |
-
'setting-notebook-research',
|
| 3194 |
-
'setting-notebook-chat',
|
| 3195 |
'setting-research-sub-agent-model'
|
| 3196 |
];
|
| 3197 |
|
|
@@ -3218,19 +3260,12 @@ function populateModelDropdowns() {
|
|
| 3218 |
}
|
| 3219 |
});
|
| 3220 |
|
| 3221 |
-
// Set values from settings
|
| 3222 |
-
const
|
| 3223 |
-
|
| 3224 |
-
|
| 3225 |
-
|
| 3226 |
-
const chatDropdown = document.getElementById('setting-notebook-chat');
|
| 3227 |
const subAgentDropdown = document.getElementById('setting-research-sub-agent-model');
|
| 3228 |
-
|
| 3229 |
-
if (commandDropdown) commandDropdown.value = notebooks.command || '';
|
| 3230 |
-
if (agentDropdown) agentDropdown.value = notebooks.agent || '';
|
| 3231 |
-
if (codeDropdown) codeDropdown.value = notebooks.code || '';
|
| 3232 |
-
if (researchDropdown) researchDropdown.value = notebooks.research || '';
|
| 3233 |
-
if (chatDropdown) chatDropdown.value = notebooks.chat || '';
|
| 3234 |
if (subAgentDropdown) subAgentDropdown.value = settings.researchSubAgentModel || '';
|
| 3235 |
}
|
| 3236 |
|
|
@@ -3433,12 +3468,11 @@ function openSettings() {
|
|
| 3433 |
}
|
| 3434 |
|
| 3435 |
async function saveSettings() {
|
| 3436 |
-
// Get notebook model selections from dropdowns
|
| 3437 |
-
const
|
| 3438 |
-
const
|
| 3439 |
-
|
| 3440 |
-
|
| 3441 |
-
const chatModel = document.getElementById('setting-notebook-chat')?.value || '';
|
| 3442 |
const researchSubAgentModel = document.getElementById('setting-research-sub-agent-model')?.value || '';
|
| 3443 |
|
| 3444 |
// Get other settings
|
|
@@ -3460,13 +3494,7 @@ async function saveSettings() {
|
|
| 3460 |
}
|
| 3461 |
|
| 3462 |
// Update settings
|
| 3463 |
-
settings.notebooks =
|
| 3464 |
-
command: commandModel,
|
| 3465 |
-
agent: agentModel,
|
| 3466 |
-
code: codeModel,
|
| 3467 |
-
research: researchModel,
|
| 3468 |
-
chat: chatModel
|
| 3469 |
-
};
|
| 3470 |
settings.e2bKey = e2bKey;
|
| 3471 |
settings.serperKey = serperKey;
|
| 3472 |
settings.researchSubAgentModel = researchSubAgentModel;
|
|
|
|
| 1 |
+
// ============================================================
|
| 2 |
+
// Agent Type Registry — single source of truth for the frontend
|
| 3 |
+
// To add a new agent type, add an entry here and in backend/agents.py
|
| 4 |
+
// ============================================================
|
| 5 |
+
const AGENT_REGISTRY = {
|
| 6 |
+
command: { label: 'TASKS', hasCounter: false, inMenu: false, inLauncher: false, placeholder: 'Enter message...' },
|
| 7 |
+
agent: { label: 'AGENT', hasCounter: true, inMenu: false, inLauncher: true, placeholder: 'Enter message...' },
|
| 8 |
+
code: { label: 'CODE', hasCounter: true, inMenu: true, inLauncher: true, placeholder: 'Enter message...' },
|
| 9 |
+
research: { label: 'RESEARCH', hasCounter: true, inMenu: true, inLauncher: true, placeholder: 'Enter message...' },
|
| 10 |
+
chat: { label: 'CHAT', hasCounter: true, inMenu: true, inLauncher: true, placeholder: 'Enter message...' },
|
| 11 |
+
};
|
| 12 |
+
// Virtual types used only in timeline rendering (not real agents)
|
| 13 |
+
const VIRTUAL_TYPE_LABELS = { search: 'SEARCH', browse: 'BROWSE' };
|
| 14 |
+
|
| 15 |
+
// Derived helpers from registry
|
| 16 |
+
function getTypeLabel(type) {
|
| 17 |
+
if (AGENT_REGISTRY[type]) return AGENT_REGISTRY[type].label;
|
| 18 |
+
if (VIRTUAL_TYPE_LABELS[type]) return VIRTUAL_TYPE_LABELS[type];
|
| 19 |
+
return type.toUpperCase();
|
| 20 |
+
}
|
| 21 |
+
function getPlaceholder(type) {
|
| 22 |
+
return AGENT_REGISTRY[type]?.placeholder || 'Enter message...';
|
| 23 |
+
}
|
| 24 |
+
function getDefaultCounters() {
|
| 25 |
+
const counters = {};
|
| 26 |
+
for (const [key, agent] of Object.entries(AGENT_REGISTRY)) {
|
| 27 |
+
if (agent.hasCounter) counters[key] = 0;
|
| 28 |
+
}
|
| 29 |
+
return counters;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
// State management
|
| 33 |
let tabCounter = 1;
|
| 34 |
let activeTabId = 0;
|
|
|
|
| 53 |
// New provider/model structure
|
| 54 |
providers: {}, // providerId -> {name, endpoint, token}
|
| 55 |
models: {}, // modelId -> {name, providerId, modelId (API model string)}
|
| 56 |
+
notebooks: Object.fromEntries(Object.keys(AGENT_REGISTRY).map(k => [k, ''])),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
// Service API keys
|
| 58 |
e2bKey: '',
|
| 59 |
serperKey: '',
|
|
|
|
| 76 |
// Track notebooks by task_id for reuse (maps task_id -> tabId)
|
| 77 |
const taskIdToTabId = {};
|
| 78 |
|
| 79 |
+
// Track notebook counters for each type (derived from registry)
|
| 80 |
+
let notebookCounters = getDefaultCounters();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
// Debounce timer for workspace saving
|
| 83 |
let saveWorkspaceTimer = null;
|
|
|
|
| 102 |
Object.keys(taskIdToTabId).forEach(k => delete taskIdToTabId[k]);
|
| 103 |
researchQueryTabIds = {};
|
| 104 |
showAllTurns = false;
|
| 105 |
+
notebookCounters = getDefaultCounters();
|
| 106 |
|
| 107 |
// Reset timeline data
|
| 108 |
Object.keys(timelineData).forEach(k => delete timelineData[k]);
|
|
|
|
| 374 |
function renderNotebookTimeline(tabId, notebook, isNested = false) {
|
| 375 |
const isActive = activeTabId === tabId;
|
| 376 |
const isClosed = notebook.isClosed || false;
|
| 377 |
+
const typeLabel = getTypeLabel(notebook.type);
|
|
|
|
| 378 |
|
| 379 |
let html = `<div class="tl-widget${isNested ? '' : ' compact'}${isActive ? ' active' : ''}${isClosed ? ' closed' : ''}" data-tab-id="${tabId}">`;
|
| 380 |
|
|
|
|
| 434 |
if (event.childTabId !== null) {
|
| 435 |
const childNotebook = timelineData[event.childTabId];
|
| 436 |
if (childNotebook) {
|
| 437 |
+
const childTypeLabel = getTypeLabel(childNotebook.type);
|
| 438 |
const childIsGenerating = childNotebook.isGenerating;
|
| 439 |
const turnCount = childNotebook.events.length;
|
| 440 |
|
|
|
|
| 601 |
showSessionSelector(sessionsData.sessions);
|
| 602 |
});
|
| 603 |
|
| 604 |
+
// ============================================================
|
| 605 |
+
// Dynamic HTML generation from AGENT_REGISTRY
|
| 606 |
+
// ============================================================
|
| 607 |
+
|
| 608 |
+
function renderNewTabMenu() {
|
| 609 |
+
const menu = document.getElementById('newTabMenu');
|
| 610 |
+
if (!menu) return;
|
| 611 |
+
menu.innerHTML = '';
|
| 612 |
+
for (const [key, agent] of Object.entries(AGENT_REGISTRY)) {
|
| 613 |
+
if (!agent.inMenu) continue;
|
| 614 |
+
const item = document.createElement('div');
|
| 615 |
+
item.className = 'menu-item';
|
| 616 |
+
item.dataset.type = key;
|
| 617 |
+
item.textContent = agent.label;
|
| 618 |
+
menu.appendChild(item);
|
| 619 |
+
}
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
function renderLauncherButtons() {
|
| 623 |
+
const container = document.getElementById('launcherButtons');
|
| 624 |
+
if (!container) return;
|
| 625 |
+
// Insert before the debug button (keep it at the end)
|
| 626 |
+
const debugBtn = container.querySelector('#debugBtn');
|
| 627 |
+
for (const [key, agent] of Object.entries(AGENT_REGISTRY)) {
|
| 628 |
+
if (!agent.inLauncher) continue;
|
| 629 |
+
const btn = document.createElement('button');
|
| 630 |
+
btn.className = 'launcher-btn';
|
| 631 |
+
btn.dataset.type = key;
|
| 632 |
+
btn.textContent = agent.label;
|
| 633 |
+
container.insertBefore(btn, debugBtn);
|
| 634 |
+
}
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
function renderNotebookModelSelectors() {
|
| 638 |
+
const grid = document.getElementById('notebookModelsGrid');
|
| 639 |
+
if (!grid) return;
|
| 640 |
+
grid.innerHTML = '';
|
| 641 |
+
for (const [key, agent] of Object.entries(AGENT_REGISTRY)) {
|
| 642 |
+
const label = document.createElement('label');
|
| 643 |
+
label.textContent = `${agent.label}:`;
|
| 644 |
+
const select = document.createElement('select');
|
| 645 |
+
select.id = `setting-notebook-${key}`;
|
| 646 |
+
select.className = 'settings-select';
|
| 647 |
+
grid.appendChild(label);
|
| 648 |
+
grid.appendChild(select);
|
| 649 |
+
}
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
function initializeEventListeners() {
|
| 653 |
+
// Generate dynamic UI elements from registry
|
| 654 |
+
renderLauncherButtons();
|
| 655 |
+
renderNewTabMenu();
|
| 656 |
+
renderNotebookModelSelectors();
|
| 657 |
+
|
| 658 |
// Launcher buttons in command center
|
| 659 |
document.querySelectorAll('.launcher-btn').forEach(btn => {
|
| 660 |
btn.addEventListener('click', (e) => {
|
|
|
|
| 1187 |
notebookCounters[type]++;
|
| 1188 |
title = `New ${type} task`;
|
| 1189 |
} else {
|
| 1190 |
+
title = getTypeLabel(type);
|
| 1191 |
}
|
| 1192 |
|
| 1193 |
// Register in timeline
|
|
|
|
| 1269 |
return document.querySelector('[data-content-id="0"]').innerHTML;
|
| 1270 |
}
|
| 1271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1272 |
// Use unique ID combining type and tabId to ensure unique container IDs
|
| 1273 |
const uniqueId = `${type}-${tabId}`;
|
| 1274 |
|
|
|
|
| 1279 |
<div class="notebook-interface">
|
| 1280 |
<div class="notebook-header">
|
| 1281 |
<div>
|
| 1282 |
+
<div class="notebook-type">${getTypeLabel(type)}</div>
|
| 1283 |
<h2>${escapeHtml(displayTitle)}</h2>
|
| 1284 |
</div>
|
| 1285 |
</div>
|
|
|
|
| 1289 |
</div>
|
| 1290 |
<div class="input-area">
|
| 1291 |
<div class="input-container">
|
| 1292 |
+
<textarea placeholder="${getPlaceholder(type)}" id="input-${uniqueId}" rows="1"></textarea>
|
| 1293 |
<button>SEND</button>
|
| 1294 |
</div>
|
| 1295 |
</div>
|
|
|
|
| 2485 |
function restoreWorkspace(workspace) {
|
| 2486 |
// Restore counters
|
| 2487 |
tabCounter = workspace.tabCounter || 1;
|
| 2488 |
+
notebookCounters = workspace.notebookCounters || getDefaultCounters();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2489 |
|
| 2490 |
// Restore timeline data before tabs so renderTimeline works
|
| 2491 |
if (workspace.timelineData) {
|
|
|
|
| 2522 |
tab.className = 'tab';
|
| 2523 |
tab.dataset.tabId = tabData.id;
|
| 2524 |
tab.innerHTML = `
|
| 2525 |
+
<span class="tab-title">${tabData.title || getTypeLabel(tabData.type) || 'TAB'}</span>
|
| 2526 |
<span class="tab-status" style="display: none;"><span></span><span></span><span></span></span>
|
| 2527 |
<span class="tab-close">×</span>
|
| 2528 |
`;
|
|
|
|
| 2861 |
const tabData = {
|
| 2862 |
id: tabId,
|
| 2863 |
type: type,
|
| 2864 |
+
title: tabEl?.querySelector('.tab-title')?.textContent || getTypeLabel(type) || 'TAB',
|
| 2865 |
messages: []
|
| 2866 |
};
|
| 2867 |
|
|
|
|
| 3090 |
|
| 3091 |
// Migrate notebook-specific models if they existed
|
| 3092 |
const oldModels = oldSettings.models || {};
|
| 3093 |
+
const notebookTypes = Object.keys(AGENT_REGISTRY).filter(k => AGENT_REGISTRY[k].hasCounter);
|
| 3094 |
notebookTypes.forEach(type => {
|
| 3095 |
if (oldModels[type]) {
|
| 3096 |
const specificModelId = `model_${type}`;
|
|
|
|
| 3231 |
const models = settings.models || {};
|
| 3232 |
const notebooks = settings.notebooks || {};
|
| 3233 |
|
| 3234 |
+
// Build dropdown IDs from registry + special dropdowns
|
| 3235 |
const dropdownIds = [
|
| 3236 |
+
...Object.keys(AGENT_REGISTRY).map(t => `setting-notebook-${t}`),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3237 |
'setting-research-sub-agent-model'
|
| 3238 |
];
|
| 3239 |
|
|
|
|
| 3260 |
}
|
| 3261 |
});
|
| 3262 |
|
| 3263 |
+
// Set values from settings (driven by registry)
|
| 3264 |
+
for (const type of Object.keys(AGENT_REGISTRY)) {
|
| 3265 |
+
const dropdown = document.getElementById(`setting-notebook-${type}`);
|
| 3266 |
+
if (dropdown) dropdown.value = notebooks[type] || '';
|
| 3267 |
+
}
|
|
|
|
| 3268 |
const subAgentDropdown = document.getElementById('setting-research-sub-agent-model');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3269 |
if (subAgentDropdown) subAgentDropdown.value = settings.researchSubAgentModel || '';
|
| 3270 |
}
|
| 3271 |
|
|
|
|
| 3468 |
}
|
| 3469 |
|
| 3470 |
async function saveSettings() {
|
| 3471 |
+
// Get notebook model selections from dropdowns (driven by registry)
|
| 3472 |
+
const notebookModels = {};
|
| 3473 |
+
for (const type of Object.keys(AGENT_REGISTRY)) {
|
| 3474 |
+
notebookModels[type] = document.getElementById(`setting-notebook-${type}`)?.value || '';
|
| 3475 |
+
}
|
|
|
|
| 3476 |
const researchSubAgentModel = document.getElementById('setting-research-sub-agent-model')?.value || '';
|
| 3477 |
|
| 3478 |
// Get other settings
|
|
|
|
| 3494 |
}
|
| 3495 |
|
| 3496 |
// Update settings
|
| 3497 |
+
settings.notebooks = notebookModels;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3498 |
settings.e2bKey = e2bKey;
|
| 3499 |
settings.serperKey = serperKey;
|
| 3500 |
settings.researchSubAgentModel = researchSubAgentModel;
|