mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-05-19 19:50:26 +08:00
feat(opencode): Phase 7 - Frontend TypeScript type definitions
- Add "opencode" to AppId type in lib/api/types.ts - Extend McpApps interface with opencode field in types.ts - Add OpenCode-specific types: OpenCodeModel, OpenCodeProviderOptions, OpenCodeProviderConfig, OpenCodeMcpServerSpec - Add opencodeConfigDir to Settings interface - Add importOpenCodeFromLive() to providersApi - Fix type errors across components: - AppSwitcher: add opencode to icon/name mappings - McpFormModal: add opencode to enabledApps state - PromptFormModal/Panel: add opencode filename mapping - EndpointSpeedTest: add opencode timeout config - useBaseUrlState: add opencode to appType union - ProxyToggle: add opencode label - App.tsx: handle opencode fallback for SkillsPage - Update ProxyTakeoverStatus with opencode field (always false) - Fix test mocks in tests/msw/state.ts
This commit is contained in:
+1
-1
@@ -454,7 +454,7 @@ function App() {
|
||||
/>
|
||||
);
|
||||
case "skillsDiscovery":
|
||||
return <SkillsPage ref={skillsPageRef} initialApp={activeApp} />;
|
||||
return <SkillsPage ref={skillsPageRef} initialApp={activeApp === "opencode" ? "claude" : activeApp} />;
|
||||
case "mcp":
|
||||
return (
|
||||
<UnifiedMcpPanel
|
||||
|
||||
@@ -16,11 +16,13 @@ export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) {
|
||||
claude: "claude",
|
||||
codex: "openai",
|
||||
gemini: "gemini",
|
||||
opencode: "opencode",
|
||||
};
|
||||
const appDisplayName: Record<AppId, string> = {
|
||||
claude: "Claude",
|
||||
codex: "Codex",
|
||||
gemini: "Gemini",
|
||||
opencode: "OpenCode",
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -65,6 +65,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
claude: boolean;
|
||||
codex: boolean;
|
||||
gemini: boolean;
|
||||
opencode: boolean;
|
||||
}>(() => {
|
||||
if (initialData?.apps) {
|
||||
return { ...initialData.apps };
|
||||
@@ -73,6 +74,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
claude: defaultEnabledApps.includes("claude"),
|
||||
codex: defaultEnabledApps.includes("codex"),
|
||||
gemini: defaultEnabledApps.includes("gemini"),
|
||||
opencode: defaultEnabledApps.includes("opencode"),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ const PromptFormModal: React.FC<PromptFormModalProps> = ({
|
||||
claude: "CLAUDE.md",
|
||||
codex: "AGENTS.md",
|
||||
gemini: "GEMINI.md",
|
||||
opencode: "OPENCODE.md",
|
||||
};
|
||||
const filename = filenameMap[appId];
|
||||
const [name, setName] = useState("");
|
||||
|
||||
@@ -28,6 +28,7 @@ const PromptFormPanel: React.FC<PromptFormPanelProps> = ({
|
||||
claude: "CLAUDE.md",
|
||||
codex: "AGENTS.md",
|
||||
gemini: "GEMINI.md",
|
||||
opencode: "OPENCODE.md",
|
||||
};
|
||||
const filename = filenameMap[appId];
|
||||
const [name, setName] = useState("");
|
||||
|
||||
@@ -9,11 +9,12 @@ import { FullScreenPanel } from "@/components/common/FullScreenPanel";
|
||||
import type { CustomEndpoint, EndpointCandidate } from "@/types";
|
||||
|
||||
// 端点测速超时配置(秒)
|
||||
const ENDPOINT_TIMEOUT_SECS = {
|
||||
const ENDPOINT_TIMEOUT_SECS: Record<AppId, number> = {
|
||||
codex: 12,
|
||||
claude: 8,
|
||||
gemini: 8, // 新增 gemini
|
||||
} as const;
|
||||
gemini: 8,
|
||||
opencode: 8,
|
||||
};
|
||||
|
||||
interface TestResult {
|
||||
url: string;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import type { ProviderCategory } from "@/types";
|
||||
|
||||
interface UseBaseUrlStateProps {
|
||||
appType: "claude" | "codex" | "gemini";
|
||||
appType: "claude" | "codex" | "gemini" | "opencode";
|
||||
category: ProviderCategory | undefined;
|
||||
settingsConfig: string;
|
||||
codexConfig?: string;
|
||||
|
||||
@@ -37,7 +37,9 @@ export function ProxyToggle({ className, activeApp }: ProxyToggleProps) {
|
||||
? "Claude"
|
||||
: activeApp === "codex"
|
||||
? "Codex"
|
||||
: "Gemini";
|
||||
: activeApp === "gemini"
|
||||
? "Gemini"
|
||||
: "OpenCode";
|
||||
|
||||
const tooltipText = takeoverEnabled
|
||||
? isRunning
|
||||
|
||||
@@ -74,6 +74,14 @@ export const providersApi = {
|
||||
async openTerminal(providerId: string, appId: AppId): Promise<boolean> {
|
||||
return await invoke("open_provider_terminal", { providerId, app: appId });
|
||||
},
|
||||
|
||||
/**
|
||||
* 从 OpenCode live 配置导入供应商到数据库
|
||||
* OpenCode 特有功能:由于累加模式,用户可能已在 opencode.json 中配置供应商
|
||||
*/
|
||||
async importOpenCodeFromLive(): Promise<number> {
|
||||
return await invoke("import_opencode_providers_from_live");
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// 前端统一使用 AppId 作为应用标识(与后端命令参数 `app` 一致)
|
||||
export type AppId = "claude" | "codex" | "gemini"; // 新增 gemini
|
||||
export type AppId = "claude" | "codex" | "gemini" | "opencode";
|
||||
|
||||
@@ -125,6 +125,8 @@ export interface Settings {
|
||||
codexConfigDir?: string;
|
||||
// 覆盖 Gemini 配置目录(可选)
|
||||
geminiConfigDir?: string;
|
||||
// 覆盖 OpenCode 配置目录(可选)
|
||||
opencodeConfigDir?: string;
|
||||
|
||||
// ===== 当前供应商 ID(设备级)=====
|
||||
// 当前 Claude 供应商 ID(优先于数据库 is_current)
|
||||
@@ -156,6 +158,7 @@ export interface McpApps {
|
||||
claude: boolean;
|
||||
codex: boolean;
|
||||
gemini: boolean;
|
||||
opencode: boolean;
|
||||
}
|
||||
|
||||
// MCP 服务器条目(v3.7.0 统一结构)
|
||||
@@ -247,3 +250,45 @@ export interface UniversalProvider {
|
||||
|
||||
// 统一供应商映射(id -> UniversalProvider)
|
||||
export type UniversalProvidersMap = Record<string, UniversalProvider>;
|
||||
|
||||
// ============================================================================
|
||||
// OpenCode 专属配置(v3.9.2+)
|
||||
// ============================================================================
|
||||
|
||||
// OpenCode 模型配置
|
||||
export interface OpenCodeModel {
|
||||
name: string;
|
||||
limit?: {
|
||||
context?: number;
|
||||
output?: number;
|
||||
};
|
||||
}
|
||||
|
||||
// OpenCode 供应商选项
|
||||
export interface OpenCodeProviderOptions {
|
||||
baseURL?: string;
|
||||
apiKey?: string;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
// OpenCode 供应商配置(settings_config 结构)
|
||||
export interface OpenCodeProviderConfig {
|
||||
npm: string; // AI SDK 包名,如 "@ai-sdk/openai-compatible"
|
||||
name?: string; // 供应商显示名称
|
||||
options: OpenCodeProviderOptions;
|
||||
models: Record<string, OpenCodeModel>;
|
||||
}
|
||||
|
||||
// OpenCode MCP 服务器配置(与统一格式不同)
|
||||
export interface OpenCodeMcpServerSpec {
|
||||
type: "local" | "remote";
|
||||
// local 类型字段
|
||||
command?: string[]; // 与统一格式不同:命令和参数合并为数组
|
||||
environment?: Record<string, string>; // 与统一格式不同:使用 environment 而非 env
|
||||
// remote 类型字段
|
||||
url?: string;
|
||||
headers?: Record<string, string>;
|
||||
// 通用字段
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface ProxyTakeoverStatus {
|
||||
claude: boolean;
|
||||
codex: boolean;
|
||||
gemini: boolean;
|
||||
opencode: boolean;
|
||||
}
|
||||
|
||||
export interface ProviderHealth {
|
||||
|
||||
+8
-4
@@ -57,12 +57,14 @@ const createDefaultProviders = (): ProvidersByApp => ({
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
},
|
||||
opencode: {},
|
||||
});
|
||||
|
||||
const createDefaultCurrent = (): CurrentProviderState => ({
|
||||
claude: "claude-1",
|
||||
codex: "codex-1",
|
||||
gemini: "gemini-1",
|
||||
opencode: "",
|
||||
});
|
||||
|
||||
let providers = createDefaultProviders();
|
||||
@@ -82,7 +84,7 @@ let mcpConfigs: McpConfigState = {
|
||||
id: "sample",
|
||||
name: "Sample Claude Server",
|
||||
enabled: true,
|
||||
apps: { claude: true, codex: false, gemini: false },
|
||||
apps: { claude: true, codex: false, gemini: false, opencode: false },
|
||||
server: {
|
||||
type: "stdio",
|
||||
command: "claude-server",
|
||||
@@ -94,7 +96,7 @@ let mcpConfigs: McpConfigState = {
|
||||
id: "httpServer",
|
||||
name: "HTTP Codex Server",
|
||||
enabled: false,
|
||||
apps: { claude: false, codex: true, gemini: false },
|
||||
apps: { claude: false, codex: true, gemini: false, opencode: false },
|
||||
server: {
|
||||
type: "http",
|
||||
url: "http://localhost:3000",
|
||||
@@ -102,6 +104,7 @@ let mcpConfigs: McpConfigState = {
|
||||
},
|
||||
},
|
||||
gemini: {},
|
||||
opencode: {},
|
||||
};
|
||||
|
||||
const cloneProviders = (value: ProvidersByApp) =>
|
||||
@@ -125,7 +128,7 @@ export const resetProviderState = () => {
|
||||
id: "sample",
|
||||
name: "Sample Claude Server",
|
||||
enabled: true,
|
||||
apps: { claude: true, codex: false, gemini: false },
|
||||
apps: { claude: true, codex: false, gemini: false, opencode: false },
|
||||
server: {
|
||||
type: "stdio",
|
||||
command: "claude-server",
|
||||
@@ -137,7 +140,7 @@ export const resetProviderState = () => {
|
||||
id: "httpServer",
|
||||
name: "HTTP Codex Server",
|
||||
enabled: false,
|
||||
apps: { claude: false, codex: true, gemini: false },
|
||||
apps: { claude: false, codex: true, gemini: false, opencode: false },
|
||||
server: {
|
||||
type: "http",
|
||||
url: "http://localhost:3000",
|
||||
@@ -145,6 +148,7 @@ export const resetProviderState = () => {
|
||||
},
|
||||
},
|
||||
gemini: {},
|
||||
opencode: {},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user