From ab427731dc10549d2b00e606c2a0445af57b5af6 Mon Sep 17 00:00:00 2001 From: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com> Date: Wed, 14 Jan 2026 23:29:18 +0800 Subject: [PATCH 001/302] feat: add AIO sandbox provider and auto title generation (#1) - Add AioSandboxProvider for Docker-based sandbox execution with configurable container lifecycle, volume mounts, and port management - Add TitleMiddleware to auto-generate thread titles after first user-assistant exchange using LLM - Add Claude Code documentation (CLAUDE.md, AGENTS.md) - Extend SandboxConfig with Docker-specific options (image, port, mounts) - Fix hardcoded mount path to use expanduser - Add agent-sandbox and dotenv dependencies Co-authored-by: Claude Opus 4.5 --- backend/.claude/settings.local.json | 7 + backend/AGENTS.md | 2 + backend/CLAUDE.md | 76 ++++++ backend/Makefile | 2 +- backend/docs/AUTO_TITLE_GENERATION.md | 256 ++++++++++++++++++ backend/docs/BACKEND_TODO.md | 125 +++++++++ .../docs/TITLE_GENERATION_IMPLEMENTATION.md | 222 +++++++++++++++ backend/pyproject.toml | 3 + backend/src/agents/lead_agent/agent.py | 5 +- backend/src/agents/lead_agent/prompt.py | 3 +- .../agents/middlewares/title_middleware.py | 93 +++++++ backend/src/agents/thread_state.py | 7 +- backend/src/community/aio_sandbox/__init__.py | 7 + .../src/community/aio_sandbox/aio_sandbox.py | 113 ++++++++ .../aio_sandbox/aio_sandbox_provider.py | 233 ++++++++++++++++ backend/src/config/app_config.py | 9 + backend/src/config/sandbox_config.py | 52 +++- backend/src/config/title_config.py | 53 ++++ backend/src/sandbox/middleware.py | 4 +- backend/tests/test_title_generation.py | 90 ++++++ backend/uv.lock | 130 ++++++++- 21 files changed, 1479 insertions(+), 13 deletions(-) create mode 100644 backend/.claude/settings.local.json create mode 100644 backend/AGENTS.md create mode 100644 backend/CLAUDE.md create mode 100644 backend/docs/AUTO_TITLE_GENERATION.md create mode 100644 backend/docs/BACKEND_TODO.md create mode 100644 backend/docs/TITLE_GENERATION_IMPLEMENTATION.md create mode 100644 backend/src/agents/middlewares/title_middleware.py create mode 100644 backend/src/community/aio_sandbox/__init__.py create mode 100644 backend/src/community/aio_sandbox/aio_sandbox.py create mode 100644 backend/src/community/aio_sandbox/aio_sandbox_provider.py create mode 100644 backend/src/config/title_config.py create mode 100644 backend/tests/test_title_generation.py diff --git a/backend/.claude/settings.local.json b/backend/.claude/settings.local.json new file mode 100644 index 000000000..e1a2aec82 --- /dev/null +++ b/backend/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(make lint:*)" + ] + } +} diff --git a/backend/AGENTS.md b/backend/AGENTS.md new file mode 100644 index 000000000..4c7646bca --- /dev/null +++ b/backend/AGENTS.md @@ -0,0 +1,2 @@ +For the backend architeture and design patterns: +@./CLAUDE.md \ No newline at end of file diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md new file mode 100644 index 000000000..104afd544 --- /dev/null +++ b/backend/CLAUDE.md @@ -0,0 +1,76 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +DeerFlow is a LangGraph-based AI agent backend that provides a "super agent" with sandbox execution capabilities. The agent can execute code, browse the web, and manage files in isolated sandbox environments. + +## Commands + +```bash +# Install dependencies +make install + +# Run development server (LangGraph Studio) +make dev + +# Lint +make lint + +# Format code +make format +``` + +## Architecture + +### Configuration System + +The app uses a YAML-based configuration system loaded from `config.yaml`. Configuration priority: +1. Explicit `config_path` argument +2. `DEER_FLOW_CONFIG_PATH` environment variable +3. `config.yaml` in current directory +4. `config.yaml` in parent directory + +Config values starting with `$` are resolved as environment variables (e.g., `$OPENAI_API_KEY`). + +### Core Components + +**Agent Graph** (`src/agents/`) +- `lead_agent` is the main entry point registered in `langgraph.json` +- Uses `ThreadState` which extends `AgentState` with sandbox state +- Agent is created via `create_agent()` with model, tools, middleware, and system prompt + +**Sandbox System** (`src/sandbox/`) +- Abstract `Sandbox` base class defines interface: `execute_command`, `read_file`, `write_file`, `list_dir` +- `SandboxProvider` manages sandbox lifecycle: `acquire`, `get`, `release` +- `SandboxMiddleware` automatically acquires sandbox on agent start and injects into state +- `LocalSandboxProvider` is a singleton implementation for local execution +- Sandbox tools (`bash`, `ls`, `read_file`, `write_file`, `str_replace`) extract sandbox from tool runtime + +**Model Factory** (`src/models/`) +- `create_chat_model()` instantiates LLM from config using reflection +- Supports `thinking_enabled` flag with per-model `when_thinking_enabled` overrides + +**Tool System** (`src/tools/`) +- Tools defined in config with `use` path (e.g., `src.sandbox.tools:bash_tool`) +- `get_available_tools()` resolves tool paths via reflection +- Community tools in `src/community/`: Jina AI (web fetch), Tavily (web search) + +**Reflection System** (`src/reflection/`) +- `resolve_variable()` imports module and returns variable (e.g., `module:variable`) +- `resolve_class()` imports and validates class against base class + +### Config Schema + +Models, tools, and sandbox providers are configured in `config.yaml`: +- `models[]`: LLM configurations with `use` class path +- `tools[]`: Tool configurations with `use` variable path and `group` +- `sandbox.use`: Sandbox provider class path + +## Code Style + +- Uses `ruff` for linting and formatting +- Line length: 240 characters +- Python 3.12+ with type hints +- Double quotes, space indentation diff --git a/backend/Makefile b/backend/Makefile index 77dc02d80..94f380b0b 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -8,4 +8,4 @@ lint: uvx ruff check . format: - uvx ruff format . + uvx ruff check . --fix && uvx ruff format . diff --git a/backend/docs/AUTO_TITLE_GENERATION.md b/backend/docs/AUTO_TITLE_GENERATION.md new file mode 100644 index 000000000..855f2f9de --- /dev/null +++ b/backend/docs/AUTO_TITLE_GENERATION.md @@ -0,0 +1,256 @@ +# 自动 Thread Title 生成功能 + +## 功能说明 + +自动为对话线程生成标题,在用户首次提问并收到回复后自动触发。 + +## 实现方式 + +使用 `TitleMiddleware` 在 `after_agent` 钩子中: +1. 检测是否是首次对话(1个用户消息 + 1个助手回复) +2. 检查 state 是否已有 title +3. 调用 LLM 生成简洁的标题(默认最多6个词) +4. 将 title 存储到 `ThreadState` 中(会被 checkpointer 持久化) + +## ⚠️ 重要:存储机制 + +### Title 存储位置 + +Title 存储在 **`ThreadState.title`** 中,而非 thread metadata: + +```python +class ThreadState(AgentState): + sandbox: SandboxState | None = None + title: str | None = None # ✅ Title stored here +``` + +### 持久化说明 + +| 部署方式 | 持久化 | 说明 | +|---------|--------|------| +| **LangGraph Studio (本地)** | ❌ 否 | 仅内存存储,重启后丢失 | +| **LangGraph Platform** | ✅ 是 | 自动持久化到数据库 | +| **自定义 + Checkpointer** | ✅ 是 | 需配置 PostgreSQL/SQLite checkpointer | + +### 如何启用持久化 + +如果需要在本地开发时也持久化 title,需要配置 checkpointer: + +```python +# 在 langgraph.json 同级目录创建 checkpointer.py +from langgraph.checkpoint.postgres import PostgresSaver + +checkpointer = PostgresSaver.from_conn_string( + "postgresql://user:pass@localhost/dbname" +) +``` + +然后在 `langgraph.json` 中引用: + +```json +{ + "graphs": { + "lead_agent": "src.agents:lead_agent" + }, + "checkpointer": "checkpointer:checkpointer" +} +``` + +## 配置 + +在 `config.yaml` 中添加(可选): + +```yaml +title: + enabled: true + max_words: 6 + max_chars: 60 + model_name: null # 使用默认模型 +``` + +或在代码中配置: + +```python +from src.config.title_config import TitleConfig, set_title_config + +set_title_config(TitleConfig( + enabled=True, + max_words=8, + max_chars=80, +)) +``` + +## 客户端使用 + +### 获取 Thread Title + +```typescript +// 方式1: 从 thread state 获取 +const state = await client.threads.getState(threadId); +const title = state.values.title || "New Conversation"; + +// 方式2: 监听 stream 事件 +for await (const chunk of client.runs.stream(threadId, assistantId, { + input: { messages: [{ role: "user", content: "Hello" }] } +})) { + if (chunk.event === "values" && chunk.data.title) { + console.log("Title:", chunk.data.title); + } +} +``` + +### 显示 Title + +```typescript +// 在对话列表中显示 +function ConversationList() { + const [threads, setThreads] = useState([]); + + useEffect(() => { + async function loadThreads() { + const allThreads = await client.threads.list(); + + // 获取每个 thread 的 state 来读取 title + const threadsWithTitles = await Promise.all( + allThreads.map(async (t) => { + const state = await client.threads.getState(t.thread_id); + return { + id: t.thread_id, + title: state.values.title || "New Conversation", + updatedAt: t.updated_at, + }; + }) + ); + + setThreads(threadsWithTitles); + } + loadThreads(); + }, []); + + return ( + + ); +} +``` + +## 工作流程 + +```mermaid +sequenceDiagram + participant User + participant Client + participant LangGraph + participant TitleMiddleware + participant LLM + participant Checkpointer + + User->>Client: 发送首条消息 + Client->>LangGraph: POST /threads/{id}/runs + LangGraph->>Agent: 处理消息 + Agent-->>LangGraph: 返回回复 + LangGraph->>TitleMiddleware: after_agent() + TitleMiddleware->>TitleMiddleware: 检查是否需要生成 title + TitleMiddleware->>LLM: 生成 title + LLM-->>TitleMiddleware: 返回 title + TitleMiddleware->>LangGraph: return {"title": "..."} + LangGraph->>Checkpointer: 保存 state (含 title) + LangGraph-->>Client: 返回响应 + Client->>Client: 从 state.values.title 读取 +``` + +## 优势 + +✅ **可靠持久化** - 使用 LangGraph 的 state 机制,自动持久化 +✅ **完全后端处理** - 客户端无需额外逻辑 +✅ **自动触发** - 首次对话后自动生成 +✅ **可配置** - 支持自定义长度、模型等 +✅ **容错性强** - 失败时使用 fallback 策略 +✅ **架构一致** - 与现有 SandboxMiddleware 保持一致 + +## 注意事项 + +1. **读取方式不同**:Title 在 `state.values.title` 而非 `thread.metadata.title` +2. **性能考虑**:title 生成会增加约 0.5-1 秒延迟,可通过使用更快的模型优化 +3. **并发安全**:middleware 在 agent 执行后运行,不会阻塞主流程 +4. **Fallback 策略**:如果 LLM 调用失败,会使用用户消息的前几个词作为 title + +## 测试 + +```python +# 测试 title 生成 +import pytest +from src.agents.title_middleware import TitleMiddleware + +def test_title_generation(): + # TODO: 添加单元测试 + pass +``` + +## 故障排查 + +### Title 没有生成 + +1. 检查配置是否启用:`get_title_config().enabled == True` +2. 检查日志:查找 "Generated thread title" 或错误信息 +3. 确认是首次对话:只有 1 个用户消息和 1 个助手回复时才会触发 + +### Title 生成但客户端看不到 + +1. 确认读取位置:应该从 `state.values.title` 读取,而非 `thread.metadata.title` +2. 检查 API 响应:确认 state 中包含 title 字段 +3. 尝试重新获取 state:`client.threads.getState(threadId)` + +### Title 重启后丢失 + +1. 检查是否配置了 checkpointer(本地开发需要) +2. 确认部署方式:LangGraph Platform 会自动持久化 +3. 查看数据库:确认 checkpointer 正常工作 + +## 架构设计 + +### 为什么使用 State 而非 Metadata? + +| 特性 | State | Metadata | +|------|-------|----------| +| **持久化** | ✅ 自动(通过 checkpointer) | ⚠️ 取决于实现 | +| **版本控制** | ✅ 支持时间旅行 | ❌ 不支持 | +| **类型安全** | ✅ TypedDict 定义 | ❌ 任意字典 | +| **可追溯** | ✅ 每次更新都记录 | ⚠️ 只有最新值 | +| **标准化** | ✅ LangGraph 核心机制 | ⚠️ 扩展功能 | + +### 实现细节 + +```python +# TitleMiddleware 核心逻辑 +@override +def after_agent(self, state: TitleMiddlewareState, runtime: Runtime) -> dict | None: + """Generate and set thread title after the first agent response.""" + if self._should_generate_title(state, runtime): + title = self._generate_title(runtime) + print(f"Generated thread title: {title}") + + # ✅ 返回 state 更新,会被 checkpointer 自动持久化 + return {"title": title} + + return None +``` + +## 相关文件 + +- [`src/agents/thread_state.py`](../src/agents/thread_state.py) - ThreadState 定义 +- [`src/agents/title_middleware.py`](../src/agents/title_middleware.py) - TitleMiddleware 实现 +- [`src/config/title_config.py`](../src/config/title_config.py) - 配置管理 +- [`config.yaml`](../config.yaml) - 配置文件 +- [`src/agents/lead_agent/agent.py`](../src/agents/lead_agent/agent.py) - Middleware 注册 + +## 参考资料 + +- [LangGraph Checkpointer 文档](https://langchain-ai.github.io/langgraph/concepts/persistence/) +- [LangGraph State 管理](https://langchain-ai.github.io/langgraph/concepts/low_level/#state) +- [LangGraph Middleware](https://langchain-ai.github.io/langgraph/concepts/middleware/) diff --git a/backend/docs/BACKEND_TODO.md b/backend/docs/BACKEND_TODO.md new file mode 100644 index 000000000..0b49b7953 --- /dev/null +++ b/backend/docs/BACKEND_TODO.md @@ -0,0 +1,125 @@ +# DeerFlow Backend TODO List + +## 📋 项目概述 + +DeerFlow Backend 是一个基于 LangGraph 的 AI Agent 框架,采用配置驱动架构,支持多种 Sandbox 实现和工具扩展。 + +## 🚨 高优先级问题 (P0) + +### 1. LocalSandboxProvider 返回类型不一致 +**文件**: `src/sandbox/local/local_sandbox_provider.py` +**问题**: +- `acquire()` 声明返回 `Sandbox` 但实际返回 `str` +- `get()` 声明返回 `None` 但实际返回 `LocalSandbox` +**影响**: 类型安全破坏,IDE 检查报错 +**解决方案**: 修正方法签名,确保与抽象基类契约一致 + +### 2. Sandbox 资源泄漏风险 +**文件**: `src/sandbox/middleware.py` +**问题**: +- 只有 `before_agent` 获取 sandbox +- 没有 `after_agent` 释放机制 +- `LocalSandboxProvider.release()` 是空实现 +**影响**: 资源泄漏,Docker 容器堆积 +**解决方案**: 实现完整的生命周期管理 + +## 🟡 中优先级问题 (P1) + +### 3. 硬编码路径和个人信息 ✅ 已完成 +**文件**: `src/agents/lead_agent/prompt.py` +**问题**: +- `MOUNT_POINT = "/Users/henry/mnt"` +- 个人信息出现在系统提示中 +**影响**: 可移植性差,违反配置分离原则 +**解决方案**: 移至配置文件中 + +### 4. 异常处理过于简单 +**文件**: `src/sandbox/tools.py` +**问题**: 所有异常被吞掉,缺乏结构化错误信息 +**影响**: 调试困难,用户体验差 +**解决方案**: 实现分层异常处理机制 + +### 5. 全局单例缺乏生命周期管理 +**文件**: `src/config/app_config.py`, `src/sandbox/sandbox_provider.py` +**问题**: 全局变量难以测试,无法重新加载配置 +**影响**: 可测试性差,多线程风险 +**解决方案**: 引入依赖注入或 ContextVar + +## 🟢 低优先级问题 (P2) + +### 6. 缺乏异步支持 +**文件**: `src/community/aio_sandbox/aio_sandbox.py` +**问题**: 所有操作都是同步的 +**影响**: 并发性能受限 +**解决方案**: 添加 async/await 支持 + +### 7. 配置验证不足 +**文件**: `src/config/model_config.py` +**问题**: `extra="allow"` 允许任意字段 +**影响**: 配置错误难以发现 +**解决方案**: 使用 `extra="forbid"` 并添加验证器 + +### 8. 工具配置重复定义 +**文件**: `config.yaml` 和 `src/community/tavily/tools.py` +**问题**: 同名工具在不同地方定义 +**影响**: 配置切换混淆 +**解决方案**: 使用唯一名称或别名机制 + +## 🔧 架构优化建议 + +### 9. 自动 Thread Title 生成 ✅ 已完成 +**目的**: 自动为对话线程生成标题 +**实现**: +- 使用 `TitleMiddleware` 在首次对话后自动生成 title +- Title 存储在 `ThreadState.title` 中(而非 metadata) +- 支持通过 checkpointer 持久化 +- 详见 [AUTO_TITLE_GENERATION.md](docs/AUTO_TITLE_GENERATION.md) + +### 10. 引入依赖注入容器 +**目的**: 改善可测试性和模块化 +**实现**: 创建 `di.py` 提供类型安全的依赖管理 + +### 11. 添加健康检查接口 +**目的**: 监控系统状态 +**实现**: 创建 `health.py` 提供系统健康状态检查 + +### 12. 增加结构化日志 +**目的**: 改善可观测性 +**实现**: 集成 `structlog` 提供结构化日志输出 + +## 📊 实施计划 + +### Phase 1: 安全与稳定性 (Week 1-2) +- [ ] 修复 LocalSandboxProvider 类型问题 +- [ ] 实现 Sandbox 生命周期管理 +- [ ] 添加异常处理机制 + +### Phase 2: 架构优化 (Week 3-4) +- [ ] 引入依赖注入 +- [ ] 添加健康检查 +- [ ] 实现配置验证 +- [ ] 移除硬编码路径 + +### Phase 3: 性能与扩展性 (Week 5-6) +- [ ] 添加异步支持 +- [ ] 实现结构化日志 +- [ ] 优化工具配置管理 + +## 🎯 成功标准 + +- ✅ 所有类型检查通过 +- ✅ 配置可安全共享 +- ✅ 资源管理无泄漏 +- ✅ 异常处理完善 +- ✅ 测试覆盖率提升 +- ✅ 部署配置标准化 + +## 📝 备注 + +- 优先处理高优先级问题,确保系统稳定性和安全性 +- 中优先级问题影响开发体验和可维护性 +- 低优先级问题可在系统稳定后逐步优化 + +--- + +*最后更新: 2026-01-14* \ No newline at end of file diff --git a/backend/docs/TITLE_GENERATION_IMPLEMENTATION.md b/backend/docs/TITLE_GENERATION_IMPLEMENTATION.md new file mode 100644 index 000000000..351d73c9c --- /dev/null +++ b/backend/docs/TITLE_GENERATION_IMPLEMENTATION.md @@ -0,0 +1,222 @@ +# 自动 Title 生成功能实现总结 + +## ✅ 已完成的工作 + +### 1. 核心实现文件 + +#### [`src/agents/thread_state.py`](../src/agents/thread_state.py) +- ✅ 添加 `title: str | None = None` 字段到 `ThreadState` + +#### [`src/config/title_config.py`](../src/config/title_config.py) (新建) +- ✅ 创建 `TitleConfig` 配置类 +- ✅ 支持配置:enabled, max_words, max_chars, model_name, prompt_template +- ✅ 提供 `get_title_config()` 和 `set_title_config()` 函数 +- ✅ 提供 `load_title_config_from_dict()` 从配置文件加载 + +#### [`src/agents/title_middleware.py`](../src/agents/title_middleware.py) (新建) +- ✅ 创建 `TitleMiddleware` 类 +- ✅ 实现 `_should_generate_title()` 检查是否需要生成 +- ✅ 实现 `_generate_title()` 调用 LLM 生成标题 +- ✅ 实现 `after_agent()` 钩子,在首次对话后自动触发 +- ✅ 包含 fallback 策略(LLM 失败时使用用户消息前几个词) + +#### [`src/config/app_config.py`](../src/config/app_config.py) +- ✅ 导入 `load_title_config_from_dict` +- ✅ 在 `from_file()` 中加载 title 配置 + +#### [`src/agents/lead_agent/agent.py`](../src/agents/lead_agent/agent.py) +- ✅ 导入 `TitleMiddleware` +- ✅ 注册到 `middleware` 列表:`[SandboxMiddleware(), TitleMiddleware()]` + +### 2. 配置文件 + +#### [`config.yaml`](../config.yaml) +- ✅ 添加 title 配置段: +```yaml +title: + enabled: true + max_words: 6 + max_chars: 60 + model_name: null +``` + +### 3. 文档 + +#### [`docs/AUTO_TITLE_GENERATION.md`](../docs/AUTO_TITLE_GENERATION.md) (新建) +- ✅ 完整的功能说明文档 +- ✅ 实现方式和架构设计 +- ✅ 配置说明 +- ✅ 客户端使用示例(TypeScript) +- ✅ 工作流程图(Mermaid) +- ✅ 故障排查指南 +- ✅ State vs Metadata 对比 + +#### [`BACKEND_TODO.md`](../BACKEND_TODO.md) +- ✅ 添加功能完成记录 + +### 4. 测试 + +#### [`tests/test_title_generation.py`](../tests/test_title_generation.py) (新建) +- ✅ 配置类测试 +- ✅ Middleware 初始化测试 +- ✅ TODO: 集成测试(需要 mock Runtime) + +--- + +## 🎯 核心设计决策 + +### 为什么使用 State 而非 Metadata? + +| 方面 | State (✅ 采用) | Metadata (❌ 未采用) | +|------|----------------|---------------------| +| **持久化** | 自动(通过 checkpointer) | 取决于实现,不可靠 | +| **版本控制** | 支持时间旅行 | 不支持 | +| **类型安全** | TypedDict 定义 | 任意字典 | +| **标准化** | LangGraph 核心机制 | 扩展功能 | + +### 工作流程 + +``` +用户发送首条消息 + ↓ +Agent 处理并返回回复 + ↓ +TitleMiddleware.after_agent() 触发 + ↓ +检查:是否首次对话?是否已有 title? + ↓ +调用 LLM 生成 title + ↓ +返回 {"title": "..."} 更新 state + ↓ +Checkpointer 自动持久化(如果配置了) + ↓ +客户端从 state.values.title 读取 +``` + +--- + +## 📋 使用指南 + +### 后端配置 + +1. **启用/禁用功能** +```yaml +# config.yaml +title: + enabled: true # 设为 false 禁用 +``` + +2. **自定义配置** +```yaml +title: + enabled: true + max_words: 8 # 标题最多 8 个词 + max_chars: 80 # 标题最多 80 个字符 + model_name: null # 使用默认模型 +``` + +3. **配置持久化(可选)** + +如果需要在本地开发时持久化 title: + +```python +# checkpointer.py +from langgraph.checkpoint.sqlite import SqliteSaver + +checkpointer = SqliteSaver.from_conn_string("checkpoints.db") +``` + +```json +// langgraph.json +{ + "graphs": { + "lead_agent": "src.agents:lead_agent" + }, + "checkpointer": "checkpointer:checkpointer" +} +``` + +### 客户端使用 + +```typescript +// 获取 thread title +const state = await client.threads.getState(threadId); +const title = state.values.title || "New Conversation"; + +// 显示在对话列表 +
  • {title}
  • +``` + +**⚠️ 注意**:Title 在 `state.values.title`,而非 `thread.metadata.title` + +--- + +## 🧪 测试 + +```bash +# 运行测试 +pytest tests/test_title_generation.py -v + +# 运行所有测试 +pytest +``` + +--- + +## 🔍 故障排查 + +### Title 没有生成? + +1. 检查配置:`title.enabled = true` +2. 查看日志:搜索 "Generated thread title" +3. 确认是首次对话(1 个用户消息 + 1 个助手回复) + +### Title 生成但看不到? + +1. 确认读取位置:`state.values.title`(不是 `thread.metadata.title`) +2. 检查 API 响应是否包含 title +3. 重新获取 state + +### Title 重启后丢失? + +1. 本地开发需要配置 checkpointer +2. LangGraph Platform 会自动持久化 +3. 检查数据库确认 checkpointer 工作正常 + +--- + +## 📊 性能影响 + +- **延迟增加**:约 0.5-1 秒(LLM 调用) +- **并发安全**:在 `after_agent` 中运行,不阻塞主流程 +- **资源消耗**:每个 thread 只生成一次 + +### 优化建议 + +1. 使用更快的模型(如 `gpt-3.5-turbo`) +2. 减少 `max_words` 和 `max_chars` +3. 调整 prompt 使其更简洁 + +--- + +## 🚀 下一步 + +- [ ] 添加集成测试(需要 mock LangGraph Runtime) +- [ ] 支持自定义 prompt template +- [ ] 支持多语言 title 生成 +- [ ] 添加 title 重新生成功能 +- [ ] 监控 title 生成成功率和延迟 + +--- + +## 📚 相关资源 + +- [完整文档](../docs/AUTO_TITLE_GENERATION.md) +- [LangGraph Middleware](https://langchain-ai.github.io/langgraph/concepts/middleware/) +- [LangGraph State 管理](https://langchain-ai.github.io/langgraph/concepts/low_level/#state) +- [LangGraph Checkpointer](https://langchain-ai.github.io/langgraph/concepts/persistence/) + +--- + +*实现完成时间: 2026-01-14* diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8df486c4d..40a9813d9 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -5,6 +5,8 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = [ + "agent-sandbox>=0.0.19", + "dotenv>=0.9.9", "langchain>=1.2.3", "langchain-deepseek>=1.0.1", "langchain-openai>=1.1.7", @@ -18,5 +20,6 @@ dependencies = [ [dependency-groups] dev = [ + "pytest>=8.0.0", "ruff>=0.14.11", ] diff --git a/backend/src/agents/lead_agent/agent.py b/backend/src/agents/lead_agent/agent.py index 9c57cef5d..3c31073a0 100644 --- a/backend/src/agents/lead_agent/agent.py +++ b/backend/src/agents/lead_agent/agent.py @@ -1,15 +1,18 @@ from langchain.agents import create_agent from src.agents.lead_agent.prompt import apply_prompt_template +from src.agents.middlewares.title_middleware import TitleMiddleware from src.agents.thread_state import ThreadState from src.models import create_chat_model from src.sandbox.middleware import SandboxMiddleware from src.tools import get_available_tools +middlewares = [SandboxMiddleware(), TitleMiddleware()] + lead_agent = create_agent( model=create_chat_model(thinking_enabled=True), tools=get_available_tools(), - middleware=[SandboxMiddleware()], + middleware=middlewares, system_prompt=apply_prompt_template(), state_schema=ThreadState, ) diff --git a/backend/src/agents/lead_agent/prompt.py b/backend/src/agents/lead_agent/prompt.py index 64f0b6ebb..053792128 100644 --- a/backend/src/agents/lead_agent/prompt.py +++ b/backend/src/agents/lead_agent/prompt.py @@ -1,6 +1,7 @@ +import os from datetime import datetime -MOUNT_POINT = "/Users/henry/mnt" +MOUNT_POINT = os.path.expanduser("~/mnt") SYSTEM_PROMPT = f""" diff --git a/backend/src/agents/middlewares/title_middleware.py b/backend/src/agents/middlewares/title_middleware.py new file mode 100644 index 000000000..967ced415 --- /dev/null +++ b/backend/src/agents/middlewares/title_middleware.py @@ -0,0 +1,93 @@ +"""Middleware for automatic thread title generation.""" + +from typing import NotRequired, override + +from langchain.agents import AgentState +from langchain.agents.middleware import AgentMiddleware +from langgraph.runtime import Runtime + +from src.config.title_config import get_title_config +from src.models import create_chat_model + + +class TitleMiddlewareState(AgentState): + """Compatible with the `ThreadState` schema.""" + + title: NotRequired[str | None] + + +class TitleMiddleware(AgentMiddleware[TitleMiddlewareState]): + """Automatically generate a title for the thread after the first user message.""" + + state_schema = TitleMiddlewareState + + def _should_generate_title(self, state: TitleMiddlewareState) -> bool: + """Check if we should generate a title for this thread.""" + config = get_title_config() + if not config.enabled: + return False + + # Check if thread already has a title in state + if state.get("title"): + return False + + # Check if this is the first turn (has at least one user message and one assistant response) + messages = state.get("messages", []) + if len(messages) < 2: + return False + + # Count user and assistant messages + user_messages = [m for m in messages if m.type == "human"] + assistant_messages = [m for m in messages if m.type == "ai"] + + # Generate title after first complete exchange + return len(user_messages) == 1 and len(assistant_messages) >= 1 + + def _generate_title(self, state: TitleMiddlewareState) -> str: + """Generate a concise title based on the conversation.""" + config = get_title_config() + messages = state.get("messages", []) + + # Get first user message and first assistant response + user_msg_content = next((m.content for m in messages if m.type == "human"), "") + assistant_msg_content = next((m.content for m in messages if m.type == "ai"), "") + + # Ensure content is string (LangChain messages can have list content) + user_msg = str(user_msg_content) if user_msg_content else "" + assistant_msg = str(assistant_msg_content) if assistant_msg_content else "" + + # Use a lightweight model to generate title + model = create_chat_model(thinking_enabled=False) + + prompt = config.prompt_template.format( + max_words=config.max_words, + user_msg=user_msg[:500], + assistant_msg=assistant_msg[:500], + ) + + try: + response = model.invoke(prompt) + # Ensure response content is string + title_content = str(response.content) if response.content else "" + title = title_content.strip().strip('"').strip("'") + # Limit to max characters + return title[: config.max_chars] if len(title) > config.max_chars else title + except Exception as e: + print(f"Failed to generate title: {e}") + # Fallback: use first part of user message (by character count) + fallback_chars = min(config.max_chars, 50) # Use max_chars or 50, whichever is smaller + if len(user_msg) > fallback_chars: + return user_msg[:fallback_chars].rstrip() + "..." + return user_msg if user_msg else "New Conversation" + + @override + def after_agent(self, state: TitleMiddlewareState, runtime: Runtime) -> dict | None: + """Generate and set thread title after the first agent response.""" + if self._should_generate_title(state): + title = self._generate_title(state) + print(f"Generated thread title: {title}") + + # Store title in state (will be persisted by checkpointer if configured) + return {"title": title} + + return None diff --git a/backend/src/agents/thread_state.py b/backend/src/agents/thread_state.py index 081df2d05..fbafe1586 100644 --- a/backend/src/agents/thread_state.py +++ b/backend/src/agents/thread_state.py @@ -1,11 +1,12 @@ -from typing import TypedDict +from typing import NotRequired, TypedDict from langchain.agents import AgentState class SandboxState(TypedDict): - sandbox_id: str | None = None + sandbox_id: NotRequired[str | None] class ThreadState(AgentState): - sandbox: SandboxState | None = None + sandbox: NotRequired[SandboxState | None] + title: NotRequired[str | None] diff --git a/backend/src/community/aio_sandbox/__init__.py b/backend/src/community/aio_sandbox/__init__.py new file mode 100644 index 000000000..41d3a1cde --- /dev/null +++ b/backend/src/community/aio_sandbox/__init__.py @@ -0,0 +1,7 @@ +from .aio_sandbox import AioSandbox +from .aio_sandbox_provider import AioSandboxProvider + +__all__ = [ + "AioSandbox", + "AioSandboxProvider", +] diff --git a/backend/src/community/aio_sandbox/aio_sandbox.py b/backend/src/community/aio_sandbox/aio_sandbox.py new file mode 100644 index 000000000..52ef3582f --- /dev/null +++ b/backend/src/community/aio_sandbox/aio_sandbox.py @@ -0,0 +1,113 @@ +import logging + +from agent_sandbox import Sandbox as AioSandboxClient + +from src.sandbox.sandbox import Sandbox + +logger = logging.getLogger(__name__) + + +class AioSandbox(Sandbox): + """Sandbox implementation using the agent-infra/sandbox Docker container. + + This sandbox connects to a running AIO sandbox container via HTTP API. + """ + + def __init__(self, id: str, base_url: str, home_dir: str | None = None): + """Initialize the AIO sandbox. + + Args: + id: Unique identifier for this sandbox instance. + base_url: Base URL of the sandbox API (e.g., http://localhost:8080). + home_dir: Home directory inside the sandbox. If None, will be fetched from the sandbox. + """ + super().__init__(id) + self._base_url = base_url + self._client = AioSandboxClient(base_url=base_url) + self._home_dir = home_dir + + @property + def base_url(self) -> str: + return self._base_url + + @property + def home_dir(self) -> str: + """Get the home directory inside the sandbox.""" + if self._home_dir is None: + context = self._client.sandbox.get_context() + self._home_dir = context.home_dir + return self._home_dir + + def execute_command(self, command: str) -> str: + """Execute a shell command in the sandbox. + + Args: + command: The command to execute. + + Returns: + The output of the command. + """ + try: + result = self._client.shell.exec_command(command=command) + output = result.data.output if result.data else "" + return output if output else "(no output)" + except Exception as e: + logger.error(f"Failed to execute command in sandbox: {e}") + return f"Error: {e}" + + def read_file(self, path: str) -> str: + """Read the content of a file in the sandbox. + + Args: + path: The absolute path of the file to read. + + Returns: + The content of the file. + """ + try: + result = self._client.file.read_file(file=path) + return result.data.content if result.data else "" + except Exception as e: + logger.error(f"Failed to read file in sandbox: {e}") + return f"Error: {e}" + + def list_dir(self, path: str, max_depth: int = 2) -> list[str]: + """List the contents of a directory in the sandbox. + + Args: + path: The absolute path of the directory to list. + max_depth: The maximum depth to traverse. Default is 2. + + Returns: + The contents of the directory. + """ + try: + # Use shell command to list directory with depth limit + # The -L flag limits the depth for the tree command + result = self._client.shell.exec_command(command=f"find {path} -maxdepth {max_depth} -type f -o -type d 2>/dev/null | head -500") + output = result.data.output if result.data else "" + if output: + return [line.strip() for line in output.strip().split("\n") if line.strip()] + return [] + except Exception as e: + logger.error(f"Failed to list directory in sandbox: {e}") + return [] + + def write_file(self, path: str, content: str, append: bool = False) -> None: + """Write content to a file in the sandbox. + + Args: + path: The absolute path of the file to write to. + content: The text content to write to the file. + append: Whether to append the content to the file. + """ + try: + if append: + # Read existing content first and append + existing = self.read_file(path) + if not existing.startswith("Error:"): + content = existing + content + self._client.file.write_file(file=path, content=content) + except Exception as e: + logger.error(f"Failed to write file in sandbox: {e}") + raise diff --git a/backend/src/community/aio_sandbox/aio_sandbox_provider.py b/backend/src/community/aio_sandbox/aio_sandbox_provider.py new file mode 100644 index 000000000..c24b8a55f --- /dev/null +++ b/backend/src/community/aio_sandbox/aio_sandbox_provider.py @@ -0,0 +1,233 @@ +import logging +import subprocess +import time +import uuid + +import requests + +from src.config import get_app_config +from src.sandbox.sandbox import Sandbox +from src.sandbox.sandbox_provider import SandboxProvider + +from .aio_sandbox import AioSandbox + +logger = logging.getLogger(__name__) + +# Default configuration +DEFAULT_IMAGE = "enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest" +DEFAULT_PORT = 8080 +DEFAULT_CONTAINER_PREFIX = "deer-flow-sandbox" + + +class AioSandboxProvider(SandboxProvider): + """Sandbox provider that manages Docker containers running the AIO sandbox. + + Configuration options in config.yaml under sandbox: + use: src.community.aio_sandbox:AioSandboxProvider + image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # Docker image to use + port: 8080 # Base port for sandbox containers + base_url: http://localhost:8080 # If set, uses existing sandbox instead of starting new container + auto_start: true # Whether to automatically start Docker container + container_prefix: deer-flow-sandbox # Prefix for container names + mounts: # List of volume mounts + - host_path: /path/on/host + container_path: /path/in/container + read_only: false + """ + + def __init__(self): + self._sandboxes: dict[str, AioSandbox] = {} + self._containers: dict[str, str] = {} # sandbox_id -> container_id + self._config = self._load_config() + + def _load_config(self) -> dict: + """Load sandbox configuration from app config.""" + config = get_app_config() + sandbox_config = config.sandbox + + # Set defaults + return { + "image": sandbox_config.image or DEFAULT_IMAGE, + "port": sandbox_config.port or DEFAULT_PORT, + "base_url": sandbox_config.base_url, + "auto_start": sandbox_config.auto_start if sandbox_config.auto_start is not None else True, + "container_prefix": sandbox_config.container_prefix or DEFAULT_CONTAINER_PREFIX, + "mounts": sandbox_config.mounts or [], + } + + def _is_sandbox_ready(self, base_url: str, timeout: int = 30) -> bool: + """Check if sandbox is ready to accept connections. + + Args: + base_url: Base URL of the sandbox. + timeout: Maximum time to wait in seconds. + + Returns: + True if sandbox is ready, False otherwise. + """ + start_time = time.time() + while time.time() - start_time < timeout: + try: + response = requests.get(f"{base_url}/v1/sandbox", timeout=5) + if response.status_code == 200: + return True + except requests.exceptions.RequestException: + pass + time.sleep(1) + return False + + def _start_container(self, sandbox_id: str, port: int) -> str: + """Start a new Docker container for the sandbox. + + Args: + sandbox_id: Unique identifier for the sandbox. + port: Port to expose the sandbox API on. + + Returns: + The container ID. + """ + image = self._config["image"] + container_name = f"{self._config['container_prefix']}-{sandbox_id}" + + cmd = [ + "docker", + "run", + "--security-opt", + "seccomp=unconfined", + "--rm", + "-d", + "-p", + f"{port}:8080", + "--name", + container_name, + ] + + # Add volume mounts + for mount in self._config["mounts"]: + host_path = mount.host_path + container_path = mount.container_path + read_only = mount.read_only + mount_spec = f"{host_path}:{container_path}" + if read_only: + mount_spec += ":ro" + cmd.extend(["-v", mount_spec]) + + cmd.append(image) + + logger.info(f"Starting sandbox container: {' '.join(cmd)}") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + container_id = result.stdout.strip() + logger.info(f"Started sandbox container {container_name} with ID {container_id}") + return container_id + except subprocess.CalledProcessError as e: + logger.error(f"Failed to start sandbox container: {e.stderr}") + raise RuntimeError(f"Failed to start sandbox container: {e.stderr}") + + def _stop_container(self, container_id: str) -> None: + """Stop and remove a Docker container. + + Args: + container_id: The container ID to stop. + """ + try: + subprocess.run(["docker", "stop", container_id], capture_output=True, text=True, check=True) + logger.info(f"Stopped sandbox container {container_id}") + except subprocess.CalledProcessError as e: + logger.warning(f"Failed to stop sandbox container {container_id}: {e.stderr}") + + def _find_available_port(self, start_port: int) -> int: + """Find an available port starting from start_port. + + Args: + start_port: Port to start searching from. + + Returns: + An available port number. + """ + import socket + + port = start_port + while port < start_port + 100: # Search up to 100 ports + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.bind(("localhost", port)) + return port + except OSError: + port += 1 + raise RuntimeError(f"No available port found in range {start_port}-{start_port + 100}") + + def acquire(self) -> str: + """Acquire a sandbox environment and return its ID. + + If base_url is configured, uses the existing sandbox. + Otherwise, starts a new Docker container. + + Returns: + The ID of the acquired sandbox environment. + """ + sandbox_id = str(uuid.uuid4())[:8] + + # If base_url is configured, use existing sandbox + if self._config.get("base_url"): + base_url = self._config["base_url"] + logger.info(f"Using existing sandbox at {base_url}") + + if not self._is_sandbox_ready(base_url, timeout=5): + raise RuntimeError(f"Sandbox at {base_url} is not ready") + + sandbox = AioSandbox(id=sandbox_id, base_url=base_url) + self._sandboxes[sandbox_id] = sandbox + return sandbox_id + + # Otherwise, start a new container + if not self._config.get("auto_start", True): + raise RuntimeError("auto_start is disabled and no base_url is configured") + + port = self._find_available_port(self._config["port"]) + container_id = self._start_container(sandbox_id, port) + self._containers[sandbox_id] = container_id + + base_url = f"http://localhost:{port}" + + # Wait for sandbox to be ready + if not self._is_sandbox_ready(base_url, timeout=60): + # Clean up container if it didn't start properly + self._stop_container(container_id) + del self._containers[sandbox_id] + raise RuntimeError("Sandbox container failed to start within timeout") + + sandbox = AioSandbox(id=sandbox_id, base_url=base_url) + self._sandboxes[sandbox_id] = sandbox + logger.info(f"Acquired sandbox {sandbox_id} at {base_url}") + return sandbox_id + + def get(self, sandbox_id: str) -> Sandbox | None: + """Get a sandbox environment by ID. + + Args: + sandbox_id: The ID of the sandbox environment. + + Returns: + The sandbox instance if found, None otherwise. + """ + return self._sandboxes.get(sandbox_id) + + def release(self, sandbox_id: str) -> None: + """Release a sandbox environment. + + If the sandbox was started by this provider, stops the container. + + Args: + sandbox_id: The ID of the sandbox environment to release. + """ + if sandbox_id in self._sandboxes: + del self._sandboxes[sandbox_id] + logger.info(f"Released sandbox {sandbox_id}") + + # Stop container if we started it + if sandbox_id in self._containers: + container_id = self._containers[sandbox_id] + self._stop_container(container_id) + del self._containers[sandbox_id] diff --git a/backend/src/config/app_config.py b/backend/src/config/app_config.py index bfb85c324..de34cddc6 100644 --- a/backend/src/config/app_config.py +++ b/backend/src/config/app_config.py @@ -3,12 +3,16 @@ from pathlib import Path from typing import Self import yaml +from dotenv import load_dotenv from pydantic import BaseModel, ConfigDict, Field from src.config.model_config import ModelConfig from src.config.sandbox_config import SandboxConfig +from src.config.title_config import load_title_config_from_dict from src.config.tool_config import ToolConfig, ToolGroupConfig +load_dotenv() + class AppConfig(BaseModel): """Config for the DeerFlow application""" @@ -64,6 +68,11 @@ class AppConfig(BaseModel): with open(resolved_path) as f: config_data = yaml.safe_load(f) cls.resolve_env_variables(config_data) + + # Load title config if present + if "title" in config_data: + load_title_config_from_dict(config_data["title"]) + result = cls.model_validate(config_data) return result diff --git a/backend/src/config/sandbox_config.py b/backend/src/config/sandbox_config.py index 48a25a305..d4164c96a 100644 --- a/backend/src/config/sandbox_config.py +++ b/backend/src/config/sandbox_config.py @@ -1,10 +1,56 @@ -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field + + +class VolumeMountConfig(BaseModel): + """Configuration for a volume mount.""" + + host_path: str = Field(..., description="Path on the host machine") + container_path: str = Field(..., description="Path inside the container") + read_only: bool = Field(default=False, description="Whether the mount is read-only") class SandboxConfig(BaseModel): - """Config section for a sandbox""" + """Config section for a sandbox. + + Common options: + use: Class path of the sandbox provider (required) + + AioSandboxProvider specific options: + image: Docker image to use (default: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest) + port: Base port for sandbox containers (default: 8080) + base_url: If set, uses existing sandbox instead of starting new container + auto_start: Whether to automatically start Docker container (default: true) + container_prefix: Prefix for container names (default: deer-flow-sandbox) + mounts: List of volume mounts to share directories with the container + """ use: str = Field( ..., - description="Class path of the sandbox provider(e.g. src.sandbox.local:LocalSandbox)", + description="Class path of the sandbox provider (e.g. src.sandbox.local:LocalSandboxProvider)", ) + image: str | None = Field( + default=None, + description="Docker image to use for the sandbox container", + ) + port: int | None = Field( + default=None, + description="Base port for sandbox containers", + ) + base_url: str | None = Field( + default=None, + description="If set, uses existing sandbox at this URL instead of starting new container", + ) + auto_start: bool | None = Field( + default=None, + description="Whether to automatically start Docker container", + ) + container_prefix: str | None = Field( + default=None, + description="Prefix for container names", + ) + mounts: list[VolumeMountConfig] = Field( + default_factory=list, + description="List of volume mounts to share directories between host and container", + ) + + model_config = ConfigDict(extra="allow") diff --git a/backend/src/config/title_config.py b/backend/src/config/title_config.py new file mode 100644 index 000000000..f335b4952 --- /dev/null +++ b/backend/src/config/title_config.py @@ -0,0 +1,53 @@ +"""Configuration for automatic thread title generation.""" + +from pydantic import BaseModel, Field + + +class TitleConfig(BaseModel): + """Configuration for automatic thread title generation.""" + + enabled: bool = Field( + default=True, + description="Whether to enable automatic title generation", + ) + max_words: int = Field( + default=6, + ge=1, + le=20, + description="Maximum number of words in the generated title", + ) + max_chars: int = Field( + default=60, + ge=10, + le=200, + description="Maximum number of characters in the generated title", + ) + model_name: str | None = Field( + default=None, + description="Model name to use for title generation (None = use default model)", + ) + prompt_template: str = Field( + default=("Generate a concise title (max {max_words} words) for this conversation.\nUser: {user_msg}\nAssistant: {assistant_msg}\n\nReturn ONLY the title, no quotes, no explanation."), + description="Prompt template for title generation", + ) + + +# Global configuration instance +_title_config: TitleConfig = TitleConfig() + + +def get_title_config() -> TitleConfig: + """Get the current title configuration.""" + return _title_config + + +def set_title_config(config: TitleConfig) -> None: + """Set the title configuration.""" + global _title_config + _title_config = config + + +def load_title_config_from_dict(config_dict: dict) -> None: + """Load title configuration from a dictionary.""" + global _title_config + _title_config = TitleConfig(**config_dict) diff --git a/backend/src/sandbox/middleware.py b/backend/src/sandbox/middleware.py index f536031d0..630d7f254 100644 --- a/backend/src/sandbox/middleware.py +++ b/backend/src/sandbox/middleware.py @@ -1,4 +1,4 @@ -from typing import override +from typing import NotRequired, override from langchain.agents import AgentState from langchain.agents.middleware import AgentMiddleware @@ -11,7 +11,7 @@ from src.sandbox import get_sandbox_provider class SandboxMiddlewareState(AgentState): """Compatible with the `ThreadState` schema.""" - sandbox: SandboxState | None = None + sandbox: NotRequired[SandboxState | None] class SandboxMiddleware(AgentMiddleware[SandboxMiddlewareState]): diff --git a/backend/tests/test_title_generation.py b/backend/tests/test_title_generation.py new file mode 100644 index 000000000..13fd59706 --- /dev/null +++ b/backend/tests/test_title_generation.py @@ -0,0 +1,90 @@ +"""Tests for automatic thread title generation.""" + +import pytest + +from src.agents.middlewares.title_middleware import TitleMiddleware +from src.config.title_config import TitleConfig, get_title_config, set_title_config + + +class TestTitleConfig: + """Tests for TitleConfig.""" + + def test_default_config(self): + """Test default configuration values.""" + config = TitleConfig() + assert config.enabled is True + assert config.max_words == 6 + assert config.max_chars == 60 + assert config.model_name is None + + def test_custom_config(self): + """Test custom configuration.""" + config = TitleConfig( + enabled=False, + max_words=10, + max_chars=100, + model_name="gpt-4", + ) + assert config.enabled is False + assert config.max_words == 10 + assert config.max_chars == 100 + assert config.model_name == "gpt-4" + + def test_config_validation(self): + """Test configuration validation.""" + # max_words should be between 1 and 20 + with pytest.raises(ValueError): + TitleConfig(max_words=0) + with pytest.raises(ValueError): + TitleConfig(max_words=21) + + # max_chars should be between 10 and 200 + with pytest.raises(ValueError): + TitleConfig(max_chars=5) + with pytest.raises(ValueError): + TitleConfig(max_chars=201) + + def test_get_set_config(self): + """Test global config getter and setter.""" + original_config = get_title_config() + + # Set new config + new_config = TitleConfig(enabled=False, max_words=10) + set_title_config(new_config) + + # Verify it was set + assert get_title_config().enabled is False + assert get_title_config().max_words == 10 + + # Restore original config + set_title_config(original_config) + + +class TestTitleMiddleware: + """Tests for TitleMiddleware.""" + + def test_middleware_initialization(self): + """Test middleware can be initialized.""" + middleware = TitleMiddleware() + assert middleware is not None + assert middleware.state_schema is not None + + # TODO: Add integration tests with mock Runtime + # def test_should_generate_title(self): + # """Test title generation trigger logic.""" + # pass + + # def test_generate_title(self): + # """Test title generation.""" + # pass + + # def test_after_agent_hook(self): + # """Test after_agent hook.""" + # pass + + +# TODO: Add integration tests +# - Test with real LangGraph runtime +# - Test title persistence with checkpointer +# - Test fallback behavior when LLM fails +# - Test concurrent title generation diff --git a/backend/uv.lock b/backend/uv.lock index 46a950dfc..9be34eb79 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -1,7 +1,21 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" +[[package]] +name = "agent-sandbox" +version = "0.0.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", extra = ["socks"] }, + { name = "pydantic" }, + { name = "volcengine-python-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/21/62d527b1c671ad82f8f11b4caa585b85e829e5a23960ee83facae49da69b/agent_sandbox-0.0.19.tar.gz", hash = "sha256:724b40d7a20eedd1da67f254d02705a794d0835ebc30c9b5ca8aa148accf3bbd", size = 68114, upload-time = "2025-12-11T08:24:29.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/19/8c8f3d786ea65fb8a40ba7ac7e5fa0dd972fba413421a139cd6ca3679fe2/agent_sandbox-0.0.19-py2.py3-none-any.whl", hash = "sha256:063b6ffe7d035d84289e60339cbb0708169efe89f9d322e94c071ae2ee5bec5a", size = 152276, upload-time = "2025-12-11T08:24:27.682Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -117,6 +131,8 @@ name = "deer-flow" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "agent-sandbox" }, + { name = "dotenv" }, { name = "langchain" }, { name = "langchain-deepseek" }, { name = "langchain-openai" }, @@ -130,11 +146,14 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "pytest" }, { name = "ruff" }, ] [package.metadata] requires-dist = [ + { name = "agent-sandbox", specifier = ">=0.0.19" }, + { name = "dotenv", specifier = ">=0.9.9" }, { name = "langchain", specifier = ">=1.2.3" }, { name = "langchain-deepseek", specifier = ">=1.0.1" }, { name = "langchain-openai", specifier = ">=1.1.7" }, @@ -147,7 +166,10 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "ruff", specifier = ">=0.14.11" }] +dev = [ + { name = "pytest", specifier = ">=8.0.0" }, + { name = "ruff", specifier = ">=0.14.11" }, +] [[package]] name = "distro" @@ -158,6 +180,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "dotenv" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -208,6 +241,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[package.optional-dependencies] +socks = [ + { name = "socksio" }, +] + [[package]] name = "idna" version = "3.11" @@ -217,6 +255,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "jiter" version = "0.12.0" @@ -653,6 +700,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pydantic" version = "2.12.5" @@ -739,6 +795,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -949,6 +1051,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" }, +] + [[package]] name = "soupsieve" version = "2.8.1" @@ -1092,6 +1203,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/37/674b3ce25cd715b831ea8ebbd828b74c40159f04c95d1bb963b2c876fe79/uuid_utils-0.13.0-cp39-abi3-win_arm64.whl", hash = "sha256:5447a680df6ef8a5a353976aaf4c97cc3a3a22b1ee13671c44227b921e3ae2a9", size = 183518, upload-time = "2026-01-08T15:47:59.148Z" }, ] +[[package]] +name = "volcengine-python-sdk" +version = "5.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "python-dateutil" }, + { name = "six" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/5c/827674e1be2215f4e8205fc876d9a9e0267d9a1be1dbb31fb87213331288/volcengine_python_sdk-5.0.5.tar.gz", hash = "sha256:8c3b674ab5370d93dabb74356f60236418fea785d18e9c4b967390883e87d756", size = 7381857, upload-time = "2026-01-09T13:00:05.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/5c/f20856e9d337a9feb7f66a8d3d78a86886054d9fb32ff29a0a4d6ac0d2ed/volcengine_python_sdk-5.0.5-py2.py3-none-any.whl", hash = "sha256:c9b91261386d7f2c1ccfc48169c4b319c58f3c66cc5e492936b5dfb6d25e1a5f", size = 29018827, upload-time = "2026-01-09T13:00:01.827Z" }, +] + [[package]] name = "webencodings" version = "0.5.1" From c92eedc57264fa566e6eb47a06dfc3b34798c6fb Mon Sep 17 00:00:00 2001 From: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:22:30 +0800 Subject: [PATCH 002/302] feat: add thread data middleware (#2) --- backend/src/agents/lead_agent/agent.py | 4 +- .../middlewares/thread_data_middleware.py | 83 +++++++++++++++++++ backend/src/agents/thread_state.py | 7 ++ .../aio_sandbox/aio_sandbox_provider.py | 52 +++++++++++- .../sandbox/local/local_sandbox_provider.py | 3 +- backend/src/sandbox/middleware.py | 11 ++- backend/src/sandbox/sandbox_provider.py | 6 +- config.example.yaml | 29 +++++++ 8 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 backend/src/agents/middlewares/thread_data_middleware.py diff --git a/backend/src/agents/lead_agent/agent.py b/backend/src/agents/lead_agent/agent.py index 3c31073a0..077358375 100644 --- a/backend/src/agents/lead_agent/agent.py +++ b/backend/src/agents/lead_agent/agent.py @@ -1,13 +1,15 @@ from langchain.agents import create_agent from src.agents.lead_agent.prompt import apply_prompt_template +from src.agents.middlewares.thread_data_middleware import ThreadDataMiddleware from src.agents.middlewares.title_middleware import TitleMiddleware from src.agents.thread_state import ThreadState from src.models import create_chat_model from src.sandbox.middleware import SandboxMiddleware from src.tools import get_available_tools -middlewares = [SandboxMiddleware(), TitleMiddleware()] +# ThreadDataMiddleware must be before SandboxMiddleware to ensure thread_id is available +middlewares = [ThreadDataMiddleware(), SandboxMiddleware(), TitleMiddleware()] lead_agent = create_agent( model=create_chat_model(thinking_enabled=True), diff --git a/backend/src/agents/middlewares/thread_data_middleware.py b/backend/src/agents/middlewares/thread_data_middleware.py new file mode 100644 index 000000000..727e62f04 --- /dev/null +++ b/backend/src/agents/middlewares/thread_data_middleware.py @@ -0,0 +1,83 @@ +import os +from pathlib import Path +from typing import NotRequired, override + +from langchain.agents import AgentState +from langchain.agents.middleware import AgentMiddleware +from langgraph.runtime import Runtime + +from src.agents.thread_state import ThreadDataState + +# Base directory for thread data (relative to backend/) +THREAD_DATA_BASE_DIR = ".deer-flow/threads" + + +class ThreadDataMiddlewareState(AgentState): + """Compatible with the `ThreadState` schema.""" + + thread_data: NotRequired[ThreadDataState | None] + + +class ThreadDataMiddleware(AgentMiddleware[ThreadDataMiddlewareState]): + """Create thread data directories for each thread execution. + + Creates the following directory structure: + - backend/.deer-flow/threads/{thread_id}/user-data/workspace + - backend/.deer-flow/threads/{thread_id}/user-data/uploads + - backend/.deer-flow/threads/{thread_id}/user-data/outputs + """ + + state_schema = ThreadDataMiddlewareState + + def __init__(self, base_dir: str | None = None): + """Initialize the middleware. + + Args: + base_dir: Base directory for thread data. Defaults to the current working directory. + """ + super().__init__() + self._base_dir = base_dir or os.getcwd() + + def _get_thread_paths(self, thread_id: str) -> dict[str, str]: + """Get the paths for a thread's data directories. + + Args: + thread_id: The thread ID. + + Returns: + Dictionary with workspace_path, uploads_path, and outputs_path. + """ + thread_dir = Path(self._base_dir) / THREAD_DATA_BASE_DIR / thread_id / "user-data" + return { + "workspace_path": str(thread_dir / "workspace"), + "uploads_path": str(thread_dir / "uploads"), + "outputs_path": str(thread_dir / "outputs"), + } + + def _create_thread_directories(self, thread_id: str) -> dict[str, str]: + """Create the thread data directories. + + Args: + thread_id: The thread ID. + + Returns: + Dictionary with the created directory paths. + """ + paths = self._get_thread_paths(thread_id) + for path in paths.values(): + os.makedirs(path, exist_ok=True) + return paths + + @override + def before_agent(self, state: ThreadDataMiddlewareState, runtime: Runtime) -> dict | None: + # Generate new thread ID and create directories + print(runtime.context) + thread_id = runtime.context["thread_id"] + paths = self._create_thread_directories(thread_id) + print(f"Created thread data directories for thread {thread_id}") + + return { + "thread_data": { + **paths, + } + } diff --git a/backend/src/agents/thread_state.py b/backend/src/agents/thread_state.py index fbafe1586..ef4c80db0 100644 --- a/backend/src/agents/thread_state.py +++ b/backend/src/agents/thread_state.py @@ -7,6 +7,13 @@ class SandboxState(TypedDict): sandbox_id: NotRequired[str | None] +class ThreadDataState(TypedDict): + workspace_path: NotRequired[str | None] + uploads_path: NotRequired[str | None] + outputs_path: NotRequired[str | None] + + class ThreadState(AgentState): sandbox: NotRequired[SandboxState | None] + thread_data: NotRequired[ThreadDataState | None] title: NotRequired[str | None] diff --git a/backend/src/community/aio_sandbox/aio_sandbox_provider.py b/backend/src/community/aio_sandbox/aio_sandbox_provider.py index c24b8a55f..38d6bcc54 100644 --- a/backend/src/community/aio_sandbox/aio_sandbox_provider.py +++ b/backend/src/community/aio_sandbox/aio_sandbox_provider.py @@ -1,7 +1,9 @@ import logging +import os import subprocess import time import uuid +from pathlib import Path import requests @@ -13,6 +15,10 @@ from .aio_sandbox import AioSandbox logger = logging.getLogger(__name__) +# Thread data directory structure +THREAD_DATA_BASE_DIR = ".deer-flow/threads" +CONTAINER_USER_DATA_DIR = "/mnt/user-data" + # Default configuration DEFAULT_IMAGE = "enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest" DEFAULT_PORT = 8080 @@ -76,12 +82,31 @@ class AioSandboxProvider(SandboxProvider): time.sleep(1) return False - def _start_container(self, sandbox_id: str, port: int) -> str: + def _get_thread_mounts(self, thread_id: str) -> list[tuple[str, str, bool]]: + """Get the volume mounts for a thread's data directories. + + Args: + thread_id: The thread ID. + + Returns: + List of (host_path, container_path, read_only) tuples. + """ + base_dir = os.getcwd() + thread_dir = Path(base_dir) / THREAD_DATA_BASE_DIR / thread_id / "user-data" + + return [ + (str(thread_dir / "workspace"), f"{CONTAINER_USER_DATA_DIR}/workspace", False), + (str(thread_dir / "uploads"), f"{CONTAINER_USER_DATA_DIR}/uploads", False), + (str(thread_dir / "outputs"), f"{CONTAINER_USER_DATA_DIR}/outputs", False), + ] + + def _start_container(self, sandbox_id: str, port: int, extra_mounts: list[tuple[str, str, bool]] | None = None) -> str: """Start a new Docker container for the sandbox. Args: sandbox_id: Unique identifier for the sandbox. port: Port to expose the sandbox API on. + extra_mounts: Additional volume mounts as (host_path, container_path, read_only) tuples. Returns: The container ID. @@ -102,7 +127,7 @@ class AioSandboxProvider(SandboxProvider): container_name, ] - # Add volume mounts + # Add configured volume mounts for mount in self._config["mounts"]: host_path = mount.host_path container_path = mount.container_path @@ -112,6 +137,14 @@ class AioSandboxProvider(SandboxProvider): mount_spec += ":ro" cmd.extend(["-v", mount_spec]) + # Add extra mounts (e.g., thread-specific directories) + if extra_mounts: + for host_path, container_path, read_only in extra_mounts: + mount_spec = f"{host_path}:{container_path}" + if read_only: + mount_spec += ":ro" + cmd.extend(["-v", mount_spec]) + cmd.append(image) logger.info(f"Starting sandbox container: {' '.join(cmd)}") @@ -158,17 +191,28 @@ class AioSandboxProvider(SandboxProvider): port += 1 raise RuntimeError(f"No available port found in range {start_port}-{start_port + 100}") - def acquire(self) -> str: + def acquire(self, thread_id: str | None = None) -> str: """Acquire a sandbox environment and return its ID. If base_url is configured, uses the existing sandbox. Otherwise, starts a new Docker container. + Args: + thread_id: Optional thread ID for thread-specific configurations. + If provided, the sandbox will be configured with thread-specific + mounts for workspace, uploads, and outputs directories. + Returns: The ID of the acquired sandbox environment. """ sandbox_id = str(uuid.uuid4())[:8] + # Get thread-specific mounts if thread_id is provided + extra_mounts = None + if thread_id: + extra_mounts = self._get_thread_mounts(thread_id) + logger.info(f"Adding thread mounts for thread {thread_id}: {extra_mounts}") + # If base_url is configured, use existing sandbox if self._config.get("base_url"): base_url = self._config["base_url"] @@ -186,7 +230,7 @@ class AioSandboxProvider(SandboxProvider): raise RuntimeError("auto_start is disabled and no base_url is configured") port = self._find_available_port(self._config["port"]) - container_id = self._start_container(sandbox_id, port) + container_id = self._start_container(sandbox_id, port, extra_mounts=extra_mounts) self._containers[sandbox_id] = container_id base_url = f"http://localhost:{port}" diff --git a/backend/src/sandbox/local/local_sandbox_provider.py b/backend/src/sandbox/local/local_sandbox_provider.py index 467e8f060..bb96098e3 100644 --- a/backend/src/sandbox/local/local_sandbox_provider.py +++ b/backend/src/sandbox/local/local_sandbox_provider.py @@ -1,12 +1,11 @@ from src.sandbox.local.local_sandbox import LocalSandbox -from src.sandbox.sandbox import Sandbox from src.sandbox.sandbox_provider import SandboxProvider _singleton: LocalSandbox | None = None class LocalSandboxProvider(SandboxProvider): - def acquire(self) -> Sandbox: + def acquire(self, thread_id: str | None = None) -> str: global _singleton if _singleton is None: _singleton = LocalSandbox("local") diff --git a/backend/src/sandbox/middleware.py b/backend/src/sandbox/middleware.py index 630d7f254..8c056a7ca 100644 --- a/backend/src/sandbox/middleware.py +++ b/backend/src/sandbox/middleware.py @@ -4,7 +4,7 @@ from langchain.agents import AgentState from langchain.agents.middleware import AgentMiddleware from langgraph.runtime import Runtime -from src.agents.thread_state import SandboxState +from src.agents.thread_state import SandboxState, ThreadDataState from src.sandbox import get_sandbox_provider @@ -12,6 +12,7 @@ class SandboxMiddlewareState(AgentState): """Compatible with the `ThreadState` schema.""" sandbox: NotRequired[SandboxState | None] + thread_data: NotRequired[ThreadDataState | None] class SandboxMiddleware(AgentMiddleware[SandboxMiddlewareState]): @@ -19,15 +20,17 @@ class SandboxMiddleware(AgentMiddleware[SandboxMiddlewareState]): state_schema = SandboxMiddlewareState - def _acquire_sandbox(self) -> str: + def _acquire_sandbox(self, thread_id: str) -> str: provider = get_sandbox_provider() - sandbox_id = provider.acquire() + sandbox_id = provider.acquire(thread_id) print(f"Acquiring sandbox {sandbox_id}") return sandbox_id @override def before_agent(self, state: SandboxMiddlewareState, runtime: Runtime) -> dict | None: if "sandbox" not in state or state["sandbox"] is None: - sandbox_id = self._acquire_sandbox() + thread_id = runtime.context["thread_id"] + print(f"Thread ID: {thread_id}") + sandbox_id = self._acquire_sandbox(thread_id) return {"sandbox": {"sandbox_id": sandbox_id}} return super().before_agent(state, runtime) diff --git a/backend/src/sandbox/sandbox_provider.py b/backend/src/sandbox/sandbox_provider.py index 5ca8c81e2..722cc95ee 100644 --- a/backend/src/sandbox/sandbox_provider.py +++ b/backend/src/sandbox/sandbox_provider.py @@ -9,7 +9,7 @@ class SandboxProvider(ABC): """Abstract base class for sandbox providers""" @abstractmethod - def acquire(self) -> str: + def acquire(self, thread_id: str | None = None) -> str: """Acquire a sandbox environment and return its ID. Returns: @@ -39,7 +39,7 @@ class SandboxProvider(ABC): _default_sandbox_provider: SandboxProvider | None = None -def get_sandbox_provider() -> SandboxProvider: +def get_sandbox_provider(**kwargs) -> SandboxProvider: """Get the sandbox provider. Returns: @@ -49,5 +49,5 @@ def get_sandbox_provider() -> SandboxProvider: if _default_sandbox_provider is None: config = get_app_config() cls = resolve_class(config.sandbox.use, SandboxProvider) - _default_sandbox_provider = cls() + _default_sandbox_provider = cls(**kwargs) return _default_sandbox_provider diff --git a/config.example.yaml b/config.example.yaml index 1a38204d0..20a5d1825 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -64,3 +64,32 @@ tools: use: src.sandbox.tools:bash_tool sandbox: use: src.sandbox.local:LocalSandboxProvider + +# To use Docker-based AIO sandbox instead, uncomment the following: +# sandbox: +# use: src.community.aio_sandbox:AioSandboxProvider +# # Optional: Use existing sandbox at this URL (no Docker container will be started) +# # base_url: http://localhost:8080 +# # Optional: Docker image to use (default: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest) +# # image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest +# # Optional: Base port for sandbox containers (default: 8080) +# # port: 8080 +# # Optional: Whether to automatically start Docker container (default: true) +# # auto_start: true +# # Optional: Prefix for container names (default: deer-flow-sandbox) +# # container_prefix: deer-flow-sandbox +# # Optional: Mount directories from host to container +# # mounts: +# # - host_path: /path/on/host +# # container_path: /home/user/shared +# # read_only: false +# # - host_path: /another/path +# # container_path: /data +# # read_only: true + +# Automatic thread title generation +title: + enabled: true + max_words: 6 + max_chars: 60 + model_name: null # Use default model \ No newline at end of file From a39f799a7e740be121e1cffccbcea7e9f539d6b5 Mon Sep 17 00:00:00 2001 From: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:37:00 +0800 Subject: [PATCH 003/302] fix: fix local path for local sandbox (#3) --- .gitignore | 1 + backend/src/agents/lead_agent/prompt.py | 16 ++-- backend/src/sandbox/tools.py | 116 +++++++++++++++++++++++- 3 files changed, 123 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index d1e21e8aa..d60c7c5a7 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ config.yaml # Coverage report coverage.xml coverage/ +.deer-flow/ \ No newline at end of file diff --git a/backend/src/agents/lead_agent/prompt.py b/backend/src/agents/lead_agent/prompt.py index 053792128..c4ceb9658 100644 --- a/backend/src/agents/lead_agent/prompt.py +++ b/backend/src/agents/lead_agent/prompt.py @@ -1,8 +1,6 @@ import os from datetime import datetime -MOUNT_POINT = os.path.expanduser("~/mnt") - SYSTEM_PROMPT = f""" You are DeerFlow 2.0, an open-source super agent. @@ -17,7 +15,7 @@ You are DeerFlow 2.0, an open-source super agent. You have access to skills that provide optimized workflows for specific tasks. Each skill contains best practices, frameworks, and references to additional resources. **Progressive Loading Pattern:** -1. When a user query matches a skill's use case, immediately call `view` on the skill's main file located at `{MOUNT_POINT}/skills/{"{skill_name}"}/SKILL.md` +1. When a user query matches a skill's use case, immediately call `view` on the skill's main file located at `/mnt/skills/{"{skill_name}"}/SKILL.md` 2. Read and understand the skill's workflow and instructions 3. The skill file contains references to external resources under the same folder 4. Load referenced resources only when needed during execution @@ -35,12 +33,12 @@ Extract text, fill forms, merge PDFs (pypdf, pdfplumber) -- User uploads: `{MOUNT_POINT}/user-data/uploads` -- User workspace: `{MOUNT_POINT}/user-data/workspace` - - subagents: `{MOUNT_POINT}/user-data/workspace/subagents` -- Output files: `{MOUNT_POINT}/user-data/outputs` +- User uploads: `/mnt/user-data/uploads` +- User workspace: `/mnt/user-data/workspace` + - subagents: `/mnt/user-data/workspace/subagents` +- Output files: `/mnt/user-data/outputs` -All temporary work happens in `{MOUNT_POINT}/user-data/workspace`. Final deliverables must be copied to `{MOUNT_POINT}/user-data/outputs`. +All temporary work happens in `/mnt/user-data/workspace`. Final deliverables must be copied to `/mnt/user-data/outputs`. @@ -58,7 +56,7 @@ All temporary work happens in `{MOUNT_POINT}/user-data/workspace`. Final deliver - Skill First: Always load the relevant skill before starting **complex** tasks. - Progressive Loading: Load resources incrementally as referenced in skills -- Output Files: Final deliverables must be in `{MOUNT_POINT}/user-data/outputs` +- Output Files: Final deliverables must be in `/mnt/user-data/outputs` - Clarity: Be direct and helpful, avoid unnecessary meta-commentary - Multi-task: Better utilize parallel tool calling to call multiple tools at one time for better performance - Language Consistency: Keep using the same language as user's diff --git a/backend/src/sandbox/tools.py b/backend/src/sandbox/tools.py index fa77f2be2..a8ec9c549 100644 --- a/backend/src/sandbox/tools.py +++ b/backend/src/sandbox/tools.py @@ -1,10 +1,109 @@ +import re + from langchain.tools import ToolRuntime, tool from langgraph.typing import ContextT -from src.agents.thread_state import ThreadState +from src.agents.thread_state import ThreadDataState, ThreadState from src.sandbox.sandbox import Sandbox from src.sandbox.sandbox_provider import get_sandbox_provider +# Virtual path prefix used in sandbox environments +VIRTUAL_PATH_PREFIX = "/mnt/user-data" + + +def replace_virtual_path(path: str, thread_data: ThreadDataState | None) -> str: + """Replace virtual /mnt/user-data paths with actual thread data paths. + + Mapping: + /mnt/user-data/workspace/* -> thread_data['workspace_path']/* + /mnt/user-data/uploads/* -> thread_data['uploads_path']/* + /mnt/user-data/outputs/* -> thread_data['outputs_path']/* + + Args: + path: The path that may contain virtual path prefix. + thread_data: The thread data containing actual paths. + + Returns: + The path with virtual prefix replaced by actual path. + """ + if not path.startswith(VIRTUAL_PATH_PREFIX): + return path + + if thread_data is None: + return path + + # Map virtual subdirectories to thread_data keys + path_mapping = { + "workspace": thread_data.get("workspace_path"), + "uploads": thread_data.get("uploads_path"), + "outputs": thread_data.get("outputs_path"), + } + + # Extract the subdirectory after /mnt/user-data/ + relative_path = path[len(VIRTUAL_PATH_PREFIX) :].lstrip("/") + if not relative_path: + return path + + # Find which subdirectory this path belongs to + parts = relative_path.split("/", 1) + subdir = parts[0] + rest = parts[1] if len(parts) > 1 else "" + + actual_base = path_mapping.get(subdir) + if actual_base is None: + return path + + if rest: + return f"{actual_base}/{rest}" + return actual_base + + +def replace_virtual_paths_in_command(command: str, thread_data: ThreadDataState | None) -> str: + """Replace all virtual /mnt/user-data paths in a command string. + + Args: + command: The command string that may contain virtual paths. + thread_data: The thread data containing actual paths. + + Returns: + The command with all virtual paths replaced. + """ + if VIRTUAL_PATH_PREFIX not in command: + return command + + if thread_data is None: + return command + + # Pattern to match /mnt/user-data followed by path characters + pattern = re.compile(rf"{re.escape(VIRTUAL_PATH_PREFIX)}(/[^\s\"';&|<>()]*)?") + + def replace_match(match: re.Match) -> str: + full_path = match.group(0) + return replace_virtual_path(full_path, thread_data) + + return pattern.sub(replace_match, command) + + +def get_thread_data(runtime: ToolRuntime[ContextT, ThreadState] | None) -> ThreadDataState | None: + """Extract thread_data from runtime state.""" + if runtime is None: + return None + return runtime.state.get("thread_data") + + +def is_local_sandbox(runtime: ToolRuntime[ContextT, ThreadState] | None) -> bool: + """Check if the current sandbox is a local sandbox. + + Path replacement is only needed for local sandbox since aio sandbox + already has /mnt/user-data mounted in the container. + """ + if runtime is None: + return False + sandbox_state = runtime.state.get("sandbox") + if sandbox_state is None: + return False + return sandbox_state.get("sandbox_id") == "local" + def sandbox_from_runtime(runtime: ToolRuntime[ContextT, ThreadState] | None = None) -> Sandbox: if runtime is None: @@ -35,6 +134,9 @@ def bash_tool(runtime: ToolRuntime[ContextT, ThreadState], description: str, com """ try: sandbox = sandbox_from_runtime(runtime) + if is_local_sandbox(runtime): + thread_data = get_thread_data(runtime) + command = replace_virtual_paths_in_command(command, thread_data) return sandbox.execute_command(command) except Exception as e: return f"Error: {e}" @@ -50,6 +152,9 @@ def ls_tool(runtime: ToolRuntime[ContextT, ThreadState], description: str, path: """ try: sandbox = sandbox_from_runtime(runtime) + if is_local_sandbox(runtime): + thread_data = get_thread_data(runtime) + path = replace_virtual_path(path, thread_data) children = sandbox.list_dir(path) if not children: return "(empty)" @@ -74,6 +179,9 @@ def read_file_tool( """ try: sandbox = sandbox_from_runtime(runtime) + if is_local_sandbox(runtime): + thread_data = get_thread_data(runtime) + path = replace_virtual_path(path, thread_data) content = sandbox.read_file(path) if not content: return "(empty)" @@ -102,6 +210,9 @@ def write_file_tool( """ try: sandbox = sandbox_from_runtime(runtime) + if is_local_sandbox(runtime): + thread_data = get_thread_data(runtime) + path = replace_virtual_path(path, thread_data) sandbox.write_file(path, content, append) return "OK" except Exception as e: @@ -129,6 +240,9 @@ def str_replace_tool( """ try: sandbox = sandbox_from_runtime(runtime) + if is_local_sandbox(runtime): + thread_data = get_thread_data(runtime) + path = replace_virtual_path(path, thread_data) content = sandbox.read_file(path) if not content: return "OK" From b44144dd2c10dd1911c5fd86b4b3ff5cb246f357 Mon Sep 17 00:00:00 2001 From: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:05:54 +0800 Subject: [PATCH 004/302] feat: support function factory (#4) --- backend/debug.py | 75 +++++++++++++++++++ backend/langgraph.json | 2 +- backend/src/agents/__init__.py | 4 +- backend/src/agents/lead_agent/__init__.py | 4 +- backend/src/agents/lead_agent/agent.py | 20 +++-- backend/src/agents/lead_agent/prompt.py | 1 - .../middlewares/thread_data_middleware.py | 5 +- backend/src/sandbox/tools.py | 15 ++-- deer-flow.code-workspace | 29 +++++++ 9 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 backend/debug.py diff --git a/backend/debug.py b/backend/debug.py new file mode 100644 index 000000000..5d5df3deb --- /dev/null +++ b/backend/debug.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +""" +Debug script for lead_agent. +Run this file directly in VS Code with breakpoints. + +Usage: + 1. Set breakpoints in agent.py or other files + 2. Press F5 or use "Run and Debug" panel + 3. Input messages in the terminal to interact with the agent +""" + +import asyncio +import os +import sys + +# Ensure we can import from src +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +# Load environment variables +from dotenv import load_dotenv +from langchain_core.messages import HumanMessage + +from src.agents import make_lead_agent + +load_dotenv() + + +async def main(): + # Create agent with default config + config = { + "configurable": { + "thread_id": "debug-thread-001", + "thinking_enabled": True, + # Uncomment to use a specific model + "model_name": "doubao-seed-1.8", + } + } + + agent = make_lead_agent(config) + + print("=" * 50) + print("Lead Agent Debug Mode") + print("Type 'quit' or 'exit' to stop") + print("=" * 50) + + while True: + try: + user_input = input("\nYou: ").strip() + if not user_input: + continue + if user_input.lower() in ("quit", "exit"): + print("Goodbye!") + break + + # Invoke the agent + state = {"messages": [HumanMessage(content=user_input)]} + result = await agent.ainvoke(state, config=config, context={"thread_id": "debug-thread-001"}) + + # Print the response + if result.get("messages"): + last_message = result["messages"][-1] + print(f"\nAgent: {last_message.content}") + + except KeyboardInterrupt: + print("\nInterrupted. Goodbye!") + break + except Exception as e: + print(f"\nError: {e}") + import traceback + + traceback.print_exc() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/langgraph.json b/backend/langgraph.json index a3b5fe9a4..7c2c84244 100644 --- a/backend/langgraph.json +++ b/backend/langgraph.json @@ -3,6 +3,6 @@ "dependencies": ["."], "env": ".env", "graphs": { - "lead_agent": "src.agents:lead_agent" + "lead_agent": "src.agents:make_lead_agent" } } diff --git a/backend/src/agents/__init__.py b/backend/src/agents/__init__.py index cffde06d2..3bed203d0 100644 --- a/backend/src/agents/__init__.py +++ b/backend/src/agents/__init__.py @@ -1,4 +1,4 @@ -from .lead_agent import lead_agent +from .lead_agent import make_lead_agent from .thread_state import SandboxState, ThreadState -__all__ = ["lead_agent", "SandboxState", "ThreadState"] +__all__ = ["make_lead_agent", "SandboxState", "ThreadState"] diff --git a/backend/src/agents/lead_agent/__init__.py b/backend/src/agents/lead_agent/__init__.py index 925259a5f..c93ffa7b6 100644 --- a/backend/src/agents/lead_agent/__init__.py +++ b/backend/src/agents/lead_agent/__init__.py @@ -1,3 +1,3 @@ -from .agent import lead_agent +from .agent import make_lead_agent -__all__ = ["lead_agent"] +__all__ = ["make_lead_agent"] diff --git a/backend/src/agents/lead_agent/agent.py b/backend/src/agents/lead_agent/agent.py index 077358375..9f137d054 100644 --- a/backend/src/agents/lead_agent/agent.py +++ b/backend/src/agents/lead_agent/agent.py @@ -1,4 +1,5 @@ from langchain.agents import create_agent +from langchain_core.runnables import RunnableConfig from src.agents.lead_agent.prompt import apply_prompt_template from src.agents.middlewares.thread_data_middleware import ThreadDataMiddleware @@ -11,10 +12,15 @@ from src.tools import get_available_tools # ThreadDataMiddleware must be before SandboxMiddleware to ensure thread_id is available middlewares = [ThreadDataMiddleware(), SandboxMiddleware(), TitleMiddleware()] -lead_agent = create_agent( - model=create_chat_model(thinking_enabled=True), - tools=get_available_tools(), - middleware=middlewares, - system_prompt=apply_prompt_template(), - state_schema=ThreadState, -) + +def make_lead_agent(config: RunnableConfig): + thinking_enabled = config.get("configurable", {}).get("thinking_enabled", True) + model_name = config.get("configurable", {}).get("model_name") + print(f"thinking_enabled: {thinking_enabled}, model_name: {model_name}") + return create_agent( + model=create_chat_model(name=model_name, thinking_enabled=thinking_enabled), + tools=get_available_tools(), + middleware=middlewares, + system_prompt=apply_prompt_template(), + state_schema=ThreadState, + ) diff --git a/backend/src/agents/lead_agent/prompt.py b/backend/src/agents/lead_agent/prompt.py index c4ceb9658..adfa58800 100644 --- a/backend/src/agents/lead_agent/prompt.py +++ b/backend/src/agents/lead_agent/prompt.py @@ -1,4 +1,3 @@ -import os from datetime import datetime SYSTEM_PROMPT = f""" diff --git a/backend/src/agents/middlewares/thread_data_middleware.py b/backend/src/agents/middlewares/thread_data_middleware.py index 727e62f04..2367f3fe9 100644 --- a/backend/src/agents/middlewares/thread_data_middleware.py +++ b/backend/src/agents/middlewares/thread_data_middleware.py @@ -71,8 +71,9 @@ class ThreadDataMiddleware(AgentMiddleware[ThreadDataMiddlewareState]): @override def before_agent(self, state: ThreadDataMiddlewareState, runtime: Runtime) -> dict | None: # Generate new thread ID and create directories - print(runtime.context) - thread_id = runtime.context["thread_id"] + thread_id = runtime.context.get("thread_id") + if thread_id is None: + raise ValueError("Thread ID is required in the context") paths = self._create_thread_directories(thread_id) print(f"Created thread data directories for thread {thread_id}") diff --git a/backend/src/sandbox/tools.py b/backend/src/sandbox/tools.py index a8ec9c549..3ab0c90d3 100644 --- a/backend/src/sandbox/tools.py +++ b/backend/src/sandbox/tools.py @@ -168,14 +168,16 @@ def read_file_tool( runtime: ToolRuntime[ContextT, ThreadState], description: str, path: str, - view_range: tuple[int, int] | None = None, + start_line: int | None = None, + end_line: int | None = None, ) -> str: - """Read the contents of a text file. + """Read the contents of a text file. Use this to examine source code, configuration files, logs, or any text-based file. Args: - description: Explain why you are viewing this file in short words. ALWAYS PROVIDE THIS PARAMETER FIRST. + description: Explain why you are reading this file in short words. ALWAYS PROVIDE THIS PARAMETER FIRST. path: The **absolute** path to the file to read. - view_range: The range of lines to view. The range is inclusive and starts at 1. For example, (1, 10) will view the first 10 lines of the file. + start_line: Optional starting line number (1-indexed, inclusive). Use with end_line to read a specific range. + end_line: Optional ending line number (1-indexed, inclusive). Use with start_line to read a specific range. """ try: sandbox = sandbox_from_runtime(runtime) @@ -185,9 +187,8 @@ def read_file_tool( content = sandbox.read_file(path) if not content: return "(empty)" - if view_range: - start, end = view_range - content = "\n".join(content.splitlines()[start - 1 : end]) + if start_line is not None and end_line is not None: + content = "\n".join(content.splitlines()[start_line - 1 : end_line]) return content except Exception as e: return f"Error: {e}" diff --git a/deer-flow.code-workspace b/deer-flow.code-workspace index 652a3f154..8622877bf 100644 --- a/deer-flow.code-workspace +++ b/deer-flow.code-workspace @@ -13,5 +13,34 @@ "workspace": "deer-flow" } ] + }, + "launch": { + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Lead Agent", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/backend/debug.py", + "console": "integratedTerminal", + "cwd": "${workspaceFolder}/backend", + "env": { + "PYTHONPATH": "${workspaceFolder}/backend" + }, + "justMyCode": false + }, + { + "name": "Debug Lead Agent (justMyCode)", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/backend/debug.py", + "console": "integratedTerminal", + "cwd": "${workspaceFolder}/backend", + "env": { + "PYTHONPATH": "${workspaceFolder}/backend" + }, + "justMyCode": true + } + ] } } From 9f2b94ed52c6771aba17cc662e12a465b24460e5 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Thu, 15 Jan 2026 23:40:21 +0800 Subject: [PATCH 005/302] feat: implement basic web app --- frontend/package.json | 12 +- frontend/pnpm-lock.yaml | 1801 ++++++++++++----- .../src/app/api/langgraph/[...path]/route.ts | 98 + frontend/src/app/layout.tsx | 21 +- frontend/src/app/page.tsx | 4 +- .../app/workspace/chats/[thread_id]/page.tsx | 115 ++ frontend/src/app/workspace/chats/page.tsx | 18 + frontend/src/app/workspace/layout.tsx | 29 + frontend/src/app/workspace/page.tsx | 5 + .../workspace/threads/[thread_id]/page.tsx | 26 + .../ai-elements/chain-of-thought.tsx | 46 +- .../src/components/ai-elements/message.tsx | 41 +- .../components/ai-elements/prompt-input.tsx | 82 +- frontend/src/components/theme-provider.tsx | 10 + frontend/src/components/ui/breadcrumb.tsx | 109 + frontend/src/components/ui/button.tsx | 33 +- frontend/src/components/ui/collapsible.tsx | 16 +- frontend/src/components/ui/sheet.tsx | 139 ++ frontend/src/components/ui/sidebar.tsx | 726 +++++++ frontend/src/components/ui/skeleton.tsx | 13 + .../src/components/workspace/github-icon.tsx | 14 + .../src/components/workspace/input-box.tsx | 71 + .../workspace/message-list/index.ts | 1 + .../workspace/message-list/message-group.tsx | 329 +++ .../message-list/message-list-item.tsx | 71 + .../workspace/message-list/message-list.tsx | 63 + .../workspace/message-list/skeleton.tsx | 4 + .../workspace/message-list/utils.ts | 157 ++ .../src/components/workspace/overscroll.tsx | 17 + .../components/workspace/recent-chat-list.tsx | 107 + .../workspace/streaming-indicator.tsx | 34 + frontend/src/components/workspace/tooltip.tsx | 23 + .../workspace/workspace-container.tsx | 146 ++ .../components/workspace/workspace-header.tsx | 30 + .../workspace/workspace-nav-menu.tsx | 41 + .../workspace/workspace-sidebar.tsx | 29 + frontend/src/core/api/client.ts | 17 + frontend/src/core/api/hooks.ts | 45 + frontend/src/core/api/index.ts | 2 + frontend/src/core/rehype/index.ts | 49 + frontend/src/core/thread/index.ts | 1 + frontend/src/core/thread/types.ts | 8 + frontend/src/core/thread/utils.ts | 27 + frontend/src/core/utils/json.ts | 10 + frontend/src/core/utils/markdown.ts | 10 + frontend/src/core/utils/uuid.ts | 1 + frontend/src/hooks/use-mobile.ts | 19 + frontend/src/styles/globals.css | 96 +- frontend/tsconfig.json | 2 +- 49 files changed, 4142 insertions(+), 626 deletions(-) create mode 100644 frontend/src/app/api/langgraph/[...path]/route.ts create mode 100644 frontend/src/app/workspace/chats/[thread_id]/page.tsx create mode 100644 frontend/src/app/workspace/chats/page.tsx create mode 100644 frontend/src/app/workspace/layout.tsx create mode 100644 frontend/src/app/workspace/page.tsx create mode 100644 frontend/src/app/workspace/threads/[thread_id]/page.tsx create mode 100644 frontend/src/components/theme-provider.tsx create mode 100644 frontend/src/components/ui/breadcrumb.tsx create mode 100644 frontend/src/components/ui/sheet.tsx create mode 100644 frontend/src/components/ui/sidebar.tsx create mode 100644 frontend/src/components/ui/skeleton.tsx create mode 100644 frontend/src/components/workspace/github-icon.tsx create mode 100644 frontend/src/components/workspace/input-box.tsx create mode 100644 frontend/src/components/workspace/message-list/index.ts create mode 100644 frontend/src/components/workspace/message-list/message-group.tsx create mode 100644 frontend/src/components/workspace/message-list/message-list-item.tsx create mode 100644 frontend/src/components/workspace/message-list/message-list.tsx create mode 100644 frontend/src/components/workspace/message-list/skeleton.tsx create mode 100644 frontend/src/components/workspace/message-list/utils.ts create mode 100644 frontend/src/components/workspace/overscroll.tsx create mode 100644 frontend/src/components/workspace/recent-chat-list.tsx create mode 100644 frontend/src/components/workspace/streaming-indicator.tsx create mode 100644 frontend/src/components/workspace/tooltip.tsx create mode 100644 frontend/src/components/workspace/workspace-container.tsx create mode 100644 frontend/src/components/workspace/workspace-header.tsx create mode 100644 frontend/src/components/workspace/workspace-nav-menu.tsx create mode 100644 frontend/src/components/workspace/workspace-sidebar.tsx create mode 100644 frontend/src/core/api/client.ts create mode 100644 frontend/src/core/api/hooks.ts create mode 100644 frontend/src/core/api/index.ts create mode 100644 frontend/src/core/rehype/index.ts create mode 100644 frontend/src/core/thread/index.ts create mode 100644 frontend/src/core/thread/types.ts create mode 100644 frontend/src/core/thread/utils.ts create mode 100644 frontend/src/core/utils/json.ts create mode 100644 frontend/src/core/utils/markdown.ts create mode 100644 frontend/src/core/utils/uuid.ts create mode 100644 frontend/src/hooks/use-mobile.ts diff --git a/frontend/package.json b/frontend/package.json index 2c4b30f96..f67a6b92a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@langchain/core": "^1.1.15", + "@langchain/langgraph-sdk": "^1.5.3", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -28,25 +30,31 @@ "@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-use-controllable-state": "^1.2.2", "@t3-oss/env-nextjs": "^0.12.0", + "@tanstack/react-query": "^5.90.17", + "@types/hast": "^3.0.4", "@xyflow/react": "^12.10.0", "ai": "^6.0.33", + "best-effort-json-parser": "^1.2.1", "better-auth": "^1.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "embla-carousel-react": "^8.6.0", + "hast": "^1.0.0", "lucide-react": "^0.562.0", "motion": "^12.26.2", "nanoid": "^5.1.6", - "next": "^16.1.1", + "next": "^15.2.8", "next-themes": "^0.4.6", "react": "^19.0.0", "react-dom": "^19.0.0", "shiki": "^3.21.0", - "streamdown": "^2.0.1", + "streamdown": "1.5.1", "tailwind-merge": "^3.4.0", "tokenlens": "^1.3.1", + "unist-util-visit": "^5.0.0", "use-stick-to-bottom": "^1.1.1", + "uuid": "^13.0.0", "zod": "^3.24.2" }, "devDependencies": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 16a8931c3..6314b31f1 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@langchain/core': + specifier: 1.1.15 + version: 1.1.15(@opentelemetry/api@1.9.0) + '@langchain/langgraph-sdk': + specifier: 1.5.3 + version: 1.5.3(@langchain/core@1.1.15(@opentelemetry/api@1.9.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-collapsible': specifier: ^1.1.12 version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -44,15 +50,24 @@ importers: '@t3-oss/env-nextjs': specifier: ^0.12.0 version: 0.12.0(typescript@5.9.3)(zod@3.25.76) + '@tanstack/react-query': + specifier: ^5.90.17 + version: 5.90.17(react@19.2.3) + '@types/hast': + specifier: ^3.0.4 + version: 3.0.4 '@xyflow/react': specifier: ^12.10.0 version: 12.10.0(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) ai: specifier: ^6.0.33 version: 6.0.33(zod@3.25.76) + best-effort-json-parser: + specifier: ^1.2.1 + version: 1.2.1 better-auth: specifier: ^1.3 - version: 1.4.12(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 1.4.12(next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -65,6 +80,9 @@ importers: embla-carousel-react: specifier: ^8.6.0 version: 8.6.0(react@19.2.3) + hast: + specifier: ^1.0.0 + version: 1.0.0 lucide-react: specifier: ^0.562.0 version: 0.562.0(react@19.2.3) @@ -75,8 +93,8 @@ importers: specifier: ^5.1.6 version: 5.1.6 next: - specifier: ^16.1.1 - version: 16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^15.2.8 + version: 15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -90,17 +108,23 @@ importers: specifier: ^3.21.0 version: 3.21.0 streamdown: - specifier: ^2.0.1 - version: 2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3) + specifier: 1.5.1 + version: 1.5.1(@types/mdast@4.0.4)(@types/react@19.2.8)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3)(unified@11.0.5) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 tokenlens: specifier: ^1.3.1 version: 1.3.1 + unist-util-visit: + specifier: ^5.0.0 + version: 5.0.0 use-stick-to-bottom: specifier: ^1.1.1 version: 1.1.1(react@19.2.3) + uuid: + specifier: ^13.0.0 + version: 13.0.0 zod: specifier: ^3.24.2 version: 3.25.76 @@ -170,6 +194,9 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@better-auth/core@1.4.12': resolution: {integrity: sha512-VfqZwMAEl9rnGx092BIZ2Q5z8rt7jjN2OAbvPqehufSKZGmh8JsdtZRBMl/CHQir9bwi2Ev0UF4+7TQp+DXEMg==} peerDependencies: @@ -191,6 +218,27 @@ packages: '@better-fetch/fetch@1.1.21': resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@braintree/sanitize-url@7.1.1': + resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -200,162 +248,6 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.27.2': - resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.2': - resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.2': - resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.2': - resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.2': - resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.2': - resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.2': - resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.2': - resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.2': - resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.2': - resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.2': - resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.2': - resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.2': - resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.2': - resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.2': - resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.27.2': - resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.2': - resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.2': - resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.2': - resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.2': - resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.2': - resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.2': - resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.2': - resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.2': - resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.2': - resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.2': - resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -425,155 +317,125 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@img/colour@1.0.0': - resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} - engines: {node: '>=18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -594,63 +456,84 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@langchain/core@1.1.15': + resolution: {integrity: sha512-b8RN5DkWAmDAlMu/UpTZEluYwCLpm63PPWniRKlE8ie3KkkE7IuMQ38pf4kV1iaiI+d99BEQa2vafQHfCujsRA==} + engines: {node: '>=20'} + + '@langchain/langgraph-sdk@1.5.3': + resolution: {integrity: sha512-8ycRjOGsBafxvCpHyCGaWhd3s7UEXEYIEPO/vipebEzQuYaeF+E9MxS9uFSVaX1ZcNuqQNTosnBJG4ZUtirzuw==} + peerDependencies: + '@langchain/core': ^1.0.1 + react: ^18 || ^19 + react-dom: ^18 || ^19 + peerDependenciesMeta: + '@langchain/core': + optional: true + react: + optional: true + react-dom: + optional: true + + '@mermaid-js/parser@0.6.3': + resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/env@16.1.1': - resolution: {integrity: sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==} + '@next/env@15.2.8': + resolution: {integrity: sha512-TaEsAki14R7BlgywA05t2PFYfwZiNlGUHyIQHVyloXX3y+Dm0HUITe5YwTkjtuOQuDhuuLotNEad4VtnmE11Uw==} '@next/eslint-plugin-next@15.5.9': resolution: {integrity: sha512-kUzXx0iFiXw27cQAViE1yKWnz/nF8JzRmwgMRTMh8qMY90crNsdXJRh2e+R0vBpFR3kk1yvAR7wev7+fCCb79Q==} - '@next/swc-darwin-arm64@16.1.1': - resolution: {integrity: sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==} + '@next/swc-darwin-arm64@15.2.5': + resolution: {integrity: sha512-4OimvVlFTbgzPdA0kh8A1ih6FN9pQkL4nPXGqemEYgk+e7eQhsst/p35siNNqA49eQA6bvKZ1ASsDtu9gtXuog==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.1.1': - resolution: {integrity: sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==} + '@next/swc-darwin-x64@15.2.5': + resolution: {integrity: sha512-ohzRaE9YbGt1ctE0um+UGYIDkkOxHV44kEcHzLqQigoRLaiMtZzGrA11AJh2Lu0lv51XeiY1ZkUvkThjkVNBMA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.1.1': - resolution: {integrity: sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==} + '@next/swc-linux-arm64-gnu@15.2.5': + resolution: {integrity: sha512-FMSdxSUt5bVXqqOoZCc/Seg4LQep9w/fXTazr/EkpXW2Eu4IFI9FD7zBDlID8TJIybmvKk7mhd9s+2XWxz4flA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.1.1': - resolution: {integrity: sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==} + '@next/swc-linux-arm64-musl@15.2.5': + resolution: {integrity: sha512-4ZNKmuEiW5hRKkGp2HWwZ+JrvK4DQLgf8YDaqtZyn7NYdl0cHfatvlnLFSWUayx9yFAUagIgRGRk8pFxS8Qniw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.1.1': - resolution: {integrity: sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==} + '@next/swc-linux-x64-gnu@15.2.5': + resolution: {integrity: sha512-bE6lHQ9GXIf3gCDE53u2pTl99RPZW5V1GLHSRMJ5l/oB/MT+cohu9uwnCK7QUph2xIOu2a6+27kL0REa/kqwZw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.1.1': - resolution: {integrity: sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==} + '@next/swc-linux-x64-musl@15.2.5': + resolution: {integrity: sha512-y7EeQuSkQbTAkCEQnJXm1asRUuGSWAchGJ3c+Qtxh8LVjXleZast8Mn/rL7tZOm7o35QeIpIcid6ufG7EVTTcA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.1.1': - resolution: {integrity: sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==} + '@next/swc-win32-arm64-msvc@15.2.5': + resolution: {integrity: sha512-gQMz0yA8/dskZM2Xyiq2FRShxSrsJNha40Ob/M2n2+JGRrZ0JwTVjLdvtN6vCxuq4ByhOd4a9qEf60hApNR2gQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.1.1': - resolution: {integrity: sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==} + '@next/swc-win32-x64-msvc@15.2.5': + resolution: {integrity: sha512-tBDNVUcI7U03+3oMvJ11zrtVin5p0NctiuKmTGyaTIEAVj9Q77xukLXGXRnWxKRIIdFG4OTA2rUVGZDYOwgmAA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1139,6 +1022,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1262,6 +1148,14 @@ packages: '@tailwindcss/postcss@4.1.18': resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + '@tanstack/query-core@5.90.17': + resolution: {integrity: sha512-hDww+RyyYhjhUfoYQ4es6pbgxY7LNiPWxt4l1nJqhByjndxJ7HIjDxTBtfvMr5HwjYavMrd+ids5g4Rfev3lVQ==} + + '@tanstack/react-query@5.90.17': + resolution: {integrity: sha512-PGc2u9KLwohDUSchjW9MZqeDQJfJDON7y4W7REdNBgiFKxQy+Pf7eGjiFWEj5xPqKzAeHYdAb62IWI1a9UJyGQ==} + peerDependencies: + react: ^18 || ^19 + '@tokenlens/core@1.3.0': resolution: {integrity: sha512-d8YNHNC+q10bVpi95fELJwJyPVf1HfvBEI18eFQxRSZTdByXrP+f/ZtlhSzkx0Jl0aEmYVeBA5tPeeYRioLViQ==} @@ -1277,24 +1171,99 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + '@types/d3-drag@3.0.7': resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-selection@3.0.11': resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/d3-transition@3.0.9': resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} '@types/d3-zoom@3.0.8': resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1304,6 +1273,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -1333,12 +1305,18 @@ packages: '@types/react@19.2.8': resolution: {integrity: sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@typescript-eslint/eslint-plugin@8.53.0': resolution: {integrity: sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1540,6 +1518,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1608,9 +1590,11 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.9.14: - resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} - hasBin: true + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + best-effort-json-parser@1.2.1: + resolution: {integrity: sha512-UICSLibQdzS1f+PBsi3u2YE3SsdXcWicHUg3IMvfuaePS2AYnZJdJeKhGv5OM8/mqJwPt79aDrEJ1oa84tELvw==} better-auth@1.4.12: resolution: {integrity: sha512-FsFMnWgk+AGrxsIGbpWLCibgYcbm6uNhPHln3ohXFDXSRa0gk39Beuh54Q+x6ml2qYodF0snxf/tPtDpBI/JiA==} @@ -1689,6 +1673,10 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1705,6 +1693,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001764: resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==} @@ -1727,6 +1719,14 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1753,9 +1753,20 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -1763,6 +1774,18 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + console-table-printer@2.15.0: + resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==} + + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1770,10 +1793,51 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + d3-dispatch@3.0.1: resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} engines: {node: '>=12'} @@ -1782,18 +1846,88 @@ packages: resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} engines: {node: '>=12'} + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.1: + resolution: {integrity: sha512-ryitBnaRbXQtgZ/gU50GSn6jQRwinSCQclpakXymvLd8ytTgE5bmSfgYcUxD7XYL34qHhFDyVk71qqKsfSyvmA==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + d3-selection@3.0.0: resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} engines: {node: '>=12'} + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + d3-timer@3.0.1: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} @@ -1808,6 +1942,13 @@ packages: resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} engines: {node: '>=12'} + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -1823,6 +1964,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -1840,6 +1984,10 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -1857,6 +2005,9 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1875,6 +2026,9 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1935,11 +2089,6 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.27.2: - resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} - engines: {node: '>=18'} - hasBin: true - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2067,6 +2216,12 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventsource-parser@3.0.6: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} @@ -2196,6 +2351,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -2244,9 +2402,6 @@ packages: hast-util-raw@9.1.0: resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} - hast-util-sanitize@5.0.2: - resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} - hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} @@ -2262,6 +2417,10 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hast@1.0.0: + resolution: {integrity: sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA==} + deprecated: Renamed to rehype + hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} @@ -2271,6 +2430,10 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2294,6 +2457,13 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -2304,6 +2474,9 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -2365,6 +2538,10 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-network-error@1.3.0: + resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} + engines: {node: '>=16'} + is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} @@ -2430,6 +2607,9 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-tiktoken@1.0.21: + resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2464,10 +2644,34 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kysely@0.28.9: resolution: {integrity: sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA==} engines: {node: '>=20.0.0'} + langium@3.3.1: + resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} + engines: {node: '>=16.0.0'} + + langsmith@0.4.7: + resolution: {integrity: sha512-Esv5g/J8wwRwbGQr10PB9+bLsNk0mWbrXc7nnEreQDhh0azbU57I7epSnT7GC4sS4EOWavhbxk+6p8PTXtreHw==} + peerDependencies: + '@opentelemetry/api': '*' + '@opentelemetry/exporter-trace-otlp-proto': '*' + '@opentelemetry/sdk-trace-base': '*' + openai: '*' + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/exporter-trace-otlp-proto': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + openai: + optional: true + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -2475,6 +2679,12 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2557,6 +2767,12 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash-es@4.17.22: + resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -2567,6 +2783,11 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lucide-react@0.542.0: + resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.562.0: resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==} peerDependencies: @@ -2578,8 +2799,8 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - marked@17.0.1: - resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} engines: {node: '>= 20'} hasBin: true @@ -2639,6 +2860,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@11.12.2: + resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2769,6 +2993,9 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + motion-dom@12.26.2: resolution: {integrity: sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==} @@ -2792,6 +3019,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2820,13 +3051,13 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@16.1.1: - resolution: {integrity: sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==} - engines: {node: '>=20.9.0'} + next@15.2.8: + resolution: {integrity: sha512-pe2trLKZTdaCuvNER0S9Wp+SP2APf7SfFmyUP9/w1SFA2UqmW0u+IsxCKkiky3n6um7mryaQIlgiDnKrf1ZwIw==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.51.1 + '@playwright/test': ^1.41.2 babel-plugin-react-compiler: '*' react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 @@ -2887,6 +3118,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2895,6 +3130,29 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-queue@9.1.0: + resolution: {integrity: sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==} + engines: {node: '>=20'} + + p-retry@7.1.1: + resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==} + engines: {node: '>=20'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-timeout@7.0.1: + resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} + engines: {node: '>=20'} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2905,6 +3163,9 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2916,6 +3177,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2927,6 +3191,15 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3030,6 +3303,12 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -3090,9 +3369,6 @@ packages: rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} - rehype-sanitize@6.0.0: - resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} - remark-cjk-friendly-gfm-strikethrough@1.2.3: resolution: {integrity: sha512-bXfMZtsaomK6ysNN/UGRIcasQAYkC10NtPmP0oOHOV8YOhA2TXmwRXCku4qOzjIFxAPfish5+XS0eIug2PzNZA==} engines: {node: '>=16'} @@ -3128,9 +3404,6 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - remend@1.0.2: - resolution: {integrity: sha512-Kcr8qu3OcfX9pjVjzj3KC1B7jVZKofQzX1UEQ5W5LPS/pFTgOjF58zDBgdi6NDw86Ro8vOS0S0Yyf9r7CprmGg==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3151,12 +3424,21 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -3169,6 +3451,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -3196,8 +3481,8 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: @@ -3227,6 +3512,12 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + simple-wcswidth@1.1.2: + resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3241,11 +3532,15 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - streamdown@2.0.1: - resolution: {integrity: sha512-PcsA0DdoJPzS5YiTk3cAzH/HZ5A+cKuluWjYzUga/wjFBf8LQt/zWN8YtPzC7Jnt8MbAKQNKrhwrSS3T73h2Qw==} + streamdown@1.5.1: + resolution: {integrity: sha512-Nt75cjPvtWwqBcII96RNw/qzW370C0w9AW+1NVlCFGe1VRxPQeX2EOq5tU1u4jEOHiLKVCVee5+ypPcpOLiztA==} peerDependencies: react: ^18.0.0 || ^19.0.0 + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -3299,6 +3594,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3317,6 +3615,10 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -3340,6 +3642,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -3381,6 +3687,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.2: + resolution: {integrity: sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -3448,6 +3757,18 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -3457,6 +3778,26 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -3535,6 +3876,11 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + '@better-auth/core@1.4.12(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)': dependencies: '@better-auth/utils': 0.3.0 @@ -3556,6 +3902,27 @@ snapshots: '@better-fetch/fetch@1.1.21': {} + '@braintree/sanitize-url@7.1.1': {} + + '@cfworker/json-schema@4.1.1': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -3572,84 +3939,6 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.2': - optional: true - - '@esbuild/android-arm64@0.27.2': - optional: true - - '@esbuild/android-arm@0.27.2': - optional: true - - '@esbuild/android-x64@0.27.2': - optional: true - - '@esbuild/darwin-arm64@0.27.2': - optional: true - - '@esbuild/darwin-x64@0.27.2': - optional: true - - '@esbuild/freebsd-arm64@0.27.2': - optional: true - - '@esbuild/freebsd-x64@0.27.2': - optional: true - - '@esbuild/linux-arm64@0.27.2': - optional: true - - '@esbuild/linux-arm@0.27.2': - optional: true - - '@esbuild/linux-ia32@0.27.2': - optional: true - - '@esbuild/linux-loong64@0.27.2': - optional: true - - '@esbuild/linux-mips64el@0.27.2': - optional: true - - '@esbuild/linux-ppc64@0.27.2': - optional: true - - '@esbuild/linux-riscv64@0.27.2': - optional: true - - '@esbuild/linux-s390x@0.27.2': - optional: true - - '@esbuild/linux-x64@0.27.2': - optional: true - - '@esbuild/netbsd-arm64@0.27.2': - optional: true - - '@esbuild/netbsd-x64@0.27.2': - optional: true - - '@esbuild/openbsd-arm64@0.27.2': - optional: true - - '@esbuild/openbsd-x64@0.27.2': - optional: true - - '@esbuild/openharmony-arm64@0.27.2': - optional: true - - '@esbuild/sunos-x64@0.27.2': - optional: true - - '@esbuild/win32-arm64@0.27.2': - optional: true - - '@esbuild/win32-ia32@0.27.2': - optional: true - - '@esbuild/win32-x64@0.27.2': - optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -3724,101 +4013,87 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@img/colour@1.0.0': - optional: true + '@iconify/types@2.0.0': {} - '@img/sharp-darwin-arm64@0.34.5': + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.0 + + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.0.4 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-linux-arm64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm@1.0.5': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-s390x@1.0.4': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-x64@1.0.4': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-linux-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.0.4 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.0.5 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-s390x@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.0.4 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - - '@img/sharp-wasm32@0.34.5': + '@img/sharp-wasm32@0.33.5': dependencies: '@emnapi/runtime': 1.8.1 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-win32-ia32@0.33.5': optional: true - '@img/sharp-win32-ia32@0.34.5': - optional: true - - '@img/sharp-win32-x64@0.34.5': + '@img/sharp-win32-x64@0.33.5': optional: true '@jridgewell/gen-mapping@0.3.13': @@ -3840,6 +4115,38 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@langchain/core@1.1.15(@opentelemetry/api@1.9.0)': + dependencies: + '@cfworker/json-schema': 4.1.1 + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.21 + langsmith: 0.4.7(@opentelemetry/api@1.9.0) + mustache: 4.2.0 + p-queue: 6.6.2 + uuid: 10.0.0 + zod: 3.25.76 + transitivePeerDependencies: + - '@opentelemetry/api' + - '@opentelemetry/exporter-trace-otlp-proto' + - '@opentelemetry/sdk-trace-base' + - openai + + '@langchain/langgraph-sdk@1.5.3(@langchain/core@1.1.15(@opentelemetry/api@1.9.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + p-queue: 9.1.0 + p-retry: 7.1.1 + uuid: 13.0.0 + optionalDependencies: + '@langchain/core': 1.1.15(@opentelemetry/api@1.9.0) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + '@mermaid-js/parser@0.6.3': + dependencies: + langium: 3.3.1 + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.8.1 @@ -3847,34 +4154,34 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@16.1.1': {} + '@next/env@15.2.8': {} '@next/eslint-plugin-next@15.5.9': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.1.1': + '@next/swc-darwin-arm64@15.2.5': optional: true - '@next/swc-darwin-x64@16.1.1': + '@next/swc-darwin-x64@15.2.5': optional: true - '@next/swc-linux-arm64-gnu@16.1.1': + '@next/swc-linux-arm64-gnu@15.2.5': optional: true - '@next/swc-linux-arm64-musl@16.1.1': + '@next/swc-linux-arm64-musl@15.2.5': optional: true - '@next/swc-linux-x64-gnu@16.1.1': + '@next/swc-linux-x64-gnu@15.2.5': optional: true - '@next/swc-linux-x64-musl@16.1.1': + '@next/swc-linux-x64-musl@15.2.5': optional: true - '@next/swc-win32-arm64-msvc@16.1.1': + '@next/swc-win32-arm64-msvc@15.2.5': optional: true - '@next/swc-win32-x64-msvc@16.1.1': + '@next/swc-win32-x64-msvc@15.2.5': optional: true '@noble/ciphers@2.1.1': {} @@ -4355,6 +4662,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -4440,6 +4749,13 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.18 + '@tanstack/query-core@5.90.17': {} + + '@tanstack/react-query@5.90.17(react@19.2.3)': + dependencies: + '@tanstack/query-core': 5.90.17 + react: 19.2.3 + '@tokenlens/core@1.3.0': {} '@tokenlens/fetch@1.3.0': @@ -4460,18 +4776,81 @@ snapshots: tslib: 2.8.1 optional: true + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + '@types/d3-color@3.1.3': {} + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + '@types/d3-drag@3.0.7': dependencies: '@types/d3-selection': 3.0.11 + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + '@types/d3-selection@3.0.11': {} + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + '@types/d3-transition@3.0.9': dependencies: '@types/d3-selection': 3.0.11 @@ -4481,6 +4860,39 @@ snapshots: '@types/d3-interpolate': 3.0.4 '@types/d3-selection': 3.0.11 + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -4491,6 +4903,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -4519,10 +4933,15 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} + '@types/uuid@10.0.0': {} + '@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4725,6 +5144,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -4816,9 +5237,11 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.9.14: {} + base64-js@1.5.1: {} - better-auth@1.4.12(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + best-effort-json-parser@1.2.1: {} + + better-auth@1.4.12(next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@better-auth/core': 1.4.12(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0) '@better-auth/telemetry': 1.4.12(@better-auth/core@1.4.12(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)) @@ -4833,7 +5256,7 @@ snapshots: nanostores: 1.1.0 zod: 4.3.5 optionalDependencies: - next: 16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -4859,6 +5282,10 @@ snapshots: dependencies: fill-range: 7.1.1 + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4878,6 +5305,8 @@ snapshots: callsites@3.1.0: {} + camelcase@6.3.0: {} + caniuse-lite@1.0.30001764: {} ccount@2.0.1: {} @@ -4895,6 +5324,20 @@ snapshots: character-reference-invalid@2.0.1: {} + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.22 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -4923,12 +5366,40 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + comma-separated-tokens@2.0.3: {} + commander@7.2.0: {} + commander@8.3.0: {} concat-map@0.0.1: {} + confbox@0.1.8: {} + + console-table-printer@2.15.0: + dependencies: + simple-wcswidth: 1.1.2 + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4937,8 +5408,50 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + d3-color@3.1.0: {} + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + d3-dispatch@3.0.1: {} d3-drag@3.0.0: @@ -4946,14 +5459,82 @@ snapshots: d3-dispatch: 3.0.1 d3-selection: 3.0.0 + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + d3-ease@3.0.1: {} + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.1: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.1 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-selection@3.0.0: {} + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + d3-timer@3.0.1: {} d3-transition@3.0.1(d3-selection@3.0.0): @@ -4973,6 +5554,44 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.1 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.22 + damerau-levenshtein@1.0.8: {} data-view-buffer@1.0.2: @@ -4993,6 +5612,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + dayjs@1.11.19: {} + debug@3.2.7: dependencies: ms: 2.1.3 @@ -5001,6 +5622,8 @@ snapshots: dependencies: ms: 2.1.3 + decamelize@1.2.0: {} + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -5021,6 +5644,10 @@ snapshots: defu@6.1.4: {} + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -5035,6 +5662,10 @@ snapshots: dependencies: esutils: 2.0.3 + dompurify@3.3.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5163,35 +5794,6 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.27.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.2 - '@esbuild/android-arm': 0.27.2 - '@esbuild/android-arm64': 0.27.2 - '@esbuild/android-x64': 0.27.2 - '@esbuild/darwin-arm64': 0.27.2 - '@esbuild/darwin-x64': 0.27.2 - '@esbuild/freebsd-arm64': 0.27.2 - '@esbuild/freebsd-x64': 0.27.2 - '@esbuild/linux-arm': 0.27.2 - '@esbuild/linux-arm64': 0.27.2 - '@esbuild/linux-ia32': 0.27.2 - '@esbuild/linux-loong64': 0.27.2 - '@esbuild/linux-mips64el': 0.27.2 - '@esbuild/linux-ppc64': 0.27.2 - '@esbuild/linux-riscv64': 0.27.2 - '@esbuild/linux-s390x': 0.27.2 - '@esbuild/linux-x64': 0.27.2 - '@esbuild/netbsd-arm64': 0.27.2 - '@esbuild/netbsd-x64': 0.27.2 - '@esbuild/openbsd-arm64': 0.27.2 - '@esbuild/openbsd-x64': 0.27.2 - '@esbuild/openharmony-arm64': 0.27.2 - '@esbuild/sunos-x64': 0.27.2 - '@esbuild/win32-arm64': 0.27.2 - '@esbuild/win32-ia32': 0.27.2 - '@esbuild/win32-x64': 0.27.2 - escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -5394,6 +5996,10 @@ snapshots: esutils@2.0.3: {} + eventemitter3@4.0.7: {} + + eventemitter3@5.0.1: {} + eventsource-parser@3.0.6: {} extend@3.0.2: {} @@ -5519,6 +6125,8 @@ snapshots: graceful-fs@4.2.11: {} + hachure-fill@0.5.2: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -5598,12 +6206,6 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 - hast-util-sanitize@5.0.2: - dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.3.0 - unist-util-position: 5.0.0 - hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.4 @@ -5659,6 +6261,8 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hast@1.0.0: {} + hastscript@9.0.1: dependencies: '@types/hast': 3.0.4 @@ -5671,6 +6275,10 @@ snapshots: html-void-elements@3.0.0: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -5690,6 +6298,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@1.0.1: {} + + internmap@2.0.3: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -5703,6 +6315,9 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-arrayish@0.3.4: + optional: true + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -5767,6 +6382,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-network-error@1.3.0: {} + is-number-object@1.1.1: dependencies: call-bound: 1.0.4 @@ -5832,6 +6449,10 @@ snapshots: jose@6.1.3: {} + js-tiktoken@1.0.21: + dependencies: + base64-js: 1.5.1 + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -5865,14 +6486,39 @@ snapshots: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + kysely@0.28.9: {} + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + + langsmith@0.4.7(@opentelemetry/api@1.9.0): + dependencies: + '@types/uuid': 10.0.0 + chalk: 4.1.2 + console-table-printer: 2.15.0 + p-queue: 6.6.2 + semver: 7.7.3 + uuid: 10.0.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + language-subtag-registry@0.3.23: {} language-tags@1.0.9: dependencies: language-subtag-registry: 0.3.23 + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -5931,6 +6577,10 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + + lodash-es@4.17.22: {} + lodash.merge@4.6.2: {} longest-streak@3.1.0: {} @@ -5939,6 +6589,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lucide-react@0.542.0(react@19.2.3): + dependencies: + react: 19.2.3 + lucide-react@0.562.0(react@19.2.3): dependencies: react: 19.2.3 @@ -5949,7 +6603,7 @@ snapshots: markdown-table@3.0.4: {} - marked@17.0.1: {} + marked@16.4.2: {} math-intrinsics@1.1.0: {} @@ -6120,6 +6774,29 @@ snapshots: merge2@1.4.1: {} + mermaid@11.12.2: + dependencies: + '@braintree/sanitize-url': 7.1.1 + '@iconify/utils': 3.1.0 + '@mermaid-js/parser': 0.6.3 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.19 + dompurify: 3.3.1 + katex: 0.16.27 + khroma: 2.1.0 + lodash-es: 4.17.22 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -6368,6 +7045,13 @@ snapshots: minimist@1.2.8: {} + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.2 + motion-dom@12.26.2: dependencies: motion-utils: 12.24.10 @@ -6384,6 +7068,8 @@ snapshots: ms@2.1.3: {} + mustache@4.2.0: {} + nanoid@3.3.11: {} nanoid@5.1.6: {} @@ -6399,27 +7085,28 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@next/env': 16.1.1 + '@next/env': 15.2.8 + '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.9.14 + busboy: 1.6.0 caniuse-lite: 1.0.30001764 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) styled-jsx: 5.1.6(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 16.1.1 - '@next/swc-darwin-x64': 16.1.1 - '@next/swc-linux-arm64-gnu': 16.1.1 - '@next/swc-linux-arm64-musl': 16.1.1 - '@next/swc-linux-x64-gnu': 16.1.1 - '@next/swc-linux-x64-musl': 16.1.1 - '@next/swc-win32-arm64-msvc': 16.1.1 - '@next/swc-win32-x64-msvc': 16.1.1 + '@next/swc-darwin-arm64': 15.2.5 + '@next/swc-darwin-x64': 15.2.5 + '@next/swc-linux-arm64-gnu': 15.2.5 + '@next/swc-linux-arm64-musl': 15.2.5 + '@next/swc-linux-x64-gnu': 15.2.5 + '@next/swc-linux-x64-musl': 15.2.5 + '@next/swc-win32-arm64-msvc': 15.2.5 + '@next/swc-win32-x64-msvc': 15.2.5 '@opentelemetry/api': 1.9.0 - sharp: 0.34.5 + sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -6489,6 +7176,8 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-finally@1.0.0: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -6497,6 +7186,28 @@ snapshots: dependencies: p-limit: 3.1.0 + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-queue@9.1.0: + dependencies: + eventemitter3: 5.0.1 + p-timeout: 7.0.1 + + p-retry@7.1.1: + dependencies: + is-network-error: 1.3.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + p-timeout@7.0.1: {} + + package-manager-detector@1.6.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -6515,18 +7226,35 @@ snapshots: dependencies: entities: 6.0.1 + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-key@3.1.1: {} path-parse@1.0.7: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.3: {} + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + possible-typed-array-names@1.1.0: {} postcss@8.4.31: @@ -6568,6 +7296,24 @@ snapshots: react-is@16.13.1: {} + react-markdown@10.1.0(@types/react@19.2.8)(react@19.2.3): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.8 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.3 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-remove-scroll-bar@2.3.8(@types/react@19.2.8)(react@19.2.3): dependencies: react: 19.2.3 @@ -6647,11 +7393,6 @@ snapshots: hast-util-raw: 9.1.0 vfile: 6.0.3 - rehype-sanitize@6.0.0: - dependencies: - '@types/hast': 3.0.4 - hast-util-sanitize: 5.0.2 - remark-cjk-friendly-gfm-strikethrough@1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5): dependencies: micromark-extension-cjk-friendly-gfm-strikethrough: 1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2) @@ -6715,8 +7456,6 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - remend@1.0.2: {} - resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -6735,12 +7474,23 @@ snapshots: reusify@1.1.0: {} + robust-predicates@3.0.2: {} + rou3@0.7.12: {} + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -6760,6 +7510,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + scheduler@0.27.0: {} semver@6.3.1: {} @@ -6790,36 +7542,31 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - sharp@0.34.5: + sharp@0.33.5: dependencies: - '@img/colour': 1.0.0 + color: 4.2.3 detect-libc: 2.1.2 semver: 7.7.3 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 optional: true shebang-command@2.0.0: @@ -6867,6 +7614,13 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + optional: true + + simple-wcswidth@1.1.2: {} + source-map-js@1.2.1: {} space-separated-tokens@2.0.2: {} @@ -6878,35 +7632,33 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - streamdown@2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3): + streamdown@1.5.1(@types/mdast@4.0.4)(@types/react@19.2.8)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3)(unified@11.0.5): dependencies: clsx: 2.1.1 - esbuild: 0.27.2 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 katex: 0.16.27 - marked: 17.0.1 + lucide-react: 0.542.0(react@19.2.3) + marked: 16.4.2 + mermaid: 11.12.2 react: 19.2.3 + react-markdown: 10.1.0(@types/react@19.2.8)(react@19.2.3) rehype-harden: 1.1.7 rehype-katex: 7.0.1 rehype-raw: 7.0.0 - rehype-sanitize: 6.0.0 remark-cjk-friendly: 1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) remark-cjk-friendly-gfm-strikethrough: 1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) remark-gfm: 4.0.1 remark-math: 6.0.0 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - remend: 1.0.2 shiki: 3.21.0 tailwind-merge: 3.4.0 - unified: 11.0.5 - unist-util-visit: 5.0.0 transitivePeerDependencies: - '@types/mdast' + - '@types/react' - micromark - micromark-util-types - supports-color + - unified + + streamsearch@1.1.0: {} string.prototype.includes@2.0.1: dependencies: @@ -6980,6 +7732,8 @@ snapshots: client-only: 0.0.1 react: 19.2.3 + stylis@4.3.6: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -6992,6 +7746,8 @@ snapshots: tapable@2.3.0: {} + tinyexec@1.0.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -7016,6 +7772,8 @@ snapshots: dependencies: typescript: 5.9.3 + ts-dedent@2.2.0: {} + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -7077,6 +7835,8 @@ snapshots: typescript@5.9.3: {} + ufo@1.6.2: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -7180,6 +7940,12 @@ snapshots: dependencies: react: 19.2.3 + uuid@10.0.0: {} + + uuid@11.1.0: {} + + uuid@13.0.0: {} + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -7195,6 +7961,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + web-namespaces@2.0.1: {} which-boxed-primitive@1.1.1: diff --git a/frontend/src/app/api/langgraph/[...path]/route.ts b/frontend/src/app/api/langgraph/[...path]/route.ts new file mode 100644 index 000000000..b820195fb --- /dev/null +++ b/frontend/src/app/api/langgraph/[...path]/route.ts @@ -0,0 +1,98 @@ +import { type NextRequest } from "next/server"; + +export const runtime = "nodejs"; + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ path: string[] }> }, +) { + return handleProxy(request, (await params).path); +} + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ path: string[] }> }, +) { + return handleProxy(request, (await params).path); +} + +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ path: string[] }> }, +) { + return handleProxy(request, (await params).path); +} + +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ path: string[] }> }, +) { + return handleProxy(request, (await params).path); +} + +async function handleProxy(request: NextRequest, pathSegments: string[]) { + const targetUrl = `http://localhost:2024/${pathSegments.join("/")}`; + + // Preserve query parameters + const searchParams = request.nextUrl.searchParams.toString(); + const fullUrl = searchParams ? `${targetUrl}?${searchParams}` : targetUrl; + + // Prepare headers + const headers = new Headers(); + request.headers.forEach((value, key) => { + // Skip Next.js specific headers + if (!key.startsWith("x-") && key !== "host" && key !== "connection") { + headers.set(key, value); + } + }); + + // Prepare fetch options + const fetchOptions: RequestInit = { + method: request.method, + headers, + }; + + // Add body for non-GET requests + if (request.method !== "GET" && request.method !== "HEAD") { + fetchOptions.body = await request.text(); + } + + try { + const response = await fetch(fullUrl, fetchOptions); + + // Check if response is SSE + const contentType = response.headers.get("content-type"); + const isSSE = contentType?.includes("text/event-stream"); + + if (isSSE && response.body) { + // Handle SSE streaming + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); + } + + // Handle regular responses + const responseHeaders = new Headers(); + response.headers.forEach((value, key) => { + responseHeaders.set(key, value); + }); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: responseHeaders, + }); + } catch (error) { + console.error("Proxy error:", error); + return new Response(JSON.stringify({ error: "Proxy request failed" }), { + status: 502, + headers: { "Content-Type": "application/json" }, + }); + } +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 51a1fce07..78397c6fb 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -3,8 +3,10 @@ import "@/styles/globals.css"; import { type Metadata } from "next"; import { Geist } from "next/font/google"; +import { ThemeProvider } from "@/components/theme-provider"; + export const metadata: Metadata = { - title: "DeerFlow", + title: "Welcome to DeerFlow", description: "A LangChain-based framework for building super agents.", icons: [{ rel: "icon", url: "/favicon.ico" }], }; @@ -18,8 +20,21 @@ export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( - - {children} + + + + {children} + + ); } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 1f2a4b6bf..75d7e6f71 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,7 +1,5 @@ export default async function LandingPage() { return ( -
    - Welcome to DeerFlow 2 -
    +
    DeerFlow Landing Page
    ); } diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx new file mode 100644 index 000000000..c3bc0119c --- /dev/null +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -0,0 +1,115 @@ +"use client"; + +import { type HumanMessage } from "@langchain/core/messages"; +import { useStream } from "@langchain/langgraph-sdk/react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useParams, useRouter } from "next/navigation"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +import type { PromptInputMessage } from "@/components/ai-elements/prompt-input"; +import { BreadcrumbItem } from "@/components/ui/breadcrumb"; +import { InputBox } from "@/components/workspace/input-box"; +import { MessageList } from "@/components/workspace/message-list/message-list"; +import { + WorkspaceContainer, + WorkspaceBody, + WorkspaceHeader, + WorkspaceFooter, +} from "@/components/workspace/workspace-container"; +import { getLangGraphClient } from "@/core/api"; +import type { MessageThread, MessageThreadState } from "@/core/thread"; +import { titleOfThread } from "@/core/thread/utils"; +import { uuid } from "@/core/utils/uuid"; + +const langGraphClient = getLangGraphClient(); + +export default function ChatPage() { + const router = useRouter(); + const queryClient = useQueryClient(); + const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); + const isNewThread = useMemo( + () => threadIdFromPath === "new", + [threadIdFromPath], + ); + const [threadId, setThreadId] = useState(null); + useEffect(() => { + if (threadIdFromPath !== "new") { + setThreadId(threadIdFromPath); + } else { + setThreadId(uuid()); + } + }, [threadIdFromPath]); + const thread = useStream({ + client: langGraphClient, + assistantId: "lead_agent", + threadId: !isNewThread ? threadId : undefined, + reconnectOnMount: true, + fetchStateHistory: true, + onFinish() { + void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); + }, + }); + const handleSubmit = useCallback( + async (message: PromptInputMessage) => { + const text = message.text.trim(); + if (isNewThread) { + router.replace(`/workspace/chats/${threadId}`); + } + await thread.submit( + { + messages: [ + { + type: "human", + content: [ + { + type: "text", + text, + }, + ], + }, + ] as HumanMessage[], + }, + { + threadId: isNewThread ? threadId! : undefined, + streamSubgraphs: true, + streamResumable: true, + context: { + thread_id: threadId!, + model: "deepseek-v3.2", + thinking_enabled: true, + }, + }, + ); + void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); + }, + [isNewThread, queryClient, router, thread, threadId], + ); + const handleStop = useCallback(async () => { + await thread.stop(); + }, [thread]); + return ( + + + + {isNewThread + ? "New" + : titleOfThread(thread as unknown as MessageThread)} + + + +
    + +
    +
    + + + +
    + ); +} diff --git a/frontend/src/app/workspace/chats/page.tsx b/frontend/src/app/workspace/chats/page.tsx new file mode 100644 index 000000000..1127faa39 --- /dev/null +++ b/frontend/src/app/workspace/chats/page.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { + WorkspaceBody, + WorkspaceContainer, + WorkspaceHeader, +} from "@/components/workspace/workspace-container"; + +export default function ChatsPage() { + return ( + + + +
    +
    +
    + ); +} diff --git a/frontend/src/app/workspace/layout.tsx b/frontend/src/app/workspace/layout.tsx new file mode 100644 index 000000000..bf770cb51 --- /dev/null +++ b/frontend/src/app/workspace/layout.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; +import { Overscroll } from "@/components/workspace/overscroll"; +import { WorkspaceSidebar } from "@/components/workspace/workspace-sidebar"; + +const queryClient = new QueryClient(); + +export default function WorkspaceLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + + + + {children} + + + ); +} diff --git a/frontend/src/app/workspace/page.tsx b/frontend/src/app/workspace/page.tsx new file mode 100644 index 000000000..8ce04cfc4 --- /dev/null +++ b/frontend/src/app/workspace/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function WorkspacePage() { + return redirect("/workspace/chats/new"); +} diff --git a/frontend/src/app/workspace/threads/[thread_id]/page.tsx b/frontend/src/app/workspace/threads/[thread_id]/page.tsx new file mode 100644 index 000000000..0b71518ec --- /dev/null +++ b/frontend/src/app/workspace/threads/[thread_id]/page.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { useStream } from "@langchain/langgraph-sdk/react"; +import { useParams } from "next/navigation"; + +import { getLangGraphClient } from "@/core/api"; +import type { MessageThreadState } from "@/core/thread"; + +const apiClient = getLangGraphClient(); + +export default function TestPage() { + const { thread_id: threadId } = useParams<{ thread_id: string }>(); + const thread = useStream({ + client: apiClient, + assistantId: "lead_agent", + threadId, + reconnectOnMount: true, + fetchStateHistory: true, + }); + return ( +
    +
    {threadId}
    +
    {thread.isLoading ? "loading" : "not loading"}
    +
    + ); +} diff --git a/frontend/src/components/ai-elements/chain-of-thought.tsx b/frontend/src/components/ai-elements/chain-of-thought.tsx index 195c465c7..d7beb035b 100644 --- a/frontend/src/components/ai-elements/chain-of-thought.tsx +++ b/frontend/src/components/ai-elements/chain-of-thought.tsx @@ -23,14 +23,14 @@ type ChainOfThoughtContextValue = { }; const ChainOfThoughtContext = createContext( - null + null, ); const useChainOfThought = () => { const context = useContext(ChainOfThoughtContext); if (!context) { throw new Error( - "ChainOfThought components must be used within ChainOfThought" + "ChainOfThought components must be used within ChainOfThought", ); } return context; @@ -59,7 +59,7 @@ export const ChainOfThought = memo( const chainOfThoughtContext = useMemo( () => ({ isOpen, setIsOpen }), - [isOpen, setIsOpen] + [isOpen, setIsOpen], ); return ( @@ -72,40 +72,42 @@ export const ChainOfThought = memo( ); - } + }, ); export type ChainOfThoughtHeaderProps = ComponentProps< typeof CollapsibleTrigger ->; +> & { + icon?: React.ReactElement; +}; export const ChainOfThoughtHeader = memo( - ({ className, children, ...props }: ChainOfThoughtHeaderProps) => { + ({ className, children, icon, ...props }: ChainOfThoughtHeaderProps) => { const { isOpen, setIsOpen } = useChainOfThought(); return ( - + {icon ?? } {children ?? "Chain of Thought"} ); - } + }, ); export type ChainOfThoughtStepProps = ComponentProps<"div"> & { @@ -137,13 +139,13 @@ export const ChainOfThoughtStep = memo( "flex gap-2 text-sm", statusStyles[status], "fade-in-0 slide-in-from-top-2 animate-in", - className + className, )} {...props} >
    -
    +
    {label}
    @@ -154,7 +156,7 @@ export const ChainOfThoughtStep = memo(
    ); - } + }, ); export type ChainOfThoughtSearchResultsProps = ComponentProps<"div">; @@ -165,7 +167,7 @@ export const ChainOfThoughtSearchResults = memo( className={cn("flex flex-wrap items-center gap-2", className)} {...props} /> - ) + ), ); export type ChainOfThoughtSearchResultProps = ComponentProps; @@ -173,13 +175,13 @@ export type ChainOfThoughtSearchResultProps = ComponentProps; export const ChainOfThoughtSearchResult = memo( ({ className, children, ...props }: ChainOfThoughtSearchResultProps) => ( {children} - ) + ), ); export type ChainOfThoughtContentProps = ComponentProps< @@ -195,8 +197,8 @@ export const ChainOfThoughtContent = memo( @@ -204,7 +206,7 @@ export const ChainOfThoughtContent = memo( ); - } + }, ); export type ChainOfThoughtImageProps = ComponentProps<"div"> & { @@ -214,12 +216,12 @@ export type ChainOfThoughtImageProps = ComponentProps<"div"> & { export const ChainOfThoughtImage = memo( ({ className, children, caption, ...props }: ChainOfThoughtImageProps) => (
    -
    +
    {children}
    {caption &&

    {caption}

    }
    - ) + ), ); ChainOfThought.displayName = "ChainOfThought"; diff --git a/frontend/src/components/ai-elements/message.tsx b/frontend/src/components/ai-elements/message.tsx index 73d6997ef..f929e1475 100644 --- a/frontend/src/components/ai-elements/message.tsx +++ b/frontend/src/components/ai-elements/message.tsx @@ -1,10 +1,7 @@ "use client"; import { Button } from "@/components/ui/button"; -import { - ButtonGroup, - ButtonGroupText, -} from "@/components/ui/button-group"; +import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group"; import { Tooltip, TooltipContent, @@ -30,9 +27,9 @@ export type MessageProps = HTMLAttributes & { export const Message = ({ className, from, ...props }: MessageProps) => (
    @@ -47,10 +44,10 @@ export const MessageContent = ({ }: MessageContentProps) => (
    @@ -116,7 +113,7 @@ type MessageBranchContextType = { }; const MessageBranchContext = createContext( - null + null, ); const useMessageBranch = () => { @@ -124,7 +121,7 @@ const useMessageBranch = () => { if (!context) { throw new Error( - "MessageBranch components must be used within MessageBranch" + "MessageBranch components must be used within MessageBranch", ); } @@ -201,7 +198,7 @@ export const MessageBranchContent = ({
    div]:pb-0", - index === currentBranch ? "block" : "hidden" + index === currentBranch ? "block" : "hidden", )} key={branch.key} {...props} @@ -294,8 +291,8 @@ export const MessageBranchPage = ({ return ( @@ -311,12 +308,12 @@ export const MessageResponse = memo( *:first-child]:mt-0 [&>*:last-child]:mb-0", - className + className, )} {...props} /> ), - (prevProps, nextProps) => prevProps.children === nextProps.children + (prevProps, nextProps) => prevProps.children === nextProps.children, ); MessageResponse.displayName = "MessageResponse"; @@ -343,7 +340,7 @@ export function MessageAttachment({
    @@ -359,7 +356,7 @@ export function MessageAttachment({ {onRemove && ( + ); +} + +function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { + const { toggleSidebar } = useSidebar(); + + return ( + + + ); +} From b72eb613027e9596d012a97467a591133e6bd875 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 19:50:41 +0800 Subject: [PATCH 023/302] refactor: simplify parameter --- frontend/src/core/threads/utils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/core/threads/utils.ts b/frontend/src/core/threads/utils.ts index bf03e87d6..dba38aaf3 100644 --- a/frontend/src/core/threads/utils.ts +++ b/frontend/src/core/threads/utils.ts @@ -2,11 +2,8 @@ import type { BaseMessage } from "@langchain/core/messages"; import type { AgentThread } from "./types"; -export function pathOfThread(thread: AgentThread, includeAssistantId = true) { - if (includeAssistantId) { - return `/workspace/chats/${thread.thread_id}`; - } - return `/workspace/chats/${thread.thread_id}`; +export function pathOfThread(threadId: string) { + return `/workspace/chats/${threadId}`; } export function textOfMessage(message: BaseMessage) { From 7066a3b6910752a70a692389a78ca0e061bc39d1 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 19:51:14 +0800 Subject: [PATCH 024/302] feat: adjust layout and style of tooltip --- frontend/src/components/ui/tooltip.tsx | 23 +++++++++---------- frontend/src/components/workspace/tooltip.tsx | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx index a4e90d4e9..ac4a426e6 100644 --- a/frontend/src/components/ui/tooltip.tsx +++ b/frontend/src/components/ui/tooltip.tsx @@ -1,9 +1,9 @@ -"use client" +"use client"; -import * as React from "react" -import * as TooltipPrimitive from "@radix-ui/react-tooltip" +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function TooltipProvider({ delayDuration = 0, @@ -15,7 +15,7 @@ function TooltipProvider({ delayDuration={delayDuration} {...props} /> - ) + ); } function Tooltip({ @@ -25,13 +25,13 @@ function Tooltip({ - ) + ); } function TooltipTrigger({ ...props }: React.ComponentProps) { - return + return ; } function TooltipContent({ @@ -46,16 +46,15 @@ function TooltipContent({ data-slot="tooltip-content" sideOffset={sideOffset} className={cn( - "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", - className + "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-background/80 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md border px-3 py-1.5 text-xs text-balance text-white", + className, )} {...props} > {children} - - ) + ); } -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/frontend/src/components/workspace/tooltip.tsx b/frontend/src/components/workspace/tooltip.tsx index 62d1d6baa..ece59ba42 100644 --- a/frontend/src/components/workspace/tooltip.tsx +++ b/frontend/src/components/workspace/tooltip.tsx @@ -15,9 +15,9 @@ export function Tooltip({ content?: React.ReactNode; }) { return ( - + {children} - {content} + {content} ); } From 03f0e3f0c7be3b920ed4be1f5056716f85d81d21 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 19:51:39 +0800 Subject: [PATCH 025/302] refactor: move biz logic to core --- .../app/workspace/chats/[thread_id]/page.tsx | 49 ++------- .../message-list/message-list-item.tsx | 13 +++ .../workspace/message-list/message-list.tsx | 2 +- .../components/workspace/recent-chat-list.tsx | 4 +- frontend/src/core/threads/hooks.ts | 102 +++++++++++------- 5 files changed, 89 insertions(+), 81 deletions(-) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 2970ac45b..0cc15bf24 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -1,11 +1,8 @@ "use client"; -import { type HumanMessage } from "@langchain/core/messages"; -import { useQueryClient } from "@tanstack/react-query"; import { useParams, useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; -import type { PromptInputMessage } from "@/components/ai-elements/prompt-input"; import { BreadcrumbItem } from "@/components/ui/breadcrumb"; import { InputBox } from "@/components/workspace/input-box"; import { MessageList } from "@/components/workspace/message-list/message-list"; @@ -17,13 +14,12 @@ import { } from "@/components/workspace/workspace-container"; import { useLocalSettings } from "@/core/settings"; import { type AgentThread } from "@/core/threads"; -import { useThreadStream } from "@/core/threads/hooks"; -import { titleOfThread } from "@/core/threads/utils"; +import { useSubmitThread, useThreadStream } from "@/core/threads/hooks"; +import { pathOfThread, titleOfThread } from "@/core/threads/utils"; import { uuid } from "@/core/utils/uuid"; export default function ChatPage() { const router = useRouter(); - const queryClient = useQueryClient(); const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); const isNewThread = useMemo( () => threadIdFromPath === "new", @@ -43,40 +39,15 @@ export default function ChatPage() { isNewThread, threadId, }); - const handleSubmit = useCallback( - async (message: PromptInputMessage) => { - const text = message.text.trim(); - if (isNewThread) { - router.replace(`/workspace/chats/${threadId}`); - } - await thread.submit( - { - messages: [ - { - type: "human", - content: [ - { - type: "text", - text, - }, - ], - }, - ] as HumanMessage[], - }, - { - threadId: isNewThread ? threadId! : undefined, - streamSubgraphs: true, - streamResumable: true, - context: { - ...threadContext, - thread_id: threadId!, - }, - }, - ); - void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); + const handleSubmit = useSubmitThread({ + isNewThread, + threadId, + thread, + threadContext, + afterSubmit() { + router.push(pathOfThread(threadId!)); }, - [isNewThread, queryClient, router, thread, threadContext, threadId], - ); + }); const handleStop = useCallback(async () => { await thread.stop(); }, [thread]); diff --git a/frontend/src/components/workspace/message-list/message-list-item.tsx b/frontend/src/components/workspace/message-list/message-list-item.tsx index 8fa238f96..1b8569f21 100644 --- a/frontend/src/components/workspace/message-list/message-list-item.tsx +++ b/frontend/src/components/workspace/message-list/message-list-item.tsx @@ -5,6 +5,7 @@ import { Message as AIElementMessage, MessageContent as AIElementMessageContent, MessageResponse as AIElementMessageResponse, + MessageToolbar, } from "@/components/ai-elements/message"; import { extractContentFromMessage, @@ -15,6 +16,7 @@ import { useRehypeSplitWordsIntoSpans } from "@/core/rehype"; import { cn } from "@/lib/utils"; import { MessageGroup } from "./message-group"; +import { CopyButton } from "../copy-button"; export function MessageListItem({ className, @@ -38,6 +40,17 @@ export function MessageListItem({ messagesInGroup={messagesInGroup} isLoading={isLoading} /> + +
    + +
    +
    ); } diff --git a/frontend/src/components/workspace/message-list/message-list.tsx b/frontend/src/components/workspace/message-list/message-list.tsx index 9d5e72f27..520ba316c 100644 --- a/frontend/src/components/workspace/message-list/message-list.tsx +++ b/frontend/src/components/workspace/message-list/message-list.tsx @@ -31,7 +31,7 @@ export function MessageList({ {groupMessages( thread.messages, - (groupedMessages, groupIndex, isLastGroup) => { + (groupedMessages) => { if (groupedMessages[0] && hasContent(groupedMessages[0])) { const message = groupedMessages[0]; return ( diff --git a/frontend/src/components/workspace/recent-chat-list.tsx b/frontend/src/components/workspace/recent-chat-list.tsx index d7ce2dd77..0b5c98fc1 100644 --- a/frontend/src/components/workspace/recent-chat-list.tsx +++ b/frontend/src/components/workspace/recent-chat-list.tsx @@ -57,7 +57,7 @@ export function RecentChatList() {
    {threads.map((thread) => { - const isActive = pathOfThread(thread, false) === pathname; + const isActive = pathOfThread(thread.thread_id) === pathname; return ( {titleOfThread(thread)} diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index f0c642ac6..d472cacbe 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -22,16 +22,37 @@ export function useThreadStream({ threadId: string | null | undefined; }) { const queryClient = useQueryClient(); - return useStream({ + const thread = useStream({ client: getAPIClient(), assistantId: "lead_agent", threadId: isNewThread ? undefined : threadId, reconnectOnMount: true, fetchStateHistory: true, - onFinish() { - void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); + onFinish(state) { + // void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); + queryClient.setQueriesData( + { + queryKey: ["threads", "search"], + exact: false, + }, + (oldData: Array) => { + return oldData.map((t) => { + if (t.thread_id === threadId) { + return { + ...t, + values: { + ...t.values, + title: state.values.title, + }, + }; + } + return t; + }); + }, + ); }, }); + return thread; } export function useSubmitThread({ @@ -39,43 +60,47 @@ export function useSubmitThread({ thread, threadContext, isNewThread, - message, + afterSubmit, }: { isNewThread: boolean; - threadId: string; + threadId: string | null | undefined; thread: UseStream; - threadContext: AgentThreadContext; - message: PromptInputMessage; + threadContext: Omit; + afterSubmit?: () => void; }) { const queryClient = useQueryClient(); - const text = message.text.trim(); - const callback = useCallback(async () => { - await thread.submit( - { - messages: [ - { - type: "human", - content: [ - { - type: "text", - text, - }, - ], - }, - ] as HumanMessage[], - }, - { - threadId: isNewThread ? threadId : undefined, - streamSubgraphs: true, - streamResumable: true, - context: { - ...threadContext, - thread_id: threadId, + const callback = useCallback( + async (message: PromptInputMessage) => { + const text = message.text.trim(); + await thread.submit( + { + messages: [ + { + type: "human", + content: [ + { + type: "text", + text, + }, + ], + }, + ] as HumanMessage[], }, - }, - ); - void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); - }, [queryClient, thread, threadContext, threadId, isNewThread, text]); + { + threadId: isNewThread ? threadId! : undefined, + streamSubgraphs: true, + streamResumable: true, + context: { + ...threadContext, + thread_id: threadId, + }, + }, + ); + void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); + afterSubmit?.(); + }, + [thread, isNewThread, threadId, threadContext, queryClient, afterSubmit], + ); return callback; } @@ -86,12 +111,11 @@ export function useThreads( sortOrder: "desc", }, ) { - const langGraphClient = getAPIClient(); + const apiClient = getAPIClient(); return useQuery({ queryKey: ["threads", "search", params], queryFn: async () => { - const response = - await langGraphClient.threads.search(params); + const response = await apiClient.threads.search(params); return response as AgentThread[]; }, }); @@ -99,10 +123,10 @@ export function useThreads( export function useDeleteThread() { const queryClient = useQueryClient(); - const langGraphClient = getAPIClient(); + const apiClient = getAPIClient(); return useMutation({ mutationFn: async ({ threadId }: { threadId: string }) => { - await langGraphClient.threads.delete(threadId); + await apiClient.threads.delete(threadId); }, onSuccess(_, { threadId }) { queryClient.setQueriesData( From c265734c6e9c76aaf5303418be9a3b3d3f6e1f91 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 20:06:30 +0800 Subject: [PATCH 026/302] feat: adjust layout --- .../workspace/message-list/message-list-item.tsx | 7 ++++--- .../src/components/workspace/message-list/message-list.tsx | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/workspace/message-list/message-list-item.tsx b/frontend/src/components/workspace/message-list/message-list-item.tsx index 1b8569f21..cc6ee6e4c 100644 --- a/frontend/src/components/workspace/message-list/message-list-item.tsx +++ b/frontend/src/components/workspace/message-list/message-list-item.tsx @@ -15,9 +15,10 @@ import { import { useRehypeSplitWordsIntoSpans } from "@/core/rehype"; import { cn } from "@/lib/utils"; -import { MessageGroup } from "./message-group"; import { CopyButton } from "../copy-button"; +import { MessageGroup } from "./message-group"; + export function MessageListItem({ className, message, @@ -31,11 +32,11 @@ export function MessageListItem({ }) { return ( - + {groupMessages( thread.messages, (groupedMessages) => { From 91eff99f01831c3dd1ed5d65e43a922647889a3e Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 20:40:09 +0800 Subject: [PATCH 027/302] feat: add flip display effect --- .../src/components/workspace/flip-display.tsx | 29 +++++++++++++++++++ .../workspace/message-list/message-group.tsx | 17 +++++++---- .../workspace/message-list/message-list.tsx | 2 +- 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/workspace/flip-display.tsx diff --git a/frontend/src/components/workspace/flip-display.tsx b/frontend/src/components/workspace/flip-display.tsx new file mode 100644 index 000000000..6c3a2ca34 --- /dev/null +++ b/frontend/src/components/workspace/flip-display.tsx @@ -0,0 +1,29 @@ +import { AnimatePresence, motion } from "motion/react"; + +import { cn } from "@/lib/utils"; + +export function FlipDisplay({ + uniqueKey, + children, + className, +}: { + uniqueKey: string; + children: React.ReactNode; + className?: string; +}) { + return ( +
    + + + {children} + + +
    + ); +} diff --git a/frontend/src/components/workspace/message-list/message-group.tsx b/frontend/src/components/workspace/message-list/message-group.tsx index 16f9d8264..dcc7ae130 100644 --- a/frontend/src/components/workspace/message-list/message-group.tsx +++ b/frontend/src/components/workspace/message-list/message-group.tsx @@ -29,6 +29,8 @@ import { useRehypeSplitWordsIntoSpans } from "@/core/rehype"; import { extractTitleFromMarkdown } from "@/core/utils/markdown"; import { cn } from "@/lib/utils"; +import { FlipDisplay } from "../flip-display"; + export function MessageGroup({ className, messages, @@ -62,17 +64,20 @@ export function MessageGroup({ {open && steps.length > 1 ? (
    {steps.length} steps
    ) : ( - - {label} - + + + {label} + + )}
    {!open && steps.length > 1 && ( -
    - {steps.length - 1} more step - {steps.length - 1 > 1 ? "s" : ""} +
    + {steps.length - 1 > 1 + ? `${steps.length - 1} more steps` + : `${steps.length - 1} more step`}
    )}
    diff --git a/frontend/src/components/workspace/message-list/message-list.tsx b/frontend/src/components/workspace/message-list/message-list.tsx index 215b6ab09..f947e38f5 100644 --- a/frontend/src/components/workspace/message-list/message-list.tsx +++ b/frontend/src/components/workspace/message-list/message-list.tsx @@ -28,7 +28,7 @@ export function MessageList({ - + {groupMessages( thread.messages, (groupedMessages) => { From 1517e8675d5c876eb92b13bafaae3e4c71f55d08 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 21:48:00 +0800 Subject: [PATCH 028/302] feat: add present_file tool --- backend/src/tools/builtins/__init__.py | 3 +++ .../src/tools/builtins/present_file_tool.py | 24 +++++++++++++++++++ backend/src/tools/tools.py | 8 ++++++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 backend/src/tools/builtins/__init__.py create mode 100644 backend/src/tools/builtins/present_file_tool.py diff --git a/backend/src/tools/builtins/__init__.py b/backend/src/tools/builtins/__init__.py new file mode 100644 index 000000000..5c87b04cb --- /dev/null +++ b/backend/src/tools/builtins/__init__.py @@ -0,0 +1,3 @@ +from .present_file_tool import present_file_tool + +__all__ = ["present_file_tool"] diff --git a/backend/src/tools/builtins/present_file_tool.py b/backend/src/tools/builtins/present_file_tool.py new file mode 100644 index 000000000..bc1fef211 --- /dev/null +++ b/backend/src/tools/builtins/present_file_tool.py @@ -0,0 +1,24 @@ +from langchain.tools import tool + + +@tool("present_files", parse_docstring=True) +def present_file_tool(filepaths: list[str]) -> str: + """Make files visible to the user for viewing and rendering in the client interface. + + When to use the present_files tool: + + - Making any file available for the user to view, download, or interact with + - Presenting multiple related files at once + - After creating a file that should be presented to the user + + When NOT to use the present_files tool: + - When you only need to read file contents for your own processing + - For temporary or intermediate files not meant for user viewing + + Args: + filepaths: List of absolute file paths to present to the user. **Only** files in `/mnt/user-data/outputs` can be presented. + + Returns: + "OK" if the files were presented successfully. + """ + return "OK" diff --git a/backend/src/tools/tools.py b/backend/src/tools/tools.py index 870df6b35..a89a4b100 100644 --- a/backend/src/tools/tools.py +++ b/backend/src/tools/tools.py @@ -2,9 +2,15 @@ from langchain.tools import BaseTool from src.config import get_app_config from src.reflection import resolve_variable +from src.tools.builtins import present_file_tool + +BUILTIN_TOOLS = [ + present_file_tool, +] def get_available_tools(groups: list[str] | None = None) -> list[BaseTool]: """Get all available tools from config""" config = get_app_config() - return [resolve_variable(tool.use, BaseTool) for tool in config.tools if groups is None or tool.group in groups] + loaded_tools = [resolve_variable(tool.use, BaseTool) for tool in config.tools if groups is None or tool.group in groups] + return loaded_tools + BUILTIN_TOOLS From 68fbf53fb2966fa3a1a6e3658e9f335d23a0482d Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 21:54:54 +0800 Subject: [PATCH 029/302] chore: add resizable --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 46 +++++++++++++++++++ frontend/src/components/ui/resizable.tsx | 56 ++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 frontend/src/components/ui/resizable.tsx diff --git a/frontend/package.json b/frontend/package.json index f67a6b92a..a5e18d659 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,6 +48,7 @@ "next-themes": "^0.4.6", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-resizable-panels": "^4.4.1", "shiki": "^3.21.0", "streamdown": "1.5.1", "tailwind-merge": "^3.4.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index f3b8dc6f4..e02d35866 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.2.3(react@19.2.3) + react-resizable-panels: + specifier: ^4.4.1 + version: 4.4.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) shiki: specifier: ^3.21.0 version: 3.21.0 @@ -349,67 +352,79 @@ packages: resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} @@ -491,24 +506,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@15.2.5': resolution: {integrity: sha512-4ZNKmuEiW5hRKkGp2HWwZ+JrvK4DQLgf8YDaqtZyn7NYdl0cHfatvlnLFSWUayx9yFAUagIgRGRk8pFxS8Qniw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@15.2.5': resolution: {integrity: sha512-bE6lHQ9GXIf3gCDE53u2pTl99RPZW5V1GLHSRMJ5l/oB/MT+cohu9uwnCK7QUph2xIOu2a6+27kL0REa/kqwZw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@15.2.5': resolution: {integrity: sha512-y7EeQuSkQbTAkCEQnJXm1asRUuGSWAchGJ3c+Qtxh8LVjXleZast8Mn/rL7tZOm7o35QeIpIcid6ufG7EVTTcA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@15.2.5': resolution: {integrity: sha512-gQMz0yA8/dskZM2Xyiq2FRShxSrsJNha40Ob/M2n2+JGRrZ0JwTVjLdvtN6vCxuq4ByhOd4a9qEf60hApNR2gQ==} @@ -1078,24 +1097,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -1398,41 +1421,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -2696,24 +2727,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -3297,6 +3332,12 @@ packages: '@types/react': optional: true + react-resizable-panels@4.4.1: + resolution: {integrity: sha512-dpM9oI6rGlAq7VYDeafSRA1JmkJv8aNuKySR+tZLQQLfaeqTnQLSM52EcoI/QdowzsjVUCk6jViKS0xHWITVRQ==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -7301,6 +7342,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.8 + react-resizable-panels@4.4.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-style-singleton@2.2.3(@types/react@19.2.8)(react@19.2.3): dependencies: get-nonce: 1.0.1 diff --git a/frontend/src/components/ui/resizable.tsx b/frontend/src/components/ui/resizable.tsx new file mode 100644 index 000000000..2a04e8efe --- /dev/null +++ b/frontend/src/components/ui/resizable.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { GripVerticalIcon } from "lucide-react"; +import * as React from "react"; +import * as ResizablePrimitive from "react-resizable-panels"; + +import { cn } from "@/lib/utils"; + +function ResizablePanelGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function ResizablePanel({ + ...props +}: React.ComponentProps) { + return ; +} + +function ResizableHandle({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean; +}) { + return ( + div]:rotate-90", + className, + )} + {...props} + > + {withHandle && ( +
    + +
    + )} +
    + ); +} + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; From 93a231cfb1aba3edefe114fb5d2f0a4d6b350750 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 21:55:31 +0800 Subject: [PATCH 030/302] feat: integrated with artifact resizable --- .../app/workspace/chats/[thread_id]/page.tsx | 36 +++++++++++-------- .../workspace/message-list/message-group.tsx | 22 ++++++++++++ .../workspace/workspace-container.tsx | 18 ---------- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 0cc15bf24..f9d0c4c13 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -4,13 +4,13 @@ import { useParams, useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; import { BreadcrumbItem } from "@/components/ui/breadcrumb"; +import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; import { InputBox } from "@/components/workspace/input-box"; import { MessageList } from "@/components/workspace/message-list/message-list"; import { WorkspaceContainer, WorkspaceBody, WorkspaceHeader, - WorkspaceFooter, } from "@/components/workspace/workspace-container"; import { useLocalSettings } from "@/core/settings"; import { type AgentThread } from "@/core/threads"; @@ -61,21 +61,27 @@ export default function ChatPage() { -
    - -
    + + +
    + +
    +
    + +
    +
    + {/* + */} +
    - - - ); } diff --git a/frontend/src/components/workspace/message-list/message-group.tsx b/frontend/src/components/workspace/message-list/message-group.tsx index dcc7ae130..3c849b9cf 100644 --- a/frontend/src/components/workspace/message-list/message-group.tsx +++ b/frontend/src/components/workspace/message-list/message-group.tsx @@ -1,6 +1,7 @@ import type { Message } from "@langchain/langgraph-sdk"; import { BookOpenTextIcon, + FileTextIcon, FolderOpenIcon, GlobeIcon, LightbulbIcon, @@ -219,6 +220,24 @@ function ToolCall({ )} ); + } else if (name === "present_files") { + return ( + 1 ? "s" : ""}`} + icon={FileTextIcon} + > + + {(args as { filepaths: string[] }).filepaths.map( + (filepath: string) => ( + + {filepath} + + ), + )} + + + ); } else { const description: string | undefined = (args as { description: string }) ?.description; @@ -324,6 +343,9 @@ function describeStep(step: CoTStep | undefined): { } else if (step.name === "bash") { label = "Execute command"; icon = ; + } else if (step.name === "present_files") { + label = `Present file${(step.args as { filepaths: string[] }).filepaths.length > 1 ? "s" : ""}`; + icon = ; } else { label = `Call tool "${step.name}"`; icon = ; diff --git a/frontend/src/components/workspace/workspace-container.tsx b/frontend/src/components/workspace/workspace-container.tsx index bd15631f4..71898ef85 100644 --- a/frontend/src/components/workspace/workspace-container.tsx +++ b/frontend/src/components/workspace/workspace-container.tsx @@ -122,24 +122,6 @@ export function WorkspaceBody({ ); } -export function WorkspaceFooter({ - className, - children, - ...props -}: React.ComponentProps<"footer">) { - return ( -
    - {children} -
    - ); -} - function nameOfSegment(segment: string | undefined) { if (!segment) return "Home"; return segment[0]?.toUpperCase() + segment.slice(1); From c0e63c5308b3d9fda113473d93c5e58fb9fb521c Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 22:10:08 +0800 Subject: [PATCH 031/302] docs: rewording --- backend/src/tools/builtins/present_file_tool.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/tools/builtins/present_file_tool.py b/backend/src/tools/builtins/present_file_tool.py index bc1fef211..8f2356069 100644 --- a/backend/src/tools/builtins/present_file_tool.py +++ b/backend/src/tools/builtins/present_file_tool.py @@ -9,16 +9,17 @@ def present_file_tool(filepaths: list[str]) -> str: - Making any file available for the user to view, download, or interact with - Presenting multiple related files at once - - After creating a file that should be presented to the user + - After creating files that should be presented to the user When NOT to use the present_files tool: - When you only need to read file contents for your own processing - For temporary or intermediate files not meant for user viewing + Notes: + - You should call this tool after creating files and moving them to the `/mnt/user-data/outputs` directory. + - Use non-parallel tool calling to present files in a single step. + Args: filepaths: List of absolute file paths to present to the user. **Only** files in `/mnt/user-data/outputs` can be presented. - - Returns: - "OK" if the files were presented successfully. """ return "OK" From 4b69aed47b3c0afb3f6469a4d42f368df208882d Mon Sep 17 00:00:00 2001 From: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:28:19 +0800 Subject: [PATCH 032/302] feat: add thread-safety and graceful shutdown to AioSandboxProvider (#7) Add thread-safe port allocation and proper cleanup on process exit to prevent port conflicts in concurrent environments and ensure containers are stopped when the application terminates. Co-authored-by: Claude Opus 4.5 --- .../aio_sandbox/aio_sandbox_provider.py | 141 +++++++++++++----- backend/src/utils/network.py | 135 +++++++++++++++++ 2 files changed, 238 insertions(+), 38 deletions(-) create mode 100644 backend/src/utils/network.py diff --git a/backend/src/community/aio_sandbox/aio_sandbox_provider.py b/backend/src/community/aio_sandbox/aio_sandbox_provider.py index 47a634538..c835c3e94 100644 --- a/backend/src/community/aio_sandbox/aio_sandbox_provider.py +++ b/backend/src/community/aio_sandbox/aio_sandbox_provider.py @@ -1,6 +1,9 @@ +import atexit import logging import os +import signal import subprocess +import threading import time import uuid from pathlib import Path @@ -10,6 +13,7 @@ import requests from src.config import get_app_config from src.sandbox.sandbox import Sandbox from src.sandbox.sandbox_provider import SandboxProvider +from src.utils.network import get_free_port, release_port from .aio_sandbox import AioSandbox @@ -42,9 +46,39 @@ class AioSandboxProvider(SandboxProvider): """ def __init__(self): + self._lock = threading.Lock() self._sandboxes: dict[str, AioSandbox] = {} self._containers: dict[str, str] = {} # sandbox_id -> container_id + self._ports: dict[str, int] = {} # sandbox_id -> port self._config = self._load_config() + self._shutdown_called = False + + # Register shutdown handler to clean up containers on exit + atexit.register(self.shutdown) + self._register_signal_handlers() + + def _register_signal_handlers(self) -> None: + """Register signal handlers for graceful shutdown.""" + self._original_sigterm = signal.getsignal(signal.SIGTERM) + self._original_sigint = signal.getsignal(signal.SIGINT) + + def signal_handler(signum, frame): + self.shutdown() + # Call original handler + original = self._original_sigterm if signum == signal.SIGTERM else self._original_sigint + if callable(original): + original(signum, frame) + elif original == signal.SIG_DFL: + # Re-raise the signal with default handler + signal.signal(signum, signal.SIG_DFL) + signal.raise_signal(signum) + + try: + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + except ValueError: + # Signal handling can only be set from the main thread + logger.debug("Could not register signal handlers (not main thread)") def _load_config(self) -> dict: """Load sandbox configuration from app config.""" @@ -190,33 +224,14 @@ class AioSandboxProvider(SandboxProvider): except subprocess.CalledProcessError as e: logger.warning(f"Failed to stop sandbox container {container_id}: {e.stderr}") - def _find_available_port(self, start_port: int) -> int: - """Find an available port starting from start_port. - - Args: - start_port: Port to start searching from. - - Returns: - An available port number. - """ - import socket - - port = start_port - while port < start_port + 100: # Search up to 100 ports - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - try: - s.bind(("localhost", port)) - return port - except OSError: - port += 1 - raise RuntimeError(f"No available port found in range {start_port}-{start_port + 100}") - def acquire(self, thread_id: str | None = None) -> str: """Acquire a sandbox environment and return its ID. If base_url is configured, uses the existing sandbox. Otherwise, starts a new Docker container. + This method is thread-safe. + Args: thread_id: Optional thread ID for thread-specific configurations. If provided, the sandbox will be configured with thread-specific @@ -244,60 +259,110 @@ class AioSandboxProvider(SandboxProvider): base_url = self._config["base_url"] logger.info(f"Using existing sandbox at {base_url}") - if not self._is_sandbox_ready(base_url, timeout=5): + if not self._is_sandbox_ready(base_url, timeout=60): raise RuntimeError(f"Sandbox at {base_url} is not ready") sandbox = AioSandbox(id=sandbox_id, base_url=base_url) - self._sandboxes[sandbox_id] = sandbox + with self._lock: + self._sandboxes[sandbox_id] = sandbox return sandbox_id # Otherwise, start a new container if not self._config.get("auto_start", True): raise RuntimeError("auto_start is disabled and no base_url is configured") - port = self._find_available_port(self._config["port"]) - container_id = self._start_container(sandbox_id, port, extra_mounts=extra_mounts if extra_mounts else None) - self._containers[sandbox_id] = container_id + # Allocate port using thread-safe utility + port = get_free_port(start_port=self._config["port"]) + try: + container_id = self._start_container(sandbox_id, port, extra_mounts=extra_mounts if extra_mounts else None) + except Exception: + # Release port if container failed to start + release_port(port) + raise base_url = f"http://localhost:{port}" # Wait for sandbox to be ready if not self._is_sandbox_ready(base_url, timeout=60): - # Clean up container if it didn't start properly + # Clean up container and release port if it didn't start properly self._stop_container(container_id) - del self._containers[sandbox_id] + release_port(port) raise RuntimeError("Sandbox container failed to start within timeout") sandbox = AioSandbox(id=sandbox_id, base_url=base_url) - self._sandboxes[sandbox_id] = sandbox + with self._lock: + self._sandboxes[sandbox_id] = sandbox + self._containers[sandbox_id] = container_id + self._ports[sandbox_id] = port logger.info(f"Acquired sandbox {sandbox_id} at {base_url}") return sandbox_id def get(self, sandbox_id: str) -> Sandbox | None: """Get a sandbox environment by ID. + This method is thread-safe. + Args: sandbox_id: The ID of the sandbox environment. Returns: The sandbox instance if found, None otherwise. """ - return self._sandboxes.get(sandbox_id) + with self._lock: + return self._sandboxes.get(sandbox_id) def release(self, sandbox_id: str) -> None: """Release a sandbox environment. - If the sandbox was started by this provider, stops the container. + If the sandbox was started by this provider, stops the container + and releases the allocated port. + + This method is thread-safe. Args: sandbox_id: The ID of the sandbox environment to release. """ - if sandbox_id in self._sandboxes: - del self._sandboxes[sandbox_id] - logger.info(f"Released sandbox {sandbox_id}") + container_id = None + port = None - # Stop container if we started it - if sandbox_id in self._containers: - container_id = self._containers[sandbox_id] + with self._lock: + if sandbox_id in self._sandboxes: + del self._sandboxes[sandbox_id] + logger.info(f"Released sandbox {sandbox_id}") + + # Get container and port info while holding the lock + if sandbox_id in self._containers: + container_id = self._containers.pop(sandbox_id) + + if sandbox_id in self._ports: + port = self._ports.pop(sandbox_id) + + # Stop container and release port outside the lock to avoid blocking + if container_id: self._stop_container(container_id) - del self._containers[sandbox_id] + + if port: + release_port(port) + + def shutdown(self) -> None: + """Shutdown all sandbox containers managed by this provider. + + This method should be called when the application is shutting down + to ensure all containers are properly stopped and ports are released. + + This method is thread-safe and idempotent (safe to call multiple times). + """ + # Prevent multiple shutdown calls + with self._lock: + if self._shutdown_called: + return + self._shutdown_called = True + sandbox_ids = list(self._sandboxes.keys()) + + logger.info(f"Shutting down {len(sandbox_ids)} sandbox container(s)") + + for sandbox_id in sandbox_ids: + try: + self.release(sandbox_id) + except Exception as e: + logger.error(f"Failed to release sandbox {sandbox_id} during shutdown: {e}") diff --git a/backend/src/utils/network.py b/backend/src/utils/network.py new file mode 100644 index 000000000..4802dbef6 --- /dev/null +++ b/backend/src/utils/network.py @@ -0,0 +1,135 @@ +"""Thread-safe network utilities.""" + +import socket +import threading +from contextlib import contextmanager + + +class PortAllocator: + """Thread-safe port allocator that prevents port conflicts in concurrent environments. + + This class maintains a set of reserved ports and uses a lock to ensure that + port allocation is atomic. Once a port is allocated, it remains reserved until + explicitly released. + + Usage: + allocator = PortAllocator() + + # Option 1: Manual allocation and release + port = allocator.allocate(start_port=8080) + try: + # Use the port... + finally: + allocator.release(port) + + # Option 2: Context manager (recommended) + with allocator.allocate_context(start_port=8080) as port: + # Use the port... + # Port is automatically released when exiting the context + """ + + def __init__(self): + self._lock = threading.Lock() + self._reserved_ports: set[int] = set() + + def _is_port_available(self, port: int) -> bool: + """Check if a port is available for binding. + + Args: + port: The port number to check. + + Returns: + True if the port is available, False otherwise. + """ + if port in self._reserved_ports: + return False + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.bind(("localhost", port)) + return True + except OSError: + return False + + def allocate(self, start_port: int = 8080, max_range: int = 100) -> int: + """Allocate an available port in a thread-safe manner. + + This method is thread-safe. It finds an available port, marks it as reserved, + and returns it. The port remains reserved until release() is called. + + Args: + start_port: The port number to start searching from. + max_range: Maximum number of ports to search. + + Returns: + An available port number. + + Raises: + RuntimeError: If no available port is found in the specified range. + """ + with self._lock: + for port in range(start_port, start_port + max_range): + if self._is_port_available(port): + self._reserved_ports.add(port) + return port + + raise RuntimeError(f"No available port found in range {start_port}-{start_port + max_range}") + + def release(self, port: int) -> None: + """Release a previously allocated port. + + Args: + port: The port number to release. + """ + with self._lock: + self._reserved_ports.discard(port) + + @contextmanager + def allocate_context(self, start_port: int = 8080, max_range: int = 100): + """Context manager for port allocation with automatic release. + + Args: + start_port: The port number to start searching from. + max_range: Maximum number of ports to search. + + Yields: + An available port number. + """ + port = self.allocate(start_port, max_range) + try: + yield port + finally: + self.release(port) + + +# Global port allocator instance for shared use across the application +_global_port_allocator = PortAllocator() + + +def get_free_port(start_port: int = 8080, max_range: int = 100) -> int: + """Get a free port in a thread-safe manner. + + This function uses a global port allocator to ensure that concurrent calls + don't return the same port. The port is marked as reserved until release_port() + is called. + + Args: + start_port: The port number to start searching from. + max_range: Maximum number of ports to search. + + Returns: + An available port number. + + Raises: + RuntimeError: If no available port is found in the specified range. + """ + return _global_port_allocator.allocate(start_port, max_range) + + +def release_port(port: int) -> None: + """Release a previously allocated port. + + Args: + port: The port number to release. + """ + _global_port_allocator.release(port) From f9853f037c52c397d1fb394b5c47612dcb754110 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 22:35:20 +0800 Subject: [PATCH 033/302] feat: support basic file presenting --- .../workspace/message-list/message-list.tsx | 19 ++++++++++- .../message-list/present-file-list.tsx | 32 +++++++++++++++++++ frontend/src/core/messages/utils.ts | 30 +++++++++++++++-- frontend/src/core/utils/files.ts | 25 +++++++++++++++ 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/workspace/message-list/present-file-list.tsx create mode 100644 frontend/src/core/utils/files.ts diff --git a/frontend/src/components/workspace/message-list/message-list.tsx b/frontend/src/components/workspace/message-list/message-list.tsx index f947e38f5..862b4d25f 100644 --- a/frontend/src/components/workspace/message-list/message-list.tsx +++ b/frontend/src/components/workspace/message-list/message-list.tsx @@ -4,7 +4,12 @@ import { Conversation, ConversationContent, } from "@/components/ai-elements/conversation"; -import { groupMessages, hasContent } from "@/core/messages/utils"; +import { + extractPresentFilesFromMessage, + groupMessages, + hasContent, + hasPresentFiles, +} from "@/core/messages/utils"; import type { AgentThreadState } from "@/core/threads"; import { cn } from "@/lib/utils"; @@ -12,6 +17,7 @@ import { StreamingIndicator } from "../streaming-indicator"; import { MessageGroup } from "./message-group"; import { MessageListItem } from "./message-list-item"; +import { PresentFileList } from "./present-file-list"; import { MessageListSkeleton } from "./skeleton"; export function MessageList({ @@ -43,6 +49,17 @@ export function MessageList({ /> ); } + if (groupedMessages[0] && hasPresentFiles(groupedMessages[0])) { + const files = []; + for (const message of groupedMessages) { + if (hasPresentFiles(message)) { + files.push(...extractPresentFilesFromMessage(message)); + } + } + return ( + + ); + } return ( + {files.map((file) => ( + + + {getFileName(file)} + {getFileExtension(file)} file + + + + + + ))} + + ); +} diff --git a/frontend/src/core/messages/utils.ts b/frontend/src/core/messages/utils.ts index d0fded729..2d35c8848 100644 --- a/frontend/src/core/messages/utils.ts +++ b/frontend/src/core/messages/utils.ts @@ -46,9 +46,14 @@ export function groupMessages( messageIndex === messages.length - 1 && isLoading) ) { - // Assistant messages without any content are folded into the previous group - // Normally, these are tool calls (with or without thinking) - currentGroup.push(message); + if (message.tool_calls?.[0]?.name === "present_files") { + yieldCurrentGroup(); + currentGroup.push(message); + } else { + // Assistant messages without any content are folded into the previous group + // Normally, these are tool calls (with or without thinking) + currentGroup.push(message); + } } else { // Assistant messages with content (text or images) are shown as a group if they have content // No matter whether it has tool calls or not @@ -144,6 +149,25 @@ export function hasToolCalls(message: Message) { ); } +export function hasPresentFiles(message: Message) { + return ( + message.type === "ai" && message.tool_calls?.[0]?.name === "present_files" + ); +} + +export function extractPresentFilesFromMessage(message: Message) { + if (message.type !== "ai" || !hasPresentFiles(message)) { + return []; + } + const files = []; + for (const toolCall of message.tool_calls ?? []) { + if (toolCall.name === "present_files") { + files.push(...(toolCall.args.filepaths as string[])); + } + } + return files; +} + export function findToolCallResult(toolCallId: string, messages: Message[]) { for (const message of messages) { if (message.type === "tool" && message.tool_call_id === toolCallId) { diff --git a/frontend/src/core/utils/files.ts b/frontend/src/core/utils/files.ts new file mode 100644 index 000000000..ec65b10e8 --- /dev/null +++ b/frontend/src/core/utils/files.ts @@ -0,0 +1,25 @@ +export function getFileName(filepath: string) { + return filepath.split("/").pop()!; +} + +export function getFileExtension(filepath: string) { + const fileName = getFileName(filepath); + const extension = fileName.split(".").pop()!.toLocaleLowerCase(); + switch (extension) { + case "doc": + case "docx": + return "Word"; + case "md": + return "Markdown"; + case "txt": + return "Text"; + case "ppt": + case "pptx": + return "PowerPoint"; + case "xls": + case "xlsx": + return "Excel"; + default: + return extension.toUpperCase(); + } +} From 6464a6723018e8c44dd8019800ca9dd42012e05a Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 23:03:39 +0800 Subject: [PATCH 034/302] feat: remember sidebar state --- .../app/workspace/chats/[thread_id]/page.tsx | 8 ++-- frontend/src/app/workspace/layout.tsx | 16 +++++++ frontend/src/core/settings/hooks.ts | 44 ++++++++++++------- frontend/src/core/settings/local.ts | 25 +++++------ 4 files changed, 59 insertions(+), 34 deletions(-) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index f9d0c4c13..66ef937fc 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -26,7 +26,7 @@ export default function ChatPage() { [threadIdFromPath], ); const [threadId, setThreadId] = useState(null); - const { threadContext, setThreadContext } = useLocalSettings(); + const [settings, setSettings] = useLocalSettings(); useEffect(() => { if (threadIdFromPath !== "new") { @@ -43,7 +43,7 @@ export default function ChatPage() { isNewThread, threadId, thread, - threadContext, + threadContext: settings.context, afterSubmit() { router.push(pathOfThread(threadId!)); }, @@ -71,8 +71,8 @@ export default function ChatPage() { className="w-full max-w-(--container-width-md)" autoFocus={isNewThread} status={thread.isLoading ? "streaming" : "ready"} - context={threadContext} - onContextChange={setThreadContext} + context={settings.context} + onContextChange={(context) => setSettings("context", context)} onSubmit={handleSubmit} onStop={handleStop} /> diff --git a/frontend/src/app/workspace/layout.tsx b/frontend/src/app/workspace/layout.tsx index bf770cb51..80f3bd9e3 100644 --- a/frontend/src/app/workspace/layout.tsx +++ b/frontend/src/app/workspace/layout.tsx @@ -1,16 +1,30 @@ "use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useCallback, useEffect, useState } from "react"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { Overscroll } from "@/components/workspace/overscroll"; import { WorkspaceSidebar } from "@/components/workspace/workspace-sidebar"; +import { useLocalSettings } from "@/core/settings"; const queryClient = new QueryClient(); export default function WorkspaceLayout({ children, }: Readonly<{ children: React.ReactNode }>) { + const [settings, setSettings] = useLocalSettings(); + const [open, setOpen] = useState(false); + useEffect(() => { + setOpen(!settings.layout.sidebar_collapsed); + }, [settings.layout.sidebar_collapsed]); + const handleOpenChange = useCallback( + (open: boolean) => { + setOpen(open); + setSettings("layout", { sidebar_collapsed: !open }); + }, + [setSettings], + ); return ( diff --git a/frontend/src/core/settings/hooks.ts b/frontend/src/core/settings/hooks.ts index 798476331..07606eaf3 100644 --- a/frontend/src/core/settings/hooks.ts +++ b/frontend/src/core/settings/hooks.ts @@ -1,34 +1,46 @@ import { useCallback, useState } from "react"; import { useEffect } from "react"; -import type { AgentThreadContext } from "../threads"; - import { DEFAULT_LOCAL_SETTINGS, getLocalSettings, - updateContextOfLocalSettings, + saveLocalSettings, + type LocalSettings, } from "./local"; -export function useLocalSettings() { +export function useLocalSettings(): [ + LocalSettings, + ( + key: keyof LocalSettings, + value: Partial, + ) => void, +] { const [mounted, setMounted] = useState(false); - const [threadContextState, setThreadContextState] = useState< - Omit - >(DEFAULT_LOCAL_SETTINGS.context); + const [state, setState] = useState(DEFAULT_LOCAL_SETTINGS); useEffect(() => { if (!mounted) { - setThreadContextState(getLocalSettings().context); + setState(getLocalSettings()); } setMounted(true); }, [mounted]); - const setThreadContext = useCallback( - (context: Omit) => { - setThreadContextState(context); - updateContextOfLocalSettings(context); + const setter = useCallback( + ( + key: keyof LocalSettings, + value: Partial, + ) => { + setState((prev) => { + const newState = { + ...prev, + [key]: { + ...prev[key], + ...value, + }, + }; + saveLocalSettings(newState); + return newState; + }); }, [], ); - return { - threadContext: threadContextState, - setThreadContext, - }; + return [state, setter]; } diff --git a/frontend/src/core/settings/local.ts b/frontend/src/core/settings/local.ts index 419ba9dd8..41312c85b 100644 --- a/frontend/src/core/settings/local.ts +++ b/frontend/src/core/settings/local.ts @@ -5,12 +5,18 @@ export const DEFAULT_LOCAL_SETTINGS: LocalSettings = { model_name: "deepseek-v3.2", thinking_enabled: true, }, + layout: { + sidebar_collapsed: false, + }, }; const LOCAL_SETTINGS_KEY = "deerflow.local-settings"; export interface LocalSettings { context: Omit; + layout: { + sidebar_collapsed: boolean; + }; } export function getLocalSettings(): LocalSettings { @@ -20,7 +26,11 @@ export function getLocalSettings(): LocalSettings { const json = localStorage.getItem(LOCAL_SETTINGS_KEY); try { if (json) { - return JSON.parse(json); + const settings = JSON.parse(json); + return { + ...DEFAULT_LOCAL_SETTINGS, + ...settings, + }; } } catch {} return DEFAULT_LOCAL_SETTINGS; @@ -29,16 +39,3 @@ export function getLocalSettings(): LocalSettings { export function saveLocalSettings(settings: LocalSettings) { localStorage.setItem(LOCAL_SETTINGS_KEY, JSON.stringify(settings)); } - -export function updateContextOfLocalSettings( - context: LocalSettings["context"], -) { - const settings = getLocalSettings(); - saveLocalSettings({ - ...settings, - context: { - ...settings.context, - ...context, - }, - }); -} From facde645d7693234bb09fb01a72d29594d4107df Mon Sep 17 00:00:00 2001 From: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:04:38 +0800 Subject: [PATCH 035/302] feat: add artifacts logic (#8) --- backend/src/agents/thread_state.py | 1 + .../aio_sandbox/aio_sandbox_provider.py | 29 ++++++- backend/src/gateway/app.py | 5 +- backend/src/gateway/routers/__init__.py | 4 +- backend/src/gateway/routers/artifacts.py | 75 +++++++++++++++++++ .../src/tools/builtins/present_file_tool.py | 22 +++++- 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 backend/src/gateway/routers/artifacts.py diff --git a/backend/src/agents/thread_state.py b/backend/src/agents/thread_state.py index ef4c80db0..32cc4970e 100644 --- a/backend/src/agents/thread_state.py +++ b/backend/src/agents/thread_state.py @@ -17,3 +17,4 @@ class ThreadState(AgentState): sandbox: NotRequired[SandboxState | None] thread_data: NotRequired[ThreadDataState | None] title: NotRequired[str | None] + artifacts: NotRequired[list[str] | None] diff --git a/backend/src/community/aio_sandbox/aio_sandbox_provider.py b/backend/src/community/aio_sandbox/aio_sandbox_provider.py index c835c3e94..ea179dc2c 100644 --- a/backend/src/community/aio_sandbox/aio_sandbox_provider.py +++ b/backend/src/community/aio_sandbox/aio_sandbox_provider.py @@ -50,6 +50,7 @@ class AioSandboxProvider(SandboxProvider): self._sandboxes: dict[str, AioSandbox] = {} self._containers: dict[str, str] = {} # sandbox_id -> container_id self._ports: dict[str, int] = {} # sandbox_id -> port + self._thread_sandboxes: dict[str, str] = {} # thread_id -> sandbox_id (for reusing sandbox across turns) self._config = self._load_config() self._shutdown_called = False @@ -230,16 +231,33 @@ class AioSandboxProvider(SandboxProvider): If base_url is configured, uses the existing sandbox. Otherwise, starts a new Docker container. + For the same thread_id, this method will return the same sandbox_id, + allowing sandbox reuse across multiple turns in a conversation. + This method is thread-safe. Args: thread_id: Optional thread ID for thread-specific configurations. If provided, the sandbox will be configured with thread-specific mounts for workspace, uploads, and outputs directories. + The same thread_id will reuse the same sandbox. Returns: The ID of the acquired sandbox environment. """ + # Check if we already have a sandbox for this thread + if thread_id: + with self._lock: + if thread_id in self._thread_sandboxes: + existing_sandbox_id = self._thread_sandboxes[thread_id] + # Verify the sandbox still exists + if existing_sandbox_id in self._sandboxes: + logger.info(f"Reusing existing sandbox {existing_sandbox_id} for thread {thread_id}") + return existing_sandbox_id + else: + # Sandbox was released, remove stale mapping + del self._thread_sandboxes[thread_id] + sandbox_id = str(uuid.uuid4())[:8] # Get thread-specific mounts if thread_id is provided @@ -265,6 +283,8 @@ class AioSandboxProvider(SandboxProvider): sandbox = AioSandbox(id=sandbox_id, base_url=base_url) with self._lock: self._sandboxes[sandbox_id] = sandbox + if thread_id: + self._thread_sandboxes[thread_id] = sandbox_id return sandbox_id # Otherwise, start a new container @@ -294,7 +314,9 @@ class AioSandboxProvider(SandboxProvider): self._sandboxes[sandbox_id] = sandbox self._containers[sandbox_id] = container_id self._ports[sandbox_id] = port - logger.info(f"Acquired sandbox {sandbox_id} at {base_url}") + if thread_id: + self._thread_sandboxes[thread_id] = sandbox_id + logger.info(f"Acquired sandbox {sandbox_id} for thread {thread_id} at {base_url}") return sandbox_id def get(self, sandbox_id: str) -> Sandbox | None: @@ -330,6 +352,11 @@ class AioSandboxProvider(SandboxProvider): del self._sandboxes[sandbox_id] logger.info(f"Released sandbox {sandbox_id}") + # Remove thread_id -> sandbox_id mapping + thread_ids_to_remove = [tid for tid, sid in self._thread_sandboxes.items() if sid == sandbox_id] + for tid in thread_ids_to_remove: + del self._thread_sandboxes[tid] + # Get container and port info while holding the lock if sandbox_id in self._containers: container_id = self._containers.pop(sandbox_id) diff --git a/backend/src/gateway/app.py b/backend/src/gateway/app.py index f6fb68868..f251f35ae 100644 --- a/backend/src/gateway/app.py +++ b/backend/src/gateway/app.py @@ -6,7 +6,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from src.gateway.config import get_gateway_config -from src.gateway.routers import models, proxy +from src.gateway.routers import artifacts, models, proxy logger = logging.getLogger(__name__) @@ -49,6 +49,9 @@ def create_app() -> FastAPI: # Models API is mounted at /api/models app.include_router(models.router) + # Artifacts API is mounted at /api/threads/{thread_id}/artifacts + app.include_router(artifacts.router) + # Proxy router handles all LangGraph paths (must be last due to catch-all) app.include_router(proxy.router) diff --git a/backend/src/gateway/routers/__init__.py b/backend/src/gateway/routers/__init__.py index 199f2f4fe..d4c803517 100644 --- a/backend/src/gateway/routers/__init__.py +++ b/backend/src/gateway/routers/__init__.py @@ -1,3 +1,3 @@ -from . import models, proxy +from . import artifacts, models, proxy -__all__ = ["models", "proxy"] +__all__ = ["artifacts", "models", "proxy"] diff --git a/backend/src/gateway/routers/artifacts.py b/backend/src/gateway/routers/artifacts.py new file mode 100644 index 000000000..9dbae102a --- /dev/null +++ b/backend/src/gateway/routers/artifacts.py @@ -0,0 +1,75 @@ +import os +from pathlib import Path + +from fastapi import APIRouter, HTTPException +from fastapi.responses import FileResponse + +# Base directory for thread data (relative to backend/) +THREAD_DATA_BASE_DIR = ".deer-flow/threads" + +# Virtual path prefix used in sandbox environments (without leading slash for URL path matching) +VIRTUAL_PATH_PREFIX = "mnt/user-data" + +router = APIRouter(prefix="/api", tags=["artifacts"]) + + +def _resolve_artifact_path(thread_id: str, artifact_path: str) -> Path: + """Resolve a virtual artifact path to the actual filesystem path. + + Args: + thread_id: The thread ID. + artifact_path: The virtual path (e.g., mnt/user-data/outputs/file.txt). + + Returns: + The resolved filesystem path. + + Raises: + HTTPException: If the path is invalid or outside allowed directories. + """ + # Validate and remove virtual path prefix + if not artifact_path.startswith(VIRTUAL_PATH_PREFIX): + raise HTTPException(status_code=400, detail=f"Path must start with /{VIRTUAL_PATH_PREFIX}") + relative_path = artifact_path[len(VIRTUAL_PATH_PREFIX) :].lstrip("/") + + # Build the actual path + base_dir = Path(os.getcwd()) / THREAD_DATA_BASE_DIR / thread_id / "user-data" + actual_path = base_dir / relative_path + + # Security check: ensure the path is within the thread's user-data directory + try: + actual_path = actual_path.resolve() + base_dir = base_dir.resolve() + if not str(actual_path).startswith(str(base_dir)): + raise HTTPException(status_code=403, detail="Access denied: path traversal detected") + except (ValueError, RuntimeError): + raise HTTPException(status_code=400, detail="Invalid path") + + return actual_path + + +@router.get("/threads/{thread_id}/artifacts/{path:path}") +async def get_artifact(thread_id: str, path: str) -> FileResponse: + """Get an artifact file by its path. + + Args: + thread_id: The thread ID. + path: The artifact path with virtual prefix (e.g., mnt/user-data/outputs/file.txt). + + Returns: + The file content as a FileResponse. + + Raises: + HTTPException: 404 if file not found, 403 if access denied. + """ + actual_path = _resolve_artifact_path(thread_id, path) + + if not actual_path.exists(): + raise HTTPException(status_code=404, detail=f"Artifact not found: {path}") + + if not actual_path.is_file(): + raise HTTPException(status_code=400, detail=f"Path is not a file: {path}") + + return FileResponse( + path=actual_path, + filename=actual_path.name, + ) diff --git a/backend/src/tools/builtins/present_file_tool.py b/backend/src/tools/builtins/present_file_tool.py index 8f2356069..ab0ec7fe5 100644 --- a/backend/src/tools/builtins/present_file_tool.py +++ b/backend/src/tools/builtins/present_file_tool.py @@ -1,8 +1,19 @@ -from langchain.tools import tool +from typing import Annotated + +from langchain.tools import InjectedToolCallId, ToolRuntime, tool +from langchain_core.messages import ToolMessage +from langgraph.types import Command +from langgraph.typing import ContextT + +from src.agents.thread_state import ThreadState @tool("present_files", parse_docstring=True) -def present_file_tool(filepaths: list[str]) -> str: +def present_file_tool( + runtime: ToolRuntime[ContextT, ThreadState], + filepaths: list[str], + tool_call_id: Annotated[str, InjectedToolCallId], +) -> Command: """Make files visible to the user for viewing and rendering in the client interface. When to use the present_files tool: @@ -22,4 +33,9 @@ def present_file_tool(filepaths: list[str]) -> str: Args: filepaths: List of absolute file paths to present to the user. **Only** files in `/mnt/user-data/outputs` can be presented. """ - return "OK" + existing_artifacts = runtime.state.get("artifacts") or [] + new_artifacts = existing_artifacts + filepaths + runtime.state["artifacts"] = new_artifacts + return Command( + update={"artifacts": new_artifacts, "messages": [ToolMessage("Successfully presented files", tool_call_id=tool_call_id)]}, + ) From 34ca58ed1b6e4957cb39a5f08388fad98614e7b5 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 23:15:53 +0800 Subject: [PATCH 036/302] fix: fix broken when SSE --- .../workspace/message-list/message-group.tsx | 23 ++++++++----------- .../message-list/present-file-list.tsx | 2 +- frontend/src/core/messages/utils.ts | 7 ++++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/workspace/message-list/message-group.tsx b/frontend/src/components/workspace/message-list/message-group.tsx index 3c849b9cf..8f5f926e2 100644 --- a/frontend/src/components/workspace/message-list/message-group.tsx +++ b/frontend/src/components/workspace/message-list/message-group.tsx @@ -222,19 +222,16 @@ function ToolCall({ ); } else if (name === "present_files") { return ( - 1 ? "s" : ""}`} - icon={FileTextIcon} - > + - {(args as { filepaths: string[] }).filepaths.map( - (filepath: string) => ( - - {filepath} - - ), - )} + {Array.isArray((args as { filepaths: string[] }).filepaths) && + (args as { filepaths: string[] }).filepaths.map( + (filepath: string) => ( + + {filepath} + + ), + )} ); @@ -344,7 +341,7 @@ function describeStep(step: CoTStep | undefined): { label = "Execute command"; icon = ; } else if (step.name === "present_files") { - label = `Present file${(step.args as { filepaths: string[] }).filepaths.length > 1 ? "s" : ""}`; + label = "Present files"; icon = ; } else { label = `Call tool "${step.name}"`; diff --git a/frontend/src/components/workspace/message-list/present-file-list.tsx b/frontend/src/components/workspace/message-list/present-file-list.tsx index 2d5f14370..314c4df8d 100644 --- a/frontend/src/components/workspace/message-list/present-file-list.tsx +++ b/frontend/src/components/workspace/message-list/present-file-list.tsx @@ -12,7 +12,7 @@ import { getFileExtension, getFileName } from "@/core/utils/files"; export function PresentFileList({ files }: { files: string[] }) { return ( -
      +
        {files.map((file) => ( diff --git a/frontend/src/core/messages/utils.ts b/frontend/src/core/messages/utils.ts index 2d35c8848..bfa8f8079 100644 --- a/frontend/src/core/messages/utils.ts +++ b/frontend/src/core/messages/utils.ts @@ -159,9 +159,12 @@ export function extractPresentFilesFromMessage(message: Message) { if (message.type !== "ai" || !hasPresentFiles(message)) { return []; } - const files = []; + const files: string[] = []; for (const toolCall of message.tool_calls ?? []) { - if (toolCall.name === "present_files") { + if ( + toolCall.name === "present_files" && + Array.isArray(toolCall.args.filepaths) + ) { files.push(...(toolCall.args.filepaths as string[])); } } From 9d64c7e076000f1c6e6d385a7dd400526bf4ec5b Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 00:02:03 +0800 Subject: [PATCH 037/302] feat: integrated with artifacts --- .../app/workspace/chats/[thread_id]/page.tsx | 107 ++++++++++++------ .../artifacts/artifact-file-detail.tsx | 14 +++ .../artifact-file-list.tsx} | 27 ++++- .../workspace/artifacts/context.tsx | 58 ++++++++++ .../components/workspace/artifacts/index.ts | 3 + .../workspace/message-list/message-list.tsx | 4 +- 6 files changed, 174 insertions(+), 39 deletions(-) create mode 100644 frontend/src/components/workspace/artifacts/artifact-file-detail.tsx rename frontend/src/components/workspace/{message-list/present-file-list.tsx => artifacts/artifact-file-list.tsx} (55%) create mode 100644 frontend/src/components/workspace/artifacts/context.tsx create mode 100644 frontend/src/components/workspace/artifacts/index.ts diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 66ef937fc..02755a909 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -1,10 +1,20 @@ "use client"; +import type { UseStream } from "@langchain/langgraph-sdk/react"; import { useParams, useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; import { BreadcrumbItem } from "@/components/ui/breadcrumb"; -import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "@/components/ui/resizable"; +import { ArtifactFileDetail } from "@/components/workspace/artifacts"; +import { + ArtifactsProvider, + useArtifacts, +} from "@/components/workspace/artifacts/context"; import { InputBox } from "@/components/workspace/input-box"; import { MessageList } from "@/components/workspace/message-list/message-list"; import { @@ -13,20 +23,18 @@ import { WorkspaceHeader, } from "@/components/workspace/workspace-container"; import { useLocalSettings } from "@/core/settings"; -import { type AgentThread } from "@/core/threads"; +import { type AgentThread, type AgentThreadState } from "@/core/threads"; import { useSubmitThread, useThreadStream } from "@/core/threads/hooks"; import { pathOfThread, titleOfThread } from "@/core/threads/utils"; import { uuid } from "@/core/utils/uuid"; export default function ChatPage() { - const router = useRouter(); const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); const isNewThread = useMemo( () => threadIdFromPath === "new", [threadIdFromPath], ); const [threadId, setThreadId] = useState(null); - const [settings, setSettings] = useLocalSettings(); useEffect(() => { if (threadIdFromPath !== "new") { @@ -39,6 +47,40 @@ export default function ChatPage() { isNewThread, threadId, }); + return ( + + + + {isNewThread + ? "New" + : titleOfThread(thread as unknown as AgentThread)} + + + + + + + + + ); +} + +function ThreadDetail({ + threadId, + thread, + isNewThread, +}: { + threadId?: string | null; + thread: UseStream; + isNewThread: boolean; +}) { + const router = useRouter(); + const [settings, setSettings] = useLocalSettings(); + const { open, selectedArtifact } = useArtifacts(); const handleSubmit = useSubmitThread({ isNewThread, threadId, @@ -52,36 +94,33 @@ export default function ChatPage() { await thread.stop(); }, [thread]); return ( - - - - {isNewThread - ? "New" - : titleOfThread(thread as unknown as AgentThread)} - - - - - -
        - -
        -
        - setSettings("context", context)} - onSubmit={handleSubmit} - onStop={handleStop} - /> -
        + + +
        + +
        +
        + setSettings("context", context)} + onSubmit={handleSubmit} + onStop={handleStop} + /> +
        +
        + {open && ( + <> + + + {selectedArtifact && ( + + )} - {/* - */} -
        -
        -
        + + )} + ); } diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx new file mode 100644 index 000000000..6ae28ff47 --- /dev/null +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -0,0 +1,14 @@ +import { FileIcon } from "lucide-react"; + +export function ArtifactFileDetail({ filepath }: { filepath: string }) { + return ( +
        +
        +
        + +
        +
        {filepath}
        +
        +
        + ); +} diff --git a/frontend/src/components/workspace/message-list/present-file-list.tsx b/frontend/src/components/workspace/artifacts/artifact-file-list.tsx similarity index 55% rename from frontend/src/components/workspace/message-list/present-file-list.tsx rename to frontend/src/components/workspace/artifacts/artifact-file-list.tsx index 314c4df8d..44309cb12 100644 --- a/frontend/src/components/workspace/message-list/present-file-list.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-list.tsx @@ -1,4 +1,5 @@ import { DownloadIcon } from "lucide-react"; +import { useCallback } from "react"; import { Button } from "@/components/ui/button"; import { @@ -9,12 +10,32 @@ import { CardTitle, } from "@/components/ui/card"; import { getFileExtension, getFileName } from "@/core/utils/files"; +import { cn } from "@/lib/utils"; -export function PresentFileList({ files }: { files: string[] }) { +import { useArtifacts } from "./context"; + +export function ArtifactFileList({ + className, + files, +}: { + className?: string; + files: string[]; +}) { + const { openArtifact } = useArtifacts(); + const handleClick = useCallback( + (filepath: string) => { + openArtifact(filepath); + }, + [openArtifact], + ); return ( -
          +
            {files.map((file) => ( - + handleClick(file)} + > {getFileName(file)} {getFileExtension(file)} file diff --git a/frontend/src/components/workspace/artifacts/context.tsx b/frontend/src/components/workspace/artifacts/context.tsx new file mode 100644 index 000000000..a5858703f --- /dev/null +++ b/frontend/src/components/workspace/artifacts/context.tsx @@ -0,0 +1,58 @@ +import { createContext, useContext, useState, type ReactNode } from "react"; + +export interface ArtifactsContextType { + artifacts: string[]; + selectedArtifact: string | null; + + open: boolean; + setOpen: (open: boolean) => void; + + addArtifacts: (artifacts: string[]) => void; + openArtifact: (artifact: string) => void; +} + +const ArtifactsContext = createContext( + undefined, +); + +interface ArtifactsProviderProps { + children: ReactNode; +} + +export function ArtifactsProvider({ children }: ArtifactsProviderProps) { + const [artifacts, setArtifacts] = useState([]); + const [selectedArtifact, setSelectedArtifact] = useState(null); + const [open, setOpen] = useState(false); + + const addArtifacts = (newArtifacts: string[]) => { + setArtifacts((prev) => [...prev, ...newArtifacts]); + }; + + const openArtifact = (artifact: string) => { + setSelectedArtifact(artifact); + setOpen(true); + }; + + const value: ArtifactsContextType = { + artifacts, + selectedArtifact, + open, + setOpen, + addArtifacts, + openArtifact, + }; + + return ( + + {children} + + ); +} + +export function useArtifacts() { + const context = useContext(ArtifactsContext); + if (context === undefined) { + throw new Error("useArtifacts must be used within an ArtifactsProvider"); + } + return context; +} diff --git a/frontend/src/components/workspace/artifacts/index.ts b/frontend/src/components/workspace/artifacts/index.ts new file mode 100644 index 000000000..bedf505e3 --- /dev/null +++ b/frontend/src/components/workspace/artifacts/index.ts @@ -0,0 +1,3 @@ +export * from "./artifact-file-detail"; +export * from "./artifact-file-list"; +export * from "./context"; diff --git a/frontend/src/components/workspace/message-list/message-list.tsx b/frontend/src/components/workspace/message-list/message-list.tsx index 862b4d25f..07d56550c 100644 --- a/frontend/src/components/workspace/message-list/message-list.tsx +++ b/frontend/src/components/workspace/message-list/message-list.tsx @@ -13,11 +13,11 @@ import { import type { AgentThreadState } from "@/core/threads"; import { cn } from "@/lib/utils"; +import { ArtifactFileList } from "../artifacts/artifact-file-list"; import { StreamingIndicator } from "../streaming-indicator"; import { MessageGroup } from "./message-group"; import { MessageListItem } from "./message-list-item"; -import { PresentFileList } from "./present-file-list"; import { MessageListSkeleton } from "./skeleton"; export function MessageList({ @@ -57,7 +57,7 @@ export function MessageList({ } } return ( - + ); } return ( From 4613d6e16e0587f8d44cddec562d8b6cd8f97c19 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 00:05:19 +0800 Subject: [PATCH 038/302] refactor: rename --- .../app/workspace/chats/[thread_id]/page.tsx | 6 +++--- .../artifacts/artifact-file-detail.tsx | 18 ++++++++++++++++-- .../{message-list => messages}/index.ts | 0 .../message-group.tsx | 0 .../message-list-item.tsx | 0 .../message-list.tsx | 0 .../{message-list => messages}/skeleton.tsx | 0 7 files changed, 19 insertions(+), 5 deletions(-) rename frontend/src/components/workspace/{message-list => messages}/index.ts (100%) rename frontend/src/components/workspace/{message-list => messages}/message-group.tsx (100%) rename frontend/src/components/workspace/{message-list => messages}/message-list-item.tsx (100%) rename frontend/src/components/workspace/{message-list => messages}/message-list.tsx (100%) rename frontend/src/components/workspace/{message-list => messages}/skeleton.tsx (100%) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 02755a909..25cb8f946 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -10,13 +10,13 @@ import { ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; -import { ArtifactFileDetail } from "@/components/workspace/artifacts"; import { + ArtifactFileDetail, ArtifactsProvider, useArtifacts, -} from "@/components/workspace/artifacts/context"; +} from "@/components/workspace/artifacts"; import { InputBox } from "@/components/workspace/input-box"; -import { MessageList } from "@/components/workspace/message-list/message-list"; +import { MessageList } from "@/components/workspace/messages"; import { WorkspaceContainer, WorkspaceBody, diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 6ae28ff47..1080859c3 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -1,8 +1,22 @@ -import { FileIcon } from "lucide-react"; +import { FileIcon, XIcon } from "lucide-react"; +import { useCallback } from "react"; + +import { Button } from "@/components/ui/button"; + +import { useArtifacts } from "./context"; export function ArtifactFileDetail({ filepath }: { filepath: string }) { + const { setOpen } = useArtifacts(); + const handleClose = useCallback(() => { + setOpen(false); + }, [setOpen]); return ( -
            +
            +
            + +
            diff --git a/frontend/src/components/workspace/message-list/index.ts b/frontend/src/components/workspace/messages/index.ts similarity index 100% rename from frontend/src/components/workspace/message-list/index.ts rename to frontend/src/components/workspace/messages/index.ts diff --git a/frontend/src/components/workspace/message-list/message-group.tsx b/frontend/src/components/workspace/messages/message-group.tsx similarity index 100% rename from frontend/src/components/workspace/message-list/message-group.tsx rename to frontend/src/components/workspace/messages/message-group.tsx diff --git a/frontend/src/components/workspace/message-list/message-list-item.tsx b/frontend/src/components/workspace/messages/message-list-item.tsx similarity index 100% rename from frontend/src/components/workspace/message-list/message-list-item.tsx rename to frontend/src/components/workspace/messages/message-list-item.tsx diff --git a/frontend/src/components/workspace/message-list/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx similarity index 100% rename from frontend/src/components/workspace/message-list/message-list.tsx rename to frontend/src/components/workspace/messages/message-list.tsx diff --git a/frontend/src/components/workspace/message-list/skeleton.tsx b/frontend/src/components/workspace/messages/skeleton.tsx similarity index 100% rename from frontend/src/components/workspace/message-list/skeleton.tsx rename to frontend/src/components/workspace/messages/skeleton.tsx From bb92dec8d5751aef5260245d8373b26b28eaf456 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 00:12:57 +0800 Subject: [PATCH 039/302] feat: ignore components from 3rd parties --- frontend/eslint.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index ce07fdbee..7a801cd04 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -7,7 +7,7 @@ const compat = new FlatCompat({ export default tseslint.config( { - ignores: [".next"], + ignores: [".next", "src/components/ui/**", "src/components/ai-elements/**"], }, ...compat.extends("next/core-web-vitals"), { From 4e7256a9d860209dd2153d0f355f9fdf3c440e59 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 00:13:17 +0800 Subject: [PATCH 040/302] feat: make BETTER_AUTH_* optional --- frontend/src/env.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/env.js b/frontend/src/env.js index aea3e979d..52a4bbe91 100644 --- a/frontend/src/env.js +++ b/frontend/src/env.js @@ -11,8 +11,8 @@ export const env = createEnv({ process.env.NODE_ENV === "production" ? z.string() : z.string().optional(), - BETTER_AUTH_GITHUB_CLIENT_ID: z.string(), - BETTER_AUTH_GITHUB_CLIENT_SECRET: z.string(), + BETTER_AUTH_GITHUB_CLIENT_ID: z.string().optional(), + BETTER_AUTH_GITHUB_CLIENT_SECRET: z.string().optional(), NODE_ENV: z .enum(["development", "test", "production"]) .default("development"), From 664ccb922fa2126235dd5409cb0fbf55cf3f2874 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 00:13:24 +0800 Subject: [PATCH 041/302] style: format --- frontend/src/server/better-auth/server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/server/better-auth/server.ts b/frontend/src/server/better-auth/server.ts index e760c1e20..064cd349c 100644 --- a/frontend/src/server/better-auth/server.ts +++ b/frontend/src/server/better-auth/server.ts @@ -1,7 +1,8 @@ -import { auth } from "."; import { headers } from "next/headers"; import { cache } from "react"; +import { auth } from "."; + export const getSession = cache(async () => auth.api.getSession({ headers: await headers() }), ); From a973c82a1fd531640cd2ae63a0e3acce032b4a16 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 10:05:55 +0800 Subject: [PATCH 042/302] chore: downgrade `shiki` since breaking changes --- frontend/package.json | 5 +- frontend/pnpm-lock.yaml | 2063 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 1911 insertions(+), 157 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index a5e18d659..7fff1eaf8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,11 +46,12 @@ "nanoid": "^5.1.6", "next": "^15.2.8", "next-themes": "^0.4.6", + "nuxt-og-image": "^5.1.13", "react": "^19.0.0", "react-dom": "^19.0.0", "react-resizable-panels": "^4.4.1", - "shiki": "^3.21.0", - "streamdown": "1.5.1", + "shiki": "3.15.0", + "streamdown": "1.4.0", "tailwind-merge": "^3.4.0", "tokenlens": "^1.3.1", "unist-util-visit": "^5.0.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index e02d35866..3d0e597a4 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -67,7 +67,7 @@ importers: version: 1.2.1 better-auth: specifier: ^1.3 - version: 1.4.12(next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 1.4.12(next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vue@3.5.26(typescript@5.9.3)) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -98,6 +98,9 @@ importers: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + nuxt-og-image: + specifier: ^5.1.13 + version: 5.1.13(@unhead/vue@2.1.2(vue@3.5.26(typescript@5.9.3)))(unstorage@1.17.4)(vite@7.3.1(@types/node@20.19.29)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.26(typescript@5.9.3)) react: specifier: ^19.0.0 version: 19.2.3 @@ -108,11 +111,11 @@ importers: specifier: ^4.4.1 version: 4.4.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) shiki: - specifier: ^3.21.0 - version: 3.21.0 + specifier: 3.15.0 + version: 3.15.0 streamdown: - specifier: 1.5.1 - version: 1.5.1(@types/mdast@4.0.4)(@types/react@19.2.8)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3)(unified@11.0.5) + specifier: 1.4.0 + version: 1.4.0(@types/react@19.2.8)(react@19.2.3) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -200,6 +203,23 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + '@better-auth/core@1.4.12': resolution: {integrity: sha512-VfqZwMAEl9rnGx092BIZ2Q5z8rt7jjN2OAbvPqehufSKZGmh8JsdtZRBMl/CHQir9bwi2Ev0UF4+7TQp+DXEMg==} peerDependencies: @@ -251,6 +271,162 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -565,10 +741,22 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@nuxt/devtools-kit@3.1.1': + resolution: {integrity: sha512-sjiKFeDCOy1SyqezSgyV4rYNfQewC64k/GhOsuJgRF+wR2qr6KTVhO6u2B+csKs74KrMrnJprQBgud7ejvOXAQ==} + peerDependencies: + vite: '>=6.0' + + '@nuxt/kit@4.2.2': + resolution: {integrity: sha512-ZAgYBrPz/yhVgDznBNdQj2vhmOp31haJbO0I0iah/P9atw+OHH7NJLUZ3PK+LOz/0fblKTN1XJVSi8YQ1TQ0KA==} + engines: {node: '>=18.12.0'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -995,33 +1183,267 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@resvg/resvg-js-android-arm-eabi@2.6.2': + resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@resvg/resvg-js-android-arm64@2.6.2': + resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@resvg/resvg-js-darwin-arm64@2.6.2': + resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@resvg/resvg-js-darwin-x64@2.6.2': + resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': + resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': + resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@resvg/resvg-js-linux-arm64-musl@2.6.2': + resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@resvg/resvg-js-linux-x64-gnu@2.6.2': + resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@resvg/resvg-js-linux-x64-musl@2.6.2': + resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': + resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': + resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@resvg/resvg-js-win32-x64-msvc@2.6.2': + resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@resvg/resvg-js@2.6.2': + resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==} + engines: {node: '>= 10'} + + '@resvg/resvg-wasm@2.6.2': + resolution: {integrity: sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==} + engines: {node: '>= 10'} + + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + cpu: [x64] + os: [win32] + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} '@rushstack/eslint-patch@1.15.0': resolution: {integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==} - '@shikijs/core@3.21.0': - resolution: {integrity: sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==} + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@shikijs/engine-javascript@3.21.0': - resolution: {integrity: sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==} + '@shikijs/core@3.15.0': + resolution: {integrity: sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==} - '@shikijs/engine-oniguruma@3.21.0': - resolution: {integrity: sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==} + '@shikijs/engine-javascript@3.15.0': + resolution: {integrity: sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==} - '@shikijs/langs@3.21.0': - resolution: {integrity: sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==} + '@shikijs/engine-oniguruma@3.15.0': + resolution: {integrity: sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==} - '@shikijs/themes@3.21.0': - resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==} + '@shikijs/langs@3.15.0': + resolution: {integrity: sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==} - '@shikijs/types@3.21.0': - resolution: {integrity: sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==} + '@shikijs/themes@3.15.0': + resolution: {integrity: sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==} + + '@shikijs/types@3.15.0': + resolution: {integrity: sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@shuding/opentype.js@1.4.0-beta.0': + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} + engines: {node: '>= 8.0.0'} + hasBin: true + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1382,6 +1804,27 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@unhead/vue@2.1.2': + resolution: {integrity: sha512-w5yxH/fkkLWAFAOnMSIbvAikNHYn6pgC7zGF/BasXf+K3CO1cYIPFehYAk5jpcsbiNPMc3goyyw1prGLoyD14g==} + peerDependencies: + vue: '>=3.5.18' + + '@unocss/core@66.6.0': + resolution: {integrity: sha512-Sxm7HmhsPIIzxbPnWembPyobuCeA5j9KxL+jIOW2c+kZiTFjHeju7vuVWX9jmAMMC+UyDuuCQ4yE+kBo3Y7SWQ==} + + '@unocss/extractor-arbitrary-variants@66.6.0': + resolution: {integrity: sha512-AsCmpbre4hQb+cKOf3gHUeYlF7guR/aCKZvw53VBk12qY5wNF7LdfIx4zWc5LFVCoRxIZlU2C7L4/Tt7AkiFMA==} + + '@unocss/preset-mini@66.6.0': + resolution: {integrity: sha512-8bQyTuMJcry/z4JTDsQokI0187/1CJIkVx9hr9eEbKf/gWti538P8ktKEmHCf8IyT0At5dfP9oLHLCUzVetdbA==} + + '@unocss/preset-wind3@66.6.0': + resolution: {integrity: sha512-7gzswF810BCSru7pF01BsMzGZbfrsWT5GV6JJLkhROS2pPjeNOpqy2VEfiavv5z09iGSIESeOFMlXr5ORuLZrg==} + + '@unocss/rule-utils@66.6.0': + resolution: {integrity: sha512-v16l6p5VrefDx8P/gzWnp0p6/hCA0vZ4UMUN6SxHGVE6V+IBpX6I6Du3Egk9TdkhZ7o+Pe1NHxksHcjT0V/tww==} + engines: {node: '>=14'} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -1489,6 +1932,35 @@ packages: resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} engines: {node: '>= 20'} + '@vue/compiler-core@3.5.26': + resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} + + '@vue/compiler-dom@3.5.26': + resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} + + '@vue/compiler-sfc@3.5.26': + resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + + '@vue/compiler-ssr@3.5.26': + resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + + '@vue/reactivity@3.5.26': + resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + + '@vue/runtime-core@3.5.26': + resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + + '@vue/runtime-dom@3.5.26': + resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + + '@vue/server-renderer@3.5.26': + resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} + peerDependencies: + vue: 3.5.26 + + '@vue/shared@3.5.26': + resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + '@xyflow/react@12.10.0': resolution: {integrity: sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==} peerDependencies: @@ -1525,6 +1997,10 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1593,6 +2069,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@0.0.8: + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} + engines: {node: '>= 0.4'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1680,6 +2160,14 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + c12@3.3.3: + resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} + peerDependencies: + magicast: '*' + peerDependenciesMeta: + magicast: + optional: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1700,6 +2188,9 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + caniuse-lite@1.0.30001764: resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==} @@ -1730,6 +2221,18 @@ packages: chevrotain@11.0.3: resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + chrome-launcher@1.2.1: + resolution: {integrity: sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A==} + engines: {node: '>=12.13.0'} + hasBin: true + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1780,9 +2283,19 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + console-table-printer@2.15.0: resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==} + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -1793,6 +2306,26 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-background-parser@0.1.0: + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} + + css-box-shadow@1.0.0-3: + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-gradient-parser@0.0.17: + resolution: {integrity: sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg==} + engines: {node: '>=16'} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -2015,6 +2548,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2032,6 +2568,10 @@ packages: dompurify@3.3.1: resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2049,6 +2589,10 @@ packages: embla-carousel@8.6.0: resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} + emoji-regex-xs@2.0.1: + resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} + engines: {node: '>=10.0.0'} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -2060,6 +2604,13 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + + errx@0.1.0: + resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==} + es-abstract@1.24.1: resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} @@ -2092,6 +2643,14 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2215,6 +2774,12 @@ packages: estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2229,6 +2794,17 @@ packages: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -2257,6 +2833,13 @@ packages: picomatch: optional: true + fflate@0.7.4: + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -2294,6 +2877,11 @@ packages: react-dom: optional: true + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -2308,10 +2896,6 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} - engines: {node: '>=18'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2324,6 +2908,14 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} @@ -2331,6 +2923,10 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2354,6 +2950,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + h3@1.15.5: + resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} + hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} @@ -2427,12 +3026,27 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hex-rgb@4.3.0: + resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} + engines: {node: '>=6'} + + hookable@6.0.1: + resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2445,6 +3059,11 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + image-size@2.0.2: + resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} + engines: {node: '>=16.x'} + hasBin: true + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -2467,6 +3086,9 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -2514,6 +3136,11 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2569,6 +3196,14 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -2581,6 +3216,10 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -2593,6 +3232,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -2616,6 +3259,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -2650,6 +3296,13 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + + knitwork@1.3.0: + resolution: {integrity: sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw==} + kysely@0.28.9: resolution: {integrity: sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA==} engines: {node: '>=20.0.0'} @@ -2692,6 +3345,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lighthouse-logger@2.0.2: + resolution: {integrity: sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==} + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -2766,6 +3422,9 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + linebreak@1.1.0: + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2786,6 +3445,10 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + engines: {node: 20 || >=22} + lucide-react@0.542.0: resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} peerDependencies: @@ -2807,6 +3470,9 @@ packages: engines: {node: '>= 20'} hasBin: true + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2859,6 +3525,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2869,35 +3538,6 @@ packages: micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - micromark-extension-cjk-friendly-gfm-strikethrough@1.2.3: - resolution: {integrity: sha512-gSPnxgHDDqXYOBvQRq6lerrq9mjDhdtKn+7XETuXjxWcL62yZEfUdA28Ml1I2vDIPfAOIKLa0h2XDSGkInGHFQ==} - engines: {node: '>=16'} - peerDependencies: - micromark: ^4.0.0 - micromark-util-types: ^2.0.0 - peerDependenciesMeta: - micromark-util-types: - optional: true - - micromark-extension-cjk-friendly-util@2.1.1: - resolution: {integrity: sha512-egs6+12JU2yutskHY55FyR48ZiEcFOJFyk9rsiyIhcJ6IvWB6ABBqVrBw8IobqJTDZ/wdSr9eoXDPb5S2nW1bg==} - engines: {node: '>=16'} - peerDependencies: - micromark-util-types: '*' - peerDependenciesMeta: - micromark-util-types: - optional: true - - micromark-extension-cjk-friendly@1.2.3: - resolution: {integrity: sha512-gRzVLUdjXBLX6zNPSnHGDoo+ZTp5zy+MZm0g3sv+3chPXY7l9gW+DnrcHcZh/jiPR6MjPKO4AEJNp4Aw6V9z5Q==} - engines: {node: '>=16'} - peerDependencies: - micromark: ^4.0.0 - micromark-util-types: ^2.0.0 - peerDependenciesMeta: - micromark-util-types: - optional: true - micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} @@ -2986,6 +3626,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2999,6 +3643,9 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mocked-exports@0.1.1: + resolution: {integrity: sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==} + motion-dom@12.26.2: resolution: {integrity: sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==} @@ -3019,6 +3666,10 @@ packages: react-dom: optional: true + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3075,6 +3726,42 @@ packages: sass: optional: true + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + nuxt-og-image@5.1.13: + resolution: {integrity: sha512-H9kqGlmcEb9agWURwT5iFQjbr7Ec7tcQHZZaYSpC/JXKq2/dFyRyAoo6oXTk6ob20dK9aNjkJDcX2XmgZy67+w==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@unhead/vue': ^2.0.5 + unstorage: ^1.15.0 + + nuxt-site-config-kit@3.2.17: + resolution: {integrity: sha512-46I4c+JWfmbeYTFYE4PYGfi3Olke2VKr+vQvi69VAuNGw03pr0Dyhz33eV5SmGQ0vGXNkO0PjY+HdtdvazAJbA==} + + nuxt-site-config@3.2.17: + resolution: {integrity: sha512-gmQkBWesVsEt0r10Jo16awufLpQYV4Ql2sOcmDz6CPdk0msZG/pDN6mzFVtMMorEtqixjqPVSIcFK++AueTKFw==} + + nypm@0.6.2: + resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3107,6 +3794,16 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + oniguruma-parser@0.12.1: resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} @@ -3156,13 +3853,23 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-css-color@0.2.1: + resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -3177,12 +3884,19 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + perfect-debounce@2.0.0: + resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3197,6 +3911,14 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + playwright-core@1.57.0: + resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} + engines: {node: '>=18'} + hasBin: true + points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -3207,6 +3929,9 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -3285,6 +4010,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -3298,6 +4027,12 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + react-dom@19.2.3: resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: @@ -3352,6 +4087,10 @@ packages: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -3378,26 +4117,6 @@ packages: rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} - remark-cjk-friendly-gfm-strikethrough@1.2.3: - resolution: {integrity: sha512-bXfMZtsaomK6ysNN/UGRIcasQAYkC10NtPmP0oOHOV8YOhA2TXmwRXCku4qOzjIFxAPfish5+XS0eIug2PzNZA==} - engines: {node: '>=16'} - peerDependencies: - '@types/mdast': ^4.0.0 - unified: ^11.0.0 - peerDependenciesMeta: - '@types/mdast': - optional: true - - remark-cjk-friendly@1.2.3: - resolution: {integrity: sha512-UvAgxwlNk+l9Oqgl/9MWK2eWRS7zgBW/nXX9AthV7nd/3lNejF138E7Xbmk9Zs4WjTJGs721r7fAEc7tNFoH7g==} - engines: {node: '>=16'} - peerDependencies: - '@types/mdast': ^4.0.0 - unified: ^11.0.0 - peerDependenciesMeta: - '@types/mdast': - optional: true - remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -3436,6 +4155,11 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} @@ -3463,9 +4187,19 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + satori-html@0.3.2: + resolution: {integrity: sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==} + + satori@0.18.4: + resolution: {integrity: sha512-HanEzgXHlX3fzpGgxPoR3qI7FDpc/B+uE/KplzA6BkZGlWMaH98B/1Amq+OBF1pYPlGNzAXPYNHlrEVBvRBnHQ==} + engines: {node: '>=16'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3502,8 +4236,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@3.21.0: - resolution: {integrity: sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==} + shiki@3.15.0: + resolution: {integrity: sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==} side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} @@ -3521,12 +4255,25 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-swizzle@0.2.4: resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} simple-wcswidth@1.1.2: resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + site-config-stack@3.2.17: + resolution: {integrity: sha512-b1KQPgUcMixgWBPuvD/Xbs+HvWimkj6aUslI/hol2uKkYdBcj6RIpfQFVbUTptxUe1lA82PNBqnI3yctLrvQoQ==} + peerDependencies: + vue: ^3 + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3537,12 +4284,15 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - streamdown@1.5.1: - resolution: {integrity: sha512-Nt75cjPvtWwqBcII96RNw/qzW370C0w9AW+1NVlCFGe1VRxPQeX2EOq5tU1u4jEOHiLKVCVee5+ypPcpOLiztA==} + streamdown@1.4.0: + resolution: {integrity: sha512-ylhDSQ4HpK5/nAH9v7OgIIdGJxlJB2HoYrYkJNGrO8lMpnWuKUcrz/A8xAMwA6eILA27469vIavcOTjmxctrKg==} peerDependencies: react: ^18.0.0 || ^19.0.0 @@ -3550,6 +4300,9 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + string.prototype.codepointat@0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -3580,10 +4333,21 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -3624,6 +4388,9 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -3639,6 +4406,10 @@ packages: tokenlens@1.3.1: resolution: {integrity: sha512-7oxmsS5PNCX3z+b+z07hL5vCzlgHKkCGrEQjQmWl5l+v5cUrtL7S1cuST4XThaL1XyjbTX8J5hfP0cjDJRkaLA==} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -3699,13 +4470,35 @@ packages: ufo@1.6.2: resolution: {integrity: sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==} + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + unctx@2.5.0: + resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unhead@2.1.2: + resolution: {integrity: sha512-vSihrxyb+zsEUfEbraZBCjdE0p/WSoc2NGDrpwwSNAwuPxhYK1nH3eegf02IENLpn1sUhL8IoO84JWmRQ6tILA==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3730,9 +4523,82 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + unstorage@1.17.4: + resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + untyped@2.0.0: + resolution: {integrity: sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==} + hasBin: true + + unwasm@0.5.3: + resolution: {integrity: sha512-keBgTSfp3r6+s9ZcSma+0chwxQdmLbB5+dAD9vjtB21UTMYuKAxHXCU1K2CbCtnP09EaWeRvACnXk0EJtUx+hw==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3787,6 +4653,46 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vscode-jsonrpc@8.2.0: resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} engines: {node: '>=14.0.0'} @@ -3807,9 +4713,20 @@ packages: vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vue@3.5.26: + resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3839,6 +4756,16 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + + yoga-wasm-web@0.3.3: + resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -3890,6 +4817,19 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@better-auth/core@1.4.12(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)': dependencies: '@better-auth/utils': 0.3.0 @@ -3948,6 +4888,84 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -4211,8 +5229,43 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@nuxt/devtools-kit@3.1.1(vite@7.3.1(@types/node@20.19.29)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@nuxt/kit': 4.2.2 + execa: 8.0.1 + vite: 7.3.1(@types/node@20.19.29)(jiti@2.6.1)(lightningcss@1.30.2) + transitivePeerDependencies: + - magicast + + '@nuxt/kit@4.2.2': + dependencies: + c12: 3.3.3 + consola: 3.4.2 + defu: 6.1.4 + destr: 2.0.5 + errx: 0.1.0 + exsolve: 1.0.8 + ignore: 7.0.5 + jiti: 2.6.1 + klona: 2.0.6 + mlly: 1.8.0 + ohash: 2.0.11 + pathe: 2.0.3 + pkg-types: 2.3.0 + rc9: 2.1.2 + scule: 1.3.0 + semver: 7.7.3 + tinyglobby: 0.2.15 + ufo: 1.6.2 + unctx: 2.5.0 + untyped: 2.0.0 + transitivePeerDependencies: + - magicast + '@opentelemetry/api@1.9.0': {} + '@polka/url@1.0.0-next.29': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -4632,43 +5685,180 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@resvg/resvg-js-android-arm-eabi@2.6.2': + optional: true + + '@resvg/resvg-js-android-arm64@2.6.2': + optional: true + + '@resvg/resvg-js-darwin-arm64@2.6.2': + optional: true + + '@resvg/resvg-js-darwin-x64@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm64-musl@2.6.2': + optional: true + + '@resvg/resvg-js-linux-x64-gnu@2.6.2': + optional: true + + '@resvg/resvg-js-linux-x64-musl@2.6.2': + optional: true + + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': + optional: true + + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': + optional: true + + '@resvg/resvg-js-win32-x64-msvc@2.6.2': + optional: true + + '@resvg/resvg-js@2.6.2': + optionalDependencies: + '@resvg/resvg-js-android-arm-eabi': 2.6.2 + '@resvg/resvg-js-android-arm64': 2.6.2 + '@resvg/resvg-js-darwin-arm64': 2.6.2 + '@resvg/resvg-js-darwin-x64': 2.6.2 + '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2 + '@resvg/resvg-js-linux-arm64-gnu': 2.6.2 + '@resvg/resvg-js-linux-arm64-musl': 2.6.2 + '@resvg/resvg-js-linux-x64-gnu': 2.6.2 + '@resvg/resvg-js-linux-x64-musl': 2.6.2 + '@resvg/resvg-js-win32-arm64-msvc': 2.6.2 + '@resvg/resvg-js-win32-ia32-msvc': 2.6.2 + '@resvg/resvg-js-win32-x64-msvc': 2.6.2 + + '@resvg/resvg-wasm@2.6.2': {} + + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true + + '@rollup/rollup-android-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-x64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.55.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.55.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.55.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.55.1': + optional: true + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.15.0': {} - '@shikijs/core@3.21.0': + '@sec-ant/readable-stream@0.4.1': {} + + '@shikijs/core@3.15.0': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.21.0': + '@shikijs/engine-javascript@3.15.0': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.4 - '@shikijs/engine-oniguruma@3.21.0': + '@shikijs/engine-oniguruma@3.15.0': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.21.0': + '@shikijs/langs@3.15.0': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 3.15.0 - '@shikijs/themes@3.21.0': + '@shikijs/themes@3.15.0': dependencies: - '@shikijs/types': 3.21.0 + '@shikijs/types': 3.15.0 - '@shikijs/types@3.21.0': + '@shikijs/types@3.15.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 '@shikijs/vscode-textmate@10.0.2': {} + '@shuding/opentype.js@1.4.0-beta.0': + dependencies: + fflate: 0.7.4 + string.prototype.codepointat: 0.2.1 + + '@sindresorhus/merge-streams@4.0.0': {} + '@standard-schema/spec@1.1.0': {} '@swc/counter@0.1.3': {} @@ -5044,6 +6234,35 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@unhead/vue@2.1.2(vue@3.5.26(typescript@5.9.3))': + dependencies: + hookable: 6.0.1 + unhead: 2.1.2 + vue: 3.5.26(typescript@5.9.3) + + '@unocss/core@66.6.0': {} + + '@unocss/extractor-arbitrary-variants@66.6.0': + dependencies: + '@unocss/core': 66.6.0 + + '@unocss/preset-mini@66.6.0': + dependencies: + '@unocss/core': 66.6.0 + '@unocss/extractor-arbitrary-variants': 66.6.0 + '@unocss/rule-utils': 66.6.0 + + '@unocss/preset-wind3@66.6.0': + dependencies: + '@unocss/core': 66.6.0 + '@unocss/preset-mini': 66.6.0 + '@unocss/rule-utils': 66.6.0 + + '@unocss/rule-utils@66.6.0': + dependencies: + '@unocss/core': 66.6.0 + magic-string: 0.30.21 + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -5105,6 +6324,60 @@ snapshots: '@vercel/oidc@3.1.0': {} + '@vue/compiler-core@3.5.26': + dependencies: + '@babel/parser': 7.28.6 + '@vue/shared': 3.5.26 + entities: 7.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.26': + dependencies: + '@vue/compiler-core': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/compiler-sfc@3.5.26': + dependencies: + '@babel/parser': 7.28.6 + '@vue/compiler-core': 3.5.26 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.26': + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/reactivity@3.5.26': + dependencies: + '@vue/shared': 3.5.26 + + '@vue/runtime-core@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/runtime-dom@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/runtime-core': 3.5.26 + '@vue/shared': 3.5.26 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26(typescript@5.9.3) + + '@vue/shared@3.5.26': {} + '@xyflow/react@12.10.0(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@xyflow/system': 0.0.74 @@ -5155,6 +6428,11 @@ snapshots: ansi-styles@5.2.0: {} + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -5246,11 +6524,13 @@ snapshots: balanced-match@1.0.2: {} + base64-js@0.0.8: {} + base64-js@1.5.1: {} best-effort-json-parser@1.2.1: {} - better-auth@1.4.12(next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + better-auth@1.4.12(next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vue@3.5.26(typescript@5.9.3)): dependencies: '@better-auth/core': 1.4.12(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0) '@better-auth/telemetry': 1.4.12(@better-auth/core@1.4.12(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)) @@ -5268,6 +6548,7 @@ snapshots: next: 15.2.8(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + vue: 3.5.26(typescript@5.9.3) better-call@1.1.7(zod@4.3.5): dependencies: @@ -5295,6 +6576,21 @@ snapshots: dependencies: streamsearch: 1.1.0 + c12@3.3.3: + dependencies: + chokidar: 5.0.0 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 17.2.3 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -5316,6 +6612,8 @@ snapshots: camelcase@6.3.0: {} + camelize@1.0.1: {} + caniuse-lite@1.0.30001764: {} ccount@2.0.1: {} @@ -5347,6 +6645,23 @@ snapshots: '@chevrotain/utils': 11.0.3 lodash-es: 4.17.21 + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + chrome-launcher@1.2.1: + dependencies: + '@types/node': 20.19.29 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 2.0.2 + transitivePeerDependencies: + - supports-color + + citty@0.1.6: + dependencies: + consola: 3.4.2 + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -5397,10 +6712,16 @@ snapshots: confbox@0.1.8: {} + confbox@0.2.2: {} + + consola@3.4.2: {} + console-table-printer@2.15.0: dependencies: simple-wcswidth: 1.1.2 + cookie-es@1.2.2: {} + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -5415,6 +6736,24 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-background-parser@0.1.0: {} + + css-box-shadow@1.0.0-3: {} + + css-color-keywords@1.0.0: {} + + css-gradient-parser@0.0.17: {} + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + csstype@3.2.3: {} cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): @@ -5659,6 +6998,8 @@ snapshots: dequal@2.0.3: {} + destr@2.0.5: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -5675,6 +7016,8 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + dotenv@17.2.3: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5693,6 +7036,8 @@ snapshots: embla-carousel@8.6.0: {} + emoji-regex-xs@2.0.1: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.18.4: @@ -5702,6 +7047,10 @@ snapshots: entities@6.0.1: {} + entities@7.0.0: {} + + errx@0.1.0: {} + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 @@ -5803,6 +7152,37 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -6003,6 +7383,12 @@ snapshots: estree-util-is-identifier-name@3.0.0: {} + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} eventemitter3@4.0.7: {} @@ -6011,6 +7397,35 @@ snapshots: eventsource-parser@3.0.6: {} + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + exsolve@1.0.8: {} + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -6035,6 +7450,12 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.7.4: {} + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -6068,6 +7489,9 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -6083,8 +7507,6 @@ snapshots: generator-function@2.0.1: {} - get-east-asian-width@1.4.0: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6105,6 +7527,13 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@8.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 @@ -6115,6 +7544,15 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.2 + pathe: 2.0.3 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -6134,6 +7572,18 @@ snapshots: graceful-fs@4.2.11: {} + h3@1.15.5: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.3 + uncrypto: 0.1.3 + hachure-fill@0.5.2: {} has-bigints@1.1.0: {} @@ -6280,10 +7730,18 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 + hex-rgb@4.3.0: {} + + hookable@6.0.1: {} + html-url-attributes@3.0.1: {} html-void-elements@3.0.0: {} + human-signals@5.0.0: {} + + human-signals@8.0.1: {} + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -6292,6 +7750,8 @@ snapshots: ignore@7.0.5: {} + image-size@2.0.2: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -6311,6 +7771,8 @@ snapshots: internmap@2.0.3: {} + iron-webcrypto@1.2.1: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -6367,6 +7829,8 @@ snapshots: is-decimal@2.0.1: {} + is-docker@2.2.1: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -6415,6 +7879,10 @@ snapshots: dependencies: call-bound: 1.0.4 + is-stream@3.0.0: {} + + is-stream@4.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -6430,6 +7898,8 @@ snapshots: dependencies: which-typed-array: 1.1.19 + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -6441,6 +7911,10 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + isarray@2.0.5: {} isexe@2.0.0: {} @@ -6464,6 +7938,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -6497,6 +7973,10 @@ snapshots: khroma@2.1.0: {} + klona@2.0.6: {} + + knitwork@1.3.0: {} + kysely@0.28.9: {} langium@3.3.1: @@ -6533,6 +8013,13 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lighthouse-logger@2.0.2: + dependencies: + debug: 4.4.3 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + lightningcss-android-arm64@1.30.2: optional: true @@ -6582,6 +8069,11 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + linebreak@1.1.0: + dependencies: + base64-js: 0.0.8 + unicode-trie: 2.0.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -6598,6 +8090,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@11.2.4: {} + lucide-react@0.542.0(react@19.2.3): dependencies: react: 19.2.3 @@ -6614,6 +8108,8 @@ snapshots: marked@16.4.2: {} + marky@1.3.0: {} + math-intrinsics@1.1.0: {} mdast-util-find-and-replace@3.0.2: @@ -6781,6 +8277,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + merge-stream@2.0.0: {} + merge2@1.4.1: {} mermaid@11.12.2: @@ -6825,38 +8323,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-cjk-friendly-gfm-strikethrough@1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2): - dependencies: - devlop: 1.1.0 - get-east-asian-width: 1.4.0 - micromark: 4.0.2 - micromark-extension-cjk-friendly-util: 2.1.1(micromark-util-types@2.0.2) - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - optionalDependencies: - micromark-util-types: 2.0.2 - - micromark-extension-cjk-friendly-util@2.1.1(micromark-util-types@2.0.2): - dependencies: - get-east-asian-width: 1.4.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - optionalDependencies: - micromark-util-types: 2.0.2 - - micromark-extension-cjk-friendly@1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2): - dependencies: - devlop: 1.1.0 - micromark: 4.0.2 - micromark-extension-cjk-friendly-util: 2.1.1(micromark-util-types@2.0.2) - micromark-util-chunked: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - optionalDependencies: - micromark-util-types: 2.0.2 - micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.1 @@ -7044,6 +8510,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mimic-fn@4.0.0: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -7061,6 +8529,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.2 + mocked-exports@0.1.1: {} + motion-dom@12.26.2: dependencies: motion-utils: 12.24.10 @@ -7075,6 +8545,8 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + mrmime@2.0.1: {} + ms@2.1.3: {} mustache@4.2.0: {} @@ -7120,6 +8592,94 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-fetch-native@1.6.7: {} + + node-mock-http@1.0.4: {} + + normalize-path@3.0.0: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nuxt-og-image@5.1.13(@unhead/vue@2.1.2(vue@3.5.26(typescript@5.9.3)))(unstorage@1.17.4)(vite@7.3.1(@types/node@20.19.29)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.26(typescript@5.9.3)): + dependencies: + '@nuxt/devtools-kit': 3.1.1(vite@7.3.1(@types/node@20.19.29)(jiti@2.6.1)(lightningcss@1.30.2)) + '@nuxt/kit': 4.2.2 + '@resvg/resvg-js': 2.6.2 + '@resvg/resvg-wasm': 2.6.2 + '@unhead/vue': 2.1.2(vue@3.5.26(typescript@5.9.3)) + '@unocss/core': 66.6.0 + '@unocss/preset-wind3': 66.6.0 + chrome-launcher: 1.2.1 + consola: 3.4.2 + defu: 6.1.4 + execa: 9.6.1 + image-size: 2.0.2 + magic-string: 0.30.21 + mocked-exports: 0.1.1 + nuxt-site-config: 3.2.17(vue@3.5.26(typescript@5.9.3)) + nypm: 0.6.2 + ofetch: 1.5.1 + ohash: 2.0.11 + pathe: 2.0.3 + pkg-types: 2.3.0 + playwright-core: 1.57.0 + radix3: 1.1.2 + satori: 0.18.4 + satori-html: 0.3.2 + sirv: 3.0.2 + std-env: 3.10.0 + strip-literal: 3.1.0 + ufo: 1.6.2 + unplugin: 2.3.11 + unstorage: 1.17.4 + unwasm: 0.5.3 + yoga-wasm-web: 0.3.3 + transitivePeerDependencies: + - magicast + - supports-color + - vite + - vue + + nuxt-site-config-kit@3.2.17(vue@3.5.26(typescript@5.9.3)): + dependencies: + '@nuxt/kit': 4.2.2 + pkg-types: 2.3.0 + site-config-stack: 3.2.17(vue@3.5.26(typescript@5.9.3)) + std-env: 3.10.0 + ufo: 1.6.2 + transitivePeerDependencies: + - magicast + - vue + + nuxt-site-config@3.2.17(vue@3.5.26(typescript@5.9.3)): + dependencies: + '@nuxt/kit': 4.2.2 + h3: 1.15.5 + nuxt-site-config-kit: 3.2.17(vue@3.5.26(typescript@5.9.3)) + pathe: 2.0.3 + pkg-types: 2.3.0 + sirv: 3.0.2 + site-config-stack: 3.2.17(vue@3.5.26(typescript@5.9.3)) + ufo: 1.6.2 + transitivePeerDependencies: + - magicast + - vue + + nypm@0.6.2: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.2 + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -7162,6 +8722,18 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.2 + + ohash@2.0.11: {} + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + oniguruma-parser@0.12.1: {} oniguruma-to-es@4.3.4: @@ -7217,10 +8789,17 @@ snapshots: package-manager-detector@1.6.0: {} + pako@0.2.9: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-css-color@0.2.1: + dependencies: + color-name: 1.1.4 + hex-rgb: 4.3.0 + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -7231,6 +8810,8 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-ms@4.0.0: {} + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -7241,10 +8822,14 @@ snapshots: path-key@3.1.1: {} + path-key@4.0.0: {} + path-parse@1.0.7: {} pathe@2.0.3: {} + perfect-debounce@2.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -7257,6 +8842,14 @@ snapshots: mlly: 1.8.0 pathe: 2.0.3 + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + + playwright-core@1.57.0: {} + points-on-curve@0.2.0: {} points-on-path@0.2.1: @@ -7266,6 +8859,8 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-value-parser@4.2.0: {} + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -7286,6 +8881,10 @@ snapshots: prettier@3.7.4: {} + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -7298,6 +8897,13 @@ snapshots: queue-microtask@1.2.3: {} + radix3@1.1.2: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + react-dom@19.2.3(react@19.2.3): dependencies: react: 19.2.3 @@ -7357,6 +8963,8 @@ snapshots: react@19.2.3: {} + readdirp@5.0.0: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -7407,26 +9015,6 @@ snapshots: hast-util-raw: 9.1.0 vfile: 6.0.3 - remark-cjk-friendly-gfm-strikethrough@1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5): - dependencies: - micromark-extension-cjk-friendly-gfm-strikethrough: 1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2) - unified: 11.0.5 - optionalDependencies: - '@types/mdast': 4.0.4 - transitivePeerDependencies: - - micromark - - micromark-util-types - - remark-cjk-friendly@1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5): - dependencies: - micromark-extension-cjk-friendly: 1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2) - unified: 11.0.5 - optionalDependencies: - '@types/mdast': 4.0.4 - transitivePeerDependencies: - - micromark - - micromark-util-types - remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 @@ -7490,6 +9078,37 @@ snapshots: robust-predicates@3.0.2: {} + rollup@4.55.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 + fsevents: 2.3.3 + rou3@0.7.12: {} roughjs@4.6.6: @@ -7526,8 +9145,28 @@ snapshots: safer-buffer@2.1.2: {} + satori-html@0.3.2: + dependencies: + ultrahtml: 1.6.0 + + satori@0.18.4: + dependencies: + '@shuding/opentype.js': 1.4.0-beta.0 + css-background-parser: 0.1.0 + css-box-shadow: 1.0.0-3 + css-gradient-parser: 0.0.17 + css-to-react-native: 3.2.0 + emoji-regex-xs: 2.0.1 + escape-html: 1.0.3 + linebreak: 1.1.0 + parse-css-color: 0.2.1 + postcss-value-parser: 4.2.0 + yoga-layout: 3.2.1 + scheduler@0.27.0: {} + scule@1.3.0: {} + semver@6.3.1: {} semver@7.7.3: {} @@ -7589,14 +9228,14 @@ snapshots: shebang-regex@3.0.0: {} - shiki@3.21.0: + shiki@3.15.0: dependencies: - '@shikijs/core': 3.21.0 - '@shikijs/engine-javascript': 3.21.0 - '@shikijs/engine-oniguruma': 3.21.0 - '@shikijs/langs': 3.21.0 - '@shikijs/themes': 3.21.0 - '@shikijs/types': 3.21.0 + '@shikijs/core': 3.15.0 + '@shikijs/engine-javascript': 3.15.0 + '@shikijs/engine-oniguruma': 3.15.0 + '@shikijs/langs': 3.15.0 + '@shikijs/themes': 3.15.0 + '@shikijs/types': 3.15.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -7628,6 +9267,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@4.1.0: {} + simple-swizzle@0.2.4: dependencies: is-arrayish: 0.3.4 @@ -7635,18 +9276,31 @@ snapshots: simple-wcswidth@1.1.2: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + site-config-stack@3.2.17(vue@3.5.26(typescript@5.9.3)): + dependencies: + ufo: 1.6.2 + vue: 3.5.26(typescript@5.9.3) + source-map-js@1.2.1: {} space-separated-tokens@2.0.2: {} stable-hash@0.0.5: {} + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 - streamdown@1.5.1(@types/mdast@4.0.4)(@types/react@19.2.8)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3)(unified@11.0.5): + streamdown@1.4.0(@types/react@19.2.8)(react@19.2.3): dependencies: clsx: 2.1.1 katex: 0.16.27 @@ -7658,22 +9312,18 @@ snapshots: rehype-harden: 1.1.7 rehype-katex: 7.0.1 rehype-raw: 7.0.0 - remark-cjk-friendly: 1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) - remark-cjk-friendly-gfm-strikethrough: 1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) remark-gfm: 4.0.1 remark-math: 6.0.0 - shiki: 3.21.0 + shiki: 3.15.0 tailwind-merge: 3.4.0 transitivePeerDependencies: - - '@types/mdast' - '@types/react' - - micromark - - micromark-util-types - supports-color - - unified streamsearch@1.1.0: {} + string.prototype.codepointat@0.2.1: {} + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -7731,8 +9381,16 @@ snapshots: strip-bom@3.0.0: {} + strip-final-newline@3.0.0: {} + + strip-final-newline@4.0.0: {} + strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -7760,6 +9418,8 @@ snapshots: tapable@2.3.0: {} + tiny-inflate@1.0.3: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -7778,6 +9438,8 @@ snapshots: '@tokenlens/helpers': 1.3.1 '@tokenlens/models': 1.3.0 + totalist@3.0.1: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -7851,6 +9513,10 @@ snapshots: ufo@1.6.2: {} + ufo@1.6.3: {} + + ultrahtml@1.6.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -7858,8 +9524,28 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + uncrypto@0.1.3: {} + + unctx@2.5.0: + dependencies: + acorn: 8.15.0 + estree-walker: 3.0.3 + magic-string: 0.30.21 + unplugin: 2.3.11 + undici-types@6.21.0: {} + unhead@2.1.2: + dependencies: + hookable: 6.0.1 + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + + unicorn-magic@0.3.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -7903,6 +9589,13 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 @@ -7927,6 +9620,34 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + unstorage@1.17.4: + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.5 + lru-cache: 11.2.4 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.3 + + untyped@2.0.0: + dependencies: + citty: 0.1.6 + defu: 6.1.4 + jiti: 2.6.1 + knitwork: 1.3.0 + scule: 1.3.0 + + unwasm@0.5.3: + dependencies: + exsolve: 1.0.8 + knitwork: 1.3.0 + magic-string: 0.30.21 + mlly: 1.8.0 + pathe: 2.0.3 + pkg-types: 2.3.0 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -7975,6 +9696,20 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + vite@7.3.1(@types/node@20.19.29)(jiti@2.6.1)(lightningcss@1.30.2): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.55.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.29 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + vscode-jsonrpc@8.2.0: {} vscode-languageserver-protocol@3.17.5: @@ -7992,8 +9727,20 @@ snapshots: vscode-uri@3.0.8: {} + vue@3.5.26(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3)) + '@vue/shared': 3.5.26 + optionalDependencies: + typescript: 5.9.3 + web-namespaces@2.0.1: {} + webpack-virtual-modules@0.6.2: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -8043,6 +9790,12 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} + + yoga-layout@3.2.1: {} + + yoga-wasm-web@0.3.3: {} + zod@3.25.76: {} zod@4.3.5: {} From 9d1cf89532f7a6f364e5cb456935c88f66fb2081 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 10:09:43 +0800 Subject: [PATCH 043/302] chore: remove unused components --- .../components/ai-elements/confirmation.tsx | 182 ------------------ .../components/ai-elements/prompt-input.tsx | 2 +- frontend/src/components/ai-elements/tool.tsx | 165 ---------------- 3 files changed, 1 insertion(+), 348 deletions(-) delete mode 100644 frontend/src/components/ai-elements/confirmation.tsx delete mode 100644 frontend/src/components/ai-elements/tool.tsx diff --git a/frontend/src/components/ai-elements/confirmation.tsx b/frontend/src/components/ai-elements/confirmation.tsx deleted file mode 100644 index 1cd9b7ff8..000000000 --- a/frontend/src/components/ai-elements/confirmation.tsx +++ /dev/null @@ -1,182 +0,0 @@ -"use client"; - -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; -import type { ToolUIPart } from "ai"; -import { - type ComponentProps, - createContext, - type ReactNode, - useContext, -} from "react"; - -type ToolUIPartApproval = - | { - id: string; - approved?: never; - reason?: never; - } - | { - id: string; - approved: boolean; - reason?: string; - } - | { - id: string; - approved: true; - reason?: string; - } - | { - id: string; - approved: true; - reason?: string; - } - | { - id: string; - approved: false; - reason?: string; - } - | undefined; - -type ConfirmationContextValue = { - approval: ToolUIPartApproval; - state: ToolUIPart["state"]; -}; - -const ConfirmationContext = createContext( - null -); - -const useConfirmation = () => { - const context = useContext(ConfirmationContext); - - if (!context) { - throw new Error("Confirmation components must be used within Confirmation"); - } - - return context; -}; - -export type ConfirmationProps = ComponentProps & { - approval?: ToolUIPartApproval; - state: ToolUIPart["state"]; -}; - -export const Confirmation = ({ - className, - approval, - state, - ...props -}: ConfirmationProps) => { - if (!approval || state === "input-streaming" || state === "input-available") { - return null; - } - - return ( - - - - ); -}; - -export type ConfirmationTitleProps = ComponentProps; - -export const ConfirmationTitle = ({ - className, - ...props -}: ConfirmationTitleProps) => ( - -); - -export type ConfirmationRequestProps = { - children?: ReactNode; -}; - -export const ConfirmationRequest = ({ children }: ConfirmationRequestProps) => { - const { state } = useConfirmation(); - - // Only show when approval is requested - // @ts-expect-error state only available in AI SDK v6 - if (state !== "approval-requested") { - return null; - } - - return children; -}; - -export type ConfirmationAcceptedProps = { - children?: ReactNode; -}; - -export const ConfirmationAccepted = ({ - children, -}: ConfirmationAcceptedProps) => { - const { approval, state } = useConfirmation(); - - // Only show when approved and in response states - if ( - !approval?.approved || - // @ts-expect-error state only available in AI SDK v6 - (state !== "approval-responded" && - // @ts-expect-error state only available in AI SDK v6 - state !== "output-denied" && - state !== "output-available") - ) { - return null; - } - - return children; -}; - -export type ConfirmationRejectedProps = { - children?: ReactNode; -}; - -export const ConfirmationRejected = ({ - children, -}: ConfirmationRejectedProps) => { - const { approval, state } = useConfirmation(); - - // Only show when rejected and in response states - if ( - approval?.approved !== false || - // @ts-expect-error state only available in AI SDK v6 - (state !== "approval-responded" && - // @ts-expect-error state only available in AI SDK v6 - state !== "output-denied" && - state !== "output-available") - ) { - return null; - } - - return children; -}; - -export type ConfirmationActionsProps = ComponentProps<"div">; - -export const ConfirmationActions = ({ - className, - ...props -}: ConfirmationActionsProps) => { - const { state } = useConfirmation(); - - // Only show when approval is requested - // @ts-expect-error state only available in AI SDK v6 - if (state !== "approval-requested") { - return null; - } - - return ( -
            - ); -}; - -export type ConfirmationActionProps = ComponentProps; - -export const ConfirmationAction = (props: ConfirmationActionProps) => ( - + + )} +
            + +
            +
            + +
            +
            + setSettings("context", context)} + onSubmit={handleSubmit} + onStop={handleStop} + /> +
            +
            +
            + + + +
            + +
            +
            + {selectedArtifact ? ( + + ) : ( +
            + } + title="No artifact selected" + description="Select an artifact to view its details" + /> +
            + )}
            - {open && ( - <> - - - {selectedArtifact && ( - - )} - - - )} ); } diff --git a/frontend/src/app/workspace/layout.tsx b/frontend/src/app/workspace/layout.tsx index 80f3bd9e3..04e5960e7 100644 --- a/frontend/src/app/workspace/layout.tsx +++ b/frontend/src/app/workspace/layout.tsx @@ -28,6 +28,7 @@ export default function WorkspaceLayout({ return ( { - setOpen(false); - }, [setOpen]); +export function ArtifactFileDetail({ + className, + filepath, +}: { + className?: string; + filepath: string; +}) { return ( -
            -
            - -
            -
            +
            +
            diff --git a/frontend/src/components/workspace/artifacts/context.tsx b/frontend/src/components/workspace/artifacts/context.tsx index a5858703f..41a9d721e 100644 --- a/frontend/src/components/workspace/artifacts/context.tsx +++ b/frontend/src/components/workspace/artifacts/context.tsx @@ -1,5 +1,7 @@ import { createContext, useContext, useState, type ReactNode } from "react"; +import { useSidebar } from "@/components/ui/sidebar"; + export interface ArtifactsContextType { artifacts: string[]; selectedArtifact: string | null; @@ -23,6 +25,7 @@ export function ArtifactsProvider({ children }: ArtifactsProviderProps) { const [artifacts, setArtifacts] = useState([]); const [selectedArtifact, setSelectedArtifact] = useState(null); const [open, setOpen] = useState(false); + const { setOpen: setSidebarOpen } = useSidebar(); const addArtifacts = (newArtifacts: string[]) => { setArtifacts((prev) => [...prev, ...newArtifacts]); @@ -31,6 +34,7 @@ export function ArtifactsProvider({ children }: ArtifactsProviderProps) { const openArtifact = (artifact: string) => { setSelectedArtifact(artifact); setOpen(true); + setSidebarOpen(false); }; const value: ArtifactsContextType = { diff --git a/frontend/src/components/workspace/messages/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx index 07d56550c..7bc16b2c7 100644 --- a/frontend/src/components/workspace/messages/message-list.tsx +++ b/frontend/src/components/workspace/messages/message-list.tsx @@ -32,9 +32,9 @@ export function MessageList({ } return ( - + {groupMessages( thread.messages, (groupedMessages) => { From 962d8f04ec93cee36516dc9d9cb45199a8ba1e29 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 15:09:44 +0800 Subject: [PATCH 045/302] feat: support artifact preview --- backend/src/gateway/app.py | 2 +- backend/src/gateway/routers/artifacts.py | 38 ++++- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 14 ++ .../app/workspace/chats/[thread_id]/page.tsx | 35 ++-- frontend/src/app/workspace/layout.tsx | 2 + frontend/src/components/ui/sonner.tsx | 40 +++++ .../artifacts/artifact-file-detail.tsx | 96 +++++++++-- .../artifacts/artifact-file-list.tsx | 27 +++- .../workspace/artifacts/file-viewer.tsx | 64 ++++++++ .../workspace/messages/message-list.tsx | 8 +- frontend/src/core/artifacts/hooks.ts | 20 +++ frontend/src/core/artifacts/index.ts | 1 + frontend/src/core/artifacts/loader.ts | 14 ++ frontend/src/core/artifacts/utils.ts | 11 ++ frontend/src/core/utils/files.ts | 151 ++++++++++++++++++ 16 files changed, 482 insertions(+), 42 deletions(-) create mode 100644 frontend/src/components/ui/sonner.tsx create mode 100644 frontend/src/components/workspace/artifacts/file-viewer.tsx create mode 100644 frontend/src/core/artifacts/hooks.ts create mode 100644 frontend/src/core/artifacts/index.ts create mode 100644 frontend/src/core/artifacts/loader.ts create mode 100644 frontend/src/core/artifacts/utils.ts diff --git a/backend/src/gateway/app.py b/backend/src/gateway/app.py index f251f35ae..41d0aa394 100644 --- a/backend/src/gateway/app.py +++ b/backend/src/gateway/app.py @@ -39,7 +39,7 @@ def create_app() -> FastAPI: # Add CORS middleware app.add_middleware( CORSMiddleware, - allow_origins=config.cors_origins, + allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/backend/src/gateway/routers/artifacts.py b/backend/src/gateway/routers/artifacts.py index 9dbae102a..452c8cf8a 100644 --- a/backend/src/gateway/routers/artifacts.py +++ b/backend/src/gateway/routers/artifacts.py @@ -1,8 +1,9 @@ +import mimetypes import os from pathlib import Path -from fastapi import APIRouter, HTTPException -from fastapi.responses import FileResponse +from fastapi import APIRouter, HTTPException, Request, Response +from fastapi.responses import FileResponse, HTMLResponse, PlainTextResponse # Base directory for thread data (relative to backend/) THREAD_DATA_BASE_DIR = ".deer-flow/threads" @@ -47,8 +48,19 @@ def _resolve_artifact_path(thread_id: str, artifact_path: str) -> Path: return actual_path +def is_text_file_by_content(path: Path, sample_size: int = 8192) -> bool: + """Check if file is text by examining content for null bytes.""" + try: + with open(path, "rb") as f: + chunk = f.read(sample_size) + # Text files shouldn't contain null bytes + return b"\x00" not in chunk + except Exception: + return False + + @router.get("/threads/{thread_id}/artifacts/{path:path}") -async def get_artifact(thread_id: str, path: str) -> FileResponse: +async def get_artifact(thread_id: str, path: str, request: Request) -> FileResponse: """Get an artifact file by its path. Args: @@ -69,7 +81,19 @@ async def get_artifact(thread_id: str, path: str) -> FileResponse: if not actual_path.is_file(): raise HTTPException(status_code=400, detail=f"Path is not a file: {path}") - return FileResponse( - path=actual_path, - filename=actual_path.name, - ) + mime_type, _ = mimetypes.guess_type(actual_path) + + # if `download` query parameter is true, return the file as a download + if request.query_params.get("download"): + return FileResponse(path=actual_path, filename=actual_path.name, media_type=mime_type, headers={"Content-Disposition": f'attachment; filename="{actual_path.name}"'}) + + if mime_type and mime_type == "text/html": + return HTMLResponse(content=actual_path.read_text()) + + if mime_type and mime_type.startswith("text/"): + return PlainTextResponse(content=actual_path.read_text(), media_type=mime_type) + + if is_text_file_by_content(actual_path): + return PlainTextResponse(content=actual_path.read_text(), media_type=mime_type) + + return Response(content=actual_path.read_bytes(), media_type=mime_type, headers={"Content-Disposition": f'inline; filename="{actual_path.name}"'}) diff --git a/frontend/package.json b/frontend/package.json index 7fff1eaf8..0b24b044c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -51,6 +51,7 @@ "react-dom": "^19.0.0", "react-resizable-panels": "^4.4.1", "shiki": "3.15.0", + "sonner": "^2.0.7", "streamdown": "1.4.0", "tailwind-merge": "^3.4.0", "tokenlens": "^1.3.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 3d0e597a4..e39d0b04a 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -113,6 +113,9 @@ importers: shiki: specifier: 3.15.0 version: 3.15.0 + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) streamdown: specifier: 1.4.0 version: 1.4.0(@types/react@19.2.8)(react@19.2.3) @@ -4274,6 +4277,12 @@ packages: peerDependencies: vue: ^3 + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -9287,6 +9296,11 @@ snapshots: ufo: 1.6.2 vue: 3.5.26(typescript@5.9.3) + sonner@2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + source-map-js@1.2.1: {} space-separated-tokens@2.0.2: {} diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 99a40edbe..3234a59e6 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -4,6 +4,7 @@ import { FilesIcon, XIcon } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; +import { ConversationEmptyState } from "@/components/ai-elements/conversation"; import { Button } from "@/components/ui/button"; import { ResizableHandle, @@ -25,7 +26,6 @@ import { useSubmitThread, useThreadStream } from "@/core/threads/hooks"; import { pathOfThread, titleOfThread } from "@/core/threads/utils"; import { uuid } from "@/core/utils/uuid"; import { cn } from "@/lib/utils"; -import { ConversationEmptyState } from "@/components/ai-elements/conversation"; export default function ChatPage() { const router = useRouter(); @@ -81,7 +81,7 @@ export default function ChatPage() { minSize={30} >
            -
            +
            - +
            -
            - -
            ) : ( -
            +
            +
            + +
            } title="No artifact selected" diff --git a/frontend/src/app/workspace/layout.tsx b/frontend/src/app/workspace/layout.tsx index 04e5960e7..9243e0188 100644 --- a/frontend/src/app/workspace/layout.tsx +++ b/frontend/src/app/workspace/layout.tsx @@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useCallback, useEffect, useState } from "react"; +import { Toaster } from "sonner"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { Overscroll } from "@/components/workspace/overscroll"; @@ -41,6 +42,7 @@ export default function WorkspaceLayout({ {children} + ); } diff --git a/frontend/src/components/ui/sonner.tsx b/frontend/src/components/ui/sonner.tsx new file mode 100644 index 000000000..9b20afe2e --- /dev/null +++ b/frontend/src/components/ui/sonner.tsx @@ -0,0 +1,40 @@ +"use client" + +import { + CircleCheckIcon, + InfoIcon, + Loader2Icon, + OctagonXIcon, + TriangleAlertIcon, +} from "lucide-react" +import { useTheme } from "next-themes" +import { Toaster as Sonner, type ToasterProps } from "sonner" + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme() + + return ( + , + info: , + warning: , + error: , + loading: , + }} + style={ + { + "--normal-bg": "var(--popover)", + "--normal-text": "var(--popover-foreground)", + "--normal-border": "var(--border)", + "--border-radius": "var(--radius)", + } as React.CSSProperties + } + {...props} + /> + ) +} + +export { Toaster } diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 72bedb5e3..2fc8e8760 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -1,27 +1,99 @@ -import { FileIcon } from "lucide-react"; +import { CopyIcon, DownloadIcon, XIcon } from "lucide-react"; +import { useMemo } from "react"; +import { toast } from "sonner"; +import { + Artifact, + ArtifactAction, + ArtifactActions, + ArtifactContent, + ArtifactDescription, + ArtifactHeader, + ArtifactTitle, +} from "@/components/ai-elements/artifact"; +import { useArtifactContent } from "@/core/artifacts/hooks"; +import { urlOfArtifact } from "@/core/artifacts/utils"; +import { + checkCodeFile, + getFileExtensionDisplayName, + getFileName, +} from "@/core/utils/files"; import { cn } from "@/lib/utils"; +import { useArtifacts } from "./context"; +import { FileViewer } from "./file-viewer"; + export function ArtifactFileDetail({ className, filepath, + threadId, }: { className?: string; filepath: string; + threadId: string; }) { + const { setOpen } = useArtifacts(); + const { isCodeFile } = useMemo(() => checkCodeFile(filepath), [filepath]); + const { content } = useArtifactContent({ + threadId, + filepath, + enabled: isCodeFile, + }); return ( -
            -
            + +
            - + {getFileName(filepath)} + + {getFileExtensionDisplayName(filepath)} file +
            -
            {filepath}
            -
            -
            +
            + + {isCodeFile && ( + { + try { + await navigator.clipboard.writeText(content ?? ""); + toast.success("Copied to clipboard"); + } catch (error) { + toast.error("Failed to copy to clipboard"); + console.error(error); + } + }} + tooltip="Copy content to clipboard" + /> + )} + + console.log("Download")} + tooltip="Download file" + /> + + setOpen(false)} + tooltip="Close" + /> + +
            + + + + + ); } diff --git a/frontend/src/components/workspace/artifacts/artifact-file-list.tsx b/frontend/src/components/workspace/artifacts/artifact-file-list.tsx index 44309cb12..0730eac9d 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-list.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-list.tsx @@ -9,7 +9,8 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { getFileExtension, getFileName } from "@/core/utils/files"; +import { urlOfArtifact } from "@/core/artifacts/utils"; +import { getFileExtensionDisplayName, getFileName } from "@/core/utils/files"; import { cn } from "@/lib/utils"; import { useArtifacts } from "./context"; @@ -17,9 +18,11 @@ import { useArtifacts } from "./context"; export function ArtifactFileList({ className, files, + threadId, }: { className?: string; files: string[]; + threadId: string; }) { const { openArtifact } = useArtifacts(); const handleClick = useCallback( @@ -38,12 +41,24 @@ export function ArtifactFileList({ > {getFileName(file)} - {getFileExtension(file)} file + + {getFileExtensionDisplayName(file)} file + - + e.stopPropagation()} + > + + diff --git a/frontend/src/components/workspace/artifacts/file-viewer.tsx b/frontend/src/components/workspace/artifacts/file-viewer.tsx new file mode 100644 index 000000000..97604f769 --- /dev/null +++ b/frontend/src/components/workspace/artifacts/file-viewer.tsx @@ -0,0 +1,64 @@ +import { useMemo } from "react"; +import type { BundledLanguage } from "shiki"; + +import { CodeBlock } from "@/components/ai-elements/code-block"; +import { useArtifactContent } from "@/core/artifacts/hooks"; +import { urlOfArtifact } from "@/core/artifacts/utils"; +import { checkCodeFile } from "@/core/utils/files"; +import { cn } from "@/lib/utils"; + +export function FileViewer({ + className, + filepath, + threadId, +}: { + className?: string; + filepath: string; + threadId: string; +}) { + const { isCodeFile, language } = useMemo( + () => checkCodeFile(filepath), + [filepath], + ); + if (isCodeFile && language !== "html") { + return ( + + ); + } + return ( +
            + +
            + ); +} + +function CodeFileView({ + language, + filepath, + threadId, +}: { + language: BundledLanguage; + filepath: string; + threadId: string; +}) { + const { content: code } = useArtifactContent({ + filepath, + threadId, + }); + if (code) { + return ( + + ); + } +} diff --git a/frontend/src/components/workspace/messages/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx index 7bc16b2c7..1c0354f85 100644 --- a/frontend/src/components/workspace/messages/message-list.tsx +++ b/frontend/src/components/workspace/messages/message-list.tsx @@ -22,9 +22,11 @@ import { MessageListSkeleton } from "./skeleton"; export function MessageList({ className, + threadId, thread, }: { className?: string; + threadId: string; thread: UseStream; }) { if (thread.isThreadLoading) { @@ -57,7 +59,11 @@ export function MessageList({ } } return ( - + ); } return ( diff --git a/frontend/src/core/artifacts/hooks.ts b/frontend/src/core/artifacts/hooks.ts new file mode 100644 index 000000000..e7a6cd437 --- /dev/null +++ b/frontend/src/core/artifacts/hooks.ts @@ -0,0 +1,20 @@ +import { useQuery } from "@tanstack/react-query"; + +import { loadArtifactContent } from "./loader"; + +export function useArtifactContent({ + filepath, + threadId, + enabled, +}: { + filepath: string; + threadId: string; + enabled?: boolean; +}) { + const { data, isLoading, error } = useQuery({ + queryKey: ["artifact", filepath, threadId], + queryFn: () => loadArtifactContent({ filepath, threadId }), + enabled, + }); + return { content: data, isLoading, error }; +} diff --git a/frontend/src/core/artifacts/index.ts b/frontend/src/core/artifacts/index.ts new file mode 100644 index 000000000..ee5286f0f --- /dev/null +++ b/frontend/src/core/artifacts/index.ts @@ -0,0 +1 @@ +export * from "./loader"; diff --git a/frontend/src/core/artifacts/loader.ts b/frontend/src/core/artifacts/loader.ts new file mode 100644 index 000000000..d55b1b426 --- /dev/null +++ b/frontend/src/core/artifacts/loader.ts @@ -0,0 +1,14 @@ +import { urlOfArtifact } from "./utils"; + +export async function loadArtifactContent({ + filepath, + threadId, +}: { + filepath: string; + threadId: string; +}) { + const url = urlOfArtifact({ filepath, threadId }); + const response = await fetch(url); + const text = await response.text(); + return text; +} diff --git a/frontend/src/core/artifacts/utils.ts b/frontend/src/core/artifacts/utils.ts new file mode 100644 index 000000000..19755222b --- /dev/null +++ b/frontend/src/core/artifacts/utils.ts @@ -0,0 +1,11 @@ +export function urlOfArtifact({ + filepath, + threadId, + download = false, +}: { + filepath: string; + threadId: string; + download?: boolean; +}) { + return `http://localhost:8000/api/threads/${threadId}/artifacts${filepath}${download ? "?download=true" : ""}`; +} diff --git a/frontend/src/core/utils/files.ts b/frontend/src/core/utils/files.ts index ec65b10e8..f3923d00a 100644 --- a/frontend/src/core/utils/files.ts +++ b/frontend/src/core/utils/files.ts @@ -1,8 +1,159 @@ +import type { BundledLanguage } from "shiki"; + +const extensionMap: Record = { + // JavaScript/TypeScript ecosystem + js: "javascript", + jsx: "jsx", + ts: "typescript", + tsx: "tsx", + mjs: "javascript", + cjs: "javascript", + mts: "typescript", + cts: "typescript", + + // Web + html: "html", + htm: "html", + css: "css", + scss: "scss", + sass: "sass", + less: "less", + vue: "vue", + svelte: "svelte", + astro: "astro", + + // Python + py: "python", + pyi: "python", + pyw: "python", + + // Java/JVM + java: "java", + kt: "kotlin", + kts: "kotlin", + scala: "scala", + groovy: "groovy", + + // C/C++ + c: "c", + h: "c", + cpp: "cpp", + cc: "cpp", + cxx: "cpp", + hpp: "cpp", + hxx: "cpp", + hh: "cpp", + + // C# + cs: "csharp", + + // Go + go: "go", + + // Rust + rs: "rust", + + // Ruby + rb: "ruby", + rake: "ruby", + + // PHP + php: "php", + + // Shell/Bash + sh: "bash", + bash: "bash", + zsh: "zsh", + fish: "fish", + + // Config & Data + json: "json", + jsonc: "jsonc", + json5: "json5", + yaml: "yaml", + yml: "yaml", + toml: "toml", + xml: "xml", + ini: "ini", + env: "dotenv", + + // Markdown & Docs + md: "markdown", + mdx: "mdx", + rst: "rst", + + // SQL + sql: "sql", + + // Other languages + swift: "swift", + dart: "dart", + lua: "lua", + r: "r", + matlab: "matlab", + julia: "jl", + elm: "elm", + haskell: "haskell", + hs: "haskell", + elixir: "elixir", + ex: "elixir", + clj: "clojure", + cljs: "clojure", + + // Infrastructure + dockerfile: "dockerfile", + docker: "docker", + tf: "terraform", + tfvars: "terraform", + hcl: "hcl", + + // Build & Config + makefile: "makefile", + cmake: "cmake", + gradle: "groovy", + + // Git + gitignore: "git-commit", + gitattributes: "git-commit", + + // Misc + graphql: "graphql", + gql: "graphql", + proto: "protobuf", + prisma: "prisma", + wasm: "wasm", + zig: "zig", + v: "v", +}; + export function getFileName(filepath: string) { return filepath.split("/").pop()!; } export function getFileExtension(filepath: string) { + return filepath.split(".").pop()!.toLocaleLowerCase(); +} + +export function checkCodeFile( + filepath: string, +): + | { isCodeFile: true; language: BundledLanguage } + | { isCodeFile: false; language: null } { + const extension = getFileExtension(filepath); + const isCodeFile = extension in extensionMap; + if (isCodeFile) { + return { + isCodeFile: true, + language: extensionMap[extension] as unknown as BundledLanguage, + }; + } + return { + isCodeFile: false, + language: null, + }; +} + +export function getFileExtensionDisplayName(filepath: string) { const fileName = getFileName(filepath); const extension = fileName.split(".").pop()!.toLocaleLowerCase(); switch (extension) { From 5dc40a9adeac285c8e954cb4d581471919b00989 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 15:19:53 +0800 Subject: [PATCH 046/302] feat: add `open in new window` --- .../workspace/artifacts/artifact-file-detail.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 2fc8e8760..467e7dfed 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -1,4 +1,9 @@ -import { CopyIcon, DownloadIcon, XIcon } from "lucide-react"; +import { + CopyIcon, + DownloadIcon, + SquareArrowOutUpRightIcon, + XIcon, +} from "lucide-react"; import { useMemo } from "react"; import { toast } from "sonner"; @@ -50,6 +55,13 @@ export function ArtifactFileDetail({
            + + + {isCodeFile && ( Date: Sat, 17 Jan 2026 15:22:00 +0800 Subject: [PATCH 047/302] feat: shrink card size --- .../components/workspace/artifacts/artifact-file-list.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/workspace/artifacts/artifact-file-list.tsx b/frontend/src/components/workspace/artifacts/artifact-file-list.tsx index 0730eac9d..bf6c888e4 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-list.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-list.tsx @@ -35,13 +35,13 @@ export function ArtifactFileList({
              {files.map((file) => ( handleClick(file)} > - + {getFileName(file)} - + {getFileExtensionDisplayName(file)} file From 228ec49f70373b1c862ab6bd1c2053b9ef8caded Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 15:44:38 +0800 Subject: [PATCH 048/302] feat: add date time util --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 8 ++++++++ frontend/src/core/utils/datetime.ts | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100644 frontend/src/core/utils/datetime.ts diff --git a/frontend/package.json b/frontend/package.json index 0b24b044c..8493b9740 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,6 +39,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", + "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "hast": "^1.0.0", "lucide-react": "^0.562.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index e39d0b04a..3eae6a2a5 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: cmdk: specifier: ^1.1.1 version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + date-fns: + specifier: ^4.1.0 + version: 4.1.0 embla-carousel-react: specifier: ^8.6.0 version: 8.6.0(react@19.2.3) @@ -2503,6 +2506,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.19: resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} @@ -6969,6 +6975,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns@4.1.0: {} + dayjs@1.11.19: {} debug@3.2.7: diff --git a/frontend/src/core/utils/datetime.ts b/frontend/src/core/utils/datetime.ts new file mode 100644 index 000000000..71bbd851c --- /dev/null +++ b/frontend/src/core/utils/datetime.ts @@ -0,0 +1,7 @@ +import { formatDistanceToNow } from "date-fns"; + +export function formatTimeAgo(date: Date | string | number) { + return formatDistanceToNow(date, { + addSuffix: true, + }); +} From 56da1c990aad453481badf51e6d215a3834da081 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 15:44:49 +0800 Subject: [PATCH 049/302] feat: implement '/chats' --- frontend/src/app/workspace/chats/page.tsx | 49 ++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/workspace/chats/page.tsx b/frontend/src/app/workspace/chats/page.tsx index 1127faa39..c53f0858f 100644 --- a/frontend/src/app/workspace/chats/page.tsx +++ b/frontend/src/app/workspace/chats/page.tsx @@ -1,17 +1,64 @@ "use client"; +import Link from "next/link"; +import { useMemo, useState } from "react"; + +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { WorkspaceBody, WorkspaceContainer, WorkspaceHeader, } from "@/components/workspace/workspace-container"; +import { useThreads } from "@/core/threads/hooks"; +import { pathOfThread, titleOfThread } from "@/core/threads/utils"; +import { formatTimeAgo } from "@/core/utils/datetime"; export default function ChatsPage() { + const { data: threads } = useThreads(); + const [search, setSearch] = useState(""); + const filteredThreads = useMemo(() => { + return threads?.filter((thread) => { + return titleOfThread(thread).toLowerCase().includes(search.toLowerCase()); + }); + }, [threads, search]); return ( -
              +
              +
              + setSearch(e.target.value)} + /> +
              +
              + +
              + {filteredThreads?.map((thread) => ( + +
              +
              +
              {titleOfThread(thread)}
              +
              +
              + {formatTimeAgo(thread.updated_at)} +
              +
              + + ))} +
              +
              +
              +
              ); From 584eed01662562d8fe333f7414917568693ea064 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 15:48:43 +0800 Subject: [PATCH 050/302] fix: do not display 'Untitled' --- .../src/app/workspace/chats/[thread_id]/page.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 3234a59e6..7b4b29399 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -55,10 +55,15 @@ export default function ChatPage() { isNewThread, threadId, }); - const title = useMemo( - () => (isNewThread ? "" : titleOfThread(thread as unknown as AgentThread)), - [thread, isNewThread], - ); + const title = useMemo(() => { + let result = isNewThread + ? "" + : titleOfThread(thread as unknown as AgentThread); + if (result === "Untitled") { + result = ""; + } + return result; + }, [thread, isNewThread]); const handleSubmit = useSubmitThread({ isNewThread, From a663bcc37be426408cec6269d2be9d5e4b2cfff1 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 16:10:58 +0800 Subject: [PATCH 051/302] feat: merge the last thinking with the previous group --- .../workspace/messages/message-list-item.tsx | 7 ----- frontend/src/core/messages/utils.ts | 30 +++++++++---------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/workspace/messages/message-list-item.tsx b/frontend/src/components/workspace/messages/message-list-item.tsx index cc6ee6e4c..0f0c156f4 100644 --- a/frontend/src/components/workspace/messages/message-list-item.tsx +++ b/frontend/src/components/workspace/messages/message-list-item.tsx @@ -70,13 +70,6 @@ function MessageContent_({ const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading); return ( - {hasReasoning(message) && ( - - )} {extractContentFromMessage(message)} diff --git a/frontend/src/core/messages/utils.ts b/frontend/src/core/messages/utils.ts index bfa8f8079..ec7e759ac 100644 --- a/frontend/src/core/messages/utils.ts +++ b/frontend/src/core/messages/utils.ts @@ -2,29 +2,17 @@ import type { Message } from "@langchain/langgraph-sdk"; export function groupMessages( messages: Message[], - mapper: ( - groupedMessages: Message[], - groupIndex: number, - isLastGroup: boolean, - ) => T, + mapper: (groupedMessages: Message[]) => T, isLoading = false, ): T[] { if (messages.length === 0) { return []; } - const resultsOfGroups: T[] = []; + const groups: Message[][] = []; let currentGroup: Message[] = []; - const lastMessage = messages[messages.length - 1]!; const yieldCurrentGroup = () => { if (currentGroup.length > 0) { - const resultOfGroup = mapper( - currentGroup, - resultsOfGroups.length, - currentGroup.includes(lastMessage), - ); - if (resultOfGroup !== undefined && resultOfGroup !== null) { - resultsOfGroups.push(resultOfGroup); - } + groups.push(currentGroup); currentGroup = []; } }; @@ -47,6 +35,7 @@ export function groupMessages( isLoading) ) { if (message.tool_calls?.[0]?.name === "present_files") { + // When `present_files` called, put them into an individual group yieldCurrentGroup(); currentGroup.push(message); } else { @@ -57,6 +46,9 @@ export function groupMessages( } else { // Assistant messages with content (text or images) are shown as a group if they have content // No matter whether it has tool calls or not + if (hasReasoning(message)) { + currentGroup.push(message); + } yieldCurrentGroup(); currentGroup.push(message); } @@ -64,6 +56,14 @@ export function groupMessages( messageIndex++; } yieldCurrentGroup(); + + const resultsOfGroups: T[] = []; + for (const group of groups) { + const resultOfGroup = mapper(group); + if (resultOfGroup !== undefined && resultOfGroup !== null) { + resultsOfGroups.push(resultOfGroup); + } + } return resultsOfGroups; } From 1c74e9996fbb408794320689bc5447a1e1046511 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 16:17:59 +0800 Subject: [PATCH 052/302] dos: update backend TODOs --- backend/TODO.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 backend/TODO.md diff --git a/backend/TODO.md b/backend/TODO.md new file mode 100644 index 000000000..5036c32f1 --- /dev/null +++ b/backend/TODO.md @@ -0,0 +1,10 @@ +# TODO List + +## Features + +[ ] Launch the sandbox only after the first file system or bash tool is called +[ ] Pooling the sandbox resources to reduce the number of sandbox containers + +## Issues + +[ ] Long thinking but with empty content (answer inside thinking process) From a66d5152145b13e5559bb854e4135a2858c8dd06 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 16:25:51 +0800 Subject: [PATCH 053/302] chore: add TODO for checking duplicate files in state.artifacts --- backend/TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/TODO.md b/backend/TODO.md index 5036c32f1..97c68e077 100644 --- a/backend/TODO.md +++ b/backend/TODO.md @@ -7,4 +7,5 @@ ## Issues +[ ] Make sure that no duplicated files in `state.artifacts` [ ] Long thinking but with empty content (answer inside thinking process) From 384353d613b2a9edc5657b50615f8d4951583747 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 17:21:05 +0800 Subject: [PATCH 054/302] feat: remove ring --- frontend/src/styles/globals.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index 2f704cc5f..407b813e5 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -154,7 +154,7 @@ --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); + --ring: transparent; --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); @@ -188,7 +188,7 @@ --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); + --ring: transparent; --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); From f1c6991194ed9d89c1e2bc575bc0e3bf1a192e85 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 17:21:37 +0800 Subject: [PATCH 055/302] feat: integrated with artifacts in states --- .../app/workspace/chats/[thread_id]/page.tsx | 40 +++++++++++---- .../src/components/ai-elements/artifact.tsx | 20 ++++---- frontend/src/components/ui/select.tsx | 50 +++++++++---------- .../artifacts/artifact-file-detail.tsx | 38 +++++++++----- .../artifacts/artifact-file-list.tsx | 7 +-- .../workspace/artifacts/context.tsx | 26 ++++++---- frontend/src/core/artifacts/utils.ts | 6 +++ frontend/src/core/threads/types.ts | 1 + 8 files changed, 118 insertions(+), 70 deletions(-) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 7b4b29399..1cc824531 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -14,6 +14,7 @@ import { import { useSidebar } from "@/components/ui/sidebar"; import { ArtifactFileDetail, + ArtifactFileList, useArtifacts, } from "@/components/workspace/artifacts"; import { FlipDisplay } from "@/components/workspace/flip-display"; @@ -32,8 +33,10 @@ export default function ChatPage() { const [settings, setSettings] = useLocalSettings(); const { setOpen: setSidebarOpen } = useSidebar(); const { + artifacts, open: artifactsOpen, setOpen: setArtifactsOpen, + setArtifacts, selectedArtifact, } = useArtifacts(); @@ -65,6 +68,10 @@ export default function ChatPage() { return result; }, [thread, isNewThread]); + useEffect(() => { + setArtifacts(thread.values.artifacts); + }, [setArtifacts, thread.values.artifacts]); + const handleSubmit = useSubmitThread({ isNewThread, threadId, @@ -96,17 +103,17 @@ export default function ChatPage() {
            - {!artifactsOpen && ( - + {artifacts?.length && !artifactsOpen && ( + )} @@ -161,7 +168,7 @@ export default function ChatPage() { threadId={threadId!} /> ) : ( -
            +
            - } - title="No artifact selected" - description="Select an artifact to view its details" - /> + {thread.values.artifacts?.length === 0 ? ( + } + title="No artifact selected" + description="Select an artifact to view its details" + /> + ) : ( +
            +
            +

            Artifacts

            +
            +
            + +
            +
            + )}
            )}
            diff --git a/frontend/src/components/ai-elements/artifact.tsx b/frontend/src/components/ai-elements/artifact.tsx index c90cb5fe3..c42459dbd 100644 --- a/frontend/src/components/ai-elements/artifact.tsx +++ b/frontend/src/components/ai-elements/artifact.tsx @@ -16,8 +16,8 @@ export type ArtifactProps = HTMLAttributes; export const Artifact = ({ className, ...props }: ArtifactProps) => (
            @@ -31,8 +31,8 @@ export const ArtifactHeader = ({ }: ArtifactHeaderProps) => (
            @@ -49,8 +49,8 @@ export const ArtifactClose = ({ }: ArtifactCloseProps) => (
            diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx index eddadbaeb..ea453e810 100644 --- a/frontend/src/components/workspace/input-box.tsx +++ b/frontend/src/components/workspace/input-box.tsx @@ -12,6 +12,7 @@ import { PromptInputBody, PromptInputButton, PromptInputFooter, + PromptInputHeader, PromptInputSubmit, PromptInputTextarea, type PromptInputMessage, @@ -45,6 +46,7 @@ export function InputBox({ autoFocus, status = "ready", context, + showWelcome = false, onContextChange, onSubmit, onStop, @@ -53,6 +55,7 @@ export function InputBox({ assistantId?: string | null; status?: ChatStatus; context: Omit; + showWelcome?: boolean; onContextChange?: (context: Omit) => void; onSubmit?: (message: PromptInputMessage) => void; onStop?: () => void; @@ -94,7 +97,7 @@ export function InputBox({ return ( +
            👋 Hello, again!
            +
            +

            + Welcome to 🦌 DeerFlow, an open source super agent. With built-in and + custom +

            +

            + skills, DeerFlow helps you search on the web, analyze data, and + generate +

            {" "} +

            artifacts like slides, web pages and do almost anything.

            +
            +
            + ); +} From df65010e5f0121e8e33c488ca2ac7c9b62537372 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 19:47:51 +0800 Subject: [PATCH 063/302] fix: remove unused imports --- frontend/src/components/workspace/input-box.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx index ea453e810..508c4e452 100644 --- a/frontend/src/components/workspace/input-box.tsx +++ b/frontend/src/components/workspace/input-box.tsx @@ -12,7 +12,6 @@ import { PromptInputBody, PromptInputButton, PromptInputFooter, - PromptInputHeader, PromptInputSubmit, PromptInputTextarea, type PromptInputMessage, @@ -46,7 +45,6 @@ export function InputBox({ autoFocus, status = "ready", context, - showWelcome = false, onContextChange, onSubmit, onStop, From 094553ea42dc18a807fcc248ef7008c2e964936c Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 17 Jan 2026 20:32:27 +0800 Subject: [PATCH 064/302] feat: change light theme --- .../src/app/workspace/chats/[thread_id]/page.tsx | 2 +- frontend/src/components/ui/input-group.tsx | 2 +- frontend/src/components/ui/tooltip.tsx | 2 +- .../workspace/artifacts/artifact-file-detail.tsx | 2 +- frontend/src/styles/globals.css | 12 ++++++------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index d1feca6bc..b97346b0a 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -94,7 +94,7 @@ export default function ChatPage() { minSize={30} >
            -
            +
            {threadId && title !== "Untitled" && ( diff --git a/frontend/src/components/ui/input-group.tsx b/frontend/src/components/ui/input-group.tsx index 76ea1f026..f417d0d34 100644 --- a/frontend/src/components/ui/input-group.tsx +++ b/frontend/src/components/ui/input-group.tsx @@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) { data-slot="input-group" role="group" className={cn( - "group/input-group border-input/0 dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none", + "group/input-group border-input/50 dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none", "h-9 min-w-0 has-[>textarea]:h-auto", // Variants based on alignment. diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx index ac4a426e6..d073269fe 100644 --- a/frontend/src/components/ui/tooltip.tsx +++ b/frontend/src/components/ui/tooltip.tsx @@ -46,7 +46,7 @@ function TooltipContent({ data-slot="tooltip-content" sideOffset={sideOffset} className={cn( - "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-background/80 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md border px-3 py-1.5 text-xs text-balance text-white", + "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-background/80 text-foreground z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md border px-3 py-1.5 text-xs text-balance", className, )} {...props} diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 661f93c81..5ec2d0219 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -52,7 +52,7 @@ export function ArtifactFileDetail({