chore; linting

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-02-25 01:50:28 -08:00
parent 70686a1eb2
commit d198c8b89d
16 changed files with 228 additions and 167 deletions

View file

@ -12,7 +12,12 @@ import asyncio
import logging import logging
import os import os
from daytona import CreateSandboxFromSnapshotParams, Daytona, DaytonaConfig, SandboxState from daytona import (
CreateSandboxFromSnapshotParams,
Daytona,
DaytonaConfig,
SandboxState,
)
from deepagents.backends.protocol import ExecuteResponse from deepagents.backends.protocol import ExecuteResponse
from langchain_daytona import DaytonaSandbox from langchain_daytona import DaytonaSandbox
@ -38,9 +43,12 @@ class _TimeoutAwareSandbox(DaytonaSandbox):
truncated=False, truncated=False,
) )
async def aexecute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse: # type: ignore[override] async def aexecute(
self, command: str, *, timeout: int | None = None
) -> ExecuteResponse: # type: ignore[override]
return await asyncio.to_thread(self.execute, command, timeout=timeout) return await asyncio.to_thread(self.execute, command, timeout=timeout)
_daytona_client: Daytona | None = None _daytona_client: Daytona | None = None
THREAD_LABEL_KEY = "surfsense_thread" THREAD_LABEL_KEY = "surfsense_thread"
@ -72,9 +80,7 @@ def _find_or_create(thread_id: str) -> _TimeoutAwareSandbox:
try: try:
sandbox = client.find_one(labels=labels) sandbox = client.find_one(labels=labels)
logger.info( logger.info("Found existing sandbox %s (state=%s)", sandbox.id, sandbox.state)
"Found existing sandbox %s (state=%s)", sandbox.id, sandbox.state
)
if sandbox.state in ( if sandbox.state in (
SandboxState.STOPPED, SandboxState.STOPPED,
@ -84,7 +90,11 @@ def _find_or_create(thread_id: str) -> _TimeoutAwareSandbox:
logger.info("Starting stopped sandbox %s", sandbox.id) logger.info("Starting stopped sandbox %s", sandbox.id)
sandbox.start(timeout=60) sandbox.start(timeout=60)
logger.info("Sandbox %s is now started", sandbox.id) logger.info("Sandbox %s is now started", sandbox.id)
elif sandbox.state in (SandboxState.ERROR, SandboxState.BUILD_FAILED, SandboxState.DESTROYED): elif sandbox.state in (
SandboxState.ERROR,
SandboxState.BUILD_FAILED,
SandboxState.DESTROYED,
):
logger.warning( logger.warning(
"Sandbox %s in unrecoverable state %s — creating a new one", "Sandbox %s in unrecoverable state %s — creating a new one",
sandbox.id, sandbox.id,

View file

@ -782,7 +782,12 @@ def build_surfsense_system_prompt(
tools_instructions = _get_tools_instructions(visibility) tools_instructions = _get_tools_instructions(visibility)
citation_instructions = SURFSENSE_CITATION_INSTRUCTIONS citation_instructions = SURFSENSE_CITATION_INSTRUCTIONS
sandbox_instructions = SANDBOX_EXECUTION_INSTRUCTIONS if sandbox_enabled else "" sandbox_instructions = SANDBOX_EXECUTION_INSTRUCTIONS if sandbox_enabled else ""
return system_instructions + tools_instructions + citation_instructions + sandbox_instructions return (
system_instructions
+ tools_instructions
+ citation_instructions
+ sandbox_instructions
)
def build_configurable_system_prompt( def build_configurable_system_prompt(
@ -842,7 +847,12 @@ def build_configurable_system_prompt(
sandbox_instructions = SANDBOX_EXECUTION_INSTRUCTIONS if sandbox_enabled else "" sandbox_instructions = SANDBOX_EXECUTION_INSTRUCTIONS if sandbox_enabled else ""
return system_instructions + tools_instructions + citation_instructions + sandbox_instructions return (
system_instructions
+ tools_instructions
+ citation_instructions
+ sandbox_instructions
)
def get_default_system_instructions() -> str: def get_default_system_instructions() -> str:

View file

@ -58,7 +58,9 @@ def create_create_google_drive_file_tool(
- "Create a Google Doc called 'Meeting Notes'" - "Create a Google Doc called 'Meeting Notes'"
- "Create a spreadsheet named 'Budget 2026' with some sample data" - "Create a spreadsheet named 'Budget 2026' with some sample data"
""" """
logger.info(f"create_google_drive_file called: name='{name}', type='{file_type}'") logger.info(
f"create_google_drive_file called: name='{name}', type='{file_type}'"
)
if db_session is None or search_space_id is None or user_id is None: if db_session is None or search_space_id is None or user_id is None:
return { return {
@ -74,7 +76,9 @@ def create_create_google_drive_file_tool(
try: try:
metadata_service = GoogleDriveToolMetadataService(db_session) metadata_service = GoogleDriveToolMetadataService(db_session)
context = await metadata_service.get_creation_context(search_space_id, user_id) context = await metadata_service.get_creation_context(
search_space_id, user_id
)
if "error" in context: if "error" in context:
logger.error(f"Failed to fetch creation context: {context['error']}") logger.error(f"Failed to fetch creation context: {context['error']}")
@ -100,8 +104,12 @@ def create_create_google_drive_file_tool(
} }
) )
decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else [] decisions_raw = (
decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw] approval.get("decisions", []) if isinstance(approval, dict) else []
)
decisions = (
decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
)
decisions = [d for d in decisions if isinstance(d, dict)] decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions: if not decisions:
logger.warning("No approval decision received") logger.warning("No approval decision received")
@ -183,7 +191,9 @@ def create_create_google_drive_file_tool(
logger.info( logger.info(
f"Creating Google Drive file: name='{final_name}', type='{final_file_type}', connector={actual_connector_id}" f"Creating Google Drive file: name='{final_name}', type='{final_file_type}', connector={actual_connector_id}"
) )
client = GoogleDriveClient(session=db_session, connector_id=actual_connector_id) client = GoogleDriveClient(
session=db_session, connector_id=actual_connector_id
)
try: try:
created = await client.create_file( created = await client.create_file(
name=final_name, name=final_name,
@ -203,7 +213,9 @@ def create_create_google_drive_file_tool(
} }
raise raise
logger.info(f"Google Drive file created: id={created.get('id')}, name={created.get('name')}") logger.info(
f"Google Drive file created: id={created.get('id')}, name={created.get('name')}"
)
return { return {
"status": "success", "status": "success",
"file_id": created.get("id"), "file_id": created.get("id"),

View file

@ -52,7 +52,9 @@ def create_delete_google_drive_file_tool(
- "Delete the 'Meeting Notes' file from Google Drive" - "Delete the 'Meeting Notes' file from Google Drive"
- "Trash the 'Old Budget' spreadsheet" - "Trash the 'Old Budget' spreadsheet"
""" """
logger.info(f"delete_google_drive_file called: file_name='{file_name}', delete_from_kb={delete_from_kb}") logger.info(
f"delete_google_drive_file called: file_name='{file_name}', delete_from_kb={delete_from_kb}"
)
if db_session is None or search_space_id is None or user_id is None: if db_session is None or search_space_id is None or user_id is None:
return { return {
@ -103,8 +105,12 @@ def create_delete_google_drive_file_tool(
} }
) )
decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else [] decisions_raw = (
decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw] approval.get("decisions", []) if isinstance(approval, dict) else []
)
decisions = (
decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
)
decisions = [d for d in decisions if isinstance(d, dict)] decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions: if not decisions:
logger.warning("No approval decision received") logger.warning("No approval decision received")
@ -130,11 +136,16 @@ def create_delete_google_drive_file_tool(
final_params = decision["args"] final_params = decision["args"]
final_file_id = final_params.get("file_id", file_id) final_file_id = final_params.get("file_id", file_id)
final_connector_id = final_params.get("connector_id", connector_id_from_context) final_connector_id = final_params.get(
"connector_id", connector_id_from_context
)
final_delete_from_kb = final_params.get("delete_from_kb", delete_from_kb) final_delete_from_kb = final_params.get("delete_from_kb", delete_from_kb)
if not final_connector_id: if not final_connector_id:
return {"status": "error", "message": "No connector found for this file."} return {
"status": "error",
"message": "No connector found for this file.",
}
from sqlalchemy.future import select from sqlalchemy.future import select
@ -174,7 +185,9 @@ def create_delete_google_drive_file_tool(
} }
raise raise
logger.info(f"Google Drive file deleted (moved to trash): file_id={final_file_id}") logger.info(
f"Google Drive file deleted (moved to trash): file_id={final_file_id}"
)
trash_result: dict[str, Any] = { trash_result: dict[str, Any] = {
"status": "success", "status": "success",
@ -195,7 +208,9 @@ def create_delete_google_drive_file_tool(
await db_session.delete(document) await db_session.delete(document)
await db_session.commit() await db_session.commit()
deleted_from_kb = True deleted_from_kb = True
logger.info(f"Deleted document {document_id} from knowledge base") logger.info(
f"Deleted document {document_id} from knowledge base"
)
else: else:
logger.warning(f"Document {document_id} not found in KB") logger.warning(f"Document {document_id} not found in KB")
except Exception as e: except Exception as e:

View file

@ -47,6 +47,10 @@ from app.db import ChatVisibility
from .display_image import create_display_image_tool from .display_image import create_display_image_tool
from .generate_image import create_generate_image_tool from .generate_image import create_generate_image_tool
from .google_drive import (
create_create_google_drive_file_tool,
create_delete_google_drive_file_tool,
)
from .knowledge_base import create_search_knowledge_base_tool from .knowledge_base import create_search_knowledge_base_tool
from .linear import ( from .linear import (
create_create_linear_issue_tool, create_create_linear_issue_tool,
@ -55,10 +59,6 @@ from .linear import (
) )
from .link_preview import create_link_preview_tool from .link_preview import create_link_preview_tool
from .mcp_tool import load_mcp_tools from .mcp_tool import load_mcp_tools
from .google_drive import (
create_create_google_drive_file_tool,
create_delete_google_drive_file_tool,
)
from .notion import ( from .notion import (
create_create_notion_page_tool, create_create_notion_page_tool,
create_delete_notion_page_tool, create_delete_notion_page_tool,

View file

@ -73,7 +73,7 @@ async def download_sandbox_file(
try: try:
sandbox = await get_or_create_sandbox(thread_id) sandbox = await get_or_create_sandbox(thread_id)
raw_sandbox = sandbox._sandbox # noqa: SLF001 raw_sandbox = sandbox._sandbox
content: bytes = await asyncio.to_thread(raw_sandbox.fs.download_file, path) content: bytes = await asyncio.to_thread(raw_sandbox.fs.download_file, path)
except Exception as exc: except Exception as exc:
logger.warning("Sandbox file download failed for %s: %s", path, exc) logger.warning("Sandbox file download failed for %s: %s", path, exc)

View file

@ -10,14 +10,13 @@ Supports loading LLM configurations from:
""" """
import json import json
import logging
import re import re
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from uuid import UUID from uuid import UUID
import logging
from langchain_core.messages import HumanMessage from langchain_core.messages import HumanMessage
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select from sqlalchemy.future import select
@ -31,7 +30,13 @@ from app.agents.new_chat.llm_config import (
load_agent_config, load_agent_config,
load_llm_config_from_yaml, load_llm_config_from_yaml,
) )
from app.db import ChatVisibility, Document, Report, SurfsenseDocsDocument, async_session_maker from app.db import (
ChatVisibility,
Document,
Report,
SurfsenseDocsDocument,
async_session_maker,
)
from app.prompts import TITLE_GENERATION_PROMPT_TEMPLATE from app.prompts import TITLE_GENERATION_PROMPT_TEMPLATE
from app.services.chat_session_state_service import ( from app.services.chat_session_state_service import (
clear_ai_responding, clear_ai_responding,
@ -645,9 +650,15 @@ async def _stream_agent_events(
m = re.match(r"^Exit code:\s*(\d+)", raw_text) m = re.match(r"^Exit code:\s*(\d+)", raw_text)
exit_code_val = int(m.group(1)) if m else None exit_code_val = int(m.group(1)) if m else None
if exit_code_val is not None and exit_code_val == 0: if exit_code_val is not None and exit_code_val == 0:
completed_items = [*last_active_step_items, "Completed successfully"] completed_items = [
*last_active_step_items,
"Completed successfully",
]
elif exit_code_val is not None: elif exit_code_val is not None:
completed_items = [*last_active_step_items, f"Exit code: {exit_code_val}"] completed_items = [
*last_active_step_items,
f"Exit code: {exit_code_val}",
]
else: else:
completed_items = [*last_active_step_items, "Finished"] completed_items = [*last_active_step_items, "Finished"]
yield streaming_service.format_thinking_step( yield streaming_service.format_thinking_step(
@ -1037,13 +1048,18 @@ async def stream_new_chat(
# Optionally provision a sandboxed code execution environment # Optionally provision a sandboxed code execution environment
sandbox_backend = None sandbox_backend = None
from app.agents.new_chat.sandbox import is_sandbox_enabled, get_or_create_sandbox from app.agents.new_chat.sandbox import (
get_or_create_sandbox,
is_sandbox_enabled,
)
if is_sandbox_enabled(): if is_sandbox_enabled():
try: try:
sandbox_backend = await get_or_create_sandbox(chat_id) sandbox_backend = await get_or_create_sandbox(chat_id)
except Exception as sandbox_err: except Exception as sandbox_err:
logging.getLogger(__name__).warning( logging.getLogger(__name__).warning(
"Sandbox creation failed, continuing without execute tool: %s", sandbox_err "Sandbox creation failed, continuing without execute tool: %s",
sandbox_err,
) )
visibility = thread_visibility or ChatVisibility.PRIVATE visibility = thread_visibility or ChatVisibility.PRIVATE
@ -1426,13 +1442,18 @@ async def stream_resume_chat(
checkpointer = await get_checkpointer() checkpointer = await get_checkpointer()
sandbox_backend = None sandbox_backend = None
from app.agents.new_chat.sandbox import is_sandbox_enabled, get_or_create_sandbox from app.agents.new_chat.sandbox import (
get_or_create_sandbox,
is_sandbox_enabled,
)
if is_sandbox_enabled(): if is_sandbox_enabled():
try: try:
sandbox_backend = await get_or_create_sandbox(chat_id) sandbox_backend = await get_or_create_sandbox(chat_id)
except Exception as sandbox_err: except Exception as sandbox_err:
logging.getLogger(__name__).warning( logging.getLogger(__name__).warning(
"Sandbox creation failed, continuing without execute tool: %s", sandbox_err "Sandbox creation failed, continuing without execute tool: %s",
sandbox_err,
) )
visibility = thread_visibility or ChatVisibility.PRIVATE visibility = thread_visibility or ChatVisibility.PRIVATE

View file

@ -16,8 +16,8 @@ import {
Link2, Link2,
ShieldUser, ShieldUser,
Trash2, Trash2,
UserPlus,
User, User,
UserPlus,
Users, Users,
} from "lucide-react"; } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";

View file

@ -1,7 +1,7 @@
"use client"; "use client";
import { Slottable } from "@radix-ui/react-slot"; import { Slottable } from "@radix-ui/react-slot";
import { type ComponentPropsWithRef, type ReactNode, forwardRef } from "react"; import { type ComponentPropsWithRef, forwardRef, type ReactNode } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";

View file

@ -7,6 +7,7 @@ import {
Edit2, Edit2,
FileText, FileText,
Globe, Globe,
Logs,
type LucideIcon, type LucideIcon,
MessageCircle, MessageCircle,
MessageSquare, MessageSquare,
@ -14,7 +15,6 @@ import {
MoreHorizontal, MoreHorizontal,
Plug, Plug,
Plus, Plus,
Logs,
Settings, Settings,
Shield, Shield,
Trash2, Trash2,
@ -23,13 +23,13 @@ import {
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { myAccessAtom } from "@/atoms/members/members-query.atoms";
import { permissionsAtom } from "@/atoms/permissions/permissions-query.atoms";
import { import {
createRoleMutationAtom, createRoleMutationAtom,
deleteRoleMutationAtom, deleteRoleMutationAtom,
updateRoleMutationAtom, updateRoleMutationAtom,
} from "@/atoms/roles/roles-mutation.atoms"; } from "@/atoms/roles/roles-mutation.atoms";
import { permissionsAtom } from "@/atoms/permissions/permissions-query.atoms";
import { myAccessAtom } from "@/atoms/members/members-query.atoms";
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,

View file

@ -253,29 +253,31 @@ function ApprovalCard({
</div> </div>
)} )}
{/* Display mode */} {/* Display mode */}
{!isEditing && ( {!isEditing && (
<div className="space-y-2 px-4 py-3 bg-card"> <div className="space-y-2 px-4 py-3 bg-card">
<div>
<p className="text-xs font-medium text-muted-foreground">Name</p>
<p className="text-sm text-foreground">{committedArgs?.name ?? args.name}</p>
</div>
<div>
<p className="text-xs font-medium text-muted-foreground">Type</p>
<p className="text-sm text-foreground">
{FILE_TYPE_LABELS[committedArgs?.file_type ?? args.file_type] ?? committedArgs?.file_type ?? args.file_type}
</p>
</div>
{(committedArgs?.content ?? args.content) && (
<div> <div>
<p className="text-xs font-medium text-muted-foreground">Content</p> <p className="text-xs font-medium text-muted-foreground">Name</p>
<p className="line-clamp-4 text-sm whitespace-pre-wrap text-foreground"> <p className="text-sm text-foreground">{committedArgs?.name ?? args.name}</p>
{committedArgs?.content ?? args.content} </div>
<div>
<p className="text-xs font-medium text-muted-foreground">Type</p>
<p className="text-sm text-foreground">
{FILE_TYPE_LABELS[committedArgs?.file_type ?? args.file_type] ??
committedArgs?.file_type ??
args.file_type}
</p> </p>
</div> </div>
)} {(committedArgs?.content ?? args.content) && (
</div> <div>
)} <p className="text-xs font-medium text-muted-foreground">Content</p>
<p className="line-clamp-4 text-sm whitespace-pre-wrap text-foreground">
{committedArgs?.content ?? args.content}
</p>
</div>
)}
</div>
)}
{/* Edit mode */} {/* Edit mode */}
{isEditing && !decided && ( {isEditing && !decided && (
@ -341,26 +343,26 @@ function ApprovalCard({
</p> </p>
) : isEditing ? ( ) : isEditing ? (
<> <>
<Button <Button
size="sm" size="sm"
onClick={() => { onClick={() => {
const finalArgs = buildFinalArgs(); const finalArgs = buildFinalArgs();
setCommittedArgs(finalArgs); setCommittedArgs(finalArgs);
setDecided("edit"); setDecided("edit");
setIsEditing(false); setIsEditing(false);
onDecision({ onDecision({
type: "edit", type: "edit",
edited_action: { edited_action: {
name: interruptData.action_requests[0].name, name: interruptData.action_requests[0].name,
args: finalArgs, args: finalArgs,
}, },
}); });
}} }}
disabled={!canApprove} disabled={!canApprove}
> >
<CheckIcon /> <CheckIcon />
Approve with Changes Approve with Changes
</Button> </Button>
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
@ -376,25 +378,25 @@ function ApprovalCard({
) : ( ) : (
<> <>
{allowedDecisions.includes("approve") && ( {allowedDecisions.includes("approve") && (
<Button <Button
size="sm" size="sm"
onClick={() => { onClick={() => {
const finalArgs = buildFinalArgs(); const finalArgs = buildFinalArgs();
setCommittedArgs(finalArgs); setCommittedArgs(finalArgs);
setDecided("approve"); setDecided("approve");
onDecision({ onDecision({
type: "approve", type: "approve",
edited_action: { edited_action: {
name: interruptData.action_requests[0].name, name: interruptData.action_requests[0].name,
args: finalArgs, args: finalArgs,
}, },
}); });
}} }}
disabled={!canApprove} disabled={!canApprove}
> >
<CheckIcon /> <CheckIcon />
Approve Approve
</Button> </Button>
)} )}
{canEdit && ( {canEdit && (
<Button size="sm" variant="outline" onClick={() => setIsEditing(true)}> <Button size="sm" variant="outline" onClick={() => setIsEditing(true)}>

View file

@ -390,9 +390,7 @@ function WarningCard({ result }: { result: WarningResult }) {
</div> </div>
</div> </div>
<div className="space-y-2 px-4 py-3"> <div className="space-y-2 px-4 py-3">
{result.message && ( {result.message && <p className="text-sm text-muted-foreground">{result.message}</p>}
<p className="text-sm text-muted-foreground">{result.message}</p>
)}
<p className="text-xs text-amber-600 dark:text-amber-500">{result.warning}</p> <p className="text-xs text-amber-600 dark:text-amber-500">{result.warning}</p>
</div> </div>
</div> </div>

View file

@ -78,6 +78,13 @@ export {
type SerializablePlan, type SerializablePlan,
type TodoStatus, type TodoStatus,
} from "./plan"; } from "./plan";
export {
type ExecuteArgs,
ExecuteArgsSchema,
type ExecuteResult,
ExecuteResultSchema,
SandboxExecuteToolUI,
} from "./sandbox-execute";
export { export {
type ScrapeWebpageArgs, type ScrapeWebpageArgs,
ScrapeWebpageArgsSchema, ScrapeWebpageArgsSchema,
@ -98,11 +105,4 @@ export {
SaveMemoryResultSchema, SaveMemoryResultSchema,
SaveMemoryToolUI, SaveMemoryToolUI,
} from "./user-memory"; } from "./user-memory";
export {
type ExecuteArgs,
ExecuteArgsSchema,
type ExecuteResult,
ExecuteResultSchema,
SandboxExecuteToolUI,
} from "./sandbox-execute";
export { type WriteTodosData, WriteTodosSchema, WriteTodosToolUI } from "./write-todos"; export { type WriteTodosData, WriteTodosSchema, WriteTodosToolUI } from "./write-todos";

View file

@ -15,14 +15,10 @@ import { useCallback, useMemo, useState } from "react";
import { z } from "zod"; import { z } from "zod";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { getBearerToken } from "@/lib/auth-utils"; import { getBearerToken } from "@/lib/auth-utils";
import { BACKEND_URL } from "@/lib/env-config"; import { BACKEND_URL } from "@/lib/env-config";
import { cn } from "@/lib/utils";
// ============================================================================ // ============================================================================
// Zod Schemas // Zod Schemas
@ -75,9 +71,7 @@ function extractSandboxFiles(text: string): SandboxFile[] {
while ((match = SANDBOX_FILE_RE.exec(text)) !== null) { while ((match = SANDBOX_FILE_RE.exec(text)) !== null) {
const filePath = match[1].trim(); const filePath = match[1].trim();
if (filePath) { if (filePath) {
const name = filePath.includes("/") const name = filePath.includes("/") ? filePath.split("/").pop() || filePath : filePath;
? filePath.split("/").pop() || filePath
: filePath;
files.push({ path: filePath, name }); files.push({ path: filePath, name });
} }
} }
@ -86,14 +80,24 @@ function extractSandboxFiles(text: string): SandboxFile[] {
} }
function stripSandboxFileLines(text: string): string { function stripSandboxFileLines(text: string): string {
return text.replace(/^SANDBOX_FILE:\s*.+$/gm, "").replace(/\n{3,}/g, "\n\n").trim(); return text
.replace(/^SANDBOX_FILE:\s*.+$/gm, "")
.replace(/\n{3,}/g, "\n\n")
.trim();
} }
function parseExecuteResult(result: ExecuteResult): ParsedOutput { function parseExecuteResult(result: ExecuteResult): ParsedOutput {
const raw = result.result || result.output || ""; const raw = result.result || result.output || "";
if (result.error) { if (result.error) {
return { exitCode: null, output: result.error, displayOutput: result.error, truncated: false, isError: true, files: [] }; return {
exitCode: null,
output: result.error,
displayOutput: result.error,
truncated: false,
isError: true,
files: [],
};
} }
if (result.exit_code !== undefined && result.exit_code !== null) { if (result.exit_code !== undefined && result.exit_code !== null) {
@ -127,7 +131,14 @@ function parseExecuteResult(result: ExecuteResult): ParsedOutput {
} }
if (raw.startsWith("Error:")) { if (raw.startsWith("Error:")) {
return { exitCode: null, output: raw, displayOutput: raw, truncated: false, isError: true, files: [] }; return {
exitCode: null,
output: raw,
displayOutput: raw,
truncated: false,
isError: true,
files: [],
};
} }
const files = extractSandboxFiles(raw); const files = extractSandboxFiles(raw);
@ -240,9 +251,7 @@ function SandboxFileDownload({ file, threadId }: { file: SandboxFile; threadId:
)} )}
<FileIcon className="size-3 text-zinc-400" /> <FileIcon className="size-3 text-zinc-400" />
<span className="truncate max-w-[200px]">{file.name}</span> <span className="truncate max-w-[200px]">{file.name}</span>
{error && ( {error && <span className="text-destructive text-[10px] ml-1">{error}</span>}
<span className="text-destructive text-[10px] ml-1">{error}</span>
)}
</Button> </Button>
); );
} }
@ -270,14 +279,11 @@ function ExecuteCompleted({
variant={success ? "secondary" : "destructive"} variant={success ? "secondary" : "destructive"}
className={cn( className={cn(
"ml-auto gap-1 text-[10px] px-1.5 py-0", "ml-auto gap-1 text-[10px] px-1.5 py-0",
success && "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20" success &&
"bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20"
)} )}
> >
{success ? ( {success ? <CheckCircle2Icon className="size-3" /> : <XCircleIcon className="size-3" />}
<CheckCircle2Icon className="size-3" />
) : (
<XCircleIcon className="size-3" />
)}
{parsed.exitCode} {parsed.exitCode}
</Badge> </Badge>
); );
@ -306,7 +312,10 @@ function ExecuteCompleted({
{truncateCommand(command)} {truncateCommand(command)}
</code> </code>
{hasFiles && !open && ( {hasFiles && !open && (
<Badge variant="outline" className="gap-1 text-[10px] px-1.5 py-0 border-blue-500/30 text-blue-500"> <Badge
variant="outline"
className="gap-1 text-[10px] px-1.5 py-0 border-blue-500/30 text-blue-500"
>
<FileIcon className="size-2.5" /> <FileIcon className="size-2.5" />
{parsed.files.length} {parsed.files.length}
</Badge> </Badge>
@ -355,11 +364,7 @@ function ExecuteCompleted({
</p> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{parsed.files.map((file) => ( {parsed.files.map((file) => (
<SandboxFileDownload <SandboxFileDownload key={file.path} file={file} threadId={threadId} />
key={file.path}
file={file}
threadId={threadId}
/>
))} ))}
</div> </div>
</div> </div>
@ -412,9 +417,4 @@ export const SandboxExecuteToolUI = makeAssistantToolUI<ExecuteArgs, ExecuteResu
}, },
}); });
export { export { ExecuteArgsSchema, ExecuteResultSchema, type ExecuteArgs, type ExecuteResult };
ExecuteArgsSchema,
ExecuteResultSchema,
type ExecuteArgs,
type ExecuteResult,
};

View file

@ -40,7 +40,7 @@ function ExpandedGifOverlay({
className="max-h-[90vh] max-w-[90vw] cursor-pointer rounded-2xl shadow-2xl" className="max-h-[90vh] max-w-[90vw] cursor-pointer rounded-2xl shadow-2xl"
/> />
</motion.div>, </motion.div>,
document.body, document.body
); );
} }

View file

@ -18,8 +18,7 @@ const carouselItems = [
}, },
{ {
title: "Search & Citation", title: "Search & Citation",
description: description: "Ask questions and get cited responses from your knowledge base.",
"Ask questions and get cited responses from your knowledge base.",
src: "/homepage/hero_tutorial/BSNCGif.gif", src: "/homepage/hero_tutorial/BSNCGif.gif",
}, },
{ {
@ -121,9 +120,7 @@ function HeroCarouselCard({
<h3 className="truncate text-base font-semibold text-neutral-900 sm:text-xl dark:text-white"> <h3 className="truncate text-base font-semibold text-neutral-900 sm:text-xl dark:text-white">
{title} {title}
</h3> </h3>
<p className="text-sm text-neutral-500 dark:text-neutral-400"> <p className="text-sm text-neutral-500 dark:text-neutral-400">{description}</p>
{description}
</p>
</div> </div>
</div> </div>
<div <div
@ -141,11 +138,7 @@ function HeroCarouselCard({
className="w-full rounded-lg sm:rounded-xl" className="w-full rounded-lg sm:rounded-xl"
/> />
) : frozenFrame ? ( ) : frozenFrame ? (
<img <img src={frozenFrame} alt={title} className="w-full rounded-lg sm:rounded-xl" />
src={frozenFrame}
alt={title}
className="w-full rounded-lg sm:rounded-xl"
/>
) : ( ) : (
<div className="aspect-video w-full rounded-lg bg-neutral-100 sm:rounded-xl dark:bg-neutral-800" /> <div className="aspect-video w-full rounded-lg bg-neutral-100 sm:rounded-xl dark:bg-neutral-800" />
)} )}
@ -174,7 +167,7 @@ function HeroCarousel() {
directionRef.current = newIndex >= activeIndex ? "forward" : "backward"; directionRef.current = newIndex >= activeIndex ? "forward" : "backward";
setActiveIndex(newIndex); setActiveIndex(newIndex);
}, },
[activeIndex], [activeIndex]
); );
useEffect(() => { useEffect(() => {
@ -246,7 +239,7 @@ function HeroCarousel() {
blur: t * 6, blur: t * 6,
}; };
}, },
[activeIndex, cardWidth, baseOffset, stackGap], [activeIndex, cardWidth, baseOffset, stackGap]
); );
return ( return (
@ -287,18 +280,18 @@ function HeroCarousel() {
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }} transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }}
> >
<motion.div <motion.div
animate={{ filter: `blur(${style.blur}px)` }} animate={{ filter: `blur(${style.blur}px)` }}
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }} transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }}
> >
<HeroCarouselCard <HeroCarouselCard
index={i} index={i}
title={item.title} title={item.title}
description={item.description} description={item.description}
src={item.src} src={item.src}
isActive={i === activeIndex} isActive={i === activeIndex}
onExpandedChange={setIsGifExpanded} onExpandedChange={setIsGifExpanded}
/> />
</motion.div> </motion.div>
<motion.div <motion.div
className="pointer-events-none absolute inset-0 rounded-2xl bg-black sm:rounded-3xl" className="pointer-events-none absolute inset-0 rounded-2xl bg-black sm:rounded-3xl"
animate={{ opacity: style.overlayOpacity }} animate={{ opacity: style.overlayOpacity }}