Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions console/src/api/chat-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
export interface ChatRecentSession {
sessionId: string;
title: string;
lastActive: string;
messageCount: number;
}

export interface ChatRecentResponse {
sessions: ChatRecentSession[];
}

export interface ChatHistoryMessage {
role: 'user' | 'assistant' | 'system';
content: string;
id?: number | string | null;
}

export interface AssistantPresentation {
agentId?: string | null;
displayName?: string | null;
imageUrl?: string | null;
}

export interface BranchFamily {
anchorSessionId: string;
anchorMessageId: number | string;
variants: string[];
}

export interface BootstrapAutostart {
status: string;
fileName: string;
}

export interface ChatHistoryResponse {
sessionId?: string;
history: ChatHistoryMessage[];
assistantPresentation?: AssistantPresentation | null;
branchFamilies?: BranchFamily[];
bootstrapAutostart?: BootstrapAutostart | null;
}

export interface ChatCommandSuggestion {
id: string;
label: string;
insertText: string;
description: string;
depth?: number;
}

export interface ChatCommandsResponse {
commands: ChatCommandSuggestion[];
}

export interface ChatArtifact {
filename?: string;
path?: string;
mimeType?: string;
type?: string;
}

export interface ChatStreamTextDelta {
type: 'text';
delta: string;
}

export interface ChatStreamApproval {
type: 'approval';
approvalId: string;
prompt: string;
summary?: string;
intent?: string;
reason?: string;
toolName?: string;
args?: unknown;
allowSession?: boolean;
allowAgent?: boolean;
allowAll?: boolean;
expiresAt?: number | null;
}

export type ChatStreamEvent = ChatStreamTextDelta | ChatStreamApproval;

export interface ChatStreamResult {
status?: string;
error?: string;
sessionId?: string;
userMessageId?: number | string | null;
assistantMessageId?: number | string | null;
result?: string;
artifacts?: ChatArtifact[];
toolsUsed?: string[];
}

export interface MediaItem {
filename: string;
path: string;
mimeType: string;
}

export interface MediaUploadResponse {
media: MediaItem;
}

export interface AppStatusResponse {
defaultAgentId?: string;
version?: string;
}

export interface BranchResponse {
sessionId: string;
}

export interface CommandResponse {
status?: string;
error?: string;
}

export interface ChatMessage {
id: string;
role: 'user' | 'assistant' | 'system' | 'thinking' | 'approval';
content: string;
rawContent?: string;
sessionId: string;
messageId?: number | string | null;
media?: MediaItem[];
artifacts?: ChatArtifact[];
replayRequest?: { content: string; media: MediaItem[] } | null;
pendingApproval?: ChatStreamApproval | null;
assistantPresentation?: AssistantPresentation | null;
branchKey?: string | null;
}
108 changes: 108 additions & 0 deletions console/src/api/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type {
AppStatusResponse,
BranchResponse,
ChatCommandsResponse,
ChatHistoryResponse,
ChatRecentResponse,
CommandResponse,
MediaUploadResponse,
} from './chat-types';
import { requestJson } from './client';

export function fetchAppStatus(token: string): Promise<AppStatusResponse> {
return requestJson<AppStatusResponse>('/api/status', { token });
}

export function fetchChatRecent(
token: string,
userId: string,
channelId = 'web',
limit = 10,
): Promise<ChatRecentResponse> {
const params = new URLSearchParams({
userId,
channelId,
limit: String(limit),
});
return requestJson<ChatRecentResponse>(
`/api/chat/recent?${params.toString()}`,
{ token },
);
}

export function fetchChatHistory(
token: string,
sessionId: string,
limit = 80,
): Promise<ChatHistoryResponse> {
const params = new URLSearchParams({
sessionId,
limit: String(limit),
});
return requestJson<ChatHistoryResponse>(`/api/history?${params.toString()}`, {
token,
});
}

export function fetchChatCommands(
token: string,
query?: string,
): Promise<ChatCommandsResponse> {
const url = query
? `/api/chat/commands?q=${encodeURIComponent(query)}`
: '/api/chat/commands';
return requestJson<ChatCommandsResponse>(url, { token });
}

export function createChatBranch(
token: string,
sessionId: string,
beforeMessageId: number | string,
): Promise<BranchResponse> {
return requestJson<BranchResponse>('/api/chat/branch', {
token,
method: 'POST',
body: { sessionId, beforeMessageId },
});
}

export function executeCommand(
token: string,
sessionId: string,
userId: string,
args: string[],
): Promise<CommandResponse> {
return requestJson<CommandResponse>('/api/command', {
token,
method: 'POST',
body: {
sessionId,
guildId: null,
channelId: 'web',
userId,
username: 'web',
args,
},
});
}

export function uploadMedia(
token: string,
file: File,
): Promise<MediaUploadResponse> {
return requestJson<MediaUploadResponse>('/api/media/upload', {
token,
method: 'POST',
rawBody: file,
extraHeaders: {
'Content-Type': file.type || 'application/octet-stream',
'X-Hybridclaw-Filename': encodeURIComponent(file.name || 'upload'),
},
});
}

export function artifactUrl(path: string, token?: string): string {
const params = new URLSearchParams({ path });
if (token) params.set('token', token);
return `/api/artifact?${params.toString()}`;
}
18 changes: 13 additions & 5 deletions console/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import type {
export const TOKEN_STORAGE_KEY = 'hybridclaw_token';
export const AUTH_REQUIRED_EVENT = 'hybridclaw:auth-required';

function requestHeaders(token: string, body?: unknown): HeadersInit {
export function requestHeaders(token: string, body?: unknown): HeadersInit {
const trimmed = token.trim();
return {
...(trimmed ? { Authorization: `Bearer ${trimmed}` } : {}),
Expand All @@ -48,7 +48,7 @@ function requestHeaders(token: string, body?: unknown): HeadersInit {
};
}

function dispatchAuthRequired(message: string): void {
export function dispatchAuthRequired(message: string): void {
clearStoredToken();
window.dispatchEvent(
new CustomEvent(AUTH_REQUIRED_EVENT, {
Expand All @@ -57,19 +57,27 @@ function dispatchAuthRequired(message: string): void {
);
}

async function requestJson<T>(
export async function requestJson<T>(
pathname: string,
options: {
token: string;
method?: 'GET' | 'PUT' | 'DELETE' | 'POST';
body?: unknown;
rawBody?: BodyInit;
extraHeaders?: HeadersInit;
onAuthError?: 'dispatch' | 'ignore';
},
): Promise<T> {
const response = await fetch(pathname, {
method: options.method || 'GET',
headers: requestHeaders(options.token, options.body),
body: options.body === undefined ? undefined : JSON.stringify(options.body),
headers: {
...requestHeaders(options.token, options.body),
...options.extraHeaders,
},
body:
options.body !== undefined
? JSON.stringify(options.body)
: (options.rawBody ?? undefined),
});

const payload = (await response.json().catch(() => ({}))) as {
Expand Down
2 changes: 2 additions & 0 deletions console/src/components/sidebar/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ComponentType } from 'react';
import {
Audit,
Channels,
Chat,
Cog,
Config,
Dashboard,
Expand Down Expand Up @@ -34,6 +35,7 @@ export const SIDEBAR_NAV_GROUPS: ReadonlyArray<SidebarNavGroup> = [
label: 'Overview',
items: [
{ to: '/', label: 'Dashboard', icon: Dashboard },
{ to: '/chat', label: 'Chat', icon: Chat },
{ to: '/audit', label: 'Audit', icon: Audit },
{ to: '/jobs', label: 'Jobs', icon: Jobs },
],
Expand Down
Loading
Loading