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.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import type { AppConfig } from '../types';
|
||||
|
||||
const STORAGE_KEY = 'open-claude-design:config';
|
||||
|
||||
export const DEFAULT_CONFIG: AppConfig = {
|
||||
mode: 'daemon',
|
||||
apiKey: '',
|
||||
baseUrl: 'https://api.anthropic.com',
|
||||
model: 'claude-sonnet-4-5',
|
||||
agentId: null,
|
||||
skillId: null,
|
||||
designSystemId: null,
|
||||
onboardingCompleted: false,
|
||||
};
|
||||
|
||||
export function loadConfig(): AppConfig {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return { ...DEFAULT_CONFIG };
|
||||
const parsed = JSON.parse(raw) as Partial<AppConfig>;
|
||||
return { ...DEFAULT_CONFIG, ...parsed };
|
||||
} catch {
|
||||
return { ...DEFAULT_CONFIG };
|
||||
}
|
||||
}
|
||||
|
||||
export function saveConfig(config: AppConfig): void {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user