chore: mini-openclaw 初始版本
Next.js 前端 + FastAPI/LangChain 后端透明 agent平台 - 含 .gitignore, 敏感文件(sessions数据/.env/venv等)已排除 - 待后续添加 trajectory 自进化机制
This commit is contained in:
+256
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* API client for mini OpenClaw backend.
|
||||
* Custom SSE parser for POST requests (native EventSource only supports GET).
|
||||
*/
|
||||
|
||||
const API_BASE =
|
||||
typeof window !== "undefined"
|
||||
? `http://${window.location.hostname}:8002/api`
|
||||
: "http://localhost:8002/api";
|
||||
|
||||
export interface SSEEvent {
|
||||
event: string;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream chat messages via POST SSE.
|
||||
* Yields parsed SSE events as they arrive.
|
||||
*/
|
||||
export async function* streamChat(
|
||||
message: string,
|
||||
sessionId: string
|
||||
): AsyncGenerator<SSEEvent> {
|
||||
const response = await fetch(`${API_BASE}/chat`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message, session_id: sessionId, stream: true }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Chat API error: ${response.status}`);
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) throw new Error("No response body");
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
|
||||
let currentEvent = "message";
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("event:")) {
|
||||
currentEvent = line.slice(6).trim();
|
||||
} else if (line.startsWith("data:")) {
|
||||
const dataStr = line.slice(5).trim();
|
||||
if (dataStr) {
|
||||
try {
|
||||
const data = JSON.parse(dataStr);
|
||||
yield { event: currentEvent, data };
|
||||
} catch {
|
||||
// Skip malformed JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
// Empty line resets event type
|
||||
if (line === "") {
|
||||
currentEvent = "message";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a file from the backend.
|
||||
*/
|
||||
export async function readFile(path: string): Promise<string> {
|
||||
const resp = await fetch(`${API_BASE}/files?path=${encodeURIComponent(path)}`);
|
||||
if (!resp.ok) throw new Error(`Failed to read file: ${resp.status}`);
|
||||
const data = await resp.json();
|
||||
return data.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a file to the backend.
|
||||
*/
|
||||
export async function saveFile(path: string, content: string): Promise<void> {
|
||||
const resp = await fetch(`${API_BASE}/files`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ path, content }),
|
||||
});
|
||||
if (!resp.ok) throw new Error(`Failed to save file: ${resp.status}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all sessions.
|
||||
*/
|
||||
export async function listSessions(): Promise<
|
||||
Array<{ id: string; title: string; updated_at: number }>
|
||||
> {
|
||||
const resp = await fetch(`${API_BASE}/sessions`);
|
||||
if (!resp.ok) throw new Error(`Failed to list sessions: ${resp.status}`);
|
||||
const data = await resp.json();
|
||||
return data.sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session.
|
||||
*/
|
||||
export async function createSession(): Promise<{ id: string; title: string }> {
|
||||
const resp = await fetch(`${API_BASE}/sessions`, { method: "POST" });
|
||||
if (!resp.ok) throw new Error(`Failed to create session: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a session.
|
||||
*/
|
||||
export async function renameSession(id: string, title: string): Promise<void> {
|
||||
const resp = await fetch(`${API_BASE}/sessions/${encodeURIComponent(id)}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ title }),
|
||||
});
|
||||
if (!resp.ok) throw new Error(`Failed to rename session: ${resp.status}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a session.
|
||||
*/
|
||||
export async function deleteSession(id: string): Promise<void> {
|
||||
const resp = await fetch(`${API_BASE}/sessions/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!resp.ok) throw new Error(`Failed to delete session: ${resp.status}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw messages for a session (including system prompt).
|
||||
*/
|
||||
export async function getRawMessages(
|
||||
sessionId: string
|
||||
): Promise<{ session_id: string; title: string; messages: Array<{ role: string; content: string }> }> {
|
||||
const resp = await fetch(
|
||||
`${API_BASE}/sessions/${encodeURIComponent(sessionId)}/messages`
|
||||
);
|
||||
if (!resp.ok) throw new Error(`Failed to get raw messages: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session conversation history (no system prompt, includes tool_calls).
|
||||
*/
|
||||
export async function getSessionHistory(
|
||||
sessionId: string
|
||||
): Promise<{
|
||||
session_id: string;
|
||||
messages: Array<{ role: string; content: string; tool_calls?: Array<{ tool: string; input?: string; output?: string }> }>;
|
||||
}> {
|
||||
const resp = await fetch(
|
||||
`${API_BASE}/sessions/${encodeURIComponent(sessionId)}/history`
|
||||
);
|
||||
if (!resp.ok) throw new Error(`Failed to get session history: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* List available skills.
|
||||
*/
|
||||
export async function listSkills(): Promise<
|
||||
Array<{ name: string; path: string; description: string }>
|
||||
> {
|
||||
const resp = await fetch(`${API_BASE}/skills`);
|
||||
if (!resp.ok) throw new Error(`Failed to list skills: ${resp.status}`);
|
||||
const data = await resp.json();
|
||||
return data.skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a title for a session using AI.
|
||||
*/
|
||||
export async function generateTitle(
|
||||
sessionId: string
|
||||
): Promise<{ title: string }> {
|
||||
const resp = await fetch(
|
||||
`${API_BASE}/sessions/${encodeURIComponent(sessionId)}/generate-title`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
if (!resp.ok) throw new Error(`Failed to generate title: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token count for a session (system + messages).
|
||||
*/
|
||||
export async function getSessionTokenCount(
|
||||
sessionId: string
|
||||
): Promise<{ system_tokens: number; message_tokens: number; total_tokens: number }> {
|
||||
const resp = await fetch(
|
||||
`${API_BASE}/tokens/session/${encodeURIComponent(sessionId)}`
|
||||
);
|
||||
if (!resp.ok) throw new Error(`Failed to get token count: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token counts for a list of files.
|
||||
*/
|
||||
export async function getFileTokenCounts(
|
||||
paths: string[]
|
||||
): Promise<{ files: Array<{ path: string; tokens: number }> }> {
|
||||
const resp = await fetch(`${API_BASE}/tokens/files`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ paths }),
|
||||
});
|
||||
if (!resp.ok) throw new Error(`Failed to get file token counts: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress a session's conversation history.
|
||||
*/
|
||||
export async function compressSession(
|
||||
sessionId: string
|
||||
): Promise<{ archived_count: number; remaining_count: number }> {
|
||||
const resp = await fetch(
|
||||
`${API_BASE}/sessions/${encodeURIComponent(sessionId)}/compress`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
if (!resp.ok) throw new Error(`Failed to compress session: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current RAG mode status.
|
||||
*/
|
||||
export async function getRagMode(): Promise<{ rag_mode: boolean }> {
|
||||
const resp = await fetch(`${API_BASE}/config/rag-mode`);
|
||||
if (!resp.ok) throw new Error(`Failed to get RAG mode: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set RAG mode enabled/disabled.
|
||||
*/
|
||||
export async function setRagMode(
|
||||
enabled: boolean
|
||||
): Promise<{ rag_mode: boolean }> {
|
||||
const resp = await fetch(`${API_BASE}/config/rag-mode`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ enabled }),
|
||||
});
|
||||
if (!resp.ok) throw new Error(`Failed to set RAG mode: ${resp.status}`);
|
||||
return resp.json();
|
||||
}
|
||||
Reference in New Issue
Block a user