mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-17 10:12:02 -05:00
Compare commits
8 Commits
docs/podma
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
905373a712 | ||
|
|
ee9d39bc0f | ||
|
|
05aaf7a85e | ||
|
|
9d4dcbd9e0 | ||
|
|
074be7aea6 | ||
|
|
39d28b24fc | ||
|
|
bf79a7748a | ||
|
|
649d4ab7f5 |
9
.github/workflows/platform-backend-ci.yml
vendored
9
.github/workflows/platform-backend-ci.yml
vendored
@@ -41,13 +41,18 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 6379:6379
|
- 6379:6379
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
image: rabbitmq:3.12-management
|
image: rabbitmq:4.1.4
|
||||||
ports:
|
ports:
|
||||||
- 5672:5672
|
- 5672:5672
|
||||||
- 15672:15672
|
|
||||||
env:
|
env:
|
||||||
RABBITMQ_DEFAULT_USER: ${{ env.RABBITMQ_DEFAULT_USER }}
|
RABBITMQ_DEFAULT_USER: ${{ env.RABBITMQ_DEFAULT_USER }}
|
||||||
RABBITMQ_DEFAULT_PASS: ${{ env.RABBITMQ_DEFAULT_PASS }}
|
RABBITMQ_DEFAULT_PASS: ${{ env.RABBITMQ_DEFAULT_PASS }}
|
||||||
|
options: >-
|
||||||
|
--health-cmd "rabbitmq-diagnostics -q ping"
|
||||||
|
--health-interval 30s
|
||||||
|
--health-timeout 10s
|
||||||
|
--health-retries 5
|
||||||
|
--health-start-period 10s
|
||||||
clamav:
|
clamav:
|
||||||
image: clamav/clamav-debian:latest
|
image: clamav/clamav-debian:latest
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
6
.github/workflows/platform-frontend-ci.yml
vendored
6
.github/workflows/platform-frontend-ci.yml
vendored
@@ -6,10 +6,16 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- ".github/workflows/platform-frontend-ci.yml"
|
- ".github/workflows/platform-frontend-ci.yml"
|
||||||
- "autogpt_platform/frontend/**"
|
- "autogpt_platform/frontend/**"
|
||||||
|
- "autogpt_platform/backend/Dockerfile"
|
||||||
|
- "autogpt_platform/docker-compose.yml"
|
||||||
|
- "autogpt_platform/docker-compose.platform.yml"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/platform-frontend-ci.yml"
|
- ".github/workflows/platform-frontend-ci.yml"
|
||||||
- "autogpt_platform/frontend/**"
|
- "autogpt_platform/frontend/**"
|
||||||
|
- "autogpt_platform/backend/Dockerfile"
|
||||||
|
- "autogpt_platform/docker-compose.yml"
|
||||||
|
- "autogpt_platform/docker-compose.platform.yml"
|
||||||
merge_group:
|
merge_group:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
|||||||
@@ -53,63 +53,6 @@ COPY autogpt_platform/backend/backend/data/partial_types.py ./backend/data/parti
|
|||||||
COPY autogpt_platform/backend/gen_prisma_types_stub.py ./
|
COPY autogpt_platform/backend/gen_prisma_types_stub.py ./
|
||||||
RUN poetry run prisma generate && poetry run gen-prisma-stub
|
RUN poetry run prisma generate && poetry run gen-prisma-stub
|
||||||
|
|
||||||
# ============================== BACKEND SERVER ============================== #
|
|
||||||
|
|
||||||
FROM debian:13-slim AS server
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENV POETRY_HOME=/opt/poetry \
|
|
||||||
POETRY_NO_INTERACTION=1 \
|
|
||||||
POETRY_VIRTUALENVS_CREATE=true \
|
|
||||||
POETRY_VIRTUALENVS_IN_PROJECT=true \
|
|
||||||
DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV PATH=/opt/poetry/bin:$PATH
|
|
||||||
|
|
||||||
# Install Python, FFmpeg, ImageMagick, and CLI tools for agent use.
|
|
||||||
# bubblewrap provides OS-level sandbox (whitelist-only FS + no network)
|
|
||||||
# for the bash_exec MCP tool.
|
|
||||||
# Using --no-install-recommends saves ~650MB by skipping unnecessary deps like llvm, mesa, etc.
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
python3.13 \
|
|
||||||
python3-pip \
|
|
||||||
ffmpeg \
|
|
||||||
imagemagick \
|
|
||||||
jq \
|
|
||||||
ripgrep \
|
|
||||||
tree \
|
|
||||||
bubblewrap \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY --from=builder /usr/local/lib/python3* /usr/local/lib/python3*
|
|
||||||
COPY --from=builder /usr/local/bin/poetry /usr/local/bin/poetry
|
|
||||||
# Copy Node.js installation for Prisma
|
|
||||||
COPY --from=builder /usr/bin/node /usr/bin/node
|
|
||||||
COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules
|
|
||||||
COPY --from=builder /usr/bin/npm /usr/bin/npm
|
|
||||||
COPY --from=builder /usr/bin/npx /usr/bin/npx
|
|
||||||
COPY --from=builder /root/.cache/prisma-python/binaries /root/.cache/prisma-python/binaries
|
|
||||||
|
|
||||||
WORKDIR /app/autogpt_platform/backend
|
|
||||||
|
|
||||||
# Copy only the .venv from builder (not the entire /app directory)
|
|
||||||
# The .venv includes the generated Prisma client
|
|
||||||
COPY --from=builder /app/autogpt_platform/backend/.venv ./.venv
|
|
||||||
ENV PATH="/app/autogpt_platform/backend/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
# Copy dependency files + autogpt_libs (path dependency)
|
|
||||||
COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs
|
|
||||||
COPY autogpt_platform/backend/poetry.lock autogpt_platform/backend/pyproject.toml ./
|
|
||||||
|
|
||||||
# Copy backend code + docs (for Copilot docs search)
|
|
||||||
COPY autogpt_platform/backend ./
|
|
||||||
COPY docs /app/docs
|
|
||||||
RUN poetry install --no-ansi --only-root
|
|
||||||
|
|
||||||
ENV PORT=8000
|
|
||||||
|
|
||||||
CMD ["poetry", "run", "rest"]
|
|
||||||
|
|
||||||
# =============================== DB MIGRATOR =============================== #
|
# =============================== DB MIGRATOR =============================== #
|
||||||
|
|
||||||
# Lightweight migrate stage - only needs Prisma CLI, not full Python environment
|
# Lightweight migrate stage - only needs Prisma CLI, not full Python environment
|
||||||
@@ -141,3 +84,59 @@ COPY autogpt_platform/backend/schema.prisma ./
|
|||||||
COPY autogpt_platform/backend/backend/data/partial_types.py ./backend/data/partial_types.py
|
COPY autogpt_platform/backend/backend/data/partial_types.py ./backend/data/partial_types.py
|
||||||
COPY autogpt_platform/backend/gen_prisma_types_stub.py ./
|
COPY autogpt_platform/backend/gen_prisma_types_stub.py ./
|
||||||
COPY autogpt_platform/backend/migrations ./migrations
|
COPY autogpt_platform/backend/migrations ./migrations
|
||||||
|
|
||||||
|
# ============================== BACKEND SERVER ============================== #
|
||||||
|
|
||||||
|
FROM debian:13-slim AS server
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Install Python, FFmpeg, ImageMagick, and CLI tools for agent use.
|
||||||
|
# bubblewrap provides OS-level sandbox (whitelist-only FS + no network)
|
||||||
|
# for the bash_exec MCP tool.
|
||||||
|
# Using --no-install-recommends saves ~650MB by skipping unnecessary deps like llvm, mesa, etc.
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
python3.13 \
|
||||||
|
python3-pip \
|
||||||
|
ffmpeg \
|
||||||
|
imagemagick \
|
||||||
|
jq \
|
||||||
|
ripgrep \
|
||||||
|
tree \
|
||||||
|
bubblewrap \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy poetry (build-time only, for `poetry install --only-root` to create entry points)
|
||||||
|
COPY --from=builder /usr/local/lib/python3* /usr/local/lib/python3*
|
||||||
|
COPY --from=builder /usr/local/bin/poetry /usr/local/bin/poetry
|
||||||
|
# Copy Node.js installation for Prisma
|
||||||
|
COPY --from=builder /usr/bin/node /usr/bin/node
|
||||||
|
COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules
|
||||||
|
COPY --from=builder /usr/bin/npm /usr/bin/npm
|
||||||
|
COPY --from=builder /usr/bin/npx /usr/bin/npx
|
||||||
|
COPY --from=builder /root/.cache/prisma-python/binaries /root/.cache/prisma-python/binaries
|
||||||
|
|
||||||
|
WORKDIR /app/autogpt_platform/backend
|
||||||
|
|
||||||
|
# Copy only the .venv from builder (not the entire /app directory)
|
||||||
|
# The .venv includes the generated Prisma client
|
||||||
|
COPY --from=builder /app/autogpt_platform/backend/.venv ./.venv
|
||||||
|
ENV PATH="/app/autogpt_platform/backend/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Copy dependency files + autogpt_libs (path dependency)
|
||||||
|
COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs
|
||||||
|
COPY autogpt_platform/backend/poetry.lock autogpt_platform/backend/pyproject.toml ./
|
||||||
|
|
||||||
|
# Copy backend code + docs (for Copilot docs search)
|
||||||
|
COPY autogpt_platform/backend ./
|
||||||
|
COPY docs /app/docs
|
||||||
|
# Install the project package to create entry point scripts in .venv/bin/
|
||||||
|
# (e.g., rest, executor, ws, db, scheduler, notification - see [tool.poetry.scripts])
|
||||||
|
RUN POETRY_VIRTUALENVS_CREATE=true POETRY_VIRTUALENVS_IN_PROJECT=true \
|
||||||
|
poetry install --no-ansi --only-root
|
||||||
|
|
||||||
|
ENV PORT=8000
|
||||||
|
|
||||||
|
CMD ["rest"]
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from .model import (
|
|||||||
ChatSession,
|
ChatSession,
|
||||||
append_and_save_message,
|
append_and_save_message,
|
||||||
create_chat_session,
|
create_chat_session,
|
||||||
|
delete_chat_session,
|
||||||
get_chat_session,
|
get_chat_session,
|
||||||
get_user_sessions,
|
get_user_sessions,
|
||||||
)
|
)
|
||||||
@@ -211,6 +212,43 @@ async def create_session(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/sessions/{session_id}",
|
||||||
|
dependencies=[Security(auth.requires_user)],
|
||||||
|
status_code=204,
|
||||||
|
responses={404: {"description": "Session not found or access denied"}},
|
||||||
|
)
|
||||||
|
async def delete_session(
|
||||||
|
session_id: str,
|
||||||
|
user_id: Annotated[str, Security(auth.get_user_id)],
|
||||||
|
) -> Response:
|
||||||
|
"""
|
||||||
|
Delete a chat session.
|
||||||
|
|
||||||
|
Permanently removes a chat session and all its messages.
|
||||||
|
Only the owner can delete their sessions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session_id: The session ID to delete.
|
||||||
|
user_id: The authenticated user's ID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
204 No Content on success.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: 404 if session not found or not owned by user.
|
||||||
|
"""
|
||||||
|
deleted = await delete_chat_session(session_id, user_id)
|
||||||
|
|
||||||
|
if not deleted:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Session {session_id} not found or access denied",
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(status_code=204)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/sessions/{session_id}",
|
"/sessions/{session_id}",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -104,8 +104,8 @@ def _get_linear_config() -> tuple[LinearClient, str, str]:
|
|||||||
Raises RuntimeError if any required setting is missing.
|
Raises RuntimeError if any required setting is missing.
|
||||||
"""
|
"""
|
||||||
secrets = _get_settings().secrets
|
secrets = _get_settings().secrets
|
||||||
if not secrets.linear_api_key:
|
if not secrets.copilot_linear_api_key:
|
||||||
raise RuntimeError("LINEAR_API_KEY is not configured")
|
raise RuntimeError("COPILOT_LINEAR_API_KEY is not configured")
|
||||||
if not secrets.linear_feature_request_project_id:
|
if not secrets.linear_feature_request_project_id:
|
||||||
raise RuntimeError("LINEAR_FEATURE_REQUEST_PROJECT_ID is not configured")
|
raise RuntimeError("LINEAR_FEATURE_REQUEST_PROJECT_ID is not configured")
|
||||||
if not secrets.linear_feature_request_team_id:
|
if not secrets.linear_feature_request_team_id:
|
||||||
@@ -114,7 +114,7 @@ def _get_linear_config() -> tuple[LinearClient, str, str]:
|
|||||||
credentials = APIKeyCredentials(
|
credentials = APIKeyCredentials(
|
||||||
id="system-linear",
|
id="system-linear",
|
||||||
provider="linear",
|
provider="linear",
|
||||||
api_key=SecretStr(secrets.linear_api_key),
|
api_key=SecretStr(secrets.copilot_linear_api_key),
|
||||||
title="System Linear API Key",
|
title="System Linear API Key",
|
||||||
)
|
)
|
||||||
client = LinearClient(credentials=credentials)
|
client = LinearClient(credentials=credentials)
|
||||||
|
|||||||
@@ -662,7 +662,7 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
|
|||||||
mem0_api_key: str = Field(default="", description="Mem0 API key")
|
mem0_api_key: str = Field(default="", description="Mem0 API key")
|
||||||
elevenlabs_api_key: str = Field(default="", description="ElevenLabs API key")
|
elevenlabs_api_key: str = Field(default="", description="ElevenLabs API key")
|
||||||
|
|
||||||
linear_api_key: str = Field(
|
copilot_linear_api_key: str = Field(
|
||||||
default="", description="Linear API key for system-level operations"
|
default="", description="Linear API key for system-level operations"
|
||||||
)
|
)
|
||||||
linear_feature_request_project_id: str = Field(
|
linear_feature_request_project_id: str = Field(
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ services:
|
|||||||
|
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
<<: *agpt-services
|
<<: *agpt-services
|
||||||
image: rabbitmq:management
|
image: rabbitmq:4.1.4
|
||||||
container_name: rabbitmq
|
container_name: rabbitmq
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: rabbitmq-diagnostics -q ping
|
test: rabbitmq-diagnostics -q ping
|
||||||
@@ -66,7 +66,6 @@ services:
|
|||||||
- RABBITMQ_DEFAULT_PASS=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7
|
- RABBITMQ_DEFAULT_PASS=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7
|
||||||
ports:
|
ports:
|
||||||
- "5672:5672"
|
- "5672:5672"
|
||||||
- "15672:15672"
|
|
||||||
clamav:
|
clamav:
|
||||||
image: clamav/clamav-debian:latest
|
image: clamav/clamav-debian:latest
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
image: rabbitmq:management
|
image: rabbitmq:4.1.4
|
||||||
container_name: rabbitmq
|
container_name: rabbitmq
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: rabbitmq-diagnostics -q ping
|
test: rabbitmq-diagnostics -q ping
|
||||||
@@ -88,14 +88,13 @@ services:
|
|||||||
<<: *backend-env
|
<<: *backend-env
|
||||||
ports:
|
ports:
|
||||||
- "5672:5672"
|
- "5672:5672"
|
||||||
- "15672:15672"
|
|
||||||
|
|
||||||
rest_server:
|
rest_server:
|
||||||
build:
|
build:
|
||||||
context: ../
|
context: ../
|
||||||
dockerfile: autogpt_platform/backend/Dockerfile
|
dockerfile: autogpt_platform/backend/Dockerfile
|
||||||
target: server
|
target: server
|
||||||
command: ["python", "-m", "backend.rest"]
|
command: ["rest"] # points to entry in [tool.poetry.scripts] in pyproject.toml
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
- path: ./
|
- path: ./
|
||||||
@@ -128,7 +127,7 @@ services:
|
|||||||
context: ../
|
context: ../
|
||||||
dockerfile: autogpt_platform/backend/Dockerfile
|
dockerfile: autogpt_platform/backend/Dockerfile
|
||||||
target: server
|
target: server
|
||||||
command: ["python", "-m", "backend.exec"]
|
command: ["executor"] # points to entry in [tool.poetry.scripts] in pyproject.toml
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
- path: ./
|
- path: ./
|
||||||
@@ -163,7 +162,7 @@ services:
|
|||||||
context: ../
|
context: ../
|
||||||
dockerfile: autogpt_platform/backend/Dockerfile
|
dockerfile: autogpt_platform/backend/Dockerfile
|
||||||
target: server
|
target: server
|
||||||
command: ["python", "-m", "backend.ws"]
|
command: ["ws"] # points to entry in [tool.poetry.scripts] in pyproject.toml
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
- path: ./
|
- path: ./
|
||||||
@@ -196,7 +195,7 @@ services:
|
|||||||
context: ../
|
context: ../
|
||||||
dockerfile: autogpt_platform/backend/Dockerfile
|
dockerfile: autogpt_platform/backend/Dockerfile
|
||||||
target: server
|
target: server
|
||||||
command: ["python", "-m", "backend.db"]
|
command: ["db"] # points to entry in [tool.poetry.scripts] in pyproject.toml
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
- path: ./
|
- path: ./
|
||||||
@@ -225,7 +224,7 @@ services:
|
|||||||
context: ../
|
context: ../
|
||||||
dockerfile: autogpt_platform/backend/Dockerfile
|
dockerfile: autogpt_platform/backend/Dockerfile
|
||||||
target: server
|
target: server
|
||||||
command: ["python", "-m", "backend.scheduler"]
|
command: ["scheduler"] # points to entry in [tool.poetry.scripts] in pyproject.toml
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
- path: ./
|
- path: ./
|
||||||
@@ -273,7 +272,7 @@ services:
|
|||||||
context: ../
|
context: ../
|
||||||
dockerfile: autogpt_platform/backend/Dockerfile
|
dockerfile: autogpt_platform/backend/Dockerfile
|
||||||
target: server
|
target: server
|
||||||
command: ["python", "-m", "backend.notification"]
|
command: ["notification"] # points to entry in [tool.poetry.scripts] in pyproject.toml
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
- path: ./
|
- path: ./
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
"@rjsf/validator-ajv8": "6.1.2",
|
"@rjsf/validator-ajv8": "6.1.2",
|
||||||
"@sentry/nextjs": "10.27.0",
|
"@sentry/nextjs": "10.27.0",
|
||||||
"@streamdown/cjk": "1.0.1",
|
"@streamdown/cjk": "1.0.1",
|
||||||
"@streamdown/code": "1.0.1",
|
|
||||||
"@streamdown/math": "1.0.1",
|
"@streamdown/math": "1.0.1",
|
||||||
"@streamdown/mermaid": "1.0.1",
|
"@streamdown/mermaid": "1.0.1",
|
||||||
"@supabase/ssr": "0.7.0",
|
"@supabase/ssr": "0.7.0",
|
||||||
@@ -116,6 +115,7 @@
|
|||||||
"remark-gfm": "4.0.1",
|
"remark-gfm": "4.0.1",
|
||||||
"remark-math": "6.0.0",
|
"remark-math": "6.0.0",
|
||||||
"shepherd.js": "14.5.1",
|
"shepherd.js": "14.5.1",
|
||||||
|
"shiki": "^3.21.0",
|
||||||
"sonner": "2.0.7",
|
"sonner": "2.0.7",
|
||||||
"streamdown": "2.1.0",
|
"streamdown": "2.1.0",
|
||||||
"tailwind-merge": "2.6.0",
|
"tailwind-merge": "2.6.0",
|
||||||
|
|||||||
16
autogpt_platform/frontend/pnpm-lock.yaml
generated
16
autogpt_platform/frontend/pnpm-lock.yaml
generated
@@ -108,9 +108,6 @@ importers:
|
|||||||
'@streamdown/cjk':
|
'@streamdown/cjk':
|
||||||
specifier: 1.0.1
|
specifier: 1.0.1
|
||||||
version: 1.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@18.3.1)(unified@11.0.5)
|
version: 1.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@18.3.1)(unified@11.0.5)
|
||||||
'@streamdown/code':
|
|
||||||
specifier: 1.0.1
|
|
||||||
version: 1.0.1(react@18.3.1)
|
|
||||||
'@streamdown/math':
|
'@streamdown/math':
|
||||||
specifier: 1.0.1
|
specifier: 1.0.1
|
||||||
version: 1.0.1(react@18.3.1)
|
version: 1.0.1(react@18.3.1)
|
||||||
@@ -270,6 +267,9 @@ importers:
|
|||||||
shepherd.js:
|
shepherd.js:
|
||||||
specifier: 14.5.1
|
specifier: 14.5.1
|
||||||
version: 14.5.1
|
version: 14.5.1
|
||||||
|
shiki:
|
||||||
|
specifier: ^3.21.0
|
||||||
|
version: 3.21.0
|
||||||
sonner:
|
sonner:
|
||||||
specifier: 2.0.7
|
specifier: 2.0.7
|
||||||
version: 2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -3307,11 +3307,6 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.0.0 || ^19.0.0
|
react: ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
'@streamdown/code@1.0.1':
|
|
||||||
resolution: {integrity: sha512-U9LITfQ28tZYAoY922jdtw1ryg4kgRBdURopqK9hph7G2fBUwPeHthjH7SvaV0fvFv7EqjqCzARJuWUljLe9Ag==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^18.0.0 || ^19.0.0
|
|
||||||
|
|
||||||
'@streamdown/math@1.0.1':
|
'@streamdown/math@1.0.1':
|
||||||
resolution: {integrity: sha512-R9WdHbpERiRU7WeO7oT1aIbnLJ/jraDr89F7X9x2OM//Y8G8UMATRnLD/RUwg4VLr8Nu7QSIJ0Pa8lXd2meM4Q==}
|
resolution: {integrity: sha512-R9WdHbpERiRU7WeO7oT1aIbnLJ/jraDr89F7X9x2OM//Y8G8UMATRnLD/RUwg4VLr8Nu7QSIJ0Pa8lXd2meM4Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -11907,11 +11902,6 @@ snapshots:
|
|||||||
- micromark-util-types
|
- micromark-util-types
|
||||||
- unified
|
- unified
|
||||||
|
|
||||||
'@streamdown/code@1.0.1(react@18.3.1)':
|
|
||||||
dependencies:
|
|
||||||
react: 18.3.1
|
|
||||||
shiki: 3.21.0
|
|
||||||
|
|
||||||
'@streamdown/math@1.0.1(react@18.3.1)':
|
'@streamdown/math@1.0.1(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
katex: 0.16.28
|
katex: 0.16.28
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/molecules/DropdownMenu/DropdownMenu";
|
||||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||||
|
import { DotsThree } from "@phosphor-icons/react";
|
||||||
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
|
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
|
||||||
import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar";
|
import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar";
|
||||||
|
import { DeleteChatDialog } from "./components/DeleteChatDialog/DeleteChatDialog";
|
||||||
import { MobileDrawer } from "./components/MobileDrawer/MobileDrawer";
|
import { MobileDrawer } from "./components/MobileDrawer/MobileDrawer";
|
||||||
import { MobileHeader } from "./components/MobileHeader/MobileHeader";
|
import { MobileHeader } from "./components/MobileHeader/MobileHeader";
|
||||||
import { ScaleLoader } from "./components/ScaleLoader/ScaleLoader";
|
import { ScaleLoader } from "./components/ScaleLoader/ScaleLoader";
|
||||||
@@ -31,6 +39,12 @@ export function CopilotPage() {
|
|||||||
handleDrawerOpenChange,
|
handleDrawerOpenChange,
|
||||||
handleSelectSession,
|
handleSelectSession,
|
||||||
handleNewChat,
|
handleNewChat,
|
||||||
|
// Delete functionality
|
||||||
|
sessionToDelete,
|
||||||
|
isDeleting,
|
||||||
|
handleDeleteClick,
|
||||||
|
handleConfirmDelete,
|
||||||
|
handleCancelDelete,
|
||||||
} = useCopilotPage();
|
} = useCopilotPage();
|
||||||
|
|
||||||
if (isUserLoading || !isLoggedIn) {
|
if (isUserLoading || !isLoggedIn) {
|
||||||
@@ -60,6 +74,38 @@ export function CopilotPage() {
|
|||||||
onCreateSession={createSession}
|
onCreateSession={createSession}
|
||||||
onSend={onSend}
|
onSend={onSend}
|
||||||
onStop={stop}
|
onStop={stop}
|
||||||
|
headerSlot={
|
||||||
|
isMobile && sessionId ? (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<button
|
||||||
|
className="rounded p-1.5 hover:bg-neutral-100"
|
||||||
|
aria-label="More actions"
|
||||||
|
>
|
||||||
|
<DotsThree className="h-5 w-5 text-neutral-600" />
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
const session = sessions.find(
|
||||||
|
(s) => s.id === sessionId,
|
||||||
|
);
|
||||||
|
if (session) {
|
||||||
|
handleDeleteClick(session.id, session.title);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isDeleting}
|
||||||
|
className="text-red-600 focus:bg-red-50 focus:text-red-600"
|
||||||
|
>
|
||||||
|
Delete chat
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,6 +121,15 @@ export function CopilotPage() {
|
|||||||
onOpenChange={handleDrawerOpenChange}
|
onOpenChange={handleDrawerOpenChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{/* Delete confirmation dialog - rendered at top level for proper z-index on mobile */}
|
||||||
|
{isMobile && (
|
||||||
|
<DeleteChatDialog
|
||||||
|
session={sessionToDelete}
|
||||||
|
isDeleting={isDeleting}
|
||||||
|
onConfirm={handleConfirmDelete}
|
||||||
|
onCancel={handleCancelDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ChatInput } from "@/app/(platform)/copilot/components/ChatInput/ChatInput";
|
import { ChatInput } from "@/app/(platform)/copilot/components/ChatInput/ChatInput";
|
||||||
import { UIDataTypes, UIMessage, UITools } from "ai";
|
import { UIDataTypes, UIMessage, UITools } from "ai";
|
||||||
import { LayoutGroup, motion } from "framer-motion";
|
import { LayoutGroup, motion } from "framer-motion";
|
||||||
|
import { ReactNode } from "react";
|
||||||
import { ChatMessagesContainer } from "../ChatMessagesContainer/ChatMessagesContainer";
|
import { ChatMessagesContainer } from "../ChatMessagesContainer/ChatMessagesContainer";
|
||||||
import { CopilotChatActionsProvider } from "../CopilotChatActionsProvider/CopilotChatActionsProvider";
|
import { CopilotChatActionsProvider } from "../CopilotChatActionsProvider/CopilotChatActionsProvider";
|
||||||
import { EmptySession } from "../EmptySession/EmptySession";
|
import { EmptySession } from "../EmptySession/EmptySession";
|
||||||
@@ -16,6 +17,7 @@ export interface ChatContainerProps {
|
|||||||
onCreateSession: () => void | Promise<string>;
|
onCreateSession: () => void | Promise<string>;
|
||||||
onSend: (message: string) => void | Promise<void>;
|
onSend: (message: string) => void | Promise<void>;
|
||||||
onStop: () => void;
|
onStop: () => void;
|
||||||
|
headerSlot?: ReactNode;
|
||||||
}
|
}
|
||||||
export const ChatContainer = ({
|
export const ChatContainer = ({
|
||||||
messages,
|
messages,
|
||||||
@@ -27,6 +29,7 @@ export const ChatContainer = ({
|
|||||||
onCreateSession,
|
onCreateSession,
|
||||||
onSend,
|
onSend,
|
||||||
onStop,
|
onStop,
|
||||||
|
headerSlot,
|
||||||
}: ChatContainerProps) => {
|
}: ChatContainerProps) => {
|
||||||
const inputLayoutId = "copilot-2-chat-input";
|
const inputLayoutId = "copilot-2-chat-input";
|
||||||
|
|
||||||
@@ -41,6 +44,7 @@ export const ChatContainer = ({
|
|||||||
status={status}
|
status={status}
|
||||||
error={error}
|
error={error}
|
||||||
isLoading={isLoadingSession}
|
isLoading={isLoadingSession}
|
||||||
|
headerSlot={headerSlot}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ interface ChatMessagesContainerProps {
|
|||||||
status: string;
|
status: string;
|
||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
headerSlot?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatMessagesContainer = ({
|
export const ChatMessagesContainer = ({
|
||||||
@@ -125,6 +126,7 @@ export const ChatMessagesContainer = ({
|
|||||||
status,
|
status,
|
||||||
error,
|
error,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
headerSlot,
|
||||||
}: ChatMessagesContainerProps) => {
|
}: ChatMessagesContainerProps) => {
|
||||||
const [thinkingPhrase, setThinkingPhrase] = useState(getRandomPhrase);
|
const [thinkingPhrase, setThinkingPhrase] = useState(getRandomPhrase);
|
||||||
const lastToastTimeRef = useRef(0);
|
const lastToastTimeRef = useRef(0);
|
||||||
@@ -165,6 +167,7 @@ export const ChatMessagesContainer = ({
|
|||||||
return (
|
return (
|
||||||
<Conversation className="min-h-0 flex-1">
|
<Conversation className="min-h-0 flex-1">
|
||||||
<ConversationContent className="flex flex-1 flex-col gap-6 px-3 py-6">
|
<ConversationContent className="flex flex-1 flex-col gap-6 px-3 py-6">
|
||||||
|
{headerSlot}
|
||||||
{isLoading && messages.length === 0 && (
|
{isLoading && messages.length === 0 && (
|
||||||
<div className="flex min-h-full flex-1 items-center justify-center">
|
<div className="flex min-h-full flex-1 items-center justify-center">
|
||||||
<LoadingSpinner className="text-neutral-600" />
|
<LoadingSpinner className="text-neutral-600" />
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/chat";
|
import {
|
||||||
|
getGetV2ListSessionsQueryKey,
|
||||||
|
useDeleteV2DeleteSession,
|
||||||
|
useGetV2ListSessions,
|
||||||
|
} from "@/app/api/__generated__/endpoints/chat/chat";
|
||||||
import { Button } from "@/components/atoms/Button/Button";
|
import { Button } from "@/components/atoms/Button/Button";
|
||||||
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
|
||||||
import { Text } from "@/components/atoms/Text/Text";
|
import { Text } from "@/components/atoms/Text/Text";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/molecules/DropdownMenu/DropdownMenu";
|
||||||
|
import { toast } from "@/components/molecules/Toast/use-toast";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -12,18 +23,53 @@ import {
|
|||||||
useSidebar,
|
useSidebar,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PlusCircleIcon, PlusIcon } from "@phosphor-icons/react";
|
import { DotsThree, PlusCircleIcon, PlusIcon } from "@phosphor-icons/react";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { parseAsString, useQueryState } from "nuqs";
|
import { parseAsString, useQueryState } from "nuqs";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { DeleteChatDialog } from "../DeleteChatDialog/DeleteChatDialog";
|
||||||
|
|
||||||
export function ChatSidebar() {
|
export function ChatSidebar() {
|
||||||
const { state } = useSidebar();
|
const { state } = useSidebar();
|
||||||
const isCollapsed = state === "collapsed";
|
const isCollapsed = state === "collapsed";
|
||||||
const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString);
|
const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString);
|
||||||
|
const [sessionToDelete, setSessionToDelete] = useState<{
|
||||||
|
id: string;
|
||||||
|
title: string | null | undefined;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { data: sessionsResponse, isLoading: isLoadingSessions } =
|
const { data: sessionsResponse, isLoading: isLoadingSessions } =
|
||||||
useGetV2ListSessions({ limit: 50 });
|
useGetV2ListSessions({ limit: 50 });
|
||||||
|
|
||||||
|
const { mutate: deleteSession, isPending: isDeleting } =
|
||||||
|
useDeleteV2DeleteSession({
|
||||||
|
mutation: {
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate sessions list to refetch
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: getGetV2ListSessionsQueryKey(),
|
||||||
|
});
|
||||||
|
// If we deleted the current session, clear selection
|
||||||
|
if (sessionToDelete?.id === sessionId) {
|
||||||
|
setSessionId(null);
|
||||||
|
}
|
||||||
|
setSessionToDelete(null);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: "Failed to delete chat",
|
||||||
|
description:
|
||||||
|
error instanceof Error ? error.message : "An error occurred",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
setSessionToDelete(null);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const sessions =
|
const sessions =
|
||||||
sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : [];
|
sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : [];
|
||||||
|
|
||||||
@@ -35,6 +81,28 @@ export function ChatSidebar() {
|
|||||||
setSessionId(id);
|
setSessionId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDeleteClick(
|
||||||
|
e: React.MouseEvent,
|
||||||
|
id: string,
|
||||||
|
title: string | null | undefined,
|
||||||
|
) {
|
||||||
|
e.stopPropagation(); // Prevent session selection
|
||||||
|
if (isDeleting) return; // Prevent double-click during deletion
|
||||||
|
setSessionToDelete({ id, title });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirmDelete() {
|
||||||
|
if (sessionToDelete) {
|
||||||
|
deleteSession({ sessionId: sessionToDelete.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelDelete() {
|
||||||
|
if (!isDeleting) {
|
||||||
|
setSessionToDelete(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatDate(dateString: string) {
|
function formatDate(dateString: string) {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -61,6 +129,7 @@ export function ChatSidebar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
variant="inset"
|
variant="inset"
|
||||||
collapsible="icon"
|
collapsible="icon"
|
||||||
@@ -130,15 +199,18 @@ export function ChatSidebar() {
|
|||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
sessions.map((session) => (
|
sessions.map((session) => (
|
||||||
<button
|
<div
|
||||||
key={session.id}
|
key={session.id}
|
||||||
onClick={() => handleSelectSession(session.id)}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full rounded-lg px-3 py-2.5 text-left transition-colors",
|
"group relative w-full rounded-lg transition-colors",
|
||||||
session.id === sessionId
|
session.id === sessionId
|
||||||
? "bg-zinc-100"
|
? "bg-zinc-100"
|
||||||
: "hover:bg-zinc-50",
|
: "hover:bg-zinc-50",
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => handleSelectSession(session.id)}
|
||||||
|
className="w-full px-3 py-2.5 pr-10 text-left"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 max-w-full flex-col overflow-hidden">
|
<div className="flex min-w-0 max-w-full flex-col overflow-hidden">
|
||||||
<div className="min-w-0 max-w-full">
|
<div className="min-w-0 max-w-full">
|
||||||
@@ -159,6 +231,29 @@ export function ChatSidebar() {
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<button
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-full p-1.5 text-zinc-600 transition-all hover:bg-neutral-100"
|
||||||
|
aria-label="More actions"
|
||||||
|
>
|
||||||
|
<DotsThree className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) =>
|
||||||
|
handleDeleteClick(e, session.id, session.title)
|
||||||
|
}
|
||||||
|
disabled={isDeleting}
|
||||||
|
className="text-red-600 focus:bg-red-50 focus:text-red-600"
|
||||||
|
>
|
||||||
|
Delete chat
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -184,5 +279,13 @@ export function ChatSidebar() {
|
|||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
)}
|
)}
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
||||||
|
<DeleteChatDialog
|
||||||
|
session={sessionToDelete}
|
||||||
|
isDeleting={isDeleting}
|
||||||
|
onConfirm={handleConfirmDelete}
|
||||||
|
onCancel={handleCancelDelete}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/atoms/Button/Button";
|
||||||
|
import { Text } from "@/components/atoms/Text/Text";
|
||||||
|
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
session: { id: string; title: string | null | undefined } | null;
|
||||||
|
isDeleting: boolean;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeleteChatDialog({
|
||||||
|
session,
|
||||||
|
isDeleting,
|
||||||
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
title="Delete chat"
|
||||||
|
styling={{ maxWidth: "30rem", minWidth: "auto" }}
|
||||||
|
controlled={{
|
||||||
|
isOpen: !!session,
|
||||||
|
set: async (open) => {
|
||||||
|
if (!open && !isDeleting) {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClose={isDeleting ? undefined : onCancel}
|
||||||
|
>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Text variant="body">
|
||||||
|
Are you sure you want to delete{" "}
|
||||||
|
<Text variant="body-medium" as="span">
|
||||||
|
"{session?.title || "Untitled chat"}"
|
||||||
|
</Text>
|
||||||
|
? This action cannot be undone.
|
||||||
|
</Text>
|
||||||
|
<Dialog.Footer>
|
||||||
|
<Button variant="secondary" onClick={onCancel} disabled={isDeleting}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={onConfirm}
|
||||||
|
loading={isDeleting}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,15 +8,19 @@ interface Props {
|
|||||||
|
|
||||||
export function MobileHeader({ onOpenDrawer }: Props) {
|
export function MobileHeader({ onOpenDrawer }: Props) {
|
||||||
return (
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed z-50 flex gap-2"
|
||||||
|
style={{ left: "1rem", top: `${NAVBAR_HEIGHT_PX + 20}px` }}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="icon"
|
variant="icon"
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Open sessions"
|
aria-label="Open sessions"
|
||||||
onClick={onOpenDrawer}
|
onClick={onOpenDrawer}
|
||||||
className="fixed z-50 bg-white shadow-md"
|
className="bg-white shadow-md"
|
||||||
style={{ left: "1rem", top: `${NAVBAR_HEIGHT_PX + 20}px` }}
|
|
||||||
>
|
>
|
||||||
<ListIcon width="1.25rem" height="1.25rem" />
|
<ListIcon width="1.25rem" height="1.25rem" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/chat";
|
import {
|
||||||
|
getGetV2ListSessionsQueryKey,
|
||||||
|
useDeleteV2DeleteSession,
|
||||||
|
useGetV2ListSessions,
|
||||||
|
} from "@/app/api/__generated__/endpoints/chat/chat";
|
||||||
import { toast } from "@/components/molecules/Toast/use-toast";
|
import { toast } from "@/components/molecules/Toast/use-toast";
|
||||||
import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
||||||
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
||||||
import { useChat } from "@ai-sdk/react";
|
import { useChat } from "@ai-sdk/react";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { DefaultChatTransport } from "ai";
|
import { DefaultChatTransport } from "ai";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useChatSession } from "./useChatSession";
|
import { useChatSession } from "./useChatSession";
|
||||||
import { useLongRunningToolPolling } from "./hooks/useLongRunningToolPolling";
|
import { useLongRunningToolPolling } from "./hooks/useLongRunningToolPolling";
|
||||||
|
|
||||||
@@ -14,6 +19,11 @@ export function useCopilotPage() {
|
|||||||
const { isUserLoading, isLoggedIn } = useSupabase();
|
const { isUserLoading, isLoggedIn } = useSupabase();
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
const [pendingMessage, setPendingMessage] = useState<string | null>(null);
|
const [pendingMessage, setPendingMessage] = useState<string | null>(null);
|
||||||
|
const [sessionToDelete, setSessionToDelete] = useState<{
|
||||||
|
id: string;
|
||||||
|
title: string | null | undefined;
|
||||||
|
} | null>(null);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -24,6 +34,30 @@ export function useCopilotPage() {
|
|||||||
isCreatingSession,
|
isCreatingSession,
|
||||||
} = useChatSession();
|
} = useChatSession();
|
||||||
|
|
||||||
|
const { mutate: deleteSessionMutation, isPending: isDeleting } =
|
||||||
|
useDeleteV2DeleteSession({
|
||||||
|
mutation: {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: getGetV2ListSessionsQueryKey(),
|
||||||
|
});
|
||||||
|
if (sessionToDelete?.id === sessionId) {
|
||||||
|
setSessionId(null);
|
||||||
|
}
|
||||||
|
setSessionToDelete(null);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: "Failed to delete chat",
|
||||||
|
description:
|
||||||
|
error instanceof Error ? error.message : "An error occurred",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
setSessionToDelete(null);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
const isMobile =
|
const isMobile =
|
||||||
breakpoint === "base" || breakpoint === "sm" || breakpoint === "md";
|
breakpoint === "base" || breakpoint === "sm" || breakpoint === "md";
|
||||||
@@ -143,6 +177,26 @@ export function useCopilotPage() {
|
|||||||
if (isMobile) setIsDrawerOpen(false);
|
if (isMobile) setIsDrawerOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteClick = useCallback(
|
||||||
|
(id: string, title: string | null | undefined) => {
|
||||||
|
if (isDeleting) return;
|
||||||
|
setSessionToDelete({ id, title });
|
||||||
|
},
|
||||||
|
[isDeleting],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleConfirmDelete = useCallback(() => {
|
||||||
|
if (sessionToDelete) {
|
||||||
|
deleteSessionMutation({ sessionId: sessionToDelete.id });
|
||||||
|
}
|
||||||
|
}, [sessionToDelete, deleteSessionMutation]);
|
||||||
|
|
||||||
|
const handleCancelDelete = useCallback(() => {
|
||||||
|
if (!isDeleting) {
|
||||||
|
setSessionToDelete(null);
|
||||||
|
}
|
||||||
|
}, [isDeleting]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
messages,
|
messages,
|
||||||
@@ -165,5 +219,11 @@ export function useCopilotPage() {
|
|||||||
handleDrawerOpenChange,
|
handleDrawerOpenChange,
|
||||||
handleSelectSession,
|
handleSelectSession,
|
||||||
handleNewChat,
|
handleNewChat,
|
||||||
|
// Delete functionality
|
||||||
|
sessionToDelete,
|
||||||
|
isDeleting,
|
||||||
|
handleDeleteClick,
|
||||||
|
handleConfirmDelete,
|
||||||
|
handleCancelDelete,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1151,6 +1151,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/chat/sessions/{session_id}": {
|
"/api/chat/sessions/{session_id}": {
|
||||||
|
"delete": {
|
||||||
|
"tags": ["v2", "chat", "chat"],
|
||||||
|
"summary": "Delete Session",
|
||||||
|
"description": "Delete a chat session.\n\nPermanently removes a chat session and all its messages.\nOnly the owner can delete their sessions.\n\nArgs:\n session_id: The session ID to delete.\n user_id: The authenticated user's ID.\n\nReturns:\n 204 No Content on success.\n\nRaises:\n HTTPException: 404 if session not found or not owned by user.",
|
||||||
|
"operationId": "deleteV2DeleteSession",
|
||||||
|
"security": [{ "HTTPBearerJWT": [] }],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "session_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": { "type": "string", "title": "Session Id" }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": { "description": "Successful Response" },
|
||||||
|
"401": {
|
||||||
|
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
|
||||||
|
},
|
||||||
|
"404": { "description": "Session not found or access denied" },
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["v2", "chat", "chat"],
|
"tags": ["v2", "chat", "chat"],
|
||||||
"summary": "Get Session",
|
"summary": "Get Session",
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ const DialogFooter = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { cjk } from "@streamdown/cjk";
|
import { cjk } from "@streamdown/cjk";
|
||||||
import { code } from "@streamdown/code";
|
import { code } from "@/lib/streamdown-code-plugin";
|
||||||
import { math } from "@streamdown/math";
|
import { math } from "@streamdown/math";
|
||||||
import { mermaid } from "@streamdown/mermaid";
|
import { mermaid } from "@streamdown/mermaid";
|
||||||
import type { UIMessage } from "ai";
|
import type { UIMessage } from "ai";
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function BaseFooter({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={`flex w-full items-end justify-between gap-4 pt-6 ${className}`}
|
className={`flex w-full items-end justify-end gap-4 pt-6 ${className}`}
|
||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
70
autogpt_platform/frontend/src/lib/shiki-highlighter.ts
Normal file
70
autogpt_platform/frontend/src/lib/shiki-highlighter.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
bundledLanguages,
|
||||||
|
bundledLanguagesInfo,
|
||||||
|
createHighlighter,
|
||||||
|
type BundledLanguage,
|
||||||
|
type BundledTheme,
|
||||||
|
type HighlighterGeneric,
|
||||||
|
} from "shiki";
|
||||||
|
|
||||||
|
export type { BundledLanguage, BundledTheme };
|
||||||
|
|
||||||
|
const LANGUAGE_ALIASES: Record<string, string> = Object.fromEntries(
|
||||||
|
bundledLanguagesInfo.flatMap((lang) =>
|
||||||
|
(lang.aliases ?? []).map((alias) => [alias, lang.id]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const SUPPORTED_LANGUAGES = new Set(Object.keys(bundledLanguages));
|
||||||
|
|
||||||
|
const PRELOAD_LANGUAGES: BundledLanguage[] = [
|
||||||
|
"javascript",
|
||||||
|
"typescript",
|
||||||
|
"python",
|
||||||
|
"json",
|
||||||
|
"bash",
|
||||||
|
"yaml",
|
||||||
|
"markdown",
|
||||||
|
"html",
|
||||||
|
"css",
|
||||||
|
"sql",
|
||||||
|
"tsx",
|
||||||
|
"jsx",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SHIKI_THEMES: [BundledTheme, BundledTheme] = [
|
||||||
|
"github-light",
|
||||||
|
"github-dark",
|
||||||
|
];
|
||||||
|
|
||||||
|
let highlighterPromise: Promise<
|
||||||
|
HighlighterGeneric<BundledLanguage, BundledTheme>
|
||||||
|
> | null = null;
|
||||||
|
|
||||||
|
export function getShikiHighlighter(): Promise<
|
||||||
|
HighlighterGeneric<BundledLanguage, BundledTheme>
|
||||||
|
> {
|
||||||
|
if (!highlighterPromise) {
|
||||||
|
highlighterPromise = createHighlighter({
|
||||||
|
themes: SHIKI_THEMES,
|
||||||
|
langs: PRELOAD_LANGUAGES,
|
||||||
|
}).catch((err) => {
|
||||||
|
highlighterPromise = null;
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return highlighterPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveLanguage(lang: string): string {
|
||||||
|
const normalized = lang.trim().toLowerCase();
|
||||||
|
return LANGUAGE_ALIASES[normalized] ?? normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLanguageSupported(lang: string): boolean {
|
||||||
|
return SUPPORTED_LANGUAGES.has(resolveLanguage(lang));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSupportedLanguages(): BundledLanguage[] {
|
||||||
|
return Array.from(SUPPORTED_LANGUAGES) as BundledLanguage[];
|
||||||
|
}
|
||||||
159
autogpt_platform/frontend/src/lib/streamdown-code-plugin.ts
Normal file
159
autogpt_platform/frontend/src/lib/streamdown-code-plugin.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import type { CodeHighlighterPlugin } from "streamdown";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type BundledLanguage,
|
||||||
|
type BundledTheme,
|
||||||
|
getShikiHighlighter,
|
||||||
|
getSupportedLanguages,
|
||||||
|
isLanguageSupported,
|
||||||
|
resolveLanguage,
|
||||||
|
SHIKI_THEMES,
|
||||||
|
} from "./shiki-highlighter";
|
||||||
|
|
||||||
|
interface HighlightResult {
|
||||||
|
tokens: {
|
||||||
|
content: string;
|
||||||
|
color?: string;
|
||||||
|
htmlStyle?: Record<string, string>;
|
||||||
|
}[][];
|
||||||
|
fg?: string;
|
||||||
|
bg?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type HighlightCallback = (result: HighlightResult) => void;
|
||||||
|
|
||||||
|
const MAX_CACHE_SIZE = 500;
|
||||||
|
const tokenCache = new Map<string, HighlightResult>();
|
||||||
|
const pendingCallbacks = new Map<string, Set<HighlightCallback>>();
|
||||||
|
const inFlightLanguageLoads = new Map<string, Promise<void>>();
|
||||||
|
|
||||||
|
function simpleHash(str: string): string {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const char = str.charCodeAt(i);
|
||||||
|
hash = (hash << 5) - hash + char;
|
||||||
|
hash = hash & hash;
|
||||||
|
}
|
||||||
|
return hash.toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCacheKey(
|
||||||
|
code: string,
|
||||||
|
lang: string,
|
||||||
|
themes: readonly string[],
|
||||||
|
): string {
|
||||||
|
return `${lang}:${themes.join(",")}:${simpleHash(code)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function evictOldestIfNeeded(): void {
|
||||||
|
if (tokenCache.size > MAX_CACHE_SIZE) {
|
||||||
|
const oldestKey = tokenCache.keys().next().value;
|
||||||
|
if (oldestKey) {
|
||||||
|
tokenCache.delete(oldestKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSingletonCodePlugin(): CodeHighlighterPlugin {
|
||||||
|
return {
|
||||||
|
name: "shiki",
|
||||||
|
type: "code-highlighter",
|
||||||
|
|
||||||
|
supportsLanguage(lang: BundledLanguage): boolean {
|
||||||
|
return isLanguageSupported(lang);
|
||||||
|
},
|
||||||
|
|
||||||
|
getSupportedLanguages(): BundledLanguage[] {
|
||||||
|
return getSupportedLanguages();
|
||||||
|
},
|
||||||
|
|
||||||
|
getThemes(): [BundledTheme, BundledTheme] {
|
||||||
|
return SHIKI_THEMES;
|
||||||
|
},
|
||||||
|
|
||||||
|
highlight({ code, language, themes }, callback) {
|
||||||
|
const lang = resolveLanguage(language);
|
||||||
|
const cacheKey = getCacheKey(code, lang, themes);
|
||||||
|
|
||||||
|
if (tokenCache.has(cacheKey)) {
|
||||||
|
return tokenCache.get(cacheKey)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
if (!pendingCallbacks.has(cacheKey)) {
|
||||||
|
pendingCallbacks.set(cacheKey, new Set());
|
||||||
|
}
|
||||||
|
pendingCallbacks.get(cacheKey)!.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
getShikiHighlighter()
|
||||||
|
.then(async (highlighter) => {
|
||||||
|
const loadedLanguages = highlighter.getLoadedLanguages();
|
||||||
|
|
||||||
|
if (!loadedLanguages.includes(lang) && isLanguageSupported(lang)) {
|
||||||
|
let loadPromise = inFlightLanguageLoads.get(lang);
|
||||||
|
if (!loadPromise) {
|
||||||
|
loadPromise = highlighter
|
||||||
|
.loadLanguage(lang as BundledLanguage)
|
||||||
|
.finally(() => {
|
||||||
|
inFlightLanguageLoads.delete(lang);
|
||||||
|
});
|
||||||
|
inFlightLanguageLoads.set(lang, loadPromise);
|
||||||
|
}
|
||||||
|
await loadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalLang = (
|
||||||
|
highlighter.getLoadedLanguages().includes(lang) ? lang : "text"
|
||||||
|
) as BundledLanguage;
|
||||||
|
|
||||||
|
const shikiResult = highlighter.codeToTokens(code, {
|
||||||
|
lang: finalLang,
|
||||||
|
themes: { light: themes[0], dark: themes[1] },
|
||||||
|
});
|
||||||
|
|
||||||
|
const result: HighlightResult = {
|
||||||
|
tokens: shikiResult.tokens.map((line) =>
|
||||||
|
line.map((token) => ({
|
||||||
|
content: token.content,
|
||||||
|
color: token.color,
|
||||||
|
htmlStyle: token.htmlStyle,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
fg: shikiResult.fg,
|
||||||
|
bg: shikiResult.bg,
|
||||||
|
};
|
||||||
|
|
||||||
|
evictOldestIfNeeded();
|
||||||
|
tokenCache.set(cacheKey, result);
|
||||||
|
|
||||||
|
const callbacks = pendingCallbacks.get(cacheKey);
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.forEach((cb) => {
|
||||||
|
cb(result);
|
||||||
|
});
|
||||||
|
pendingCallbacks.delete(cacheKey);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("[Shiki] Failed to highlight code:", error);
|
||||||
|
|
||||||
|
const fallback: HighlightResult = {
|
||||||
|
tokens: code.split("\n").map((line) => [{ content: line }]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const callbacks = pendingCallbacks.get(cacheKey);
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.forEach((cb) => {
|
||||||
|
cb(fallback);
|
||||||
|
});
|
||||||
|
pendingCallbacks.delete(cacheKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const code = createSingletonCodePlugin();
|
||||||
@@ -465,9 +465,13 @@ export async function navigateToAgentByName(
|
|||||||
export async function clickRunButton(page: Page): Promise<void> {
|
export async function clickRunButton(page: Page): Promise<void> {
|
||||||
const { getId } = getSelectors(page);
|
const { getId } = getSelectors(page);
|
||||||
|
|
||||||
// Wait for page to stabilize and buttons to render
|
// Wait for sidebar loading to complete before detecting buttons.
|
||||||
// The NewAgentLibraryView shows either "Setup your task" (empty state)
|
// During sidebar loading, the "New task" button appears transiently
|
||||||
// or "New task" (with items) button
|
// even for agents with no items, then switches to "Setup your task"
|
||||||
|
// once loading finishes. Waiting for network idle ensures the page
|
||||||
|
// has settled into its final state.
|
||||||
|
await page.waitForLoadState("networkidle");
|
||||||
|
|
||||||
const setupTaskButton = page.getByRole("button", {
|
const setupTaskButton = page.getByRole("button", {
|
||||||
name: /Setup your task/i,
|
name: /Setup your task/i,
|
||||||
});
|
});
|
||||||
@@ -475,8 +479,7 @@ export async function clickRunButton(page: Page): Promise<void> {
|
|||||||
const runButton = getId("agent-run-button");
|
const runButton = getId("agent-run-button");
|
||||||
const runAgainButton = getId("run-again-button");
|
const runAgainButton = getId("run-again-button");
|
||||||
|
|
||||||
// Use Promise.race with waitFor to wait for any of the buttons to appear
|
// Wait for any of the buttons to appear
|
||||||
// This handles the async rendering in CI environments
|
|
||||||
try {
|
try {
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
setupTaskButton.waitFor({ state: "visible", timeout: 15000 }),
|
setupTaskButton.waitFor({ state: "visible", timeout: 15000 }),
|
||||||
@@ -490,7 +493,7 @@ export async function clickRunButton(page: Page): Promise<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check which button is visible and click it
|
// Check which button is visible and click it
|
||||||
if (await setupTaskButton.isVisible()) {
|
if (await setupTaskButton.isVisible()) {
|
||||||
await setupTaskButton.click();
|
await setupTaskButton.click();
|
||||||
const startTaskButton = page
|
const startTaskButton = page
|
||||||
@@ -534,7 +537,9 @@ export async function runAgent(page: Page): Promise<void> {
|
|||||||
|
|
||||||
export async function waitForAgentPageLoad(page: Page): Promise<void> {
|
export async function waitForAgentPageLoad(page: Page): Promise<void> {
|
||||||
await page.waitForURL(/.*\/library\/agents\/[^/]+/);
|
await page.waitForURL(/.*\/library\/agents\/[^/]+/);
|
||||||
await page.getByTestId("Run actions").isVisible({ timeout: 10000 });
|
// Wait for sidebar data to finish loading so the page settles
|
||||||
|
// into its final state (empty view vs sidebar view)
|
||||||
|
await page.waitForLoadState("networkidle");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAgentName(page: Page): Promise<string> {
|
export async function getAgentName(page: Page): Promise<string> {
|
||||||
|
|||||||
@@ -218,17 +218,6 @@ If you initially installed Docker with Hyper-V, you **don’t need to reinstall*
|
|||||||
|
|
||||||
For more details, refer to [Docker's official documentation](https://docs.docker.com/desktop/windows/wsl/).
|
For more details, refer to [Docker's official documentation](https://docs.docker.com/desktop/windows/wsl/).
|
||||||
|
|
||||||
### ⚠️ Podman Not Supported
|
|
||||||
|
|
||||||
AutoGPT requires **Docker** (Docker Desktop or Docker Engine). **Podman and podman-compose are not supported** and may cause path resolution issues, particularly on Windows.
|
|
||||||
|
|
||||||
If you see errors like:
|
|
||||||
```text
|
|
||||||
Error: the specified Containerfile or Dockerfile does not exist, ..\..\autogpt_platform\backend\Dockerfile
|
|
||||||
```
|
|
||||||
|
|
||||||
This indicates you're using Podman instead of Docker. Please install [Docker Desktop](https://docs.docker.com/desktop/) and use `docker compose` instead of `podman-compose`.
|
|
||||||
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user