mirror of https://github.com/kortix-ai/suna.git
Merge branch 'PRODUCTION' of https://github.com/kortix-ai/suna into PRODUCTION
This commit is contained in:
commit
c802fa5745
|
@ -178,7 +178,7 @@ class ThreadManager:
|
|||
elif 'gemini' in llm_model.lower():
|
||||
max_tokens = 1000 * 1000 - 300000
|
||||
elif 'deepseek' in llm_model.lower():
|
||||
max_tokens = 163 * 1000 - 32000
|
||||
max_tokens = 128 * 1000 - 28000
|
||||
else:
|
||||
max_tokens = 41 * 1000 - 10000
|
||||
|
||||
|
|
|
@ -181,7 +181,7 @@ def prepare_params(
|
|||
item["cache_control"] = {"type": "ephemeral"}
|
||||
break # Apply to the first text block only for system prompt
|
||||
|
||||
# 2. Find and process relevant user and assistant messages
|
||||
# 2. Find and process relevant user and assistant messages (limit to 4 max)
|
||||
last_user_idx = -1
|
||||
second_last_user_idx = -1
|
||||
last_assistant_idx = -1
|
||||
|
@ -197,9 +197,10 @@ def prepare_params(
|
|||
if last_assistant_idx == -1:
|
||||
last_assistant_idx = i
|
||||
|
||||
# Stop searching if we've found all needed messages
|
||||
if last_user_idx != -1 and second_last_user_idx != -1 and last_assistant_idx != -1:
|
||||
break
|
||||
# Stop searching if we've found all needed messages (system, last user, second last user, last assistant)
|
||||
found_count = sum(idx != -1 for idx in [last_user_idx, second_last_user_idx, last_assistant_idx])
|
||||
if found_count >= 3:
|
||||
break
|
||||
|
||||
# Helper function to apply cache control
|
||||
def apply_cache_control(message_idx: int, message_role: str):
|
||||
|
@ -219,7 +220,9 @@ def prepare_params(
|
|||
if "cache_control" not in item:
|
||||
item["cache_control"] = {"type": "ephemeral"}
|
||||
|
||||
# Apply cache control to the identified messages
|
||||
# Apply cache control to the identified messages (max 4: system, last user, second last user, last assistant)
|
||||
# System message is always at index 0 if present
|
||||
apply_cache_control(0, "system")
|
||||
apply_cache_control(last_user_idx, "last user")
|
||||
apply_cache_control(second_last_user_idx, "second last user")
|
||||
apply_cache_control(last_assistant_idx, "last assistant")
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button';
|
|||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Loader2, Save, Sparkles } from 'lucide-react';
|
||||
import { useMCPServerDetails } from '@/hooks/react-query/mcp/use-mcp-servers';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { MCPConfiguration } from './types';
|
||||
|
@ -49,9 +49,27 @@ export const ConfigDialog: React.FC<ConfigDialogProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<DialogContent className="max-w-2xl max-h-[85vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Configure {server.displayName || server.name}</DialogTitle>
|
||||
<DialogContent className="max-w-5xl max-h-[85vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
{server.iconUrl ? (
|
||||
<div className="relative">
|
||||
<img
|
||||
src={server.iconUrl}
|
||||
alt={server.displayName || server.name}
|
||||
className="w-8 h-8 rounded-lg shadow-sm"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/20 to-transparent rounded-lg pointer-events-none" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center shadow-sm border border-primary/20">
|
||||
<Sparkles className="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<DialogTitle className="text-lg">Configure {server.displayName || server.name}</DialogTitle>
|
||||
</div>
|
||||
</div>
|
||||
<DialogDescription>
|
||||
Set up the connection and select which tools to enable for this MCP server.
|
||||
</DialogDescription>
|
||||
|
@ -62,75 +80,95 @@ export const ConfigDialog: React.FC<ConfigDialogProps> = ({
|
|||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea className="flex-1 px-1">
|
||||
<div className="space-y-6">
|
||||
{serverDetails?.connections?.[0]?.configSchema?.properties && (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-semibold">Connection Settings</h3>
|
||||
{Object.entries(serverDetails.connections[0].configSchema.properties).map(([key, schema]: [string, any]) => (
|
||||
<div key={key} className="space-y-2">
|
||||
<Label htmlFor={key}>
|
||||
{schema.title || key}
|
||||
{serverDetails.connections[0].configSchema.required?.includes(key) && (
|
||||
<span className="text-destructive ml-1">*</span>
|
||||
)}
|
||||
</Label>
|
||||
<Input
|
||||
id={key}
|
||||
type={schema.format === 'password' ? 'password' : 'text'}
|
||||
placeholder={schema.description || `Enter ${key}`}
|
||||
value={config[key] || ''}
|
||||
onChange={(e) => setConfig({ ...config, [key]: e.target.value })}
|
||||
/>
|
||||
{schema.description && (
|
||||
<p className="text-xs text-muted-foreground">{schema.description}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{serverDetails?.tools && serverDetails.tools.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold">Available Tools</h3>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{selectedTools.size} of {serverDetails.tools.length} selected
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-2 max-h-[300px] overflow-y-auto">
|
||||
{serverDetails.tools.map((tool: any) => (
|
||||
<div
|
||||
key={tool.name}
|
||||
className={cn(
|
||||
"flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors",
|
||||
selectedTools.has(tool.name)
|
||||
? "bg-primary/5 border-primary"
|
||||
: "hover:bg-muted/50"
|
||||
)}
|
||||
onClick={() => handleToolToggle(tool.name)}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedTools.has(tool.name)}
|
||||
onChange={() => {}}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">{tool.name}</div>
|
||||
{tool.description && (
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{tool.description}
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 grid grid-cols-2 gap-6 px-1">
|
||||
<div className="flex flex-col min-h-0">
|
||||
<h3 className="text-sm font-semibold flex items-center gap-2 mb-4">
|
||||
<div className="w-1 h-4 bg-primary rounded-full" />
|
||||
Connection Settings
|
||||
</h3>
|
||||
{serverDetails?.connections?.[0]?.configSchema?.properties ? (
|
||||
<ScrollArea className="border bg-muted/30 rounded-lg p-4 flex-1 min-h-0">
|
||||
<div>
|
||||
{Object.entries(serverDetails.connections[0].configSchema.properties).map(([key, schema]: [string, any]) => (
|
||||
<div key={key} className="space-y-2">
|
||||
<Label htmlFor={key} className="text-sm font-medium">
|
||||
{schema.title || key}
|
||||
{serverDetails.connections[0].configSchema.required?.includes(key) && (
|
||||
<span className="text-destructive ml-1">*</span>
|
||||
)}
|
||||
</div>
|
||||
</Label>
|
||||
<Input
|
||||
id={key}
|
||||
type={schema.format === 'password' ? 'password' : 'text'}
|
||||
placeholder={schema.description || `Enter ${key}`}
|
||||
value={config[key] || ''}
|
||||
onChange={(e) => setConfig({ ...config, [key]: e.target.value })}
|
||||
className="bg-background"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center text-muted-foreground">
|
||||
<p className="text-sm">No configuration required</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<div className="flex flex-col min-h-0">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-semibold flex items-center gap-2">
|
||||
<div className="w-1 h-4 bg-primary rounded-full" />
|
||||
Available Tools
|
||||
</h3>
|
||||
{serverDetails?.tools && serverDetails.tools.length > 0 && (
|
||||
<span className="text-xs text-muted-foreground bg-muted/50 px-2 py-1 rounded-full">
|
||||
{selectedTools.size} of {serverDetails.tools.length} selected
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{serverDetails?.tools && serverDetails.tools.length > 0 ? (
|
||||
<ScrollArea className="-mt-1 border bg-muted/30 rounded-lg p-4 flex-1 min-h-0">
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
{serverDetails.tools.map((tool: any) => (
|
||||
<div
|
||||
key={tool.name}
|
||||
className={cn(
|
||||
"flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-all duration-200",
|
||||
selectedTools.has(tool.name)
|
||||
? "bg-primary/10 border-primary/30 shadow-sm"
|
||||
: "hover:bg-muted/30 border-border/50 hover:border-border"
|
||||
)}
|
||||
onClick={() => handleToolToggle(tool.name)}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedTools.has(tool.name)}
|
||||
onChange={() => {}}
|
||||
className="mt-1 accent-primary"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">{tool.name}</div>
|
||||
{tool.description && (
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{tool.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center text-muted-foreground">
|
||||
<p className="text-sm">No tools available</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
|
@ -140,7 +178,9 @@ export const ConfigDialog: React.FC<ConfigDialogProps> = ({
|
|||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isLoading}
|
||||
className="bg-primary hover:bg-primary/90"
|
||||
>
|
||||
<Save className="h-4 w-4" />
|
||||
Save Configuration
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
|
@ -133,7 +133,7 @@ export function Navbar() {
|
|||
width={140}
|
||||
height={22}
|
||||
priority
|
||||
/>
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<NavMenu />
|
||||
|
@ -161,7 +161,7 @@ export function Navbar() {
|
|||
className="bg-secondary h-8 hidden md:flex items-center justify-center text-sm font-normal tracking-wide rounded-full text-primary-foreground dark:text-secondary-foreground w-fit px-4 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)] border border-white/[0.12]"
|
||||
href="/auth"
|
||||
>
|
||||
Signup
|
||||
Get started
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
@ -273,7 +273,7 @@ export function Navbar() {
|
|||
href="/auth"
|
||||
className="bg-secondary h-8 flex items-center justify-center text-sm font-normal tracking-wide rounded-full text-primary-foreground dark:text-secondary-foreground w-full px-4 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)] border border-white/[0.12] hover:bg-secondary/80 transition-all ease-out active:scale-95"
|
||||
>
|
||||
Signup
|
||||
Get Started
|
||||
</Link>
|
||||
)}
|
||||
<div className="flex justify-between">
|
||||
|
@ -286,5 +286,5 @@ export function Navbar() {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
</header>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
|
|
@ -116,34 +116,53 @@ export function renderMarkdownContent(
|
|||
|
||||
toolCalls.forEach((toolCall, index) => {
|
||||
const toolName = toolCall.functionName.replace(/_/g, '-');
|
||||
const IconComponent = getToolIcon(toolName);
|
||||
|
||||
// Extract primary parameter for display
|
||||
let paramDisplay = '';
|
||||
if (toolCall.parameters.file_path) {
|
||||
paramDisplay = toolCall.parameters.file_path;
|
||||
} else if (toolCall.parameters.command) {
|
||||
paramDisplay = toolCall.parameters.command;
|
||||
} else if (toolCall.parameters.query) {
|
||||
paramDisplay = toolCall.parameters.query;
|
||||
} else if (toolCall.parameters.url) {
|
||||
paramDisplay = toolCall.parameters.url;
|
||||
if (toolName === 'ask') {
|
||||
// Handle ask tool specially - extract text and attachments
|
||||
const askText = toolCall.parameters.text || '';
|
||||
const attachments = toolCall.parameters.attachments || [];
|
||||
|
||||
// Convert single attachment to array for consistent handling
|
||||
const attachmentArray = Array.isArray(attachments) ? attachments :
|
||||
(typeof attachments === 'string' ? attachments.split(',').map(a => a.trim()) : []);
|
||||
|
||||
// Render ask tool content with attachment UI
|
||||
contentParts.push(
|
||||
<div key={`ask-${match.index}-${index}`} className="space-y-3">
|
||||
<Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none break-words [&>:first-child]:mt-0 prose-headings:mt-3">{askText}</Markdown>
|
||||
{renderAttachments(attachmentArray, fileViewerHandler, sandboxId, project)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const IconComponent = getToolIcon(toolName);
|
||||
|
||||
// Extract primary parameter for display
|
||||
let paramDisplay = '';
|
||||
if (toolCall.parameters.file_path) {
|
||||
paramDisplay = toolCall.parameters.file_path;
|
||||
} else if (toolCall.parameters.command) {
|
||||
paramDisplay = toolCall.parameters.command;
|
||||
} else if (toolCall.parameters.query) {
|
||||
paramDisplay = toolCall.parameters.query;
|
||||
} else if (toolCall.parameters.url) {
|
||||
paramDisplay = toolCall.parameters.url;
|
||||
}
|
||||
|
||||
contentParts.push(
|
||||
<div key={`tool-${match.index}-${index}`} className="my-1">
|
||||
<button
|
||||
onClick={() => handleToolClick(messageId, toolName)}
|
||||
className="inline-flex items-center gap-1.5 py-1 px-1 text-xs text-muted-foreground bg-muted hover:bg-muted/80 rounded-md transition-colors cursor-pointer border border-neutral-200 dark:border-neutral-700/50"
|
||||
>
|
||||
<div className='border-2 bg-gradient-to-br from-neutral-200 to-neutral-300 dark:from-neutral-700 dark:to-neutral-800 flex items-center justify-center p-0.5 rounded-sm border-neutral-400/20 dark:border-neutral-600'>
|
||||
<IconComponent className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
</div>
|
||||
<span className="font-mono text-xs text-foreground">{getUserFriendlyToolName(toolName)}</span>
|
||||
{paramDisplay && <span className="ml-1 text-muted-foreground truncate max-w-[200px]" title={paramDisplay}>{paramDisplay}</span>}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
contentParts.push(
|
||||
<div key={`tool-${match.index}-${index}`} className="my-1">
|
||||
<button
|
||||
onClick={() => handleToolClick(messageId, toolName)}
|
||||
className="inline-flex items-center gap-1.5 py-1 px-1 text-xs text-muted-foreground bg-muted hover:bg-muted/80 rounded-md transition-colors cursor-pointer border border-neutral-200 dark:border-neutral-700/50"
|
||||
>
|
||||
<div className='border-2 bg-gradient-to-br from-neutral-200 to-neutral-300 dark:from-neutral-700 dark:to-neutral-800 flex items-center justify-center p-0.5 rounded-sm border-neutral-400/20 dark:border-neutral-600'>
|
||||
<IconComponent className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
</div>
|
||||
<span className="font-mono text-xs text-foreground">{getUserFriendlyToolName(toolName)}</span>
|
||||
{paramDisplay && <span className="ml-1 text-muted-foreground truncate max-w-[200px]" title={paramDisplay}>{paramDisplay}</span>}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
|
@ -224,7 +243,7 @@ export function renderMarkdownContent(
|
|||
{paramDisplay && <span className="ml-1 text-muted-foreground truncate max-w-[200px]" title={paramDisplay}>{paramDisplay}</span>}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
lastIndex = xmlRegex.lastIndex;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,13 @@ export class BillingError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export class NoAccessTokenAvailableError extends Error {
|
||||
constructor(message?: string, options?: { cause?: Error }) {
|
||||
super(message || 'No access token available', options);
|
||||
}
|
||||
name = 'NoAccessTokenAvailableError';
|
||||
}
|
||||
|
||||
// Type Definitions (moved from potential separate file for clarity)
|
||||
export type Project = {
|
||||
id: string;
|
||||
|
@ -541,7 +548,7 @@ export const startAgent = async (
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
// Check if backend URL is configured
|
||||
|
@ -633,6 +640,10 @@ export const startAgent = async (
|
|||
throw error;
|
||||
}
|
||||
|
||||
if (error instanceof NoAccessTokenAvailableError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.error('[API] Failed to start agent:', error);
|
||||
|
||||
// Handle different error types with appropriate user messages
|
||||
|
@ -673,7 +684,7 @@ export const stopAgent = async (agentRunId: string): Promise<void> => {
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
const authError = new Error('No access token available');
|
||||
const authError = new NoAccessTokenAvailableError();
|
||||
handleApiError(authError, { operation: 'stop agent', resource: 'AI assistant' });
|
||||
throw authError;
|
||||
}
|
||||
|
@ -714,7 +725,7 @@ export const getAgentStatus = async (agentRunId: string): Promise<AgentRun> => {
|
|||
|
||||
if (!session?.access_token) {
|
||||
console.error('[API] No access token available for getAgentStatus');
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const url = `${API_URL}/agent-run/${agentRunId}`;
|
||||
|
@ -771,7 +782,7 @@ export const getAgentRuns = async (threadId: string): Promise<AgentRun[]> => {
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/thread/${threadId}/agent-runs`, {
|
||||
|
@ -789,6 +800,10 @@ export const getAgentRuns = async (threadId: string): Promise<AgentRun[]> => {
|
|||
const data = await response.json();
|
||||
return data.agent_runs || [];
|
||||
} catch (error) {
|
||||
if (error instanceof NoAccessTokenAvailableError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.error('Failed to get agent runs:', error);
|
||||
handleApiError(error, { operation: 'load agent runs', resource: 'conversation history' });
|
||||
throw error;
|
||||
|
@ -875,8 +890,9 @@ export const streamAgent = (
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
const authError = new NoAccessTokenAvailableError();
|
||||
console.error('[STREAM] No auth token available');
|
||||
callbacks.onError(new Error('Authentication required'));
|
||||
callbacks.onError(authError);
|
||||
callbacks.onClose();
|
||||
return;
|
||||
}
|
||||
|
@ -1398,7 +1414,7 @@ export const initiateAgent = async (
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
if (!API_URL) {
|
||||
|
@ -1570,7 +1586,7 @@ export const createCheckoutSession = async (
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/billing/create-checkout-session`, {
|
||||
|
@ -1637,7 +1653,7 @@ export const createPortalSession = async (
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/billing/create-portal-session`, {
|
||||
|
@ -1679,7 +1695,7 @@ export const getSubscription = async (): Promise<SubscriptionStatus> => {
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/billing/subscription`, {
|
||||
|
@ -1703,6 +1719,10 @@ export const getSubscription = async (): Promise<SubscriptionStatus> => {
|
|||
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof NoAccessTokenAvailableError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.error('Failed to get subscription:', error);
|
||||
handleApiError(error, { operation: 'load subscription', resource: 'billing information' });
|
||||
throw error;
|
||||
|
@ -1717,7 +1737,7 @@ export const getAvailableModels = async (): Promise<AvailableModelsResponse> =>
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/billing/available-models`, {
|
||||
|
@ -1741,6 +1761,10 @@ export const getAvailableModels = async (): Promise<AvailableModelsResponse> =>
|
|||
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof NoAccessTokenAvailableError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.error('Failed to get available models:', error);
|
||||
handleApiError(error, { operation: 'load available models', resource: 'AI models' });
|
||||
throw error;
|
||||
|
@ -1756,7 +1780,7 @@ export const checkBillingStatus = async (): Promise<BillingStatusResponse> => {
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/billing/check-status`, {
|
||||
|
@ -1780,6 +1804,10 @@ export const checkBillingStatus = async (): Promise<BillingStatusResponse> => {
|
|||
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof NoAccessTokenAvailableError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.error('Failed to check billing status:', error);
|
||||
throw error;
|
||||
}
|
||||
|
@ -1799,7 +1827,7 @@ export const transcribeAudio = async (audioFile: File): Promise<TranscriptionRes
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
|
@ -1828,6 +1856,10 @@ export const transcribeAudio = async (audioFile: File): Promise<TranscriptionRes
|
|||
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof NoAccessTokenAvailableError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.error('Failed to transcribe audio:', error);
|
||||
handleApiError(error, { operation: 'transcribe audio', resource: 'speech-to-text' });
|
||||
throw error;
|
||||
|
@ -1841,7 +1873,7 @@ export const getAgentBuilderChatHistory = async (agentId: string): Promise<{mess
|
|||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new Error('No access token available');
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/agents/${agentId}/builder-chat-history`, {
|
||||
|
|
Loading…
Reference in New Issue