Files
open-design/src/state/projects.ts
T
pftom a98096a042 Add initial project structure with essential files
- Created .gitignore to exclude build artifacts and dependencies.
- Added index.html as the main entry point for the application.
- Included LICENSE file with Apache 2.0 terms.
- Initialized package.json and package-lock.json for project dependencies.
- Added pnpm-lock.yaml for package management.
- Created QUICKSTART.md for setup instructions.
- Added README.md and README.zh-CN.md for project documentation in English and Chinese.
2026-04-28 12:25:59 +08:00

283 lines
7.2 KiB
TypeScript

// Project / conversation / message / tab persistence — backed by the
// daemon's SQLite store. All writes round-trip through HTTP so projects
// stay coherent across multiple browser tabs and across restarts.
//
// These helpers fail soft (returning null / [] on transport errors) so
// the UI can stay rendered when the daemon is briefly unreachable.
import type {
ChatMessage,
Conversation,
OpenTabsState,
Project,
ProjectMetadata,
ProjectTemplate,
} from '../types';
export async function listProjects(): Promise<Project[]> {
try {
const resp = await fetch('/api/projects');
if (!resp.ok) return [];
const json = (await resp.json()) as { projects: Project[] };
return json.projects ?? [];
} catch {
return [];
}
}
export async function getProject(id: string): Promise<Project | null> {
try {
const resp = await fetch(`/api/projects/${encodeURIComponent(id)}`);
if (!resp.ok) return null;
const json = (await resp.json()) as { project: Project };
return json.project;
} catch {
return null;
}
}
export async function createProject(input: {
name: string;
skillId: string | null;
designSystemId: string | null;
pendingPrompt?: string;
metadata?: ProjectMetadata;
}): Promise<{ project: Project; conversationId: string } | null> {
try {
const id = crypto.randomUUID();
const resp = await fetch('/api/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, ...input }),
});
if (!resp.ok) return null;
return (await resp.json()) as { project: Project; conversationId: string };
} catch {
return null;
}
}
// ---------- templates ----------
export async function listTemplates(): Promise<ProjectTemplate[]> {
try {
const resp = await fetch('/api/templates');
if (!resp.ok) return [];
const json = (await resp.json()) as { templates: ProjectTemplate[] };
return json.templates ?? [];
} catch {
return [];
}
}
export async function getTemplate(id: string): Promise<ProjectTemplate | null> {
try {
const resp = await fetch(`/api/templates/${encodeURIComponent(id)}`);
if (!resp.ok) return null;
const json = (await resp.json()) as { template: ProjectTemplate };
return json.template;
} catch {
return null;
}
}
export async function saveTemplate(input: {
name: string;
description?: string;
sourceProjectId: string;
}): Promise<ProjectTemplate | null> {
try {
const resp = await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
if (!resp.ok) return null;
const json = (await resp.json()) as { template: ProjectTemplate };
return json.template;
} catch {
return null;
}
}
export async function deleteTemplate(id: string): Promise<boolean> {
try {
const resp = await fetch(`/api/templates/${encodeURIComponent(id)}`, {
method: 'DELETE',
});
return resp.ok;
} catch {
return false;
}
}
export async function patchProject(
id: string,
patch: Partial<Project>,
): Promise<Project | null> {
try {
const resp = await fetch(`/api/projects/${encodeURIComponent(id)}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patch),
});
if (!resp.ok) return null;
const json = (await resp.json()) as { project: Project };
return json.project;
} catch {
return null;
}
}
export async function deleteProject(id: string): Promise<boolean> {
try {
const resp = await fetch(`/api/projects/${encodeURIComponent(id)}`, {
method: 'DELETE',
});
return resp.ok;
} catch {
return false;
}
}
// ---------- conversations ----------
export async function listConversations(
projectId: string,
): Promise<Conversation[]> {
try {
const resp = await fetch(
`/api/projects/${encodeURIComponent(projectId)}/conversations`,
);
if (!resp.ok) return [];
const json = (await resp.json()) as { conversations: Conversation[] };
return json.conversations ?? [];
} catch {
return [];
}
}
export async function createConversation(
projectId: string,
title?: string,
): Promise<Conversation | null> {
try {
const resp = await fetch(
`/api/projects/${encodeURIComponent(projectId)}/conversations`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title }),
},
);
if (!resp.ok) return null;
const json = (await resp.json()) as { conversation: Conversation };
return json.conversation;
} catch {
return null;
}
}
export async function patchConversation(
projectId: string,
conversationId: string,
patch: Partial<Conversation>,
): Promise<Conversation | null> {
try {
const resp = await fetch(
`/api/projects/${encodeURIComponent(projectId)}/conversations/${encodeURIComponent(conversationId)}`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patch),
},
);
if (!resp.ok) return null;
const json = (await resp.json()) as { conversation: Conversation };
return json.conversation;
} catch {
return null;
}
}
export async function deleteConversation(
projectId: string,
conversationId: string,
): Promise<boolean> {
try {
const resp = await fetch(
`/api/projects/${encodeURIComponent(projectId)}/conversations/${encodeURIComponent(conversationId)}`,
{ method: 'DELETE' },
);
return resp.ok;
} catch {
return false;
}
}
// ---------- messages ----------
export async function listMessages(
projectId: string,
conversationId: string,
): Promise<ChatMessage[]> {
try {
const resp = await fetch(
`/api/projects/${encodeURIComponent(projectId)}/conversations/${encodeURIComponent(conversationId)}/messages`,
);
if (!resp.ok) return [];
const json = (await resp.json()) as { messages: ChatMessage[] };
return json.messages ?? [];
} catch {
return [];
}
}
export async function saveMessage(
projectId: string,
conversationId: string,
message: ChatMessage,
): Promise<void> {
try {
await fetch(
`/api/projects/${encodeURIComponent(projectId)}/conversations/${encodeURIComponent(conversationId)}/messages/${encodeURIComponent(message.id)}`,
{
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message),
},
);
} catch {
// best-effort persistence — UI keeps the message in-memory either way
}
}
// ---------- tabs ----------
export async function loadTabs(projectId: string): Promise<OpenTabsState> {
try {
const resp = await fetch(
`/api/projects/${encodeURIComponent(projectId)}/tabs`,
);
if (!resp.ok) return { tabs: [], active: null };
return (await resp.json()) as OpenTabsState;
} catch {
return { tabs: [], active: null };
}
}
export async function saveTabs(
projectId: string,
state: OpenTabsState,
): Promise<void> {
try {
await fetch(`/api/projects/${encodeURIComponent(projectId)}/tabs`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(state),
});
} catch {
// best-effort
}
}