feat: remove legacy OpenCode runtime
Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
This commit is contained in:
@@ -2,10 +2,15 @@ import { useStore } from 'zustand';
|
||||
import { createStore, type StoreApi } from 'zustand/vanilla';
|
||||
import {
|
||||
createCodingProjectConversation,
|
||||
createCodingProject,
|
||||
getCodingProjectConfig,
|
||||
listCodingProjectConversations,
|
||||
listCodingProjects,
|
||||
acknowledgeLegacyCodingConversationNotice,
|
||||
openCodingProject,
|
||||
patchCodingProjectConversation,
|
||||
removeCodingProject,
|
||||
setActiveCodingProject,
|
||||
type CodingProjectCatalog,
|
||||
} from '@/lib/coding-projects';
|
||||
import type {
|
||||
@@ -14,6 +19,7 @@ import type {
|
||||
CodingProjectConfig,
|
||||
CodingProjectSummary,
|
||||
} from '@/types/coding-project';
|
||||
import type { ProjectType } from '../../shared/project-config';
|
||||
|
||||
interface CodingWorkspaceDependencies {
|
||||
listProjects(): Promise<CodingProjectCatalog>;
|
||||
@@ -31,6 +37,20 @@ interface CodingWorkspaceDependencies {
|
||||
conversationId: string,
|
||||
patch: { title?: string; archived?: boolean; unread?: boolean },
|
||||
): Promise<CodingConversationMetadata>;
|
||||
openProject(projectPath: string): Promise<CodingProjectSummary>;
|
||||
createProject(input: {
|
||||
projectPath?: string;
|
||||
parentPath?: string;
|
||||
projectName?: string;
|
||||
projectType?: ProjectType;
|
||||
}): Promise<{ project: CodingProjectSummary; config: CodingProjectConfig; knowledgeFiles: string[] }>;
|
||||
setActiveProject(projectId: string): Promise<CodingProjectSummary>;
|
||||
removeProject(projectId: string): Promise<void>;
|
||||
acknowledgeLegacyNotice(projectId: string): Promise<{
|
||||
project: CodingProjectSummary;
|
||||
config: CodingProjectConfig;
|
||||
knowledgeFiles: string[];
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface CodingWorkspaceState {
|
||||
@@ -45,6 +65,16 @@ export interface CodingWorkspaceState {
|
||||
conversationErrorsByProjectId: Record<string, Record<string, string>>;
|
||||
creatingAgentIds: Record<string, true>;
|
||||
load(): Promise<void>;
|
||||
openProject(projectPath: string): Promise<CodingProjectSummary>;
|
||||
createProject(input: {
|
||||
projectPath?: string;
|
||||
parentPath?: string;
|
||||
projectName?: string;
|
||||
projectType?: ProjectType;
|
||||
}): Promise<CodingProjectSummary>;
|
||||
setActiveProject(projectId: string): Promise<CodingProjectSummary>;
|
||||
removeProject(projectId: string): Promise<void>;
|
||||
acknowledgeLegacyNotice(): Promise<void>;
|
||||
selectAgent(agentId: string): void;
|
||||
ensureConversation(agentId: string): Promise<CodingConversationMetadata>;
|
||||
createConversation(agentId: string): Promise<CodingConversationMetadata>;
|
||||
@@ -99,6 +129,11 @@ function defaultDependencies(): CodingWorkspaceDependencies {
|
||||
listConversations: listCodingProjectConversations,
|
||||
createConversation: createCodingProjectConversation,
|
||||
patchConversation: patchCodingProjectConversation,
|
||||
openProject: openCodingProject,
|
||||
createProject: createCodingProject,
|
||||
setActiveProject: setActiveCodingProject,
|
||||
removeProject: removeCodingProject,
|
||||
acknowledgeLegacyNotice: acknowledgeLegacyCodingConversationNotice,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,6 +212,36 @@ export function createCodingWorkspaceStore(
|
||||
await flight;
|
||||
},
|
||||
|
||||
async openProject(projectPath) {
|
||||
const project = await deps.openProject(projectPath);
|
||||
await get().load();
|
||||
return project;
|
||||
},
|
||||
|
||||
async createProject(input) {
|
||||
const snapshot = await deps.createProject(input);
|
||||
await get().load();
|
||||
return snapshot.project;
|
||||
},
|
||||
|
||||
async setActiveProject(projectId) {
|
||||
const project = await deps.setActiveProject(projectId);
|
||||
await get().load();
|
||||
return project;
|
||||
},
|
||||
|
||||
async removeProject(projectId) {
|
||||
await deps.removeProject(projectId);
|
||||
await get().load();
|
||||
},
|
||||
|
||||
async acknowledgeLegacyNotice() {
|
||||
const projectId = get().activeProjectId;
|
||||
if (!projectId) return;
|
||||
const snapshot = await deps.acknowledgeLegacyNotice(projectId);
|
||||
if (get().activeProjectId === projectId) set({ config: snapshot.config });
|
||||
},
|
||||
|
||||
selectAgent(agentId) {
|
||||
if (!enabledAgent(get().config, agentId)) return;
|
||||
set({ selectedAgentId: agentId });
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,21 @@
|
||||
import { create } from 'zustand';
|
||||
import { hostApiFetch } from '@/lib/host-api';
|
||||
import type { ProjectConfig } from '../../shared/project-config';
|
||||
import type { CodingProjectConfig, CodingProjectConfigSnapshot } from '@/types/coding-project';
|
||||
|
||||
type ProjectConfigResponse = {
|
||||
status: 'valid' | 'missing' | 'invalid';
|
||||
config?: ProjectConfig;
|
||||
config?: CodingProjectConfig;
|
||||
error?: string;
|
||||
knowledgeFiles: string[];
|
||||
};
|
||||
|
||||
type ProjectConfigState = {
|
||||
configsByProjectId: Record<string, ProjectConfig>;
|
||||
configsByProjectId: Record<string, CodingProjectConfig>;
|
||||
knowledgeByProjectId: Record<string, string[]>;
|
||||
loadingProjectId: string | null;
|
||||
errorsByProjectId: Record<string, string>;
|
||||
load: (projectId: string) => Promise<ProjectConfigResponse>;
|
||||
save: (projectId: string, config: ProjectConfig) => Promise<ProjectConfig>;
|
||||
save: (projectId: string, config: CodingProjectConfig) => Promise<CodingProjectConfig>;
|
||||
uploadKnowledge: (projectId: string, file: File) => Promise<void>;
|
||||
remove: (projectId: string) => void;
|
||||
};
|
||||
@@ -29,18 +29,21 @@ export const useProjectConfigStore = create<ProjectConfigState>((set) => ({
|
||||
async load(projectId) {
|
||||
set({ loadingProjectId: projectId });
|
||||
try {
|
||||
const response = await hostApiFetch<ProjectConfigResponse>(`/api/opencode/projects/config?projectId=${encodeURIComponent(projectId)}`);
|
||||
const response = await hostApiFetch<{ snapshot: CodingProjectConfigSnapshot }>(
|
||||
`/api/coding/projects/config?projectId=${encodeURIComponent(projectId)}`,
|
||||
);
|
||||
const result: ProjectConfigResponse = {
|
||||
status: 'valid',
|
||||
config: response.snapshot.config,
|
||||
knowledgeFiles: response.snapshot.knowledgeFiles,
|
||||
};
|
||||
set((state) => ({
|
||||
loadingProjectId: null,
|
||||
configsByProjectId: response.status === 'valid' && response.config
|
||||
? { ...state.configsByProjectId, [projectId]: response.config }
|
||||
: state.configsByProjectId,
|
||||
knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.knowledgeFiles ?? [] },
|
||||
errorsByProjectId: response.status === 'invalid'
|
||||
? { ...state.errorsByProjectId, [projectId]: response.error ?? '项目配置无效' }
|
||||
: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)),
|
||||
configsByProjectId: { ...state.configsByProjectId, [projectId]: response.snapshot.config },
|
||||
knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.snapshot.knowledgeFiles },
|
||||
errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)),
|
||||
}));
|
||||
return response;
|
||||
return result;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
set((state) => ({ loadingProjectId: null, errorsByProjectId: { ...state.errorsByProjectId, [projectId]: message } }));
|
||||
@@ -49,17 +52,16 @@ export const useProjectConfigStore = create<ProjectConfigState>((set) => ({
|
||||
},
|
||||
|
||||
async save(projectId, config) {
|
||||
const response = await hostApiFetch<{ success: boolean; config?: ProjectConfig; knowledgeFiles?: string[]; error?: string }>('/api/opencode/projects/config', {
|
||||
const response = await hostApiFetch<{ snapshot: CodingProjectConfigSnapshot }>('/api/coding/projects/config', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ projectId, config }),
|
||||
});
|
||||
if (!response.success || !response.config) throw new Error(response.error || '保存项目配置失败');
|
||||
set((state) => ({
|
||||
configsByProjectId: { ...state.configsByProjectId, [projectId]: response.config as ProjectConfig },
|
||||
knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.knowledgeFiles ?? [] },
|
||||
configsByProjectId: { ...state.configsByProjectId, [projectId]: response.snapshot.config },
|
||||
knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.snapshot.knowledgeFiles },
|
||||
errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)),
|
||||
}));
|
||||
return response.config;
|
||||
return response.snapshot.config;
|
||||
},
|
||||
|
||||
async uploadKnowledge(projectId, file) {
|
||||
@@ -68,12 +70,11 @@ export const useProjectConfigStore = create<ProjectConfigState>((set) => ({
|
||||
for (let offset = 0; offset < bytes.length; offset += 0x8000) {
|
||||
binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
|
||||
}
|
||||
const response = await hostApiFetch<{ success: boolean; knowledgeFiles?: string[]; error?: string }>('/api/opencode/projects/knowledge', {
|
||||
const response = await hostApiFetch<{ knowledgeFiles: string[] }>('/api/coding/projects/knowledge', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ projectId, fileName: file.name, contentBase64: btoa(binary) }),
|
||||
});
|
||||
if (!response.success) throw new Error(response.error || '知识文件上传失败');
|
||||
set((state) => ({ knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.knowledgeFiles ?? [] } }));
|
||||
set((state) => ({ knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.knowledgeFiles } }));
|
||||
},
|
||||
|
||||
remove(projectId) {
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
import { hostApiFetch } from '@/lib/host-api';
|
||||
import type {
|
||||
ProjectConversationState,
|
||||
ProjectSessionMetadata,
|
||||
} from '../../shared/project-conversations';
|
||||
|
||||
type ProjectConversationResponse = {
|
||||
state?: ProjectConversationState;
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type ConversationAction = 'link' | 'archive' | 'restore' | 'delete' | 'read' | 'increment-unread' | 'complete';
|
||||
|
||||
type ProjectConversationStore = {
|
||||
statesByProjectId: Record<string, ProjectConversationState>;
|
||||
loadingProjectId: string | null;
|
||||
errorsByProjectId: Record<string, string>;
|
||||
load: (projectId: string) => Promise<ProjectConversationState>;
|
||||
linkSession: (projectId: string, sessionId: string, agentId: string) => Promise<ProjectConversationState>;
|
||||
archiveSession: (projectId: string, sessionId: string) => Promise<ProjectConversationState>;
|
||||
restoreSession: (projectId: string, sessionId: string) => Promise<ProjectConversationState>;
|
||||
deleteSession: (projectId: string, sessionId: string) => Promise<ProjectConversationState>;
|
||||
markSessionRead: (projectId: string, sessionId: string) => Promise<ProjectConversationState>;
|
||||
markSessionUnread: (projectId: string, sessionId: string) => Promise<ProjectConversationState>;
|
||||
completeSession: (projectId: string, sessionId: string, incrementUnread?: boolean) => Promise<ProjectConversationState>;
|
||||
getSession: (projectId: string, sessionId: string) => ProjectSessionMetadata | undefined;
|
||||
remove: (projectId: string) => void;
|
||||
};
|
||||
|
||||
function emptyState(): ProjectConversationState {
|
||||
return { schemaVersion: 1, sessions: [], updatedAt: new Date().toISOString() };
|
||||
}
|
||||
|
||||
export const useProjectConversationStore = create<ProjectConversationStore>((set, get) => {
|
||||
async function dispatch(projectId: string, sessionId: string, action: ConversationAction, agentId?: string, unread?: boolean) {
|
||||
const response = await hostApiFetch<ProjectConversationResponse>('/api/opencode/projects/conversations', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ projectId, sessionId, action, ...(agentId ? { agentId } : {}), ...(unread ? { unread: true } : {}) }),
|
||||
});
|
||||
if (!response.success || !response.state) throw new Error(response.error || '更新会话归属失败');
|
||||
set((state) => ({
|
||||
statesByProjectId: { ...state.statesByProjectId, [projectId]: response.state as ProjectConversationState },
|
||||
errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)),
|
||||
}));
|
||||
return response.state;
|
||||
}
|
||||
|
||||
return {
|
||||
statesByProjectId: {},
|
||||
loadingProjectId: null,
|
||||
errorsByProjectId: {},
|
||||
|
||||
async load(projectId) {
|
||||
set({ loadingProjectId: projectId });
|
||||
try {
|
||||
const response = await hostApiFetch<ProjectConversationResponse>(
|
||||
`/api/opencode/projects/conversations?projectId=${encodeURIComponent(projectId)}`,
|
||||
);
|
||||
const nextState = response.state ?? emptyState();
|
||||
set((state) => ({
|
||||
loadingProjectId: null,
|
||||
statesByProjectId: { ...state.statesByProjectId, [projectId]: nextState },
|
||||
errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)),
|
||||
}));
|
||||
return nextState;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
set((state) => ({
|
||||
loadingProjectId: null,
|
||||
errorsByProjectId: { ...state.errorsByProjectId, [projectId]: message },
|
||||
}));
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
linkSession(projectId, sessionId, agentId) {
|
||||
return dispatch(projectId, sessionId, 'link', agentId);
|
||||
},
|
||||
|
||||
archiveSession(projectId, sessionId) {
|
||||
return dispatch(projectId, sessionId, 'archive');
|
||||
},
|
||||
|
||||
restoreSession(projectId, sessionId) {
|
||||
return dispatch(projectId, sessionId, 'restore');
|
||||
},
|
||||
|
||||
deleteSession(projectId, sessionId) {
|
||||
return dispatch(projectId, sessionId, 'delete');
|
||||
},
|
||||
|
||||
markSessionRead(projectId, sessionId) {
|
||||
return dispatch(projectId, sessionId, 'read');
|
||||
},
|
||||
|
||||
markSessionUnread(projectId, sessionId) {
|
||||
return dispatch(projectId, sessionId, 'increment-unread');
|
||||
},
|
||||
|
||||
completeSession(projectId, sessionId, incrementUnread = false) {
|
||||
return dispatch(projectId, sessionId, 'complete', undefined, incrementUnread);
|
||||
},
|
||||
|
||||
getSession(projectId, sessionId) {
|
||||
return get().statesByProjectId[projectId]?.sessions.find((item) => item.sessionId === sessionId);
|
||||
},
|
||||
|
||||
remove(projectId) {
|
||||
set((state) => ({
|
||||
statesByProjectId: Object.fromEntries(Object.entries(state.statesByProjectId).filter(([id]) => id !== projectId)),
|
||||
errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)),
|
||||
}));
|
||||
},
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user