mirror of https://github.com/kortix-ai/suna.git
Merge branch 'main' into unified-message-fe-be
This commit is contained in:
commit
f03503efdb
|
@ -11,7 +11,7 @@ import uuid
|
|||
|
||||
# Import the agent API module
|
||||
from agent import api as agent_api
|
||||
from sandbox.api import router as sandbox_router
|
||||
from sandbox import api as sandbox_api
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
@ -36,6 +36,9 @@ async def lifespan(app: FastAPI):
|
|||
instance_id # Pass the instance_id to agent_api
|
||||
)
|
||||
|
||||
# Initialize the sandbox API with shared resources
|
||||
sandbox_api.initialize(db)
|
||||
|
||||
# Initialize Redis before restoring agent runs
|
||||
from services import redis
|
||||
await redis.initialize_async()
|
||||
|
@ -56,17 +59,17 @@ app = FastAPI(lifespan=lifespan)
|
|||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=["https://www.suna.so", "https://suna.so", "http://localhost:3000"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
allow_headers=["Content-Type", "Authorization"],
|
||||
)
|
||||
|
||||
# Include the agent router with a prefix
|
||||
app.include_router(agent_api.router, prefix="/api")
|
||||
|
||||
# Include the sandbox router with a prefix
|
||||
app.include_router(sandbox_router, prefix="/api")
|
||||
app.include_router(sandbox_api.router, prefix="/api")
|
||||
|
||||
@app.get("/api/health-check")
|
||||
async def health_check():
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# fly.toml app configuration file generated for backend-sparkling-sea-8773 on 2025-04-18T12:56:23+01:00
|
||||
#
|
||||
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||
#
|
||||
|
||||
app = 'backend-sparkling-sea-8773'
|
||||
primary_region = 'cdg'
|
||||
|
||||
[build]
|
||||
dockerfile = 'docker/Dockerfile'
|
||||
|
||||
[http_service]
|
||||
internal_port = 8000
|
||||
force_https = true
|
||||
auto_stop_machines = 'stop'
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
processes = ['app']
|
||||
|
||||
[[vm]]
|
||||
memory = '1gb'
|
||||
cpu_kind = 'shared'
|
||||
cpus = 1
|
|
@ -1,15 +1,27 @@
|
|||
import os
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import FastAPI, UploadFile, File, HTTPException, APIRouter, Form
|
||||
from fastapi import FastAPI, UploadFile, File, HTTPException, APIRouter, Form, Depends, Request
|
||||
from fastapi.responses import Response, JSONResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from utils.logger import logger
|
||||
from utils.auth_utils import get_current_user_id, get_user_id_from_stream_auth
|
||||
from sandbox.sandbox import get_or_start_sandbox
|
||||
from services.supabase import DBConnection
|
||||
|
||||
# TODO: ADD AUTHORIZATION TO ONLY HAVE ACCESS TO SANDBOXES OF PROJECTS U HAVE ACCESS TO
|
||||
|
||||
# Initialize shared resources
|
||||
router = APIRouter(tags=["sandbox"])
|
||||
db = None
|
||||
|
||||
def initialize(_db: DBConnection):
|
||||
"""Initialize the sandbox API with resources from the main API."""
|
||||
global db
|
||||
db = _db
|
||||
logger.info("Initialized sandbox API with database connection")
|
||||
|
||||
class FileInfo(BaseModel):
|
||||
"""Model for file information"""
|
||||
name: str
|
||||
|
@ -19,16 +31,51 @@ class FileInfo(BaseModel):
|
|||
mod_time: str
|
||||
permissions: Optional[str] = None
|
||||
|
||||
# Create a router for the Sandbox API
|
||||
router = APIRouter(tags=["sandbox"])
|
||||
async def verify_sandbox_access(client, sandbox_id: str, user_id: str):
|
||||
"""
|
||||
Verify that a user has access to a specific sandbox based on account membership.
|
||||
|
||||
Args:
|
||||
client: The Supabase client
|
||||
sandbox_id: The sandbox ID to check access for
|
||||
user_id: The user ID to check permissions for
|
||||
|
||||
Returns:
|
||||
dict: Project data containing sandbox information
|
||||
|
||||
Raises:
|
||||
HTTPException: If the user doesn't have access to the sandbox or sandbox doesn't exist
|
||||
"""
|
||||
# Find the project that owns this sandbox
|
||||
project_result = await client.table('projects').select('*').filter('sandbox->>id', 'eq', sandbox_id).execute()
|
||||
|
||||
if not project_result.data or len(project_result.data) == 0:
|
||||
raise HTTPException(status_code=404, detail="Sandbox not found")
|
||||
|
||||
project_data = project_result.data[0]
|
||||
account_id = project_data.get('account_id')
|
||||
|
||||
# Verify account membership
|
||||
if account_id:
|
||||
account_user_result = await client.schema('basejump').from_('account_user').select('account_role').eq('user_id', user_id).eq('account_id', account_id).execute()
|
||||
if account_user_result.data and len(account_user_result.data) > 0:
|
||||
return project_data
|
||||
|
||||
raise HTTPException(status_code=403, detail="Not authorized to access this sandbox")
|
||||
|
||||
@router.post("/sandboxes/{sandbox_id}/files")
|
||||
async def create_file(
|
||||
sandbox_id: str,
|
||||
path: str = Form(...),
|
||||
file: UploadFile = File(...)
|
||||
file: UploadFile = File(...),
|
||||
user_id: str = Depends(get_current_user_id)
|
||||
):
|
||||
"""Create a file in the sandbox using direct file upload"""
|
||||
client = await db.client
|
||||
|
||||
# Verify the user has access to this sandbox
|
||||
await verify_sandbox_access(client, sandbox_id, user_id)
|
||||
|
||||
try:
|
||||
# Get or start sandbox instance
|
||||
sandbox = await get_or_start_sandbox(sandbox_id)
|
||||
|
@ -47,8 +94,17 @@ async def create_file(
|
|||
|
||||
# For backward compatibility, keep the JSON version too
|
||||
@router.post("/sandboxes/{sandbox_id}/files/json")
|
||||
async def create_file_json(sandbox_id: str, file_request: dict):
|
||||
async def create_file_json(
|
||||
sandbox_id: str,
|
||||
file_request: dict,
|
||||
user_id: str = Depends(get_current_user_id)
|
||||
):
|
||||
"""Create a file in the sandbox using JSON (legacy support)"""
|
||||
client = await db.client
|
||||
|
||||
# Verify the user has access to this sandbox
|
||||
await verify_sandbox_access(client, sandbox_id, user_id)
|
||||
|
||||
try:
|
||||
# Get or start sandbox instance
|
||||
sandbox = await get_or_start_sandbox(sandbox_id)
|
||||
|
@ -74,8 +130,17 @@ async def create_file_json(sandbox_id: str, file_request: dict):
|
|||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/sandboxes/{sandbox_id}/files")
|
||||
async def list_files(sandbox_id: str, path: str):
|
||||
async def list_files(
|
||||
sandbox_id: str,
|
||||
path: str,
|
||||
user_id: str = Depends(get_current_user_id)
|
||||
):
|
||||
"""List files and directories at the specified path"""
|
||||
client = await db.client
|
||||
|
||||
# Verify the user has access to this sandbox
|
||||
await verify_sandbox_access(client, sandbox_id, user_id)
|
||||
|
||||
try:
|
||||
# Get or start sandbox instance using the async function
|
||||
sandbox = await get_or_start_sandbox(sandbox_id)
|
||||
|
@ -102,8 +167,17 @@ async def list_files(sandbox_id: str, path: str):
|
|||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/sandboxes/{sandbox_id}/files/content")
|
||||
async def read_file(sandbox_id: str, path: str):
|
||||
async def read_file(
|
||||
sandbox_id: str,
|
||||
path: str,
|
||||
user_id: str = Depends(get_current_user_id)
|
||||
):
|
||||
"""Read a file from the sandbox"""
|
||||
client = await db.client
|
||||
|
||||
# Verify the user has access to this sandbox
|
||||
await verify_sandbox_access(client, sandbox_id, user_id)
|
||||
|
||||
try:
|
||||
# Get or start sandbox instance using the async function
|
||||
sandbox = await get_or_start_sandbox(sandbox_id)
|
||||
|
@ -121,3 +195,48 @@ async def read_file(sandbox_id: str, path: str):
|
|||
except Exception as e:
|
||||
logger.error(f"Error reading file in sandbox {sandbox_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/project/{project_id}/sandbox/ensure-active")
|
||||
async def ensure_project_sandbox_active(
|
||||
project_id: str,
|
||||
user_id: str = Depends(get_current_user_id)
|
||||
):
|
||||
"""
|
||||
Ensure that a project's sandbox is active and running.
|
||||
Checks the sandbox status and starts it if it's not running.
|
||||
"""
|
||||
client = await db.client
|
||||
|
||||
# Find the project and sandbox information
|
||||
project_result = await client.table('projects').select('*').eq('project_id', project_id).execute()
|
||||
|
||||
if not project_result.data or len(project_result.data) == 0:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
|
||||
project_data = project_result.data[0]
|
||||
account_id = project_data.get('account_id')
|
||||
|
||||
# Verify account membership
|
||||
if account_id:
|
||||
account_user_result = await client.schema('basejump').from_('account_user').select('account_role').eq('user_id', user_id).eq('account_id', account_id).execute()
|
||||
if not (account_user_result.data and len(account_user_result.data) > 0):
|
||||
raise HTTPException(status_code=403, detail="Not authorized to access this project")
|
||||
|
||||
# Check if project has a sandbox
|
||||
sandbox_id = project_data.get('sandbox', {}).get('id')
|
||||
if not sandbox_id:
|
||||
raise HTTPException(status_code=404, detail="No sandbox found for this project")
|
||||
|
||||
try:
|
||||
# Get or start sandbox instance
|
||||
logger.info(f"Ensuring sandbox {sandbox_id} is active for project {project_id}")
|
||||
sandbox = await get_or_start_sandbox(sandbox_id)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"sandbox_id": sandbox_id,
|
||||
"message": "Sandbox is active"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error ensuring sandbox is active for project {project_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import os
|
||||
import requests
|
||||
from time import sleep
|
||||
|
||||
from daytona_sdk import Daytona, DaytonaConfig, CreateSandboxParams, SessionExecuteRequest, Sandbox
|
||||
from daytona_sdk import Daytona, DaytonaConfig, CreateSandboxParams, Sandbox
|
||||
from daytona_api_client.models.workspace_state import WorkspaceState
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Confirm your signup to Kortix Suna</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5; color: #1f2937;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin: 0 auto; background-color: white; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); margin-top: 40px;">
|
||||
<tr>
|
||||
<td style="padding: 40px 30px; text-align: center; background-color: #f8f9fa; border-bottom: 1px solid #e5e7eb;">
|
||||
<!-- Image logo with text fallback -->
|
||||
<div>
|
||||
<!-- Text fallback if image is blocked -->
|
||||
<div style="margin-top: 8px; font-size: 24px; font-weight: 600; letter-spacing: -0.025em;">
|
||||
<span style="color: #333;">Kortix</span><span style="color: #155dfc;"> / </span><span style="color: #333;">Suna</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<h1 style="font-size: 28px; font-weight: 500; color: #111827; margin-top: 0; margin-bottom: 24px; text-align: center; letter-spacing: -0.025em;">
|
||||
Confirm your signup to <span style="color: #155dfc;">Suna</span>
|
||||
</h1>
|
||||
|
||||
<p style="font-size: 16px; line-height: 24px; color: #6b7280; margin-bottom: 32px; text-align: center;">
|
||||
Thank you for signing up! Suna, your AI Employee, is ready to assist you. Please confirm your email to get started.
|
||||
</p>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 32px;">
|
||||
<a href="{{ .ConfirmationURL }}" style="display: inline-block; background-color: #155dfc; color: white; font-weight: 600; font-size: 15px; line-height: 20px; padding: 12px 28px; border-radius: 9999px; text-decoration: none; box-shadow: 0 4px 6px rgba(21, 93, 252, 0.2); transition: all 0.2s;">
|
||||
Confirm your email
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p style="font-size: 14px; line-height: 20px; color: #6b7280; margin-bottom: 0; text-align: center;">
|
||||
If you didn't sign up for Kortix Suna, you can safely ignore this email.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 24px 30px; text-align: center; background-color: #f8f9fa; border-top: 1px solid #e5e7eb;">
|
||||
<div style="margin-bottom: 16px;">
|
||||
<a href="https://github.com/kortix-ai/suna" style="display: inline-block; margin: 0 8px; color: #6b7280; text-decoration: none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" style="fill: currentColor;">
|
||||
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://twitter.com/kortixai" style="display: inline-block; margin: 0 8px; color: #6b7280; text-decoration: none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" style="fill: currentColor;">
|
||||
<path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<p style="font-size: 13px; color: #6b7280; margin: 0;">
|
||||
© 2024 Kortix. All rights reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Footer space -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin: 0 auto;">
|
||||
<tr>
|
||||
<td style="padding: 20px 30px; text-align: center;">
|
||||
<p style="font-size: 12px; color: #9ca3af; margin: 0;">
|
||||
Kortix AI — <span style="color: #155dfc;">Suna</span>, your AI Employee
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,362 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect, Suspense } from "react";
|
||||
import Link from "next/link";
|
||||
import { FlickeringGrid } from "@/components/home/ui/flickering-grid";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useSearchParams, useRouter, usePathname } from "next/navigation";
|
||||
|
||||
function LegalContent() {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
// Get tab from URL or default to "terms"
|
||||
const tabParam = searchParams.get("tab");
|
||||
const [activeTab, setActiveTab] = useState<"terms" | "privacy">(
|
||||
(tabParam === "terms" || tabParam === "privacy") ? tabParam : "terms"
|
||||
);
|
||||
|
||||
const tablet = useMediaQuery("(max-width: 1024px)");
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [isScrolling, setIsScrolling] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
||||
// Update the URL if it doesn't match the active tab
|
||||
if (tabParam !== activeTab) {
|
||||
updateUrl(activeTab);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Update the URL when the tab changes
|
||||
useEffect(() => {
|
||||
updateUrl(activeTab);
|
||||
}, [activeTab]);
|
||||
|
||||
// Update the active tab when URL changes
|
||||
useEffect(() => {
|
||||
if (tabParam === "terms" || tabParam === "privacy") {
|
||||
setActiveTab(tabParam);
|
||||
}
|
||||
}, [tabParam]);
|
||||
|
||||
// Function to update URL without refreshing the page
|
||||
const updateUrl = (tab: string) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
params.set("tab", tab);
|
||||
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
||||
};
|
||||
|
||||
// Handle tab change
|
||||
const handleTabChange = (tab: "terms" | "privacy") => {
|
||||
setActiveTab(tab);
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-center justify-center min-h-screen w-full">
|
||||
<section className="w-full relative overflow-hidden pb-20">
|
||||
<div className="relative flex flex-col items-center w-full px-6 pt-10">
|
||||
{/* Left side flickering grid with gradient fades - similar to hero section */}
|
||||
<div className="absolute left-0 top-0 h-[600px] w-1/3 -z-10 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-transparent to-background z-10" />
|
||||
<div className="absolute inset-x-0 top-0 h-32 bg-gradient-to-b from-background via-background/90 to-transparent z-10" />
|
||||
<div className="absolute inset-x-0 bottom-0 h-48 bg-gradient-to-t from-background via-background/90 to-transparent z-10" />
|
||||
|
||||
<FlickeringGrid
|
||||
className="h-full w-full"
|
||||
squareSize={mounted && tablet ? 2 : 2.5}
|
||||
gridGap={mounted && tablet ? 2 : 2.5}
|
||||
color="var(--secondary)"
|
||||
maxOpacity={0.4}
|
||||
flickerChance={isScrolling ? 0.01 : 0.03}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right side flickering grid with gradient fades */}
|
||||
<div className="absolute right-0 top-0 h-[600px] w-1/3 -z-10 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-l from-transparent via-transparent to-background z-10" />
|
||||
<div className="absolute inset-x-0 top-0 h-32 bg-gradient-to-b from-background via-background/90 to-transparent z-10" />
|
||||
<div className="absolute inset-x-0 bottom-0 h-48 bg-gradient-to-t from-background via-background/90 to-transparent z-10" />
|
||||
|
||||
<FlickeringGrid
|
||||
className="h-full w-full"
|
||||
squareSize={mounted && tablet ? 2 : 2.5}
|
||||
gridGap={mounted && tablet ? 2 : 2.5}
|
||||
color="var(--secondary)"
|
||||
maxOpacity={0.4}
|
||||
flickerChance={isScrolling ? 0.01 : 0.03}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Center content background with rounded bottom */}
|
||||
<div className="absolute inset-x-1/4 top-0 h-[600px] -z-20 bg-background rounded-b-xl"></div>
|
||||
|
||||
<div className="max-w-4xl w-full mx-auto">
|
||||
<div className="flex items-center justify-center mb-10 relative">
|
||||
<Link
|
||||
href="/"
|
||||
className="absolute left-0 group border border-border/50 bg-background hover:bg-accent/20 hover:border-secondary/40 rounded-full text-sm h-8 px-3 flex items-center gap-2 transition-all duration-300 shadow-sm hover:shadow-md hover:scale-105"
|
||||
>
|
||||
<ArrowLeft size={14} className="text-muted-foreground" />
|
||||
<span className="font-medium text-muted-foreground text-xs tracking-wide">Back</span>
|
||||
</Link>
|
||||
|
||||
<h1 className="text-3xl md:text-4xl font-medium tracking-tighter text-center">
|
||||
Legal <span className="text-secondary">Information</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="flex space-x-4 border-b border-border">
|
||||
<button
|
||||
onClick={() => handleTabChange("terms")}
|
||||
className={`pb-2 px-4 ${
|
||||
activeTab === "terms"
|
||||
? "border-b-2 border-secondary font-medium text-secondary"
|
||||
: "text-muted-foreground hover:text-primary/80 transition-colors"
|
||||
}`}
|
||||
>
|
||||
Terms of Service
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleTabChange("privacy")}
|
||||
className={`pb-2 px-4 ${
|
||||
activeTab === "privacy"
|
||||
? "border-b-2 border-secondary font-medium text-secondary"
|
||||
: "text-muted-foreground hover:text-primary/80 transition-colors"
|
||||
}`}
|
||||
>
|
||||
Privacy Policy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-border bg-[#F3F4F6] dark:bg-[#F9FAFB]/[0.02] p-8 shadow-sm">
|
||||
<div className="prose prose-sm max-w-none dark:prose-invert">
|
||||
{activeTab === "terms" ? (
|
||||
<div>
|
||||
<h2 className="text-2xl font-medium tracking-tight mb-4">Terms of Service</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">Last updated: {new Date().toLocaleDateString()}</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Terms of Service & Privacy Policy</h3>
|
||||
<p className="text-muted-foreground text-balance mb-4">Last updated and effective date: 13 August 2024</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">PLEASE READ THESE TERMS OF USE ("AGREEMENT" OR "TERMS OF USE" or "TERMS OF SERVICE" or "TERMS AND CONDITIONS") CAREFULLY BEFORE USING THE SERVICES OFFERED BY Kortix AI Corp (701 Tillery Street Unit 12-2521 Austin, Texas 78702, United States). THIS AGREEMENT SETS FORTH THE LEGALLY BINDING TERMS AND CONDITIONS FOR YOUR USE OF THE SUNA WEBSITE AND ALL RELATED SERVICES.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Definitions</h3>
|
||||
<ul className="text-muted-foreground space-y-1 mb-6">
|
||||
<li>"Company" refers to Kortix AI Corp (701 Tillery Street Unit 12-2521 Austin, Texas 78702, United States).</li>
|
||||
<li>"Site" refers to the Suna website, including any related features, content, or applications offered from time to time by the Company.</li>
|
||||
<li>"Service" refers to the Suna website and all related services provided by the Company, including the AI-powered agent that helps you accomplish real-world tasks.</li>
|
||||
<li>"User" refers to any individual or entity using the Site or Service.</li>
|
||||
<li>"Content" refers to any text, images, code, or other material uploaded to or generated by the Site or Service by Users.</li>
|
||||
<li>"Assets" refers to the results and outputs generated by the AI models provided by the Service, including any code, applications, or reports.</li>
|
||||
<li>"Terms of Use" refers to these terms and conditions governing the use of the Site and Service.</li>
|
||||
<li>"License" refers to the permissions granted to Users to use the Site and Service as outlined in these Terms of Use.</li>
|
||||
<li>"DMCA" refers to the Digital Millennium Copyright Act.</li>
|
||||
<li>"Fees" refers to the subscription or other payments made by Users for access to certain features or levels of the Service.</li>
|
||||
<li>"Notice Address" refers to the contact address for the Company, specifically legal@kortix.ai</li>
|
||||
<li>"Privacy Policy" refers to the document outlining how the Company collects, uses, and protects User data.</li>
|
||||
<li>"Third Party" refers to any person or entity other than the Company or the User.</li>
|
||||
<li>"AAA Rules" refers to the American Arbitration Association's Consumer Arbitration Rules.</li>
|
||||
<li>"Claim" refers to any dispute, claim, demand, or cause of action that arises between the User and the Company.</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Acceptance of Terms of Use</h3>
|
||||
<p className="text-muted-foreground text-balance mb-4">The Service is offered subject to acceptance without modification of all of these Terms of Use and all other operating rules, policies, and procedures that may be published from time to time in connection with the Services by the Company. In addition, some services offered through the Service may be subject to additional terms and conditions promulgated by the Company from time to time; your use of such services is subject to those additional terms and conditions, which are incorporated into these Terms of Use by this reference.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">The Company may, in its sole discretion, refuse to offer the Service to any person or entity and change its eligibility criteria at any time. This provision is void where prohibited by law and the right to access the Service is revoked in such jurisdictions.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Rules and Conduct</h3>
|
||||
<p className="text-muted-foreground text-balance mb-4">By using the Service, you agree that it is intended solely for the purpose of using an AI assistant to help accomplish real-world tasks through natural conversation. The Service's capabilities include browser automation, file management, web crawling, search capabilities, command-line execution, website deployment, and integration with various APIs and services. You acknowledge and agree that when using the Service, you must have the necessary rights and permissions for any content or data you incorporate. You are solely responsible for ensuring that your use of the Service is legal and that you have the necessary rights for any tasks you perform. The Company is not responsible for any content created or actions taken through the Service and disclaims all liability for any issues arising from the created content or performed actions, including but not limited to copyright infringement, illegal content, or any other legal matters.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-4">As a condition of use, you promise not to use the Service for any purpose that is prohibited by the Terms of Use. By way of example, and not as a limitation, you shall not (and shall not permit any third party to) take any action (including making use of the Site, any Assets, or our models or derivatives of our models) that:</p>
|
||||
|
||||
<ul className="text-muted-foreground space-y-1 mb-6">
|
||||
<li>would constitute a violation of any applicable law, rule, or regulation;</li>
|
||||
<li>infringes upon any intellectual property or other right of any other person or entity;</li>
|
||||
<li>is threatening, abusive, harassing, defamatory, libelous, deceptive, fraudulent, invasive of another's privacy, tortious, obscene, offensive, furthering of self-harm, or profane;</li>
|
||||
<li>creates Assets that exploit or abuse children;</li>
|
||||
<li>generates or disseminates verifiably false information with the purpose of harming others;</li>
|
||||
<li>impersonates or attempts to impersonate others;</li>
|
||||
<li>generates or disseminates personally identifying or identifiable information;</li>
|
||||
<li>creates Assets that imply or promote support of a terrorist organization;</li>
|
||||
<li>creates Assets that condone or promote violence against people based on any protected legal category.</li>
|
||||
</ul>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">You agree not to use the Service for the purpose of generating illegal or harmful applications or content.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">User Responsibility for Created Content</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">You agree not to create any content or perform any actions that are illegal, infringe on the rights of any third party, or violate any applicable law, regulation, or these Terms of Use. The Company reserves the right to remove any content or disable any action that it deems to be in violation of these Terms of Use, at its sole discretion, and without notice. You are solely responsible for any content you create or actions you perform, and you agree to indemnify and hold harmless the Company from any claims, losses, damages, or expenses arising out of or related to your created content or performed actions.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Open Source License</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">Suna is licensed under the Apache License, Version 2.0. You may obtain a copy of the License at <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank" rel="noopener noreferrer" className="text-secondary hover:underline">http://www.apache.org/licenses/LICENSE-2.0</a>. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Accuracy Disclaimer</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">The Service is provided for general assistance purposes. The analysis and results generated by the AI are not guaranteed to be error-free and should be thoroughly verified before relying on them. Users assume full responsibility for any content created or actions performed using the Service.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">DMCA and Takedowns Policy</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">The Company utilizes artificial intelligence systems to generate content and perform actions. Such generation may unintentionally involve copyrighted material or trademarks held by others. We respect rights holders internationally, and we ask our users to do the same. If you believe your copyright or trademark is being infringed by the Service, please write to legal@kortixai.com and we will process and investigate your request and take appropriate actions under the Digital Millennium Copyright Act and other applicable intellectual property laws with respect to any alleged or actual infringement.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Fees and Payments</h3>
|
||||
<p className="text-muted-foreground text-balance mb-4">The Company may offer paid Services. You can learn more about our pricing after signing up. You may sign up for a subscription, payable in U.S. dollars, that will automatically renew. You can stop using the Service and cancel your subscription at any time through the website or by emailing us at legal@kortixai.com. If you cancel your subscription, you may not receive a refund or credit for any amounts that have already been billed or paid. The Company reserves the right to change its prices at any time. If you are on a subscription plan, changes to pricing will not apply until your next renewal.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">Unless otherwise stated, your subscription fees ("Fees") do not include federal, state, local, and foreign taxes, duties, and other similar assessments ("Taxes"). You are responsible for all Taxes associated with your purchase and we may invoice you for such Taxes. You agree to timely pay such Taxes and provide us with documentation showing the payment or additional evidence that we may reasonably require. If any amount of your Fees is past due, we may suspend your access to the Services after we provide you with written notice of late payment.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Termination</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">The Company may terminate your access to all or any part of the Service at any time if you fail to comply with these Terms of Use, which may result in the forfeiture and destruction of all information associated with your account. Further, either party may terminate the Services for any reason and at any time upon written notice. If you wish to terminate your account, you may do so by following the instructions on the Service. Any fees paid hereunder are non-refundable. Upon any termination, all rights and licenses granted to you in this Agreement shall immediately terminate, but all provisions hereof which by their nature should survive termination shall survive termination, including, without limitation, warranty disclaimers, indemnity, and limitations of liability.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Dispute Resolution by Binding Arbitration</h3>
|
||||
<p className="text-muted-foreground text-balance mb-4">PLEASE READ THIS SECTION CAREFULLY, AS IT AFFECTS YOUR RIGHTS.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-4"><strong>Agreement to Arbitrate.</strong> You and the Company agree that any and all disputes, claims, demands, or causes of action ("Claims") that have arisen or may arise between you and us, whether arising out of or relating to these Terms, the Site, or any aspect of the relationship or transactions between us, will be resolved exclusively through final and binding arbitration before a neutral arbitrator, rather than in a court by a judge or jury, in accordance with the terms of this Arbitration Agreement, except that you or we may (but are not required to) assert individual Claims in small claims court if such Claims are within the scope of such court's jurisdiction.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-4"><strong>Prohibition of Class and Representative Actions.</strong> YOU AND WE AGREE THAT EACH OF US MAY BRING CLAIMS AGAINST THE OTHER ONLY ON AN INDIVIDUAL BASIS AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE ACTION OR PROCEEDING.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-4"><strong>Pre-Arbitration Dispute Resolution.</strong> Before commencing any arbitration, you agree to provide the Company with a written notice of Claim, and the Company agrees to provide you with a written notice of Claim to the extent reasonably possible based on the availability of your contact information to the Company. The Notice must describe the nature and basis of the Claim in sufficient detail and set forth the specific relief sought.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">Both parties agree that they will attempt to resolve a Claim through informal negotiation within sixty (60) calendar days from the date the Notice is received. If the Claim is not resolved within sixty (60) calendar days after the Notice is received, you or we may commence an arbitration proceeding.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Choice of Law</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">Any and all Claims shall be governed by the Federal Arbitration Act and the internal substantive laws of Singapore in all respects, without regard for the jurisdiction or forum in which the user is domiciled, resides, or is located at the time of such access or use. Except as provided in the Arbitration Agreement, all Claims will be brought in the federal or state courts in Singapore, and you and the Company each unconditionally, voluntarily, and irrevocably consent to the exclusive personal jurisdiction and venue of those courts.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Links to and From Other Websites</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">You may gain access to other websites via links on the Site. These Terms apply to the Site only and do not apply to other parties' websites. Similarly, you may have come to the Site via a link from another website. The terms of use of other websites do not apply to the Site. The Company assumes no responsibility for any terms of use or material outside of the Site accessed via any link.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Modification of Terms of Use</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">At its sole discretion, the Company may modify or replace any of the Terms of Use, or change, suspend, or discontinue the Service (including without limitation, the availability of any feature, database, or content) at any time by posting a notice on the Site or by sending you an email. The Company may also impose limits on certain features and services or restrict your access to parts or all of the Service without notice or liability. It is your responsibility to check the Terms of Use periodically for changes. Your continued use of the Service following the posting of any changes to the Terms of Use constitutes acceptance of those changes.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Trademarks and Patents</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">All Suna logos, marks, and designations are trademarks or registered trademarks of the Company. All other trademarks mentioned on this website are the property of their respective owners. The trademarks and logos displayed on this website may not be used without the prior written consent of the Company or their respective owners. Portions, features, and/or functionality of the Company's products may be protected under the Company's patent applications or patents.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Licensing Terms</h3>
|
||||
<p className="text-muted-foreground text-balance mb-4">Subject to your compliance with this Agreement, the conditions herein, and any limitations applicable to the Company or by law:</p>
|
||||
<ul className="text-muted-foreground space-y-1 mb-4">
|
||||
<li>you are granted a non-exclusive, limited, non-transferable, non-sublicensable, non-assignable, freely revocable license to access and use the Service for business or personal use;</li>
|
||||
<li>you own all Assets you create with the Services, and</li>
|
||||
<li>we hereby assign to you all rights, title, and interest in and to such Assets for your personal or commercial use.</li>
|
||||
</ul>
|
||||
<p className="text-muted-foreground text-balance mb-6">Otherwise, the Company reserves all rights not expressly granted under these Terms of Use. Each person must have a unique account, and you are responsible for any activity conducted on your account. A breach or violation of any of our Terms of Use may result in an immediate termination of your right to use our Service.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Indemnification</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">You shall defend, indemnify, and hold harmless the Company, its affiliates, and each of its, and its affiliates employees, contractors, directors, suppliers, and representatives from all liabilities, losses, claims, and expenses, including reasonable attorneys' fees, that arise from or relate to (i) your use or misuse of, or access to, the Service, or (ii) your violation of the Terms of Use or any applicable law, contract, policy, regulation, or other obligation. The Company reserves the right to assume the exclusive defense and control of any matter otherwise subject to indemnification by you, in which event you will assist and cooperate with the Company in connection therewith.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Limitation of Liability</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">IN NO EVENT SHALL THE COMPANY OR ITS DIRECTORS, EMPLOYEES, AGENTS, PARTNERS, SUPPLIERS, OR CONTENT PROVIDERS, BE LIABLE UNDER CONTRACT, TORT, STRICT LIABILITY, NEGLIGENCE, OR ANY OTHER LEGAL OR EQUITABLE THEORY WITH RESPECT TO THE SERVICE (I) FOR ANY LOST PROFITS, DATA LOSS, COST OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR SPECIAL, INDIRECT, INCIDENTAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES OF ANY KIND WHATSOVER, OR SUBSTITUTE GOODS OR SERVICES, (II) FOR YOUR RELIANCE ON THE SERVICE, INCLUDING ANY APPLICATIONS CREATED USING THE AI, OR (III) FOR ANY DIRECT DAMAGES IN EXCESS (IN THE AGGREGATE) OF THE FEES PAID BY YOU FOR THE SERVICE OR, IF GREATER, $100. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE LIMITATIONS AND EXCLUSIONS MAY NOT APPLY TO YOU.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Disclaimer</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">ALL USE OF THE SERVICE AND ANY CONTENT IS UNDERTAKEN ENTIRELY AT YOUR OWN RISK. THE SERVICE (INCLUDING, WITHOUT LIMITATION, THE SUNA WEB APP AND ANY CONTENT) IS PROVIDED "AS IS" AND "AS AVAILABLE" AND IS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE, AND ANY WARRANTIES IMPLIED BY ANY COURSE OF PERFORMANCE OR USAGE OF TRADE, ALL OF WHICH ARE EXPRESSLY DISCLAIMED. SUNA DOES NOT GUARANTEE THE ACCURACY, COMPLETENESS, OR RELIABILITY OF THE AI-GENERATED CONTENT, AND USERS ASSUME FULL RESPONSIBILITY FOR ANY APPLICATIONS CREATED USING THE SERVICE. SOME STATES DO NOT ALLOW LIMITATIONS ON HOW LONG AN IMPLIED WARRANTY LASTS, SO THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Age Requirements</h3>
|
||||
<p className="text-muted-foreground text-balance mb-4">By accessing the Services, you confirm that you're at least 18 years old and meet the minimum age of digital consent in your country. If you are not old enough to consent to our Terms of Use in your country, your parent or guardian must agree to this Agreement on your behalf.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">Please ask your parent or guardian to read these terms with you. If you're a parent or legal guardian, and you allow your teenager to use the Services, then these terms also apply to you and you're responsible for your teenager's activity on the Services. No assurances are made as to the suitability of the Assets for you.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Contact Us</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">For questions regarding the Service, you can get in touch by emailing us at <a href="mailto:legal@kortixai.com" className="text-secondary hover:underline">legal@kortixai.com</a>.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<h2 className="text-2xl font-medium tracking-tight mb-4">Privacy Policy</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">Last updated: {new Date().toLocaleDateString()}</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Privacy</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">Our commitment to privacy and data protection is reflected in this Privacy Statement which describes how we collect and process "personal information" that identifies you, like your name or email address. Any other information besides this is "non-personal information." If we store personal information with non-personal information, we'll consider that combination to be personal information.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">References to our "Services" at Suna in this statement include our website, apps, and other products and services. This statement applies to our Services that display or reference this Privacy Statement. Third-party services that we integrate with are governed under their own privacy policies.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">Suna does not collect biometric or identifying information. All data is processed securely and any data is deleted upon account removal.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Information Gathering</h3>
|
||||
<p className="text-muted-foreground text-balance mb-4">We learn information about you when:</p>
|
||||
|
||||
<p className="font-medium mb-2">You directly provide it to us.</p>
|
||||
<p className="text-muted-foreground mb-2">For example, we collect:</p>
|
||||
<ul className="text-muted-foreground space-y-1 mb-4">
|
||||
<li>Name and contact information. We collect details such as name and email address.</li>
|
||||
<li>Payment information. If you make a purchase, we collect credit card numbers, financial account information, and other payment details.</li>
|
||||
<li>Content and files. We collect and retain the videos, documents, or other files you send to us in connection with delivering our Services, including via email or chat.</li>
|
||||
</ul>
|
||||
|
||||
<p className="font-medium mb-2">We collect it automatically through our products and services.</p>
|
||||
<p className="text-muted-foreground mb-2">For instance, we collect:</p>
|
||||
<ul className="text-muted-foreground space-y-1 mb-4">
|
||||
<li>Identifiers and device information. When you visit our websites, our web servers log your Internet Protocol (IP) address and information about your device, including device identifiers, device type, operating system, browser, and other software including type, version, language, settings, and configuration.</li>
|
||||
<li>Geolocation data. Depending on your device and app settings, we collect geolocation data when you use our Services.</li>
|
||||
<li>Usage data. We log your activity on our website, including the URL of the website from which you came to our site, pages you viewed on our website, how long you spent on a page, access times, and other details about your use of and actions on our website. We also collect information about which web-elements or objects you interact with on our Service, metadata about your activity on the Service, changes in your user state, and the duration of your use of our Service.</li>
|
||||
</ul>
|
||||
|
||||
<p className="font-medium mb-2">Someone else tells us information about you.</p>
|
||||
<p className="text-muted-foreground mb-2">Third-party sources include, for example:</p>
|
||||
<ul className="text-muted-foreground space-y-1 mb-4">
|
||||
<li>Third-party partners. Third-party applications and services, including social networks you choose to connect with or interact with through our services.</li>
|
||||
<li>Service providers. Third parties that collect or provide data in connection with work they do on our behalf, for example, companies that determine your device's location based on its IP address.</li>
|
||||
</ul>
|
||||
|
||||
<p className="font-medium mb-2">When we try and understand more about you based on information you've given to us.</p>
|
||||
<p className="text-muted-foreground text-balance mb-6">We infer new information from other data we collect, including using automated means to generate information about your likely preferences or other characteristics ("inferences"). For example, we infer your general geographic location based on your IP address.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Information Use</h3>
|
||||
<p className="text-muted-foreground text-balance mb-2">We use each category of personal information about you:</p>
|
||||
<ul className="text-muted-foreground space-y-1 mb-6">
|
||||
<li>To provide you with our Services</li>
|
||||
<li>To improve and develop our Services</li>
|
||||
<li>To communicate with you</li>
|
||||
<li>To provide customer support</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Information Sharing</h3>
|
||||
<p className="text-muted-foreground text-balance mb-2">We share information about you:</p>
|
||||
<ul className="text-muted-foreground space-y-1 mb-4">
|
||||
<li>When we've asked & received your consent to share it.</li>
|
||||
<li>As needed, including to third-party service providers, to process or provide Services or products to you, but only if those entities agree to provide at least the same level of privacy protection we're committed to under this Privacy Statement.</li>
|
||||
<li>To comply with laws or to respond to lawful requests and legal processes, provided that we'll notify you unless we're legally prohibited from doing so. We'll only release personal information if we believe in good faith that it's legally required.</li>
|
||||
<li>Only if we reasonably believe it's necessary to prevent harm to the rights, property, or safety of you or others.</li>
|
||||
<li>In the event of a corporate restructuring or change in our organizational structure or status to a successor or affiliate.</li>
|
||||
</ul>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-4">Please note that some of our Services include integrations, references, or links to services provided by third parties whose privacy practices differ from ours. If you provide personal information to any of those third parties, or allow us to share personal information with them, that data is governed by their privacy statements.</p>
|
||||
|
||||
<p className="text-muted-foreground text-balance mb-6">Finally, we may share non-personal information in accordance with applicable law.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Information Protection</h3>
|
||||
<p className="text-muted-foreground text-balance mb-6">We implement physical, business, and technical security measures to safeguard your personal information. In the event of a security breach, we'll notify you so that you can take appropriate protective steps. We only keep your personal information for as long as is needed to do what we collected it for. After that, we destroy it unless required by law.</p>
|
||||
|
||||
<h3 className="text-lg font-medium tracking-tight">Contact Us</h3>
|
||||
<p className="text-muted-foreground text-balance">You can get in touch by emailing us at <a href="mailto:legal@kortixai.com" className="text-secondary hover:underline">legal@kortixai.com</a>.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 text-center pb-10">
|
||||
<Link
|
||||
href="/"
|
||||
className="group inline-flex h-10 items-center justify-center gap-2 text-sm font-medium tracking-wide rounded-full text-primary-foreground dark:text-secondary-foreground px-6 shadow-[inset_0_1px_2px_rgba(255,255,255,0.25),0_3px_3px_-1.5px_rgba(16,24,40,0.06),0_1px_1px_rgba(16,24,40,0.08)] bg-primary hover:bg-primary/90 transition-all duration-200 w-fit"
|
||||
>
|
||||
<span>Return to Home</span>
|
||||
<span className="inline-flex items-center justify-center size-5 rounded-full bg-white/20 group-hover:bg-white/30 transition-colors duration-200">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="text-white">
|
||||
<path d="M7 17L17 7M17 7H8M17 7V16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
// Wrap the LegalContent component with Suspense to handle useSearchParams()
|
||||
export default function LegalPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="flex items-center justify-center min-h-screen">Loading...</div>}>
|
||||
<LegalContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
|
@ -43,6 +43,8 @@ interface GoogleButtonOptions {
|
|||
size?: string;
|
||||
text?: string;
|
||||
shape?: string;
|
||||
logoAlignment?: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
interface GoogleNotification {
|
||||
|
@ -60,7 +62,6 @@ interface GoogleSignInProps {
|
|||
|
||||
export default function GoogleSignIn({ returnUrl }: GoogleSignInProps) {
|
||||
const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleGoogleSignIn = useCallback(async (response: GoogleSignInResponse) => {
|
||||
|
@ -80,12 +81,6 @@ export default function GoogleSignIn({ returnUrl }: GoogleSignInProps) {
|
|||
}
|
||||
}, [returnUrl]);
|
||||
|
||||
const handleCustomButtonClick = useCallback(() => {
|
||||
if (window.google) {
|
||||
window.google.accounts.id.prompt();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Assign the callback to window object so it can be called from the Google button
|
||||
window.handleGoogleSignIn = handleGoogleSignIn;
|
||||
|
@ -128,7 +123,7 @@ export default function GoogleSignIn({ returnUrl }: GoogleSignInProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Hidden div for One Tap popup */}
|
||||
{/* Google One Tap container */}
|
||||
<div
|
||||
id="g_id_onload"
|
||||
data-client_id={googleClientId}
|
||||
|
@ -139,51 +134,44 @@ export default function GoogleSignIn({ returnUrl }: GoogleSignInProps) {
|
|||
data-callback="handleGoogleSignIn"
|
||||
/>
|
||||
|
||||
{/* Custom Google button that matches site style */}
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={handleCustomButtonClick}
|
||||
disabled={isLoading}
|
||||
className={`
|
||||
group w-full h-12 flex items-center justify-center gap-3 text-sm font-medium tracking-wide
|
||||
rounded-full border border-border bg-background hover:bg-accent/5 transition-all duration-200
|
||||
relative overflow-hidden ${isLoading ? 'opacity-70 cursor-not-allowed' : ''}
|
||||
`}
|
||||
>
|
||||
{/* Google logo without background */}
|
||||
<span className="relative z-10 flex items-center justify-center">
|
||||
<svg className="w-5 h-5 relative" viewBox="0 0 24 24">
|
||||
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
|
||||
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
||||
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
|
||||
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<span className="relative z-10">{isLoading ? "Connecting..." : "Continue with Google"}</span>
|
||||
|
||||
{/* External link indicator */}
|
||||
<span className="relative z-10 inline-flex items-center justify-center size-4 rounded-full bg-muted/30 group-hover:bg-muted/50 transition-colors duration-200 ml-1">
|
||||
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="text-muted-foreground">
|
||||
<path d="M7 17L17 7M17 7H8M17 7V16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Hidden original Google button with no styles */}
|
||||
<div
|
||||
className="g_id_signin hidden"
|
||||
data-type="standard"
|
||||
data-size="large"
|
||||
data-theme="outline"
|
||||
data-text="sign_in_with"
|
||||
data-shape="rectangular"
|
||||
data-logo_alignment="left"
|
||||
/>
|
||||
{/* Google Sign-In button container styled to match site design */}
|
||||
<div id="google-signin-button" className="w-full h-12">
|
||||
{/* The Google button will be rendered here */}
|
||||
</div>
|
||||
|
||||
<Script
|
||||
src="https://accounts.google.com/gsi/client"
|
||||
strategy="afterInteractive"
|
||||
onLoad={() => {
|
||||
if (window.google && googleClientId) {
|
||||
// Style the button after Google script loads
|
||||
const buttonContainer = document.getElementById('google-signin-button');
|
||||
if (buttonContainer) {
|
||||
window.google.accounts.id.renderButton(buttonContainer, {
|
||||
type: 'standard',
|
||||
theme: 'outline',
|
||||
size: 'large',
|
||||
text: 'continue_with',
|
||||
shape: 'pill',
|
||||
logoAlignment: 'left',
|
||||
width: buttonContainer.offsetWidth
|
||||
});
|
||||
|
||||
// Apply custom styles to match site design
|
||||
setTimeout(() => {
|
||||
const googleButton = buttonContainer.querySelector('div[role="button"]');
|
||||
if (googleButton instanceof HTMLElement) {
|
||||
googleButton.style.borderRadius = '9999px';
|
||||
googleButton.style.width = '100%';
|
||||
googleButton.style.height = '56px';
|
||||
googleButton.style.border = '1px solid var(--border)';
|
||||
googleButton.style.background = 'var(--background)';
|
||||
googleButton.style.transition = 'all 0.2s';
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -120,6 +120,33 @@ export const getProject = async (projectId: string): Promise<Project> => {
|
|||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// If project has a sandbox, ensure it's started
|
||||
if (data.sandbox?.id) {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (session?.access_token) {
|
||||
console.log(`Ensuring sandbox is active for project ${projectId}...`);
|
||||
const response = await fetch(`${API_URL}/project/${projectId}/sandbox/ensure-active`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => 'No error details available');
|
||||
console.warn(`Failed to ensure sandbox is active: ${response.status} ${response.statusText}`, errorText);
|
||||
} else {
|
||||
console.log('Sandbox activation successful');
|
||||
}
|
||||
}
|
||||
} catch (sandboxError) {
|
||||
console.warn('Failed to ensure sandbox is active:', sandboxError);
|
||||
// Non-blocking error - continue with the project data
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
apiCache.setProject(projectId, data);
|
||||
|
@ -734,22 +761,20 @@ export const createSandboxFile = async (sandboxId: string, filePath: string, con
|
|||
throw new Error('No access token available');
|
||||
}
|
||||
|
||||
// Determine if content is likely binary (contains non-printable characters)
|
||||
const isProbablyBinary = /[\x00-\x08\x0E-\x1F\x80-\xFF]/.test(content) ||
|
||||
content.startsWith('data:') ||
|
||||
/^[A-Za-z0-9+/]*={0,2}$/.test(content);
|
||||
// Use FormData to handle both text and binary content more reliably
|
||||
const formData = new FormData();
|
||||
formData.append('path', filePath);
|
||||
|
||||
// Create a Blob from the content string and append as a file
|
||||
const blob = new Blob([content], { type: 'application/octet-stream' });
|
||||
formData.append('file', blob, filePath.split('/').pop() || 'file');
|
||||
|
||||
const response = await fetch(`${API_URL}/sandboxes/${sandboxId}/files`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
path: filePath,
|
||||
content: content,
|
||||
is_base64: isProbablyBinary
|
||||
}),
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
@ -765,6 +790,41 @@ export const createSandboxFile = async (sandboxId: string, filePath: string, con
|
|||
}
|
||||
};
|
||||
|
||||
// Fallback method for legacy support using JSON
|
||||
export const createSandboxFileJson = async (sandboxId: string, filePath: string, content: string): Promise<void> => {
|
||||
try {
|
||||
const supabase = createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/sandboxes/${sandboxId}/files/json`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
path: filePath,
|
||||
content: content
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => 'No error details available');
|
||||
console.error(`Error creating sandbox file (JSON): ${response.status} ${response.statusText}`, errorText);
|
||||
throw new Error(`Error creating sandbox file: ${response.statusText} (${response.status})`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
console.error('Failed to create sandbox file with JSON:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export interface FileInfo {
|
||||
name: string;
|
||||
path: string;
|
||||
|
|
|
@ -1027,8 +1027,8 @@ export const siteConfig = {
|
|||
{
|
||||
title: "Legal",
|
||||
links: [
|
||||
{ id: 9, title: "Privacy Policy", url: "https://suna.so/legal/privacy" },
|
||||
{ id: 10, title: "Terms of Service", url: "https://suna.so/legal/terms" },
|
||||
{ id: 9, title: "Privacy Policy", url: "https://suna.so/legal?tab=privacy" },
|
||||
{ id: 10, title: "Terms of Service", url: "https://suna.so/legal?tab=terms" },
|
||||
{ id: 11, title: "License Apache 2.0", url: "https://github.com/Kortix-ai/Suna/blob/main/LICENSE" },
|
||||
],
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue