Spaces:
Running
Running
v0.15
Browse files- .gitignore +218 -0
- backend/__pycache__/command.cpython-312.pyc +0 -0
- backend/command.py +2 -1
- backend/main.py +124 -12
- index.html +139 -121
- script.js +221 -24
- style.css +313 -5
.gitignore
CHANGED
|
@@ -1 +1,219 @@
|
|
| 1 |
settings.json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
settings.json
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
# Byte-compiled / optimized / DLL files
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.py[codz]
|
| 7 |
+
*$py.class
|
| 8 |
+
|
| 9 |
+
# C extensions
|
| 10 |
+
*.so
|
| 11 |
+
|
| 12 |
+
# Distribution / packaging
|
| 13 |
+
.Python
|
| 14 |
+
build/
|
| 15 |
+
develop-eggs/
|
| 16 |
+
dist/
|
| 17 |
+
downloads/
|
| 18 |
+
eggs/
|
| 19 |
+
.eggs/
|
| 20 |
+
lib/
|
| 21 |
+
lib64/
|
| 22 |
+
parts/
|
| 23 |
+
sdist/
|
| 24 |
+
var/
|
| 25 |
+
wheels/
|
| 26 |
+
share/python-wheels/
|
| 27 |
+
*.egg-info/
|
| 28 |
+
.installed.cfg
|
| 29 |
+
*.egg
|
| 30 |
+
MANIFEST
|
| 31 |
+
|
| 32 |
+
# PyInstaller
|
| 33 |
+
# Usually these files are written by a python script from a template
|
| 34 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 35 |
+
*.manifest
|
| 36 |
+
*.spec
|
| 37 |
+
|
| 38 |
+
# Installer logs
|
| 39 |
+
pip-log.txt
|
| 40 |
+
pip-delete-this-directory.txt
|
| 41 |
+
|
| 42 |
+
# Unit test / coverage reports
|
| 43 |
+
htmlcov/
|
| 44 |
+
.tox/
|
| 45 |
+
.nox/
|
| 46 |
+
.coverage
|
| 47 |
+
.coverage.*
|
| 48 |
+
.cache
|
| 49 |
+
nosetests.xml
|
| 50 |
+
coverage.xml
|
| 51 |
+
*.cover
|
| 52 |
+
*.py.cover
|
| 53 |
+
.hypothesis/
|
| 54 |
+
.pytest_cache/
|
| 55 |
+
cover/
|
| 56 |
+
|
| 57 |
+
# Translations
|
| 58 |
+
*.mo
|
| 59 |
+
*.pot
|
| 60 |
+
|
| 61 |
+
# Django stuff:
|
| 62 |
+
*.log
|
| 63 |
+
local_settings.py
|
| 64 |
+
db.sqlite3
|
| 65 |
+
db.sqlite3-journal
|
| 66 |
+
|
| 67 |
+
# Flask stuff:
|
| 68 |
+
instance/
|
| 69 |
+
.webassets-cache
|
| 70 |
+
|
| 71 |
+
# Scrapy stuff:
|
| 72 |
+
.scrapy
|
| 73 |
+
|
| 74 |
+
# Sphinx documentation
|
| 75 |
+
docs/_build/
|
| 76 |
+
|
| 77 |
+
# PyBuilder
|
| 78 |
+
.pybuilder/
|
| 79 |
+
target/
|
| 80 |
+
|
| 81 |
+
# Jupyter Notebook
|
| 82 |
+
.ipynb_checkpoints
|
| 83 |
+
|
| 84 |
+
# IPython
|
| 85 |
+
profile_default/
|
| 86 |
+
ipython_config.py
|
| 87 |
+
|
| 88 |
+
# pyenv
|
| 89 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 90 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 91 |
+
# .python-version
|
| 92 |
+
|
| 93 |
+
# pipenv
|
| 94 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 95 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 96 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 97 |
+
# install all needed dependencies.
|
| 98 |
+
# Pipfile.lock
|
| 99 |
+
|
| 100 |
+
# UV
|
| 101 |
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
| 102 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 103 |
+
# commonly ignored for libraries.
|
| 104 |
+
# uv.lock
|
| 105 |
+
|
| 106 |
+
# poetry
|
| 107 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 108 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 109 |
+
# commonly ignored for libraries.
|
| 110 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 111 |
+
# poetry.lock
|
| 112 |
+
# poetry.toml
|
| 113 |
+
|
| 114 |
+
# pdm
|
| 115 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 116 |
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
| 117 |
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
| 118 |
+
# pdm.lock
|
| 119 |
+
# pdm.toml
|
| 120 |
+
.pdm-python
|
| 121 |
+
.pdm-build/
|
| 122 |
+
|
| 123 |
+
# pixi
|
| 124 |
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
| 125 |
+
# pixi.lock
|
| 126 |
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
| 127 |
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
| 128 |
+
.pixi
|
| 129 |
+
|
| 130 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 131 |
+
__pypackages__/
|
| 132 |
+
|
| 133 |
+
# Celery stuff
|
| 134 |
+
celerybeat-schedule
|
| 135 |
+
celerybeat.pid
|
| 136 |
+
|
| 137 |
+
# Redis
|
| 138 |
+
*.rdb
|
| 139 |
+
*.aof
|
| 140 |
+
*.pid
|
| 141 |
+
|
| 142 |
+
# RabbitMQ
|
| 143 |
+
mnesia/
|
| 144 |
+
rabbitmq/
|
| 145 |
+
rabbitmq-data/
|
| 146 |
+
|
| 147 |
+
# ActiveMQ
|
| 148 |
+
activemq-data/
|
| 149 |
+
|
| 150 |
+
# SageMath parsed files
|
| 151 |
+
*.sage.py
|
| 152 |
+
|
| 153 |
+
# Environments
|
| 154 |
+
.env
|
| 155 |
+
.envrc
|
| 156 |
+
.venv
|
| 157 |
+
env/
|
| 158 |
+
venv/
|
| 159 |
+
ENV/
|
| 160 |
+
env.bak/
|
| 161 |
+
venv.bak/
|
| 162 |
+
|
| 163 |
+
# Spyder project settings
|
| 164 |
+
.spyderproject
|
| 165 |
+
.spyproject
|
| 166 |
+
|
| 167 |
+
# Rope project settings
|
| 168 |
+
.ropeproject
|
| 169 |
+
|
| 170 |
+
# mkdocs documentation
|
| 171 |
+
/site
|
| 172 |
+
|
| 173 |
+
# mypy
|
| 174 |
+
.mypy_cache/
|
| 175 |
+
.dmypy.json
|
| 176 |
+
dmypy.json
|
| 177 |
+
|
| 178 |
+
# Pyre type checker
|
| 179 |
+
.pyre/
|
| 180 |
+
|
| 181 |
+
# pytype static type analyzer
|
| 182 |
+
.pytype/
|
| 183 |
+
|
| 184 |
+
# Cython debug symbols
|
| 185 |
+
cython_debug/
|
| 186 |
+
|
| 187 |
+
# PyCharm
|
| 188 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 189 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 190 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 191 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 192 |
+
# .idea/
|
| 193 |
+
|
| 194 |
+
# Abstra
|
| 195 |
+
# Abstra is an AI-powered process automation framework.
|
| 196 |
+
# Ignore directories containing user credentials, local state, and settings.
|
| 197 |
+
# Learn more at https://abstra.io/docs
|
| 198 |
+
.abstra/
|
| 199 |
+
|
| 200 |
+
# Visual Studio Code
|
| 201 |
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 202 |
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 203 |
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 204 |
+
# you could uncomment the following to ignore the entire vscode folder
|
| 205 |
+
# .vscode/
|
| 206 |
+
|
| 207 |
+
# Ruff stuff:
|
| 208 |
+
.ruff_cache/
|
| 209 |
+
|
| 210 |
+
# PyPI configuration file
|
| 211 |
+
.pypirc
|
| 212 |
+
|
| 213 |
+
# Marimo
|
| 214 |
+
marimo/_static/
|
| 215 |
+
marimo/_lsp/
|
| 216 |
+
__marimo__/
|
| 217 |
+
|
| 218 |
+
# Streamlit
|
| 219 |
+
.streamlit/secrets.toml
|
backend/__pycache__/command.cpython-312.pyc
DELETED
|
Binary file (4.72 kB)
|
|
|
backend/command.py
CHANGED
|
@@ -143,7 +143,8 @@ def stream_command_center(client, model: str, messages: List[Dict]):
|
|
| 143 |
yield {
|
| 144 |
"type": "launch",
|
| 145 |
"notebook_type": notebook_type,
|
| 146 |
-
"initial_message": initial_message
|
|
|
|
| 147 |
}
|
| 148 |
|
| 149 |
# Add tool call to message history for context
|
|
|
|
| 143 |
yield {
|
| 144 |
"type": "launch",
|
| 145 |
"notebook_type": notebook_type,
|
| 146 |
+
"initial_message": initial_message,
|
| 147 |
+
"tool_call_id": tool_call.id
|
| 148 |
}
|
| 149 |
|
| 150 |
# Add tool call to message history for context
|
backend/main.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import List, Optional, Dict
|
|
| 6 |
import json
|
| 7 |
import httpx
|
| 8 |
import os
|
|
|
|
| 9 |
|
| 10 |
app = FastAPI(title="Productive API")
|
| 11 |
|
|
@@ -39,6 +40,14 @@ except ImportError:
|
|
| 39 |
SANDBOXES: Dict[str, any] = {}
|
| 40 |
SANDBOX_TIMEOUT = 300
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
# CORS middleware for frontend connection
|
| 43 |
app.add_middleware(
|
| 44 |
CORSMiddleware,
|
|
@@ -167,6 +176,23 @@ Focus on being conversational, helpful, and easy to understand.
|
|
| 167 |
}
|
| 168 |
|
| 169 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
class Message(BaseModel):
|
| 171 |
role: str
|
| 172 |
content: str
|
|
@@ -209,7 +235,8 @@ async def stream_code_notebook(
|
|
| 209 |
token: Optional[str],
|
| 210 |
model: str,
|
| 211 |
e2b_key: str,
|
| 212 |
-
session_id: str
|
|
|
|
| 213 |
):
|
| 214 |
"""Handle code notebook with execution capabilities"""
|
| 215 |
|
|
@@ -238,6 +265,9 @@ async def stream_code_notebook(
|
|
| 238 |
{"role": "system", "content": system_prompt}
|
| 239 |
] + messages
|
| 240 |
|
|
|
|
|
|
|
|
|
|
| 241 |
# Stream code execution
|
| 242 |
for update in stream_code_execution(client, model, full_messages, sbx):
|
| 243 |
# Forward updates to frontend
|
|
@@ -288,7 +318,8 @@ async def stream_research_notebook(
|
|
| 288 |
serper_key: str,
|
| 289 |
sub_agent_model: Optional[str] = None,
|
| 290 |
parallel_workers: Optional[int] = None,
|
| 291 |
-
max_websites: Optional[int] = None
|
|
|
|
| 292 |
):
|
| 293 |
"""Handle research notebook with web search"""
|
| 294 |
|
|
@@ -314,6 +345,10 @@ async def stream_research_notebook(
|
|
| 314 |
# Get system prompt for research
|
| 315 |
system_prompt = SYSTEM_PROMPTS.get("research", "")
|
| 316 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
# Use sub-agent model if provided, otherwise fall back to main model
|
| 318 |
analysis_model = sub_agent_model if sub_agent_model else model
|
| 319 |
|
|
@@ -338,31 +373,62 @@ async def stream_command_center_notebook(
|
|
| 338 |
messages: List[dict],
|
| 339 |
endpoint: str,
|
| 340 |
token: Optional[str],
|
| 341 |
-
model: str
|
|
|
|
| 342 |
):
|
| 343 |
"""Handle command center with tool-based notebook launching"""
|
| 344 |
|
| 345 |
if not COMMAND_AVAILABLE:
|
| 346 |
# Fallback to regular chat if command tools not available
|
| 347 |
-
async for chunk in stream_chat_response(messages, endpoint, token, model, "command"):
|
| 348 |
yield chunk
|
| 349 |
return
|
| 350 |
|
|
|
|
|
|
|
| 351 |
try:
|
| 352 |
# Create OpenAI client
|
| 353 |
client = OpenAI(base_url=endpoint, api_key=token)
|
| 354 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
# Add system prompt for command center
|
| 356 |
system_prompt = SYSTEM_PROMPTS["command"]
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
|
| 361 |
# Stream command center execution
|
| 362 |
for update in stream_command_center(client, model, full_messages):
|
| 363 |
# Forward updates to frontend
|
| 364 |
yield f"data: {json.dumps(update)}\n\n"
|
| 365 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
except Exception as e:
|
| 367 |
import traceback
|
| 368 |
error_message = f"Command center error: {str(e)}\n{traceback.format_exc()}"
|
|
@@ -375,7 +441,8 @@ async def stream_chat_response(
|
|
| 375 |
endpoint: str,
|
| 376 |
token: Optional[str],
|
| 377 |
model: str,
|
| 378 |
-
notebook_type: str
|
|
|
|
| 379 |
):
|
| 380 |
"""Proxy stream from user's configured LLM endpoint"""
|
| 381 |
|
|
@@ -392,6 +459,9 @@ async def stream_chat_response(
|
|
| 392 |
{"role": "system", "content": system_prompt}
|
| 393 |
] + messages
|
| 394 |
|
|
|
|
|
|
|
|
|
|
| 395 |
# Handle Hugging Face endpoint with fallback to HF_TOKEN
|
| 396 |
if not token and "huggingface.co" in endpoint:
|
| 397 |
token = os.getenv("HF_TOKEN")
|
|
@@ -543,6 +613,9 @@ async def chat_stream(request: ChatRequest):
|
|
| 543 |
# Convert Pydantic models to dicts
|
| 544 |
messages = [{"role": msg.role, "content": msg.content} for msg in request.messages]
|
| 545 |
|
|
|
|
|
|
|
|
|
|
| 546 |
# Route to code execution handler for code notebooks
|
| 547 |
if request.notebook_type == "code":
|
| 548 |
# Use notebook_id as session key, fallback to "default" if not provided
|
|
@@ -555,7 +628,8 @@ async def chat_stream(request: ChatRequest):
|
|
| 555 |
request.token,
|
| 556 |
request.model or "gpt-4",
|
| 557 |
request.e2b_key or "",
|
| 558 |
-
session_id
|
|
|
|
| 559 |
),
|
| 560 |
media_type="text/event-stream",
|
| 561 |
headers={
|
|
@@ -575,7 +649,9 @@ async def chat_stream(request: ChatRequest):
|
|
| 575 |
request.model or "gpt-4",
|
| 576 |
request.serper_key or "",
|
| 577 |
request.research_sub_agent_model,
|
| 578 |
-
request.research_parallel_workers
|
|
|
|
|
|
|
| 579 |
),
|
| 580 |
media_type="text/event-stream",
|
| 581 |
headers={
|
|
@@ -592,7 +668,8 @@ async def chat_stream(request: ChatRequest):
|
|
| 592 |
messages,
|
| 593 |
request.endpoint,
|
| 594 |
request.token,
|
| 595 |
-
request.model or "gpt-4"
|
|
|
|
| 596 |
),
|
| 597 |
media_type="text/event-stream",
|
| 598 |
headers={
|
|
@@ -609,7 +686,8 @@ async def chat_stream(request: ChatRequest):
|
|
| 609 |
request.endpoint,
|
| 610 |
request.token,
|
| 611 |
request.model or "gpt-4",
|
| 612 |
-
request.notebook_type
|
|
|
|
| 613 |
),
|
| 614 |
media_type="text/event-stream",
|
| 615 |
headers={
|
|
@@ -680,6 +758,40 @@ async def stop_sandbox(request: SandboxStopRequest):
|
|
| 680 |
return {"success": True, "message": "No sandbox found for this session"}
|
| 681 |
|
| 682 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 683 |
@app.get("/health")
|
| 684 |
async def health():
|
| 685 |
"""Health check endpoint"""
|
|
|
|
| 6 |
import json
|
| 7 |
import httpx
|
| 8 |
import os
|
| 9 |
+
from datetime import datetime
|
| 10 |
|
| 11 |
app = FastAPI(title="Productive API")
|
| 12 |
|
|
|
|
| 40 |
SANDBOXES: Dict[str, any] = {}
|
| 41 |
SANDBOX_TIMEOUT = 300
|
| 42 |
|
| 43 |
+
# Debug: Store message history for debugging per tab
|
| 44 |
+
# Structure: {tab_id: [{call_number: int, timestamp: str, messages: List[dict]}]}
|
| 45 |
+
MESSAGE_HISTORY: Dict[str, List[Dict]] = {}
|
| 46 |
+
|
| 47 |
+
# Conversation history per tab (persistent across requests)
|
| 48 |
+
# Structure: {tab_id: [messages...]}
|
| 49 |
+
CONVERSATION_HISTORY: Dict[str, List[Dict]] = {}
|
| 50 |
+
|
| 51 |
# CORS middleware for frontend connection
|
| 52 |
app.add_middleware(
|
| 53 |
CORSMiddleware,
|
|
|
|
| 176 |
}
|
| 177 |
|
| 178 |
|
| 179 |
+
def record_api_call(tab_id: str, messages: List[dict]):
|
| 180 |
+
"""Record an API call for debugging purposes"""
|
| 181 |
+
global MESSAGE_HISTORY
|
| 182 |
+
|
| 183 |
+
if tab_id not in MESSAGE_HISTORY:
|
| 184 |
+
MESSAGE_HISTORY[tab_id] = []
|
| 185 |
+
|
| 186 |
+
call_number = len(MESSAGE_HISTORY[tab_id]) + 1
|
| 187 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 188 |
+
|
| 189 |
+
MESSAGE_HISTORY[tab_id].append({
|
| 190 |
+
"call_number": call_number,
|
| 191 |
+
"timestamp": timestamp,
|
| 192 |
+
"messages": messages
|
| 193 |
+
})
|
| 194 |
+
|
| 195 |
+
|
| 196 |
class Message(BaseModel):
|
| 197 |
role: str
|
| 198 |
content: str
|
|
|
|
| 235 |
token: Optional[str],
|
| 236 |
model: str,
|
| 237 |
e2b_key: str,
|
| 238 |
+
session_id: str,
|
| 239 |
+
tab_id: str = "default"
|
| 240 |
):
|
| 241 |
"""Handle code notebook with execution capabilities"""
|
| 242 |
|
|
|
|
| 265 |
{"role": "system", "content": system_prompt}
|
| 266 |
] + messages
|
| 267 |
|
| 268 |
+
# Store for debugging
|
| 269 |
+
record_api_call(tab_id, full_messages)
|
| 270 |
+
|
| 271 |
# Stream code execution
|
| 272 |
for update in stream_code_execution(client, model, full_messages, sbx):
|
| 273 |
# Forward updates to frontend
|
|
|
|
| 318 |
serper_key: str,
|
| 319 |
sub_agent_model: Optional[str] = None,
|
| 320 |
parallel_workers: Optional[int] = None,
|
| 321 |
+
max_websites: Optional[int] = None,
|
| 322 |
+
tab_id: str = "default"
|
| 323 |
):
|
| 324 |
"""Handle research notebook with web search"""
|
| 325 |
|
|
|
|
| 345 |
# Get system prompt for research
|
| 346 |
system_prompt = SYSTEM_PROMPTS.get("research", "")
|
| 347 |
|
| 348 |
+
# Store for debugging (simplified version for research)
|
| 349 |
+
full_messages = [{"role": "system", "content": system_prompt}] + messages
|
| 350 |
+
record_api_call(tab_id, full_messages)
|
| 351 |
+
|
| 352 |
# Use sub-agent model if provided, otherwise fall back to main model
|
| 353 |
analysis_model = sub_agent_model if sub_agent_model else model
|
| 354 |
|
|
|
|
| 373 |
messages: List[dict],
|
| 374 |
endpoint: str,
|
| 375 |
token: Optional[str],
|
| 376 |
+
model: str,
|
| 377 |
+
tab_id: str = "0"
|
| 378 |
):
|
| 379 |
"""Handle command center with tool-based notebook launching"""
|
| 380 |
|
| 381 |
if not COMMAND_AVAILABLE:
|
| 382 |
# Fallback to regular chat if command tools not available
|
| 383 |
+
async for chunk in stream_chat_response(messages, endpoint, token, model, "command", tab_id):
|
| 384 |
yield chunk
|
| 385 |
return
|
| 386 |
|
| 387 |
+
global CONVERSATION_HISTORY
|
| 388 |
+
|
| 389 |
try:
|
| 390 |
# Create OpenAI client
|
| 391 |
client = OpenAI(base_url=endpoint, api_key=token)
|
| 392 |
|
| 393 |
+
# Get or initialize conversation history for this tab
|
| 394 |
+
if tab_id not in CONVERSATION_HISTORY:
|
| 395 |
+
CONVERSATION_HISTORY[tab_id] = []
|
| 396 |
+
|
| 397 |
# Add system prompt for command center
|
| 398 |
system_prompt = SYSTEM_PROMPTS["command"]
|
| 399 |
+
|
| 400 |
+
# Build full messages: system + stored history + new messages
|
| 401 |
+
print(f"DEBUG: tab_id={tab_id}, incoming messages={messages}")
|
| 402 |
+
print(f"DEBUG: stored history length={len(CONVERSATION_HISTORY[tab_id])}")
|
| 403 |
+
|
| 404 |
+
# On first call, history is empty, so just use incoming messages
|
| 405 |
+
# On subsequent calls, append only new messages
|
| 406 |
+
if not CONVERSATION_HISTORY[tab_id]:
|
| 407 |
+
# First message - use all incoming messages
|
| 408 |
+
full_messages = [{"role": "system", "content": system_prompt}] + messages
|
| 409 |
+
print(f"DEBUG: First call, full_messages length={len(full_messages)}")
|
| 410 |
+
else:
|
| 411 |
+
# Subsequent messages - use stored history + incoming messages
|
| 412 |
+
# The incoming messages should only be the new user message
|
| 413 |
+
full_messages = [{"role": "system", "content": system_prompt}] + CONVERSATION_HISTORY[tab_id] + messages
|
| 414 |
+
print(f"DEBUG: Subsequent call, full_messages length={len(full_messages)}")
|
| 415 |
+
|
| 416 |
+
# Store for debugging
|
| 417 |
+
record_api_call(tab_id, full_messages)
|
| 418 |
|
| 419 |
# Stream command center execution
|
| 420 |
for update in stream_command_center(client, model, full_messages):
|
| 421 |
# Forward updates to frontend
|
| 422 |
yield f"data: {json.dumps(update)}\n\n"
|
| 423 |
|
| 424 |
+
# After streaming completes, update persistent conversation history
|
| 425 |
+
# Remove system prompt and filter out empty assistant messages
|
| 426 |
+
conversation_without_system = [
|
| 427 |
+
msg for msg in full_messages
|
| 428 |
+
if msg.get("role") != "system" and not (msg.get("role") == "assistant" and not msg.get("content") and not msg.get("tool_calls"))
|
| 429 |
+
]
|
| 430 |
+
CONVERSATION_HISTORY[tab_id] = conversation_without_system
|
| 431 |
+
|
| 432 |
except Exception as e:
|
| 433 |
import traceback
|
| 434 |
error_message = f"Command center error: {str(e)}\n{traceback.format_exc()}"
|
|
|
|
| 441 |
endpoint: str,
|
| 442 |
token: Optional[str],
|
| 443 |
model: str,
|
| 444 |
+
notebook_type: str,
|
| 445 |
+
tab_id: str = "default"
|
| 446 |
):
|
| 447 |
"""Proxy stream from user's configured LLM endpoint"""
|
| 448 |
|
|
|
|
| 459 |
{"role": "system", "content": system_prompt}
|
| 460 |
] + messages
|
| 461 |
|
| 462 |
+
# Store for debugging
|
| 463 |
+
record_api_call(tab_id, full_messages)
|
| 464 |
+
|
| 465 |
# Handle Hugging Face endpoint with fallback to HF_TOKEN
|
| 466 |
if not token and "huggingface.co" in endpoint:
|
| 467 |
token = os.getenv("HF_TOKEN")
|
|
|
|
| 613 |
# Convert Pydantic models to dicts
|
| 614 |
messages = [{"role": msg.role, "content": msg.content} for msg in request.messages]
|
| 615 |
|
| 616 |
+
# Get tab_id for debugging
|
| 617 |
+
tab_id = request.notebook_id or "0"
|
| 618 |
+
|
| 619 |
# Route to code execution handler for code notebooks
|
| 620 |
if request.notebook_type == "code":
|
| 621 |
# Use notebook_id as session key, fallback to "default" if not provided
|
|
|
|
| 628 |
request.token,
|
| 629 |
request.model or "gpt-4",
|
| 630 |
request.e2b_key or "",
|
| 631 |
+
session_id,
|
| 632 |
+
tab_id
|
| 633 |
),
|
| 634 |
media_type="text/event-stream",
|
| 635 |
headers={
|
|
|
|
| 649 |
request.model or "gpt-4",
|
| 650 |
request.serper_key or "",
|
| 651 |
request.research_sub_agent_model,
|
| 652 |
+
request.research_parallel_workers,
|
| 653 |
+
None,
|
| 654 |
+
tab_id
|
| 655 |
),
|
| 656 |
media_type="text/event-stream",
|
| 657 |
headers={
|
|
|
|
| 668 |
messages,
|
| 669 |
request.endpoint,
|
| 670 |
request.token,
|
| 671 |
+
request.model or "gpt-4",
|
| 672 |
+
tab_id
|
| 673 |
),
|
| 674 |
media_type="text/event-stream",
|
| 675 |
headers={
|
|
|
|
| 686 |
request.endpoint,
|
| 687 |
request.token,
|
| 688 |
request.model or "gpt-4",
|
| 689 |
+
request.notebook_type,
|
| 690 |
+
tab_id
|
| 691 |
),
|
| 692 |
media_type="text/event-stream",
|
| 693 |
headers={
|
|
|
|
| 758 |
return {"success": True, "message": "No sandbox found for this session"}
|
| 759 |
|
| 760 |
|
| 761 |
+
@app.post("/api/conversation/add-tool-response")
|
| 762 |
+
async def add_tool_response(request: dict):
|
| 763 |
+
"""Add a tool response to the conversation history when a notebook returns a result"""
|
| 764 |
+
global CONVERSATION_HISTORY
|
| 765 |
+
|
| 766 |
+
tab_id = request.get("tab_id", "0")
|
| 767 |
+
tool_call_id = request.get("tool_call_id")
|
| 768 |
+
content = request.get("content")
|
| 769 |
+
|
| 770 |
+
if not tool_call_id or not content:
|
| 771 |
+
return {"success": False, "error": "tool_call_id and content are required"}
|
| 772 |
+
|
| 773 |
+
# Initialize if needed
|
| 774 |
+
if tab_id not in CONVERSATION_HISTORY:
|
| 775 |
+
CONVERSATION_HISTORY[tab_id] = []
|
| 776 |
+
|
| 777 |
+
# Add tool response to conversation history
|
| 778 |
+
CONVERSATION_HISTORY[tab_id].append({
|
| 779 |
+
"role": "tool",
|
| 780 |
+
"tool_call_id": tool_call_id,
|
| 781 |
+
"content": content
|
| 782 |
+
})
|
| 783 |
+
|
| 784 |
+
return {"success": True}
|
| 785 |
+
|
| 786 |
+
|
| 787 |
+
@app.get("/api/debug/messages/{tab_id}")
|
| 788 |
+
async def get_debug_messages(tab_id: str):
|
| 789 |
+
"""Get the message history for a specific tab for debugging"""
|
| 790 |
+
if tab_id in MESSAGE_HISTORY:
|
| 791 |
+
return {"calls": MESSAGE_HISTORY[tab_id]}
|
| 792 |
+
return {"calls": []}
|
| 793 |
+
|
| 794 |
+
|
| 795 |
@app.get("/health")
|
| 796 |
async def health():
|
| 797 |
"""Health check endpoint"""
|
index.html
CHANGED
|
@@ -26,6 +26,7 @@
|
|
| 26 |
</div>
|
| 27 |
</div>
|
| 28 |
<div class="tab-bar-spacer"></div>
|
|
|
|
| 29 |
<button class="settings-btn" id="settingsBtn">SETTINGS</button>
|
| 30 |
</div>
|
| 31 |
|
|
@@ -44,6 +45,7 @@
|
|
| 44 |
<button class="launcher-btn" data-type="code">CODE</button>
|
| 45 |
<button class="launcher-btn" data-type="research">RESEARCH</button>
|
| 46 |
<button class="launcher-btn" data-type="chat">CHAT</button>
|
|
|
|
| 47 |
</div>
|
| 48 |
</div>
|
| 49 |
|
|
@@ -141,144 +143,160 @@
|
|
| 141 |
</div>
|
| 142 |
</div>
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
<div class="settings-interface">
|
| 147 |
-
<div class="settings-header">
|
| 148 |
-
<h2>SETTINGS</h2>
|
| 149 |
-
</div>
|
| 150 |
-
<div class="settings-body">
|
| 151 |
-
<div class="settings-section">
|
| 152 |
-
<label class="settings-label">
|
| 153 |
-
<span class="label-text">LLM ENDPOINT</span>
|
| 154 |
-
<span class="label-description">OpenAI-compatible API endpoint (e.g., https://api.openai.com/v1)</span>
|
| 155 |
-
</label>
|
| 156 |
-
<input type="text" id="setting-endpoint" class="settings-input" placeholder="https://api.openai.com/v1">
|
| 157 |
-
</div>
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
<input type="text" id="setting-model-agent" class="settings-input" placeholder="Use default">
|
| 199 |
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
</div>
|
| 209 |
-
</div>
|
| 210 |
|
| 211 |
-
<
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
</label>
|
| 216 |
-
<input type="text" id="setting-research-sub-agent-model" class="settings-input" placeholder="Use research model or default">
|
| 217 |
-
</div>
|
| 218 |
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
<div class="theme-preview" style="border-color: #1b5e20; background: #e8f5e9;"></div>
|
| 243 |
-
<span class="theme-name">Forest</span>
|
| 244 |
-
</div>
|
| 245 |
-
<div class="theme-option" data-theme="sapphire">
|
| 246 |
-
<div class="theme-preview" style="border-color: #0d47a1; background: #e3f2fd;"></div>
|
| 247 |
-
<span class="theme-name">Sapphire</span>
|
| 248 |
-
</div>
|
| 249 |
-
<div class="theme-option" data-theme="ocean">
|
| 250 |
-
<div class="theme-preview" style="border-color: #00796b; background: #e0f2f1;"></div>
|
| 251 |
-
<span class="theme-name">Ocean</span>
|
| 252 |
-
</div>
|
| 253 |
-
<div class="theme-option" data-theme="midnight">
|
| 254 |
-
<div class="theme-preview" style="border-color: #283593; background: #e8eaf6;"></div>
|
| 255 |
-
<span class="theme-name">Midnight</span>
|
| 256 |
-
</div>
|
| 257 |
-
<div class="theme-option" data-theme="steel">
|
| 258 |
-
<div class="theme-preview" style="border-color: #455a64; background: #eceff1;"></div>
|
| 259 |
-
<span class="theme-name">Steel</span>
|
| 260 |
-
</div>
|
| 261 |
-
<div class="theme-option" data-theme="depths">
|
| 262 |
-
<div class="theme-preview" style="border-color: #01579b; background: #e3f2fd;"></div>
|
| 263 |
-
<span class="theme-name">Depths</span>
|
| 264 |
-
</div>
|
| 265 |
-
<div class="theme-option" data-theme="ember">
|
| 266 |
-
<div class="theme-preview" style="border-color: #b71c1c; background: #fbe9e7;"></div>
|
| 267 |
-
<span class="theme-name">Ember</span>
|
| 268 |
-
</div>
|
| 269 |
-
</div>
|
| 270 |
-
<input type="hidden" id="setting-theme-color" value="forest">
|
| 271 |
-
</div>
|
| 272 |
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
</div>
|
| 277 |
-
|
| 278 |
-
<div class="settings-status" id="settingsStatus"></div>
|
| 279 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
</div>
|
|
|
|
| 282 |
</div>
|
| 283 |
</div>
|
| 284 |
|
|
|
|
| 26 |
</div>
|
| 27 |
</div>
|
| 28 |
<div class="tab-bar-spacer"></div>
|
| 29 |
+
<button class="debug-btn" id="debugBtn">DEBUG</button>
|
| 30 |
<button class="settings-btn" id="settingsBtn">SETTINGS</button>
|
| 31 |
</div>
|
| 32 |
|
|
|
|
| 45 |
<button class="launcher-btn" data-type="code">CODE</button>
|
| 46 |
<button class="launcher-btn" data-type="research">RESEARCH</button>
|
| 47 |
<button class="launcher-btn" data-type="chat">CHAT</button>
|
| 48 |
+
<button class="debug-btn" id="debugBtn">DEBUG</button>
|
| 49 |
</div>
|
| 50 |
</div>
|
| 51 |
|
|
|
|
| 143 |
</div>
|
| 144 |
</div>
|
| 145 |
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
+
<!-- Settings Panel -->
|
| 150 |
+
<div class="settings-panel" id="settingsPanel">
|
| 151 |
+
<div class="settings-panel-header">
|
| 152 |
+
<h3>SETTINGS</h3>
|
| 153 |
+
<button class="settings-panel-close" id="settingsPanelClose">×</button>
|
| 154 |
+
</div>
|
| 155 |
+
<div class="settings-panel-body" id="settingsPanelBody">
|
| 156 |
+
<div class="settings-body">
|
| 157 |
+
<div class="settings-section">
|
| 158 |
+
<label class="settings-label">
|
| 159 |
+
<span class="label-text">LLM ENDPOINT</span>
|
| 160 |
+
<span class="label-description">OpenAI-compatible API endpoint (e.g., https://api.openai.com/v1)</span>
|
| 161 |
+
</label>
|
| 162 |
+
<input type="text" id="setting-endpoint" class="settings-input" placeholder="https://api.openai.com/v1">
|
| 163 |
+
</div>
|
| 164 |
|
| 165 |
+
<div class="settings-section">
|
| 166 |
+
<label class="settings-label">
|
| 167 |
+
<span class="label-text">API TOKEN (OPTIONAL)</span>
|
| 168 |
+
<span class="label-description">Authentication token for the LLM API</span>
|
| 169 |
+
</label>
|
| 170 |
+
<input type="password" id="setting-token" class="settings-input" placeholder="Leave empty if not required">
|
| 171 |
+
</div>
|
| 172 |
|
| 173 |
+
<div class="settings-section">
|
| 174 |
+
<label class="settings-label">
|
| 175 |
+
<span class="label-text">MODEL</span>
|
| 176 |
+
<span class="label-description">Default model name (e.g., gpt-4, gpt-3.5-turbo)</span>
|
| 177 |
+
</label>
|
| 178 |
+
<input type="text" id="setting-model" class="settings-input" placeholder="gpt-4">
|
| 179 |
+
</div>
|
| 180 |
|
| 181 |
+
<div class="settings-section">
|
| 182 |
+
<label class="settings-label">
|
| 183 |
+
<span class="label-text">E2B API KEY (OPTIONAL)</span>
|
| 184 |
+
<span class="label-description">Required for code execution in CODE notebooks</span>
|
| 185 |
+
</label>
|
| 186 |
+
<input type="password" id="setting-e2b-key" class="settings-input" placeholder="Leave empty if not using code execution">
|
| 187 |
+
</div>
|
| 188 |
|
| 189 |
+
<div class="settings-section">
|
| 190 |
+
<label class="settings-label">
|
| 191 |
+
<span class="label-text">SERPER API KEY (OPTIONAL)</span>
|
| 192 |
+
<span class="label-description">Required for web search in RESEARCH notebooks</span>
|
| 193 |
+
</label>
|
| 194 |
+
<input type="password" id="setting-serper-key" class="settings-input" placeholder="Leave empty if not using research">
|
| 195 |
+
</div>
|
|
|
|
| 196 |
|
| 197 |
+
<div class="settings-section">
|
| 198 |
+
<label class="settings-label">
|
| 199 |
+
<span class="label-text">NOTEBOOK-SPECIFIC MODELS (OPTIONAL)</span>
|
| 200 |
+
<span class="label-description">Override default model for specific notebook types</span>
|
| 201 |
+
</label>
|
| 202 |
+
<div style="display: grid; grid-template-columns: auto 1fr; gap: 8px; align-items: center;">
|
| 203 |
+
<label style="font-size: 11px; color: #666;">AGENT:</label>
|
| 204 |
+
<input type="text" id="setting-model-agent" class="settings-input" placeholder="Use default">
|
| 205 |
|
| 206 |
+
<label style="font-size: 11px; color: #666;">CODE:</label>
|
| 207 |
+
<input type="text" id="setting-model-code" class="settings-input" placeholder="Use default">
|
| 208 |
|
| 209 |
+
<label style="font-size: 11px; color: #666;">RESEARCH:</label>
|
| 210 |
+
<input type="text" id="setting-model-research" class="settings-input" placeholder="Use default">
|
|
|
|
|
|
|
| 211 |
|
| 212 |
+
<label style="font-size: 11px; color: #666;">CHAT:</label>
|
| 213 |
+
<input type="text" id="setting-model-chat" class="settings-input" placeholder="Use default">
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
+
<div class="settings-section">
|
| 218 |
+
<label class="settings-label">
|
| 219 |
+
<span class="label-text">RESEARCH SUB-AGENT MODEL (OPTIONAL)</span>
|
| 220 |
+
<span class="label-description">Smaller/faster model for analyzing individual web pages during research</span>
|
| 221 |
+
</label>
|
| 222 |
+
<input type="text" id="setting-research-sub-agent-model" class="settings-input" placeholder="Use research model or default">
|
| 223 |
+
</div>
|
| 224 |
|
| 225 |
+
<div class="settings-section">
|
| 226 |
+
<label class="settings-label">
|
| 227 |
+
<span class="label-text">RESEARCH PARALLEL WORKERS (OPTIONAL)</span>
|
| 228 |
+
<span class="label-description">Number of web pages to analyze in parallel (default: 8)</span>
|
| 229 |
+
</label>
|
| 230 |
+
<input type="number" id="setting-research-parallel-workers" class="settings-input" placeholder="8" min="1" max="20">
|
| 231 |
+
</div>
|
| 232 |
|
| 233 |
+
<div class="settings-section">
|
| 234 |
+
<label class="settings-label">
|
| 235 |
+
<span class="label-text">RESEARCH MAX WEBSITES (OPTIONAL)</span>
|
| 236 |
+
<span class="label-description">Maximum number of websites to analyze per research session (default: 50)</span>
|
| 237 |
+
</label>
|
| 238 |
+
<input type="number" id="setting-research-max-websites" class="settings-input" placeholder="50" min="10" max="200">
|
| 239 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
+
<div class="settings-section">
|
| 242 |
+
<label class="settings-label">
|
| 243 |
+
<span class="label-text">THEME COLOR</span>
|
| 244 |
+
<span class="label-description">Color theme for action widgets and accents</span>
|
| 245 |
+
</label>
|
| 246 |
+
<div class="theme-color-picker" id="theme-color-picker">
|
| 247 |
+
<div class="theme-option" data-theme="forest">
|
| 248 |
+
<div class="theme-preview" style="border-color: #1b5e20; background: #e8f5e9;"></div>
|
| 249 |
+
<span class="theme-name">Forest</span>
|
| 250 |
+
</div>
|
| 251 |
+
<div class="theme-option" data-theme="sapphire">
|
| 252 |
+
<div class="theme-preview" style="border-color: #0d47a1; background: #e3f2fd;"></div>
|
| 253 |
+
<span class="theme-name">Sapphire</span>
|
| 254 |
+
</div>
|
| 255 |
+
<div class="theme-option" data-theme="ocean">
|
| 256 |
+
<div class="theme-preview" style="border-color: #00796b; background: #e0f2f1;"></div>
|
| 257 |
+
<span class="theme-name">Ocean</span>
|
| 258 |
+
</div>
|
| 259 |
+
<div class="theme-option" data-theme="midnight">
|
| 260 |
+
<div class="theme-preview" style="border-color: #283593; background: #e8eaf6;"></div>
|
| 261 |
+
<span class="theme-name">Midnight</span>
|
| 262 |
+
</div>
|
| 263 |
+
<div class="theme-option" data-theme="steel">
|
| 264 |
+
<div class="theme-preview" style="border-color: #455a64; background: #eceff1;"></div>
|
| 265 |
+
<span class="theme-name">Steel</span>
|
| 266 |
+
</div>
|
| 267 |
+
<div class="theme-option" data-theme="depths">
|
| 268 |
+
<div class="theme-preview" style="border-color: #01579b; background: #e3f2fd;"></div>
|
| 269 |
+
<span class="theme-name">Depths</span>
|
| 270 |
+
</div>
|
| 271 |
+
<div class="theme-option" data-theme="ember">
|
| 272 |
+
<div class="theme-preview" style="border-color: #b71c1c; background: #fbe9e7;"></div>
|
| 273 |
+
<span class="theme-name">Ember</span>
|
| 274 |
</div>
|
|
|
|
|
|
|
| 275 |
</div>
|
| 276 |
+
<input type="hidden" id="setting-theme-color" value="forest">
|
| 277 |
+
</div>
|
| 278 |
+
|
| 279 |
+
<div class="settings-actions">
|
| 280 |
+
<button class="settings-save-btn" id="saveSettingsBtn">SAVE</button>
|
| 281 |
+
<button class="settings-cancel-btn" id="cancelSettingsBtn">CANCEL</button>
|
| 282 |
</div>
|
| 283 |
+
|
| 284 |
+
<div class="settings-status" id="settingsStatus"></div>
|
| 285 |
+
</div>
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<!-- Debug Panel -->
|
| 290 |
+
<div class="debug-panel" id="debugPanel">
|
| 291 |
+
<div class="debug-header">
|
| 292 |
+
<h3>DEBUG: Message History</h3>
|
| 293 |
+
<button class="debug-close" id="debugClose">×</button>
|
| 294 |
+
</div>
|
| 295 |
+
<div class="debug-body">
|
| 296 |
+
<div class="debug-controls">
|
| 297 |
+
<button class="debug-refresh" id="debugRefresh">Refresh</button>
|
| 298 |
</div>
|
| 299 |
+
<pre class="debug-content" id="debugContent">No message history available yet.</pre>
|
| 300 |
</div>
|
| 301 |
</div>
|
| 302 |
|
script.js
CHANGED
|
@@ -18,6 +18,9 @@ let settings = {
|
|
| 18 |
// Track action widgets for result updates (maps tabId -> widget element)
|
| 19 |
const actionWidgets = {};
|
| 20 |
|
|
|
|
|
|
|
|
|
|
| 21 |
// Track notebook counters for each type
|
| 22 |
let notebookCounters = {
|
| 23 |
'agent': 0,
|
|
@@ -42,6 +45,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 42 |
});
|
| 43 |
|
| 44 |
function initializeEventListeners() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
// Launcher buttons in command center
|
| 46 |
document.querySelectorAll('.launcher-btn').forEach(btn => {
|
| 47 |
btn.addEventListener('click', (e) => {
|
|
@@ -107,13 +119,8 @@ function initializeEventListeners() {
|
|
| 107 |
}
|
| 108 |
});
|
| 109 |
|
| 110 |
-
// Settings button
|
| 111 |
-
|
| 112 |
-
if (settingsBtn) {
|
| 113 |
-
settingsBtn.addEventListener('click', () => {
|
| 114 |
-
openSettings();
|
| 115 |
-
});
|
| 116 |
-
}
|
| 117 |
|
| 118 |
// Save settings button
|
| 119 |
const saveSettingsBtn = document.getElementById('saveSettingsBtn');
|
|
@@ -127,7 +134,12 @@ function initializeEventListeners() {
|
|
| 127 |
const cancelSettingsBtn = document.getElementById('cancelSettingsBtn');
|
| 128 |
if (cancelSettingsBtn) {
|
| 129 |
cancelSettingsBtn.addEventListener('click', () => {
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
});
|
| 132 |
}
|
| 133 |
|
|
@@ -337,7 +349,7 @@ async function sendMessage(tabId) {
|
|
| 337 |
userMsg.className = 'message user';
|
| 338 |
userMsg.innerHTML = `
|
| 339 |
<div class="message-label">USER</div>
|
| 340 |
-
<div class="message-content">${escapeHtml(message)}</div>
|
| 341 |
`;
|
| 342 |
chatContainer.appendChild(userMsg);
|
| 343 |
|
|
@@ -356,8 +368,16 @@ async function sendMessage(tabId) {
|
|
| 356 |
// Determine notebook type from chat container ID
|
| 357 |
const notebookType = getNotebookTypeFromContainer(chatContainer);
|
| 358 |
|
| 359 |
-
//
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
|
| 362 |
// Stream response from backend
|
| 363 |
await streamChatResponse(messages, chatContainer, notebookType, tabId);
|
|
@@ -438,8 +458,29 @@ function getConversationHistory(chatContainer) {
|
|
| 438 |
if (label === 'user') {
|
| 439 |
messages.push({ role: 'user', content: content });
|
| 440 |
} else if (label === 'assistant') {
|
| 441 |
-
//
|
| 442 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
messages.push({ role: 'assistant', content: content });
|
| 444 |
}
|
| 445 |
}
|
|
@@ -629,9 +670,13 @@ async function streamChatResponse(messages, chatContainer, notebookType, tabId)
|
|
| 629 |
// Tool-based notebook launch from command center
|
| 630 |
const notebookType = data.notebook_type;
|
| 631 |
const initialMessage = data.initial_message;
|
|
|
|
| 632 |
|
|
|
|
| 633 |
handleActionToken(notebookType, initialMessage, (targetTabId) => {
|
| 634 |
showActionWidget(chatContainer, notebookType, initialMessage, targetTabId);
|
|
|
|
|
|
|
| 635 |
});
|
| 636 |
|
| 637 |
// Reset current message element so any subsequent thinking starts fresh
|
|
@@ -824,7 +869,7 @@ function showActionWidget(chatContainer, action, message, targetTabId) {
|
|
| 824 |
actionWidgets[targetTabId] = widget;
|
| 825 |
}
|
| 826 |
|
| 827 |
-
function updateActionWidgetWithResult(tabId, resultContent, figures) {
|
| 828 |
const widget = actionWidgets[tabId];
|
| 829 |
if (!widget) return;
|
| 830 |
|
|
@@ -837,6 +882,27 @@ function updateActionWidgetWithResult(tabId, resultContent, figures) {
|
|
| 837 |
statusText.textContent = statusText.textContent.replace('Opening', 'Completed').replace(/\.+$/, '');
|
| 838 |
}
|
| 839 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
// Replace <figure_x> tags with placeholders BEFORE markdown processing
|
| 841 |
let processedContent = resultContent;
|
| 842 |
const figurePlaceholders = {};
|
|
@@ -1136,9 +1202,6 @@ function openSettings() {
|
|
| 1136 |
const status = document.getElementById('settingsStatus');
|
| 1137 |
status.className = 'settings-status';
|
| 1138 |
status.textContent = '';
|
| 1139 |
-
|
| 1140 |
-
// Switch to settings view
|
| 1141 |
-
switchToTab('settings');
|
| 1142 |
}
|
| 1143 |
|
| 1144 |
function saveSettings() {
|
|
@@ -1215,43 +1278,50 @@ const themeColors = {
|
|
| 1215 |
border: '#1b5e20',
|
| 1216 |
bg: '#e8f5e9',
|
| 1217 |
hoverBg: '#c8e6c9',
|
| 1218 |
-
accent: '#1b5e20'
|
|
|
|
| 1219 |
},
|
| 1220 |
sapphire: {
|
| 1221 |
border: '#0d47a1',
|
| 1222 |
bg: '#e3f2fd',
|
| 1223 |
hoverBg: '#bbdefb',
|
| 1224 |
-
accent: '#0d47a1'
|
|
|
|
| 1225 |
},
|
| 1226 |
ocean: {
|
| 1227 |
border: '#00796b',
|
| 1228 |
bg: '#e0f2f1',
|
| 1229 |
hoverBg: '#b2dfdb',
|
| 1230 |
-
accent: '#004d40'
|
|
|
|
| 1231 |
},
|
| 1232 |
midnight: {
|
| 1233 |
border: '#283593',
|
| 1234 |
bg: '#e8eaf6',
|
| 1235 |
hoverBg: '#c5cae9',
|
| 1236 |
-
accent: '#1a237e'
|
|
|
|
| 1237 |
},
|
| 1238 |
steel: {
|
| 1239 |
border: '#455a64',
|
| 1240 |
bg: '#eceff1',
|
| 1241 |
hoverBg: '#cfd8dc',
|
| 1242 |
-
accent: '#263238'
|
|
|
|
| 1243 |
},
|
| 1244 |
depths: {
|
| 1245 |
border: '#01579b',
|
| 1246 |
bg: '#e3f2fd',
|
| 1247 |
hoverBg: '#bbdefb',
|
| 1248 |
-
accent: '#01579b'
|
|
|
|
| 1249 |
},
|
| 1250 |
ember: {
|
| 1251 |
border: '#b71c1c',
|
| 1252 |
bg: '#fbe9e7',
|
| 1253 |
hoverBg: '#ffccbc',
|
| 1254 |
-
accent: '#b71c1c'
|
|
|
|
| 1255 |
}
|
| 1256 |
};
|
| 1257 |
|
|
@@ -1264,6 +1334,7 @@ function applyTheme(themeName) {
|
|
| 1264 |
root.style.setProperty('--theme-bg', theme.bg);
|
| 1265 |
root.style.setProperty('--theme-hover-bg', theme.hoverBg);
|
| 1266 |
root.style.setProperty('--theme-accent', theme.accent);
|
|
|
|
| 1267 |
}
|
| 1268 |
|
| 1269 |
// Export settings for use in API calls
|
|
@@ -1387,3 +1458,129 @@ function openImageModal(src) {
|
|
| 1387 |
modalImg.src = src;
|
| 1388 |
modal.style.display = 'block';
|
| 1389 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
// Track action widgets for result updates (maps tabId -> widget element)
|
| 19 |
const actionWidgets = {};
|
| 20 |
|
| 21 |
+
// Track tool call IDs for result updates (maps tabId -> tool_call_id)
|
| 22 |
+
const toolCallIds = {};
|
| 23 |
+
|
| 24 |
// Track notebook counters for each type
|
| 25 |
let notebookCounters = {
|
| 26 |
'agent': 0,
|
|
|
|
| 45 |
});
|
| 46 |
|
| 47 |
function initializeEventListeners() {
|
| 48 |
+
// Constrain text selection to message-content only
|
| 49 |
+
document.addEventListener('selectstart', (e) => {
|
| 50 |
+
const target = e.target;
|
| 51 |
+
const messageContent = target.closest('.message-content');
|
| 52 |
+
if (!messageContent && target.closest('.message')) {
|
| 53 |
+
e.preventDefault();
|
| 54 |
+
}
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
// Launcher buttons in command center
|
| 58 |
document.querySelectorAll('.launcher-btn').forEach(btn => {
|
| 59 |
btn.addEventListener('click', (e) => {
|
|
|
|
| 119 |
}
|
| 120 |
});
|
| 121 |
|
| 122 |
+
// Settings button - now handled in settings panel section below
|
| 123 |
+
// (removed old openSettings() call)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
// Save settings button
|
| 126 |
const saveSettingsBtn = document.getElementById('saveSettingsBtn');
|
|
|
|
| 134 |
const cancelSettingsBtn = document.getElementById('cancelSettingsBtn');
|
| 135 |
if (cancelSettingsBtn) {
|
| 136 |
cancelSettingsBtn.addEventListener('click', () => {
|
| 137 |
+
const settingsPanel = document.getElementById('settingsPanel');
|
| 138 |
+
const settingsBtn = document.getElementById('settingsBtn');
|
| 139 |
+
const appContainer = document.querySelector('.app-container');
|
| 140 |
+
if (settingsPanel) settingsPanel.classList.remove('active');
|
| 141 |
+
if (settingsBtn) settingsBtn.classList.remove('active');
|
| 142 |
+
if (appContainer) appContainer.classList.remove('panel-open');
|
| 143 |
});
|
| 144 |
}
|
| 145 |
|
|
|
|
| 349 |
userMsg.className = 'message user';
|
| 350 |
userMsg.innerHTML = `
|
| 351 |
<div class="message-label">USER</div>
|
| 352 |
+
<div class="message-content">${escapeHtml(message.trim())}</div>
|
| 353 |
`;
|
| 354 |
chatContainer.appendChild(userMsg);
|
| 355 |
|
|
|
|
| 368 |
// Determine notebook type from chat container ID
|
| 369 |
const notebookType = getNotebookTypeFromContainer(chatContainer);
|
| 370 |
|
| 371 |
+
// For command center, only send the new user message (backend maintains history)
|
| 372 |
+
// For other notebooks, send full conversation history
|
| 373 |
+
let messages;
|
| 374 |
+
if (notebookType === 'command') {
|
| 375 |
+
// Only send the last user message
|
| 376 |
+
messages = [{ role: 'user', content: message }];
|
| 377 |
+
} else {
|
| 378 |
+
// Get full conversation history for other notebooks
|
| 379 |
+
messages = getConversationHistory(chatContainer);
|
| 380 |
+
}
|
| 381 |
|
| 382 |
// Stream response from backend
|
| 383 |
await streamChatResponse(messages, chatContainer, notebookType, tabId);
|
|
|
|
| 458 |
if (label === 'user') {
|
| 459 |
messages.push({ role: 'user', content: content });
|
| 460 |
} else if (label === 'assistant') {
|
| 461 |
+
// Check if this message has a tool call
|
| 462 |
+
const toolCallData = msg.getAttribute('data-tool-call');
|
| 463 |
+
if (toolCallData) {
|
| 464 |
+
// This is a tool call message - add it in the proper format
|
| 465 |
+
const toolCall = JSON.parse(toolCallData);
|
| 466 |
+
messages.push({
|
| 467 |
+
role: 'assistant',
|
| 468 |
+
content: '',
|
| 469 |
+
tool_calls: [{
|
| 470 |
+
id: 'tool_' + Date.now(),
|
| 471 |
+
type: 'function',
|
| 472 |
+
function: {
|
| 473 |
+
name: `launch_${toolCall.notebook_type}_notebook`,
|
| 474 |
+
arguments: JSON.stringify({
|
| 475 |
+
task: toolCall.message,
|
| 476 |
+
topic: toolCall.message,
|
| 477 |
+
message: toolCall.message
|
| 478 |
+
})
|
| 479 |
+
}
|
| 480 |
+
}]
|
| 481 |
+
});
|
| 482 |
+
} else if (content.trim() && !content.includes('msg-') && !content.includes('→ Launched')) {
|
| 483 |
+
// Regular assistant message (exclude launch notifications)
|
| 484 |
messages.push({ role: 'assistant', content: content });
|
| 485 |
}
|
| 486 |
}
|
|
|
|
| 670 |
// Tool-based notebook launch from command center
|
| 671 |
const notebookType = data.notebook_type;
|
| 672 |
const initialMessage = data.initial_message;
|
| 673 |
+
const toolCallId = data.tool_call_id;
|
| 674 |
|
| 675 |
+
// The action widget will show the launch, no need for a visible assistant message
|
| 676 |
handleActionToken(notebookType, initialMessage, (targetTabId) => {
|
| 677 |
showActionWidget(chatContainer, notebookType, initialMessage, targetTabId);
|
| 678 |
+
// Store tool call ID for this notebook tab so we can send result back
|
| 679 |
+
toolCallIds[targetTabId] = toolCallId;
|
| 680 |
});
|
| 681 |
|
| 682 |
// Reset current message element so any subsequent thinking starts fresh
|
|
|
|
| 869 |
actionWidgets[targetTabId] = widget;
|
| 870 |
}
|
| 871 |
|
| 872 |
+
async function updateActionWidgetWithResult(tabId, resultContent, figures) {
|
| 873 |
const widget = actionWidgets[tabId];
|
| 874 |
if (!widget) return;
|
| 875 |
|
|
|
|
| 882 |
statusText.textContent = statusText.textContent.replace('Opening', 'Completed').replace(/\.+$/, '');
|
| 883 |
}
|
| 884 |
|
| 885 |
+
// Send result back to backend to update conversation history
|
| 886 |
+
const toolCallId = toolCallIds[tabId];
|
| 887 |
+
if (toolCallId) {
|
| 888 |
+
try {
|
| 889 |
+
// Get the command center tab ID (tab 0)
|
| 890 |
+
const commandTabId = '0';
|
| 891 |
+
|
| 892 |
+
await fetch('http://localhost:8000/api/conversation/add-tool-response', {
|
| 893 |
+
method: 'POST',
|
| 894 |
+
headers: { 'Content-Type': 'application/json' },
|
| 895 |
+
body: JSON.stringify({
|
| 896 |
+
tab_id: commandTabId,
|
| 897 |
+
tool_call_id: toolCallId,
|
| 898 |
+
content: resultContent
|
| 899 |
+
})
|
| 900 |
+
});
|
| 901 |
+
} catch (error) {
|
| 902 |
+
console.error('Failed to update conversation history with result:', error);
|
| 903 |
+
}
|
| 904 |
+
}
|
| 905 |
+
|
| 906 |
// Replace <figure_x> tags with placeholders BEFORE markdown processing
|
| 907 |
let processedContent = resultContent;
|
| 908 |
const figurePlaceholders = {};
|
|
|
|
| 1202 |
const status = document.getElementById('settingsStatus');
|
| 1203 |
status.className = 'settings-status';
|
| 1204 |
status.textContent = '';
|
|
|
|
|
|
|
|
|
|
| 1205 |
}
|
| 1206 |
|
| 1207 |
function saveSettings() {
|
|
|
|
| 1278 |
border: '#1b5e20',
|
| 1279 |
bg: '#e8f5e9',
|
| 1280 |
hoverBg: '#c8e6c9',
|
| 1281 |
+
accent: '#1b5e20',
|
| 1282 |
+
accentRgb: '27, 94, 32'
|
| 1283 |
},
|
| 1284 |
sapphire: {
|
| 1285 |
border: '#0d47a1',
|
| 1286 |
bg: '#e3f2fd',
|
| 1287 |
hoverBg: '#bbdefb',
|
| 1288 |
+
accent: '#0d47a1',
|
| 1289 |
+
accentRgb: '13, 71, 161'
|
| 1290 |
},
|
| 1291 |
ocean: {
|
| 1292 |
border: '#00796b',
|
| 1293 |
bg: '#e0f2f1',
|
| 1294 |
hoverBg: '#b2dfdb',
|
| 1295 |
+
accent: '#004d40',
|
| 1296 |
+
accentRgb: '0, 77, 64'
|
| 1297 |
},
|
| 1298 |
midnight: {
|
| 1299 |
border: '#283593',
|
| 1300 |
bg: '#e8eaf6',
|
| 1301 |
hoverBg: '#c5cae9',
|
| 1302 |
+
accent: '#1a237e',
|
| 1303 |
+
accentRgb: '26, 35, 126'
|
| 1304 |
},
|
| 1305 |
steel: {
|
| 1306 |
border: '#455a64',
|
| 1307 |
bg: '#eceff1',
|
| 1308 |
hoverBg: '#cfd8dc',
|
| 1309 |
+
accent: '#263238',
|
| 1310 |
+
accentRgb: '38, 50, 56'
|
| 1311 |
},
|
| 1312 |
depths: {
|
| 1313 |
border: '#01579b',
|
| 1314 |
bg: '#e3f2fd',
|
| 1315 |
hoverBg: '#bbdefb',
|
| 1316 |
+
accent: '#01579b',
|
| 1317 |
+
accentRgb: '1, 87, 155'
|
| 1318 |
},
|
| 1319 |
ember: {
|
| 1320 |
border: '#b71c1c',
|
| 1321 |
bg: '#fbe9e7',
|
| 1322 |
hoverBg: '#ffccbc',
|
| 1323 |
+
accent: '#b71c1c',
|
| 1324 |
+
accentRgb: '183, 28, 28'
|
| 1325 |
}
|
| 1326 |
};
|
| 1327 |
|
|
|
|
| 1334 |
root.style.setProperty('--theme-bg', theme.bg);
|
| 1335 |
root.style.setProperty('--theme-hover-bg', theme.hoverBg);
|
| 1336 |
root.style.setProperty('--theme-accent', theme.accent);
|
| 1337 |
+
root.style.setProperty('--theme-accent-rgb', theme.accentRgb);
|
| 1338 |
}
|
| 1339 |
|
| 1340 |
// Export settings for use in API calls
|
|
|
|
| 1458 |
modalImg.src = src;
|
| 1459 |
modal.style.display = 'block';
|
| 1460 |
}
|
| 1461 |
+
|
| 1462 |
+
// ============= DEBUG PANEL =============
|
| 1463 |
+
|
| 1464 |
+
const debugPanel = document.getElementById('debugPanel');
|
| 1465 |
+
const debugBtn = document.getElementById('debugBtn');
|
| 1466 |
+
const debugClose = document.getElementById('debugClose');
|
| 1467 |
+
const debugRefresh = document.getElementById('debugRefresh');
|
| 1468 |
+
const debugContent = document.getElementById('debugContent');
|
| 1469 |
+
|
| 1470 |
+
// Toggle debug panel
|
| 1471 |
+
if (debugBtn) {
|
| 1472 |
+
debugBtn.addEventListener('click', () => {
|
| 1473 |
+
const isOpening = !debugPanel.classList.contains('active');
|
| 1474 |
+
|
| 1475 |
+
// Close settings panel if open
|
| 1476 |
+
settingsPanel.classList.remove('active');
|
| 1477 |
+
settingsBtn.classList.remove('active');
|
| 1478 |
+
|
| 1479 |
+
// Toggle debug panel
|
| 1480 |
+
debugPanel.classList.toggle('active');
|
| 1481 |
+
debugBtn.classList.toggle('active');
|
| 1482 |
+
|
| 1483 |
+
// Shift content when opening, unshift when closing
|
| 1484 |
+
if (isOpening) {
|
| 1485 |
+
appContainer.classList.add('panel-open');
|
| 1486 |
+
loadDebugMessages();
|
| 1487 |
+
} else {
|
| 1488 |
+
appContainer.classList.remove('panel-open');
|
| 1489 |
+
}
|
| 1490 |
+
});
|
| 1491 |
+
}
|
| 1492 |
+
|
| 1493 |
+
// Close debug panel
|
| 1494 |
+
if (debugClose) {
|
| 1495 |
+
debugClose.addEventListener('click', () => {
|
| 1496 |
+
debugPanel.classList.remove('active');
|
| 1497 |
+
debugBtn.classList.remove('active');
|
| 1498 |
+
appContainer.classList.remove('panel-open');
|
| 1499 |
+
});
|
| 1500 |
+
}
|
| 1501 |
+
|
| 1502 |
+
// Refresh debug messages
|
| 1503 |
+
if (debugRefresh) {
|
| 1504 |
+
debugRefresh.addEventListener('click', () => {
|
| 1505 |
+
loadDebugMessages();
|
| 1506 |
+
});
|
| 1507 |
+
}
|
| 1508 |
+
|
| 1509 |
+
// Load debug messages from backend
|
| 1510 |
+
async function loadDebugMessages() {
|
| 1511 |
+
try {
|
| 1512 |
+
debugContent.innerHTML = '<div style="padding: 10px; color: #666;">Loading...</div>';
|
| 1513 |
+
|
| 1514 |
+
// Get current active tab ID
|
| 1515 |
+
const activeTab = document.querySelector('.tab.active');
|
| 1516 |
+
const tabId = activeTab ? activeTab.dataset.tabId : '0';
|
| 1517 |
+
|
| 1518 |
+
const response = await fetch(`http://localhost:8000/api/debug/messages/${tabId}`);
|
| 1519 |
+
|
| 1520 |
+
if (!response.ok) {
|
| 1521 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
| 1522 |
+
}
|
| 1523 |
+
|
| 1524 |
+
const data = await response.json();
|
| 1525 |
+
|
| 1526 |
+
if (data.calls && data.calls.length > 0) {
|
| 1527 |
+
// Create collapsible UI for each API call
|
| 1528 |
+
let html = '';
|
| 1529 |
+
data.calls.forEach((call, index) => {
|
| 1530 |
+
const isExpanded = index === data.calls.length - 1; // Expand only the last call by default
|
| 1531 |
+
html += `<div class="debug-call-item"><div class="debug-call-header" onclick="toggleDebugCall(${index})"><span class="debug-call-arrow" id="arrow-${index}">${isExpanded ? '▼' : '▶'}</span><span class="debug-call-title">LLM Call #${call.call_number}</span><span class="debug-call-time">${call.timestamp}</span></div><pre class="debug-call-content" id="call-${index}" style="display: ${isExpanded ? 'block' : 'none'};">${JSON.stringify(call.messages, null, 2)}</pre></div>`;
|
| 1532 |
+
});
|
| 1533 |
+
debugContent.innerHTML = html;
|
| 1534 |
+
} else {
|
| 1535 |
+
debugContent.innerHTML = '<div style="padding: 10px; color: #666;">No message history available yet.<br><br>Send a message in this tab to see the message history here.</div>';
|
| 1536 |
+
}
|
| 1537 |
+
} catch (error) {
|
| 1538 |
+
console.error('Failed to load debug messages:', error);
|
| 1539 |
+
debugContent.innerHTML = `<div style="padding: 10px; color: #d32f2f;">Error loading debug messages: ${error.message}</div>`;
|
| 1540 |
+
}
|
| 1541 |
+
}
|
| 1542 |
+
|
| 1543 |
+
// Toggle debug call expansion
|
| 1544 |
+
window.toggleDebugCall = function(index) {
|
| 1545 |
+
const content = document.getElementById(`call-${index}`);
|
| 1546 |
+
const arrow = document.getElementById(`arrow-${index}`);
|
| 1547 |
+
if (content.style.display === 'none') {
|
| 1548 |
+
content.style.display = 'block';
|
| 1549 |
+
arrow.textContent = '▼';
|
| 1550 |
+
} else {
|
| 1551 |
+
content.style.display = 'none';
|
| 1552 |
+
arrow.textContent = '▶';
|
| 1553 |
+
}
|
| 1554 |
+
}
|
| 1555 |
+
|
| 1556 |
+
// ============= SETTINGS PANEL =============
|
| 1557 |
+
|
| 1558 |
+
const settingsPanel = document.getElementById('settingsPanel');
|
| 1559 |
+
const settingsPanelBody = document.getElementById('settingsPanelBody');
|
| 1560 |
+
const settingsPanelClose = document.getElementById('settingsPanelClose');
|
| 1561 |
+
const settingsBtn = document.getElementById('settingsBtn');
|
| 1562 |
+
const appContainer = document.querySelector('.app-container');
|
| 1563 |
+
|
| 1564 |
+
|
| 1565 |
+
// Open settings panel when SETTINGS button is clicked
|
| 1566 |
+
if (settingsBtn) {
|
| 1567 |
+
settingsBtn.addEventListener('click', () => {
|
| 1568 |
+
openSettings(); // Populate form fields with current values
|
| 1569 |
+
settingsPanel.classList.add('active');
|
| 1570 |
+
settingsBtn.classList.add('active');
|
| 1571 |
+
appContainer.classList.add('panel-open');
|
| 1572 |
+
// Close debug panel if open
|
| 1573 |
+
debugPanel.classList.remove('active');
|
| 1574 |
+
debugBtn.classList.remove('active');
|
| 1575 |
+
});
|
| 1576 |
+
}
|
| 1577 |
+
|
| 1578 |
+
// Close settings panel
|
| 1579 |
+
if (settingsPanelClose) {
|
| 1580 |
+
settingsPanelClose.addEventListener('click', () => {
|
| 1581 |
+
settingsPanel.classList.remove('active');
|
| 1582 |
+
settingsBtn.classList.remove('active');
|
| 1583 |
+
appContainer.classList.remove('panel-open');
|
| 1584 |
+
});
|
| 1585 |
+
}
|
| 1586 |
+
|
style.css
CHANGED
|
@@ -10,6 +10,7 @@
|
|
| 10 |
--theme-bg: #e8f5e9;
|
| 11 |
--theme-hover-bg: #c8e6c9;
|
| 12 |
--theme-accent: #1b5e20;
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
body {
|
|
@@ -20,6 +21,15 @@ body {
|
|
| 20 |
overflow: hidden;
|
| 21 |
}
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
.app-container {
|
| 24 |
display: flex;
|
| 25 |
flex-direction: column;
|
|
@@ -54,7 +64,7 @@ body {
|
|
| 54 |
display: flex;
|
| 55 |
align-items: center;
|
| 56 |
gap: 8px;
|
| 57 |
-
padding:
|
| 58 |
background: #f5f5f5;
|
| 59 |
color: #666;
|
| 60 |
cursor: pointer;
|
|
@@ -124,7 +134,7 @@ body {
|
|
| 124 |
color: #666;
|
| 125 |
border: none;
|
| 126 |
border-right: 1px solid #ccc;
|
| 127 |
-
padding:
|
| 128 |
font-size: 12px;
|
| 129 |
font-weight: 500;
|
| 130 |
letter-spacing: 1px;
|
|
@@ -138,6 +148,11 @@ body {
|
|
| 138 |
color: #1a1a1a;
|
| 139 |
}
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
.new-tab-wrapper {
|
| 142 |
position: static;
|
| 143 |
display: flex;
|
|
@@ -149,7 +164,7 @@ body {
|
|
| 149 |
color: #666;
|
| 150 |
border: none;
|
| 151 |
border-right: 1px solid #ccc;
|
| 152 |
-
padding:
|
| 153 |
font-size: 16px;
|
| 154 |
cursor: pointer;
|
| 155 |
transition: all 0.2s;
|
|
@@ -202,6 +217,11 @@ body {
|
|
| 202 |
overflow: hidden;
|
| 203 |
position: relative;
|
| 204 |
z-index: 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
}
|
| 206 |
|
| 207 |
.tab-content {
|
|
@@ -468,7 +488,19 @@ body {
|
|
| 468 |
margin-bottom: 16px;
|
| 469 |
display: flex;
|
| 470 |
flex-direction: column;
|
| 471 |
-
gap:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
}
|
| 473 |
|
| 474 |
.system-message {
|
|
@@ -487,6 +519,12 @@ body {
|
|
| 487 |
color: #666;
|
| 488 |
text-transform: uppercase;
|
| 489 |
letter-spacing: 1px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
}
|
| 491 |
|
| 492 |
.message-content {
|
|
@@ -497,6 +535,33 @@ body {
|
|
| 497 |
line-height: 1.6;
|
| 498 |
white-space: pre-wrap;
|
| 499 |
word-wrap: break-word;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
}
|
| 501 |
|
| 502 |
.message-content ul,
|
|
@@ -532,10 +597,14 @@ body {
|
|
| 532 |
}
|
| 533 |
|
| 534 |
.message-content p {
|
| 535 |
-
margin
|
| 536 |
white-space: pre-wrap;
|
| 537 |
}
|
| 538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
.message-content code {
|
| 540 |
background: #e0e0e0;
|
| 541 |
padding: 2px 6px;
|
|
@@ -1754,3 +1823,242 @@ body {
|
|
| 1754 |
font-family: 'JetBrains Mono', monospace;
|
| 1755 |
font-size: 12px;
|
| 1756 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
--theme-bg: #e8f5e9;
|
| 11 |
--theme-hover-bg: #c8e6c9;
|
| 12 |
--theme-accent: #1b5e20;
|
| 13 |
+
--theme-accent-rgb: 27, 94, 32;
|
| 14 |
}
|
| 15 |
|
| 16 |
body {
|
|
|
|
| 21 |
overflow: hidden;
|
| 22 |
}
|
| 23 |
|
| 24 |
+
/* Global selection override - disable everywhere by default */
|
| 25 |
+
*::selection {
|
| 26 |
+
background: transparent;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
*::-moz-selection {
|
| 30 |
+
background: transparent;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
.app-container {
|
| 34 |
display: flex;
|
| 35 |
flex-direction: column;
|
|
|
|
| 64 |
display: flex;
|
| 65 |
align-items: center;
|
| 66 |
gap: 8px;
|
| 67 |
+
padding: 8px 16px;
|
| 68 |
background: #f5f5f5;
|
| 69 |
color: #666;
|
| 70 |
cursor: pointer;
|
|
|
|
| 134 |
color: #666;
|
| 135 |
border: none;
|
| 136 |
border-right: 1px solid #ccc;
|
| 137 |
+
padding: 8px 16px;
|
| 138 |
font-size: 12px;
|
| 139 |
font-weight: 500;
|
| 140 |
letter-spacing: 1px;
|
|
|
|
| 148 |
color: #1a1a1a;
|
| 149 |
}
|
| 150 |
|
| 151 |
+
.settings-btn.active {
|
| 152 |
+
background: var(--theme-accent);
|
| 153 |
+
color: white;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
.new-tab-wrapper {
|
| 157 |
position: static;
|
| 158 |
display: flex;
|
|
|
|
| 164 |
color: #666;
|
| 165 |
border: none;
|
| 166 |
border-right: 1px solid #ccc;
|
| 167 |
+
padding: 8px 16px;
|
| 168 |
font-size: 16px;
|
| 169 |
cursor: pointer;
|
| 170 |
transition: all 0.2s;
|
|
|
|
| 217 |
overflow: hidden;
|
| 218 |
position: relative;
|
| 219 |
z-index: 1;
|
| 220 |
+
transition: margin-right 0.3s ease;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.panel-open .content-area {
|
| 224 |
+
margin-right: 600px;
|
| 225 |
}
|
| 226 |
|
| 227 |
.tab-content {
|
|
|
|
| 488 |
margin-bottom: 16px;
|
| 489 |
display: flex;
|
| 490 |
flex-direction: column;
|
| 491 |
+
gap: 2px;
|
| 492 |
+
user-select: none;
|
| 493 |
+
-webkit-user-select: none;
|
| 494 |
+
-moz-user-select: none;
|
| 495 |
+
-ms-user-select: none;
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
.message::selection {
|
| 499 |
+
background: transparent;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
.message > *:not(.message-content)::selection {
|
| 503 |
+
background: transparent;
|
| 504 |
}
|
| 505 |
|
| 506 |
.system-message {
|
|
|
|
| 519 |
color: #666;
|
| 520 |
text-transform: uppercase;
|
| 521 |
letter-spacing: 1px;
|
| 522 |
+
user-select: none;
|
| 523 |
+
padding: 2px 0;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.user .message-label {
|
| 527 |
+
color: var(--theme-accent);
|
| 528 |
}
|
| 529 |
|
| 530 |
.message-content {
|
|
|
|
| 535 |
line-height: 1.6;
|
| 536 |
white-space: pre-wrap;
|
| 537 |
word-wrap: break-word;
|
| 538 |
+
user-select: text;
|
| 539 |
+
-webkit-user-select: text;
|
| 540 |
+
-moz-user-select: text;
|
| 541 |
+
-ms-user-select: text;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
.message-content::selection {
|
| 545 |
+
background: rgba(var(--theme-accent-rgb), 0.2) !important;
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
.message-content::-moz-selection {
|
| 549 |
+
background: rgba(var(--theme-accent-rgb), 0.2) !important;
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
.message-content *::selection {
|
| 553 |
+
background: rgba(var(--theme-accent-rgb), 0.2) !important;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
.message-content *::-moz-selection {
|
| 557 |
+
background: rgba(var(--theme-accent-rgb), 0.2) !important;
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
.message-content * {
|
| 561 |
+
user-select: text;
|
| 562 |
+
-webkit-user-select: text;
|
| 563 |
+
-moz-user-select: text;
|
| 564 |
+
-ms-user-select: text;
|
| 565 |
}
|
| 566 |
|
| 567 |
.message-content ul,
|
|
|
|
| 597 |
}
|
| 598 |
|
| 599 |
.message-content p {
|
| 600 |
+
margin: 0;
|
| 601 |
white-space: pre-wrap;
|
| 602 |
}
|
| 603 |
|
| 604 |
+
.message-content p:not(:last-child) {
|
| 605 |
+
margin-bottom: 8px;
|
| 606 |
+
}
|
| 607 |
+
|
| 608 |
.message-content code {
|
| 609 |
background: #e0e0e0;
|
| 610 |
padding: 2px 6px;
|
|
|
|
| 1823 |
font-family: 'JetBrains Mono', monospace;
|
| 1824 |
font-size: 12px;
|
| 1825 |
}
|
| 1826 |
+
|
| 1827 |
+
/* Debug Panel */
|
| 1828 |
+
.debug-panel {
|
| 1829 |
+
position: fixed;
|
| 1830 |
+
top: 37px;
|
| 1831 |
+
right: -600px;
|
| 1832 |
+
width: 600px;
|
| 1833 |
+
height: calc(100vh - 37px);
|
| 1834 |
+
background: white;
|
| 1835 |
+
border-left: 2px solid var(--theme-accent);
|
| 1836 |
+
z-index: 1000;
|
| 1837 |
+
display: flex;
|
| 1838 |
+
flex-direction: column;
|
| 1839 |
+
transition: right 0.3s ease;
|
| 1840 |
+
}
|
| 1841 |
+
|
| 1842 |
+
.debug-panel.active {
|
| 1843 |
+
right: 0;
|
| 1844 |
+
}
|
| 1845 |
+
|
| 1846 |
+
.debug-header {
|
| 1847 |
+
padding: 8px 16px;
|
| 1848 |
+
border-bottom: 1px solid #e0e0e0;
|
| 1849 |
+
display: flex;
|
| 1850 |
+
justify-content: space-between;
|
| 1851 |
+
align-items: center;
|
| 1852 |
+
background: var(--theme-accent);
|
| 1853 |
+
}
|
| 1854 |
+
|
| 1855 |
+
.debug-header h3 {
|
| 1856 |
+
margin: 0;
|
| 1857 |
+
font-size: 12px;
|
| 1858 |
+
font-weight: 600;
|
| 1859 |
+
color: white;
|
| 1860 |
+
text-transform: uppercase;
|
| 1861 |
+
letter-spacing: 0.5px;
|
| 1862 |
+
}
|
| 1863 |
+
|
| 1864 |
+
.debug-close {
|
| 1865 |
+
background: none;
|
| 1866 |
+
border: none;
|
| 1867 |
+
font-size: 20px;
|
| 1868 |
+
color: white;
|
| 1869 |
+
cursor: pointer;
|
| 1870 |
+
padding: 0;
|
| 1871 |
+
width: 24px;
|
| 1872 |
+
height: 24px;
|
| 1873 |
+
display: flex;
|
| 1874 |
+
align-items: center;
|
| 1875 |
+
justify-content: center;
|
| 1876 |
+
border-radius: 4px;
|
| 1877 |
+
transition: background 0.2s;
|
| 1878 |
+
}
|
| 1879 |
+
|
| 1880 |
+
.debug-close:hover {
|
| 1881 |
+
background: rgba(255, 255, 255, 0.2);
|
| 1882 |
+
}
|
| 1883 |
+
|
| 1884 |
+
.debug-body {
|
| 1885 |
+
flex: 1;
|
| 1886 |
+
overflow: hidden;
|
| 1887 |
+
display: flex;
|
| 1888 |
+
flex-direction: column;
|
| 1889 |
+
}
|
| 1890 |
+
|
| 1891 |
+
.debug-controls {
|
| 1892 |
+
padding: 12px 20px;
|
| 1893 |
+
border-bottom: 1px solid #e0e0e0;
|
| 1894 |
+
background: #f9f9f9;
|
| 1895 |
+
}
|
| 1896 |
+
|
| 1897 |
+
.debug-refresh {
|
| 1898 |
+
padding: 6px 12px;
|
| 1899 |
+
background: var(--theme-accent);
|
| 1900 |
+
color: white;
|
| 1901 |
+
border: none;
|
| 1902 |
+
border-radius: 4px;
|
| 1903 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1904 |
+
font-size: 11px;
|
| 1905 |
+
font-weight: 600;
|
| 1906 |
+
cursor: pointer;
|
| 1907 |
+
text-transform: uppercase;
|
| 1908 |
+
letter-spacing: 0.5px;
|
| 1909 |
+
transition: opacity 0.2s;
|
| 1910 |
+
}
|
| 1911 |
+
|
| 1912 |
+
.debug-refresh:hover {
|
| 1913 |
+
opacity: 0.9;
|
| 1914 |
+
}
|
| 1915 |
+
|
| 1916 |
+
.debug-content {
|
| 1917 |
+
flex: 1;
|
| 1918 |
+
padding: 0;
|
| 1919 |
+
margin: 0;
|
| 1920 |
+
overflow: auto;
|
| 1921 |
+
font-family: 'JetBrains Mono', monospace;
|
| 1922 |
+
font-size: 12px;
|
| 1923 |
+
line-height: 1.6;
|
| 1924 |
+
background: white;
|
| 1925 |
+
}
|
| 1926 |
+
|
| 1927 |
+
.debug-call-item {
|
| 1928 |
+
border-bottom: 1px solid #e0e0e0;
|
| 1929 |
+
background: white;
|
| 1930 |
+
}
|
| 1931 |
+
|
| 1932 |
+
.debug-call-header {
|
| 1933 |
+
padding: 10px 16px;
|
| 1934 |
+
background: white;
|
| 1935 |
+
cursor: pointer;
|
| 1936 |
+
display: flex;
|
| 1937 |
+
align-items: center;
|
| 1938 |
+
gap: 10px;
|
| 1939 |
+
transition: background 0.2s;
|
| 1940 |
+
user-select: none;
|
| 1941 |
+
}
|
| 1942 |
+
|
| 1943 |
+
.debug-call-header:hover {
|
| 1944 |
+
background: #f5f5f5;
|
| 1945 |
+
}
|
| 1946 |
+
|
| 1947 |
+
.debug-call-arrow {
|
| 1948 |
+
color: #666;
|
| 1949 |
+
font-size: 10px;
|
| 1950 |
+
width: 12px;
|
| 1951 |
+
display: inline-block;
|
| 1952 |
+
}
|
| 1953 |
+
|
| 1954 |
+
.debug-call-title {
|
| 1955 |
+
font-weight: 600;
|
| 1956 |
+
color: #333;
|
| 1957 |
+
flex: 1;
|
| 1958 |
+
}
|
| 1959 |
+
|
| 1960 |
+
.debug-call-time {
|
| 1961 |
+
color: #999;
|
| 1962 |
+
font-size: 10px;
|
| 1963 |
+
}
|
| 1964 |
+
|
| 1965 |
+
.debug-call-content {
|
| 1966 |
+
margin: 0;
|
| 1967 |
+
padding: 12px 16px;
|
| 1968 |
+
background: #fafafa;
|
| 1969 |
+
border-top: 1px solid #e0e0e0;
|
| 1970 |
+
white-space: pre-wrap;
|
| 1971 |
+
word-wrap: break-word;
|
| 1972 |
+
overflow-x: auto;
|
| 1973 |
+
}
|
| 1974 |
+
|
| 1975 |
+
.debug-btn {
|
| 1976 |
+
background: #f5f5f5;
|
| 1977 |
+
color: #666;
|
| 1978 |
+
border: none;
|
| 1979 |
+
border-right: 1px solid #ccc;
|
| 1980 |
+
padding: 8px 16px;
|
| 1981 |
+
font-size: 12px;
|
| 1982 |
+
font-weight: 500;
|
| 1983 |
+
letter-spacing: 1px;
|
| 1984 |
+
cursor: pointer;
|
| 1985 |
+
transition: all 0.2s;
|
| 1986 |
+
font-family: inherit;
|
| 1987 |
+
}
|
| 1988 |
+
|
| 1989 |
+
.debug-btn:hover {
|
| 1990 |
+
background: #eee;
|
| 1991 |
+
color: #1a1a1a;
|
| 1992 |
+
}
|
| 1993 |
+
|
| 1994 |
+
.debug-btn.active {
|
| 1995 |
+
background: var(--theme-accent);
|
| 1996 |
+
color: white;
|
| 1997 |
+
}
|
| 1998 |
+
|
| 1999 |
+
/* Settings Panel (side panel like debug) */
|
| 2000 |
+
.settings-panel {
|
| 2001 |
+
position: fixed;
|
| 2002 |
+
top: 37px;
|
| 2003 |
+
right: -600px;
|
| 2004 |
+
width: 600px;
|
| 2005 |
+
height: calc(100vh - 37px);
|
| 2006 |
+
background: white;
|
| 2007 |
+
border-left: 2px solid var(--theme-accent);
|
| 2008 |
+
z-index: 1000;
|
| 2009 |
+
display: flex;
|
| 2010 |
+
flex-direction: column;
|
| 2011 |
+
transition: right 0.3s ease;
|
| 2012 |
+
overflow-y: auto;
|
| 2013 |
+
}
|
| 2014 |
+
|
| 2015 |
+
.settings-panel.active {
|
| 2016 |
+
right: 0;
|
| 2017 |
+
}
|
| 2018 |
+
|
| 2019 |
+
.settings-panel-header {
|
| 2020 |
+
padding: 8px 16px;
|
| 2021 |
+
border-bottom: 1px solid #e0e0e0;
|
| 2022 |
+
display: flex;
|
| 2023 |
+
justify-content: space-between;
|
| 2024 |
+
align-items: center;
|
| 2025 |
+
background: var(--theme-accent);
|
| 2026 |
+
position: sticky;
|
| 2027 |
+
top: 0;
|
| 2028 |
+
z-index: 1;
|
| 2029 |
+
}
|
| 2030 |
+
|
| 2031 |
+
.settings-panel-header h3 {
|
| 2032 |
+
margin: 0;
|
| 2033 |
+
font-size: 12px;
|
| 2034 |
+
font-weight: 600;
|
| 2035 |
+
color: white;
|
| 2036 |
+
text-transform: uppercase;
|
| 2037 |
+
letter-spacing: 0.5px;
|
| 2038 |
+
}
|
| 2039 |
+
|
| 2040 |
+
.settings-panel-close {
|
| 2041 |
+
background: none;
|
| 2042 |
+
border: none;
|
| 2043 |
+
font-size: 20px;
|
| 2044 |
+
color: white;
|
| 2045 |
+
cursor: pointer;
|
| 2046 |
+
padding: 0;
|
| 2047 |
+
width: 24px;
|
| 2048 |
+
height: 24px;
|
| 2049 |
+
display: flex;
|
| 2050 |
+
align-items: center;
|
| 2051 |
+
justify-content: center;
|
| 2052 |
+
border-radius: 4px;
|
| 2053 |
+
transition: background 0.2s;
|
| 2054 |
+
}
|
| 2055 |
+
|
| 2056 |
+
.settings-panel-close:hover {
|
| 2057 |
+
background: rgba(255, 255, 255, 0.2);
|
| 2058 |
+
}
|
| 2059 |
+
|
| 2060 |
+
.settings-panel-body {
|
| 2061 |
+
flex: 1;
|
| 2062 |
+
padding: 20px;
|
| 2063 |
+
overflow-y: auto;
|
| 2064 |
+
}
|