mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-01 04:02:02 +08:00
feat(omo): add OMO Slim (oh-my-opencode-slim) support
Implement full OMO Slim profile management to align with ai-toolbox: - Backend: Slim service methods, DAO, Tauri commands, plugin conflict handling - Frontend: types, API, query hooks, form integration with isSlim parameterization - Slim variant: 6 agents (no categories), separate config file and plugin name - Mutual exclusion: standard OMO and Slim cannot coexist as plugins - i18n: zh/en/ja translations for all Slim agent descriptions
This commit is contained in:
+26
-1
@@ -61,7 +61,10 @@ import { UniversalProviderPanel } from "@/components/universal";
|
||||
import { McpIcon } from "@/components/BrandIcons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SessionManagerPage } from "@/components/sessions/SessionManagerPage";
|
||||
import { useDisableCurrentOmo } from "@/lib/query/omo";
|
||||
import {
|
||||
useDisableCurrentOmo,
|
||||
useDisableCurrentOmoSlim,
|
||||
} from "@/lib/query/omo";
|
||||
import WorkspaceFilesPanel from "@/components/workspace/WorkspaceFilesPanel";
|
||||
import EnvPanel from "@/components/openclaw/EnvPanel";
|
||||
import ToolsPanel from "@/components/openclaw/ToolsPanel";
|
||||
@@ -248,6 +251,23 @@ function App() {
|
||||
});
|
||||
};
|
||||
|
||||
const disableOmoSlimMutation = useDisableCurrentOmoSlim();
|
||||
const handleDisableOmoSlim = () => {
|
||||
disableOmoSlimMutation.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(t("omo.disabled", { defaultValue: "OMO 已停用" }));
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(
|
||||
t("omo.disableFailed", {
|
||||
defaultValue: "停用 OMO 失败: {{error}}",
|
||||
error: extractErrorMessage(error),
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
|
||||
@@ -746,6 +766,11 @@ function App() {
|
||||
onDisableOmo={
|
||||
activeApp === "opencode" ? handleDisableOmo : undefined
|
||||
}
|
||||
onDisableOmoSlim={
|
||||
activeApp === "opencode"
|
||||
? handleDisableOmoSlim
|
||||
: undefined
|
||||
}
|
||||
onDuplicate={handleDuplicateProvider}
|
||||
onConfigureUsage={setUsageProvider}
|
||||
onOpenWebsite={handleOpenWebsite}
|
||||
|
||||
@@ -29,11 +29,14 @@ interface ProviderCardProps {
|
||||
isInConfig?: boolean; // OpenCode: 是否已添加到 opencode.json
|
||||
isOmo?: boolean;
|
||||
isLastOmo?: boolean;
|
||||
isOmoSlim?: boolean;
|
||||
isLastOmoSlim?: boolean;
|
||||
onSwitch: (provider: Provider) => void;
|
||||
onEdit: (provider: Provider) => void;
|
||||
onDelete: (provider: Provider) => void;
|
||||
onRemoveFromConfig?: (provider: Provider) => void;
|
||||
onDisableOmo?: () => void;
|
||||
onDisableOmoSlim?: () => void;
|
||||
onConfigureUsage: (provider: Provider) => void;
|
||||
onOpenWebsite: (url: string) => void;
|
||||
onDuplicate: (provider: Provider) => void;
|
||||
@@ -92,11 +95,14 @@ export function ProviderCard({
|
||||
isInConfig = true,
|
||||
isOmo = false,
|
||||
isLastOmo = false,
|
||||
isOmoSlim = false,
|
||||
isLastOmoSlim = false,
|
||||
onSwitch,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onRemoveFromConfig,
|
||||
onDisableOmo,
|
||||
onDisableOmoSlim,
|
||||
onConfigureUsage,
|
||||
onOpenWebsite,
|
||||
onDuplicate,
|
||||
@@ -117,6 +123,11 @@ export function ProviderCard({
|
||||
}: ProviderCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// OMO and OMO Slim share the same card behavior
|
||||
const isAnyOmo = isOmo || isOmoSlim;
|
||||
const isLastAnyOmo = isOmo ? isLastOmo : isLastOmoSlim;
|
||||
const handleDisableAnyOmo = isOmoSlim ? onDisableOmoSlim : onDisableOmo;
|
||||
|
||||
const { data: health } = useProviderHealth(provider.id, appId);
|
||||
|
||||
const fallbackUrlText = t("provider.notConfigured", {
|
||||
@@ -186,11 +197,11 @@ export function ProviderCard({
|
||||
};
|
||||
|
||||
// 判断是否是"当前使用中"的供应商
|
||||
// - OMO 供应商:使用 isCurrent
|
||||
// - OMO/OMO Slim 供应商:使用 isCurrent
|
||||
// - 累加模式应用(OpenCode 非 OMO / OpenClaw):不存在"当前"概念,始终返回 false
|
||||
// - 故障转移模式:代理实际使用的供应商(activeProviderId)
|
||||
// - 普通模式:isCurrent
|
||||
const isActiveProvider = isOmo
|
||||
const isActiveProvider = isAnyOmo
|
||||
? isCurrent
|
||||
: appId === "opencode" || appId === "openclaw"
|
||||
? false
|
||||
@@ -198,10 +209,10 @@ export function ProviderCard({
|
||||
? activeProviderId === provider.id
|
||||
: isCurrent;
|
||||
|
||||
const shouldUseGreen = !isOmo && isProxyTakeover && isActiveProvider;
|
||||
const shouldUseGreen = !isAnyOmo && isProxyTakeover && isActiveProvider;
|
||||
const shouldUseBlue =
|
||||
(isOmo && isActiveProvider) ||
|
||||
(!isOmo && !isProxyTakeover && isActiveProvider);
|
||||
(isAnyOmo && isActiveProvider) ||
|
||||
(!isAnyOmo && !isProxyTakeover && isActiveProvider);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -265,6 +276,12 @@ export function ProviderCard({
|
||||
</span>
|
||||
)}
|
||||
|
||||
{isOmoSlim && (
|
||||
<span className="inline-flex items-center rounded-md bg-indigo-100 px-1.5 py-0.5 text-[10px] font-semibold text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300">
|
||||
Slim
|
||||
</span>
|
||||
)}
|
||||
|
||||
{isProxyRunning && isInFailoverQueue && health && (
|
||||
<ProviderHealthBadge
|
||||
consecutiveFailures={health.consecutive_failures}
|
||||
@@ -372,8 +389,8 @@ export function ProviderCard({
|
||||
isInConfig={isInConfig}
|
||||
isTesting={isTesting}
|
||||
isProxyTakeover={isProxyTakeover}
|
||||
isOmo={isOmo}
|
||||
isLastOmo={isLastOmo}
|
||||
isOmo={isAnyOmo}
|
||||
isLastOmo={isLastAnyOmo}
|
||||
onSwitch={() => onSwitch(provider)}
|
||||
onEdit={() => onEdit(provider)}
|
||||
onDuplicate={() => onDuplicate(provider)}
|
||||
@@ -385,7 +402,7 @@ export function ProviderCard({
|
||||
? () => onRemoveFromConfig(provider)
|
||||
: undefined
|
||||
}
|
||||
onDisableOmo={onDisableOmo}
|
||||
onDisableOmo={handleDisableAnyOmo}
|
||||
onOpenTerminal={
|
||||
onOpenTerminal ? () => onOpenTerminal(provider) : undefined
|
||||
}
|
||||
|
||||
@@ -33,7 +33,12 @@ import {
|
||||
useAddToFailoverQueue,
|
||||
useRemoveFromFailoverQueue,
|
||||
} from "@/lib/query/failover";
|
||||
import { useCurrentOmoProviderId, useOmoProviderCount } from "@/lib/query/omo";
|
||||
import {
|
||||
useCurrentOmoProviderId,
|
||||
useOmoProviderCount,
|
||||
useCurrentOmoSlimProviderId,
|
||||
useOmoSlimProviderCount,
|
||||
} from "@/lib/query/omo";
|
||||
import { useCallback } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -47,6 +52,7 @@ interface ProviderListProps {
|
||||
onDelete: (provider: Provider) => void;
|
||||
onRemoveFromConfig?: (provider: Provider) => void;
|
||||
onDisableOmo?: () => void;
|
||||
onDisableOmoSlim?: () => void;
|
||||
onDuplicate: (provider: Provider) => void;
|
||||
onConfigureUsage?: (provider: Provider) => void;
|
||||
onOpenWebsite: (url: string) => void;
|
||||
@@ -68,6 +74,7 @@ export function ProviderList({
|
||||
onDelete,
|
||||
onRemoveFromConfig,
|
||||
onDisableOmo,
|
||||
onDisableOmoSlim,
|
||||
onDuplicate,
|
||||
onConfigureUsage,
|
||||
onOpenWebsite,
|
||||
@@ -135,6 +142,8 @@ export function ProviderList({
|
||||
const isOpenCode = appId === "opencode";
|
||||
const { data: currentOmoId } = useCurrentOmoProviderId(isOpenCode);
|
||||
const { data: omoProviderCount } = useOmoProviderCount(isOpenCode);
|
||||
const { data: currentOmoSlimId } = useCurrentOmoSlimProviderId(isOpenCode);
|
||||
const { data: omoSlimProviderCount } = useOmoSlimProviderCount(isOpenCode);
|
||||
|
||||
const getFailoverPriority = useCallback(
|
||||
(providerId: string): number | undefined => {
|
||||
@@ -239,13 +248,20 @@ export function ProviderList({
|
||||
<div className="space-y-3">
|
||||
{filteredProviders.map((provider) => {
|
||||
const isOmo = provider.category === "omo";
|
||||
const isOmoSlim = provider.category === "omo-slim";
|
||||
const isOmoCurrent = isOmo && provider.id === (currentOmoId || "");
|
||||
const isOmoSlimCurrent =
|
||||
isOmoSlim && provider.id === (currentOmoSlimId || "");
|
||||
return (
|
||||
<SortableProviderCard
|
||||
key={provider.id}
|
||||
provider={provider}
|
||||
isCurrent={
|
||||
isOmo ? isOmoCurrent : provider.id === currentProviderId
|
||||
isOmo
|
||||
? isOmoCurrent
|
||||
: isOmoSlim
|
||||
? isOmoSlimCurrent
|
||||
: provider.id === currentProviderId
|
||||
}
|
||||
appId={appId}
|
||||
isInConfig={isProviderInConfig(provider.id)}
|
||||
@@ -253,11 +269,18 @@ export function ProviderList({
|
||||
isLastOmo={
|
||||
isOmo && (omoProviderCount ?? 0) <= 1 && isOmoCurrent
|
||||
}
|
||||
isOmoSlim={isOmoSlim}
|
||||
isLastOmoSlim={
|
||||
isOmoSlim &&
|
||||
(omoSlimProviderCount ?? 0) <= 1 &&
|
||||
isOmoSlimCurrent
|
||||
}
|
||||
onSwitch={onSwitch}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
onRemoveFromConfig={onRemoveFromConfig}
|
||||
onDisableOmo={onDisableOmo}
|
||||
onDisableOmoSlim={onDisableOmoSlim}
|
||||
onDuplicate={onDuplicate}
|
||||
onConfigureUsage={onConfigureUsage}
|
||||
onOpenWebsite={onOpenWebsite}
|
||||
@@ -371,11 +394,14 @@ interface SortableProviderCardProps {
|
||||
isInConfig: boolean;
|
||||
isOmo: boolean;
|
||||
isLastOmo: boolean;
|
||||
isOmoSlim: boolean;
|
||||
isLastOmoSlim: boolean;
|
||||
onSwitch: (provider: Provider) => void;
|
||||
onEdit: (provider: Provider) => void;
|
||||
onDelete: (provider: Provider) => void;
|
||||
onRemoveFromConfig?: (provider: Provider) => void;
|
||||
onDisableOmo?: () => void;
|
||||
onDisableOmoSlim?: () => void;
|
||||
onDuplicate: (provider: Provider) => void;
|
||||
onConfigureUsage?: (provider: Provider) => void;
|
||||
onOpenWebsite: (url: string) => void;
|
||||
@@ -401,11 +427,14 @@ function SortableProviderCard({
|
||||
isInConfig,
|
||||
isOmo,
|
||||
isLastOmo,
|
||||
isOmoSlim,
|
||||
isLastOmoSlim,
|
||||
onSwitch,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onRemoveFromConfig,
|
||||
onDisableOmo,
|
||||
onDisableOmoSlim,
|
||||
onDuplicate,
|
||||
onConfigureUsage,
|
||||
onOpenWebsite,
|
||||
@@ -445,11 +474,14 @@ function SortableProviderCard({
|
||||
isInConfig={isInConfig}
|
||||
isOmo={isOmo}
|
||||
isLastOmo={isLastOmo}
|
||||
isOmoSlim={isOmoSlim}
|
||||
isLastOmoSlim={isLastOmoSlim}
|
||||
onSwitch={onSwitch}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
onRemoveFromConfig={onRemoveFromConfig}
|
||||
onDisableOmo={onDisableOmo}
|
||||
onDisableOmoSlim={onDisableOmoSlim}
|
||||
onDuplicate={onDuplicate}
|
||||
onConfigureUsage={
|
||||
onConfigureUsage ? (item) => onConfigureUsage(item) : () => undefined
|
||||
|
||||
@@ -23,6 +23,7 @@ interface OmoCommonConfigEditorProps {
|
||||
onGlobalConfigStateChange: (config: OmoGlobalConfig) => void;
|
||||
globalConfigRef: React.RefObject<OmoGlobalConfigFieldsRef | null>;
|
||||
fieldsKey: number;
|
||||
isSlim?: boolean;
|
||||
}
|
||||
|
||||
export function OmoCommonConfigEditor({
|
||||
@@ -37,6 +38,7 @@ export function OmoCommonConfigEditor({
|
||||
onGlobalConfigStateChange,
|
||||
globalConfigRef,
|
||||
fieldsKey,
|
||||
isSlim = false,
|
||||
}: OmoCommonConfigEditorProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
@@ -153,6 +155,7 @@ export function OmoCommonConfigEditor({
|
||||
ref={globalConfigRef as React.Ref<OmoGlobalConfigFieldsRef>}
|
||||
onStateChange={onGlobalConfigStateChange}
|
||||
hideSaveButtons
|
||||
isSlim={isSlim}
|
||||
/>
|
||||
</div>
|
||||
</FullScreenPanel>
|
||||
|
||||
@@ -41,10 +41,11 @@ import {
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
import { useReadOmoLocalFile } from "@/lib/query/omo";
|
||||
import { useReadOmoLocalFile, useReadOmoSlimLocalFile } from "@/lib/query/omo";
|
||||
import {
|
||||
OMO_BUILTIN_AGENTS,
|
||||
OMO_BUILTIN_CATEGORIES,
|
||||
OMO_SLIM_BUILTIN_AGENTS,
|
||||
type OmoAgentDef,
|
||||
type OmoCategoryDef,
|
||||
} from "@/types/omo";
|
||||
@@ -69,12 +70,13 @@ interface OmoFormFieldsProps {
|
||||
>;
|
||||
agents: Record<string, Record<string, unknown>>;
|
||||
onAgentsChange: (agents: Record<string, Record<string, unknown>>) => void;
|
||||
categories: Record<string, Record<string, unknown>>;
|
||||
onCategoriesChange: (
|
||||
categories?: Record<string, Record<string, unknown>>;
|
||||
onCategoriesChange?: (
|
||||
categories: Record<string, Record<string, unknown>>,
|
||||
) => void;
|
||||
otherFieldsStr: string;
|
||||
onOtherFieldsStrChange: (value: string) => void;
|
||||
isSlim?: boolean;
|
||||
}
|
||||
|
||||
export type CustomModelItem = {
|
||||
@@ -121,6 +123,9 @@ function DeferredKeyInput({
|
||||
}
|
||||
|
||||
const BUILTIN_AGENT_KEYS = new Set(OMO_BUILTIN_AGENTS.map((a) => a.key));
|
||||
const BUILTIN_AGENT_KEYS_SLIM = new Set(
|
||||
OMO_SLIM_BUILTIN_AGENTS.map((a) => a.key),
|
||||
);
|
||||
const BUILTIN_CATEGORY_KEYS = new Set(OMO_BUILTIN_CATEGORIES.map((c) => c.key));
|
||||
const EMPTY_VARIANT_VALUE = "__cc_switch_omo_variant_empty__";
|
||||
|
||||
@@ -303,13 +308,21 @@ export function OmoFormFields({
|
||||
presetMetaMap: _presetMetaMap = {},
|
||||
agents,
|
||||
onAgentsChange,
|
||||
categories,
|
||||
categories = {},
|
||||
onCategoriesChange,
|
||||
otherFieldsStr,
|
||||
onOtherFieldsStrChange,
|
||||
isSlim = false,
|
||||
}: OmoFormFieldsProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const builtinAgentDefs = isSlim
|
||||
? OMO_SLIM_BUILTIN_AGENTS
|
||||
: OMO_BUILTIN_AGENTS;
|
||||
const builtinAgentKeys = isSlim
|
||||
? BUILTIN_AGENT_KEYS_SLIM
|
||||
: BUILTIN_AGENT_KEYS;
|
||||
|
||||
const [mainAgentsOpen, setMainAgentsOpen] = useState(true);
|
||||
const [subAgentsOpen, setSubAgentsOpen] = useState(true);
|
||||
const [categoriesOpen, setCategoriesOpen] = useState(true);
|
||||
@@ -329,7 +342,7 @@ export function OmoFormFields({
|
||||
>({});
|
||||
|
||||
const [customAgents, setCustomAgents] = useState<CustomModelItem[]>(() =>
|
||||
collectCustomModels(agents, BUILTIN_AGENT_KEYS),
|
||||
collectCustomModels(agents, builtinAgentKeys),
|
||||
);
|
||||
|
||||
const [customCategories, setCustomCategories] = useState<CustomModelItem[]>(
|
||||
@@ -337,7 +350,7 @@ export function OmoFormFields({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCustomAgents(collectCustomModels(agents, BUILTIN_AGENT_KEYS));
|
||||
setCustomAgents(collectCustomModels(agents, builtinAgentKeys));
|
||||
}, [agents]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -349,17 +362,18 @@ export function OmoFormFields({
|
||||
onAgentsChange(
|
||||
mergeCustomModelsIntoStore(
|
||||
agents,
|
||||
BUILTIN_AGENT_KEYS,
|
||||
builtinAgentKeys,
|
||||
customs,
|
||||
modelVariantsMap,
|
||||
),
|
||||
);
|
||||
},
|
||||
[agents, onAgentsChange, modelVariantsMap],
|
||||
[agents, onAgentsChange, modelVariantsMap, builtinAgentKeys],
|
||||
);
|
||||
|
||||
const syncCustomCategories = useCallback(
|
||||
(customs: CustomModelItem[]) => {
|
||||
if (!onCategoriesChange) return;
|
||||
onCategoriesChange(
|
||||
mergeCustomModelsIntoStore(
|
||||
categories,
|
||||
@@ -709,7 +723,7 @@ export function OmoFormFields({
|
||||
}
|
||||
|
||||
const updatedAgents = { ...agents };
|
||||
for (const agentDef of OMO_BUILTIN_AGENTS) {
|
||||
for (const agentDef of builtinAgentDefs) {
|
||||
const recommendedValue = resolveRecommendedModel(agentDef.recommended);
|
||||
if (recommendedValue && !updatedAgents[agentDef.key]?.model) {
|
||||
updatedAgents[agentDef.key] = {
|
||||
@@ -720,30 +734,35 @@ export function OmoFormFields({
|
||||
}
|
||||
onAgentsChange(updatedAgents);
|
||||
|
||||
const updatedCategories = { ...categories };
|
||||
for (const catDef of OMO_BUILTIN_CATEGORIES) {
|
||||
const recommendedValue = resolveRecommendedModel(catDef.recommended);
|
||||
if (recommendedValue && !updatedCategories[catDef.key]?.model) {
|
||||
updatedCategories[catDef.key] = {
|
||||
...updatedCategories[catDef.key],
|
||||
model: recommendedValue,
|
||||
};
|
||||
if (!isSlim && onCategoriesChange) {
|
||||
const updatedCategories = { ...categories };
|
||||
for (const catDef of OMO_BUILTIN_CATEGORIES) {
|
||||
const recommendedValue = resolveRecommendedModel(catDef.recommended);
|
||||
if (recommendedValue && !updatedCategories[catDef.key]?.model) {
|
||||
updatedCategories[catDef.key] = {
|
||||
...updatedCategories[catDef.key],
|
||||
model: recommendedValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
onCategoriesChange(updatedCategories);
|
||||
}
|
||||
onCategoriesChange(updatedCategories);
|
||||
};
|
||||
|
||||
const configuredAgentCount = Object.keys(agents).length;
|
||||
const configuredCategoryCount = Object.keys(categories).length;
|
||||
const mainAgents = OMO_BUILTIN_AGENTS.filter((a) => a.group === "main");
|
||||
const subAgents = OMO_BUILTIN_AGENTS.filter((a) => a.group === "sub");
|
||||
const configuredCategoryCount = isSlim ? 0 : Object.keys(categories).length;
|
||||
const mainAgents = builtinAgentDefs.filter((a) => a.group === "main");
|
||||
const subAgents = builtinAgentDefs.filter((a) => a.group === "sub");
|
||||
|
||||
const readLocalFile = useReadOmoLocalFile();
|
||||
const readSlimLocalFile = useReadOmoSlimLocalFile();
|
||||
const [localFilePath, setLocalFilePath] = useState<string | null>(null);
|
||||
|
||||
const handleImportFromLocal = useCallback(async () => {
|
||||
try {
|
||||
const data = await readLocalFile.mutateAsync();
|
||||
const data = isSlim
|
||||
? await readSlimLocalFile.mutateAsync()
|
||||
: await readLocalFile.mutateAsync();
|
||||
const importedAgents =
|
||||
(data.agents as Record<string, Record<string, unknown>> | undefined) ||
|
||||
{};
|
||||
@@ -753,16 +772,20 @@ export function OmoFormFields({
|
||||
| undefined) || {};
|
||||
|
||||
onAgentsChange(importedAgents);
|
||||
onCategoriesChange(importedCategories);
|
||||
if (!isSlim && onCategoriesChange) {
|
||||
onCategoriesChange(importedCategories);
|
||||
}
|
||||
onOtherFieldsStrChange(
|
||||
data.otherFields ? JSON.stringify(data.otherFields, null, 2) : "",
|
||||
);
|
||||
setAgentAdvancedDrafts({});
|
||||
setCategoryAdvancedDrafts({});
|
||||
setCustomAgents(collectCustomModels(importedAgents, BUILTIN_AGENT_KEYS));
|
||||
setCustomCategories(
|
||||
collectCustomModels(importedCategories, BUILTIN_CATEGORY_KEYS),
|
||||
);
|
||||
setCustomAgents(collectCustomModels(importedAgents, builtinAgentKeys));
|
||||
if (!isSlim) {
|
||||
setCustomCategories(
|
||||
collectCustomModels(importedCategories, BUILTIN_CATEGORY_KEYS),
|
||||
);
|
||||
}
|
||||
setLocalFilePath(data.filePath);
|
||||
toast.success(
|
||||
t("omo.importLocalReplaceSuccess", {
|
||||
@@ -792,7 +815,7 @@ export function OmoFormFields({
|
||||
) => {
|
||||
const isAgent = scope === "agent";
|
||||
const store = isAgent ? agents : categories;
|
||||
const setter = isAgent ? onAgentsChange : onCategoriesChange;
|
||||
const setter = isAgent ? onAgentsChange : onCategoriesChange!;
|
||||
const drafts = isAgent ? agentAdvancedDrafts : categoryAdvancedDrafts;
|
||||
const expanded = isAgent ? expandedAgents : expandedCategories;
|
||||
|
||||
@@ -866,7 +889,7 @@ export function OmoFormFields({
|
||||
) => {
|
||||
const isAgent = scope === "agent";
|
||||
const store = isAgent ? agents : categories;
|
||||
const setter = isAgent ? onAgentsChange : onCategoriesChange;
|
||||
const setter = isAgent ? onAgentsChange : onCategoriesChange!;
|
||||
const drafts = isAgent ? agentAdvancedDrafts : categoryAdvancedDrafts;
|
||||
const expanded = isAgent ? expandedAgents : expandedCategories;
|
||||
const customs = isAgent ? customAgents : customCategories;
|
||||
@@ -1153,30 +1176,31 @@ export function OmoFormFields({
|
||||
),
|
||||
})}
|
||||
|
||||
{renderModelSection({
|
||||
title: t("omo.categories", { defaultValue: "Categories" }),
|
||||
isOpen: categoriesOpen,
|
||||
onToggle: () => setCategoriesOpen(!categoriesOpen),
|
||||
badge: `${OMO_BUILTIN_CATEGORIES.length + customCategories.length}`,
|
||||
action: renderCustomAddButton(() => addCustomModel("category")),
|
||||
children: (
|
||||
<>
|
||||
{OMO_BUILTIN_CATEGORIES.map(renderCategoryRow)}
|
||||
{customCategories.length > 0 && (
|
||||
<>
|
||||
{renderCustomDivider(
|
||||
t("omo.customCategories", {
|
||||
defaultValue: "Custom Categories",
|
||||
}),
|
||||
)}
|
||||
{customCategories.map((c, i) =>
|
||||
renderCustomModelRow("category", c, i),
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
})}
|
||||
{!isSlim &&
|
||||
renderModelSection({
|
||||
title: t("omo.categories", { defaultValue: "Categories" }),
|
||||
isOpen: categoriesOpen,
|
||||
onToggle: () => setCategoriesOpen(!categoriesOpen),
|
||||
badge: `${OMO_BUILTIN_CATEGORIES.length + customCategories.length}`,
|
||||
action: renderCustomAddButton(() => addCustomModel("category")),
|
||||
children: (
|
||||
<>
|
||||
{OMO_BUILTIN_CATEGORIES.map(renderCategoryRow)}
|
||||
{customCategories.length > 0 && (
|
||||
<>
|
||||
{renderCustomDivider(
|
||||
t("omo.customCategories", {
|
||||
defaultValue: "Custom Categories",
|
||||
}),
|
||||
)}
|
||||
{customCategories.map((c, i) =>
|
||||
renderCustomModelRow("category", c, i),
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
})}
|
||||
|
||||
{renderModelSection({
|
||||
title: t("omo.otherFieldsJson", {
|
||||
|
||||
@@ -40,11 +40,18 @@ import {
|
||||
OMO_BACKGROUND_TASK_PLACEHOLDER,
|
||||
OMO_BROWSER_AUTOMATION_PLACEHOLDER,
|
||||
OMO_CLAUDE_CODE_PLACEHOLDER,
|
||||
OMO_SLIM_DISABLEABLE_AGENTS,
|
||||
OMO_SLIM_DISABLEABLE_MCPS,
|
||||
OMO_SLIM_DISABLEABLE_HOOKS,
|
||||
OMO_SLIM_DEFAULT_SCHEMA_URL,
|
||||
} from "@/types/omo";
|
||||
import {
|
||||
useOmoGlobalConfig,
|
||||
useSaveOmoGlobalConfig,
|
||||
useReadOmoLocalFile,
|
||||
useOmoSlimGlobalConfig,
|
||||
useSaveOmoSlimGlobalConfig,
|
||||
useReadOmoSlimLocalFile,
|
||||
} from "@/lib/query/omo";
|
||||
|
||||
interface PresetOption {
|
||||
@@ -61,6 +68,7 @@ export interface OmoGlobalConfigFieldsRef {
|
||||
interface OmoGlobalConfigFieldsProps {
|
||||
onStateChange?: (config: OmoGlobalConfig) => void;
|
||||
hideSaveButtons?: boolean;
|
||||
isSlim?: boolean;
|
||||
}
|
||||
|
||||
type OmoAdvancedFieldKey =
|
||||
@@ -114,6 +122,11 @@ const OMO_ADVANCED_JSON_FIELDS: ReadonlyArray<{
|
||||
},
|
||||
];
|
||||
|
||||
const OMO_SLIM_ADVANCED_KEYS: ReadonlySet<OmoAdvancedFieldKey> = new Set([
|
||||
"lspStr",
|
||||
"experimentalStr",
|
||||
]);
|
||||
|
||||
function TagListEditor({
|
||||
label,
|
||||
values,
|
||||
@@ -310,12 +323,25 @@ function JsonTextareaField({
|
||||
export const OmoGlobalConfigFields = forwardRef<
|
||||
OmoGlobalConfigFieldsRef,
|
||||
OmoGlobalConfigFieldsProps
|
||||
>(function OmoGlobalConfigFields({ onStateChange, hideSaveButtons }, ref) {
|
||||
>(function OmoGlobalConfigFields(
|
||||
{ onStateChange, hideSaveButtons, isSlim = false },
|
||||
ref,
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const { data: config } = useOmoGlobalConfig();
|
||||
const saveMutation = useSaveOmoGlobalConfig();
|
||||
const { data: standardConfig } = useOmoGlobalConfig(!isSlim);
|
||||
const { data: slimConfig } = useOmoSlimGlobalConfig(isSlim);
|
||||
const config = isSlim ? slimConfig : standardConfig;
|
||||
const standardSaveMutation = useSaveOmoGlobalConfig();
|
||||
const slimSaveMutation = useSaveOmoSlimGlobalConfig();
|
||||
const saveMutation = isSlim ? slimSaveMutation : standardSaveMutation;
|
||||
const standardReadLocal = useReadOmoLocalFile();
|
||||
const slimReadLocal = useReadOmoSlimLocalFile();
|
||||
|
||||
const [schemaUrl, setSchemaUrl] = useState(OMO_DEFAULT_SCHEMA_URL);
|
||||
const defaultSchemaUrl = isSlim
|
||||
? OMO_SLIM_DEFAULT_SCHEMA_URL
|
||||
: OMO_DEFAULT_SCHEMA_URL;
|
||||
|
||||
const [schemaUrl, setSchemaUrl] = useState(defaultSchemaUrl);
|
||||
const [sisyphusAgentStr, setSisyphusAgentStr] = useState("");
|
||||
const [disabledAgents, setDisabledAgents] = useState<string[]>([]);
|
||||
const [disabledMcps, setDisabledMcps] = useState<string[]>([]);
|
||||
@@ -330,7 +356,7 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
const applyGlobalState = useCallback((global: OmoGlobalConfig) => {
|
||||
setSchemaUrl(global.schemaUrl || OMO_DEFAULT_SCHEMA_URL);
|
||||
setSchemaUrl(global.schemaUrl || defaultSchemaUrl);
|
||||
setSisyphusAgentStr(
|
||||
global.sisyphusAgent ? JSON.stringify(global.sisyphusAgent, null, 2) : "",
|
||||
);
|
||||
@@ -545,7 +571,7 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
placeholder: t("omo.disabledAgentsPlaceholder", {
|
||||
defaultValue: "Disabled Agents",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_AGENTS,
|
||||
presets: isSlim ? OMO_SLIM_DISABLEABLE_AGENTS : OMO_DISABLEABLE_AGENTS,
|
||||
},
|
||||
{
|
||||
key: "mcps",
|
||||
@@ -555,7 +581,7 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
placeholder: t("omo.disabledMcpsPlaceholder", {
|
||||
defaultValue: "Disabled MCPs",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_MCPS,
|
||||
presets: isSlim ? OMO_SLIM_DISABLEABLE_MCPS : OMO_DISABLEABLE_MCPS,
|
||||
},
|
||||
{
|
||||
key: "hooks",
|
||||
@@ -565,21 +591,25 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
placeholder: t("omo.disabledHooksPlaceholder", {
|
||||
defaultValue: "Disabled Hooks",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_HOOKS,
|
||||
presets: isSlim ? OMO_SLIM_DISABLEABLE_HOOKS : OMO_DISABLEABLE_HOOKS,
|
||||
},
|
||||
{
|
||||
key: "skills",
|
||||
label: t("omo.disabledSkills", { defaultValue: "Skills" }),
|
||||
values: disabledSkills,
|
||||
onChange: setDisabledSkills,
|
||||
placeholder: t("omo.disabledSkillsPlaceholder", {
|
||||
defaultValue: "Disabled Skills",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_SKILLS,
|
||||
},
|
||||
] as const;
|
||||
...(!isSlim
|
||||
? [
|
||||
{
|
||||
key: "skills" as const,
|
||||
label: t("omo.disabledSkills", { defaultValue: "Skills" }),
|
||||
values: disabledSkills,
|
||||
onChange: setDisabledSkills,
|
||||
placeholder: t("omo.disabledSkillsPlaceholder", {
|
||||
defaultValue: "Disabled Skills",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_SKILLS,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
const readLocalFile = useReadOmoLocalFile();
|
||||
const readLocalFile = isSlim ? slimReadLocal : standardReadLocal;
|
||||
|
||||
const handleImportGlobalFromLocal = useCallback(async () => {
|
||||
try {
|
||||
@@ -668,25 +698,27 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
<Input
|
||||
value={schemaUrl}
|
||||
onChange={(e) => setSchemaUrl(e.target.value)}
|
||||
placeholder={OMO_DEFAULT_SCHEMA_URL}
|
||||
placeholder={defaultSchemaUrl}
|
||||
className="text-sm h-8"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("omo.sisyphusAgentConfig", {
|
||||
defaultValue: "Sisyphus Agent",
|
||||
})}
|
||||
</Label>
|
||||
<Textarea
|
||||
value={sisyphusAgentStr}
|
||||
onChange={(e) => setSisyphusAgentStr(e.target.value)}
|
||||
placeholder={OMO_SISYPHUS_AGENT_PLACEHOLDER}
|
||||
className="font-mono text-sm"
|
||||
style={{ minHeight: "140px" }}
|
||||
/>
|
||||
</div>
|
||||
{!isSlim && (
|
||||
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("omo.sisyphusAgentConfig", {
|
||||
defaultValue: "Sisyphus Agent",
|
||||
})}
|
||||
</Label>
|
||||
<Textarea
|
||||
value={sisyphusAgentStr}
|
||||
onChange={(e) => setSisyphusAgentStr(e.target.value)}
|
||||
placeholder={OMO_SISYPHUS_AGENT_PLACEHOLDER}
|
||||
className="font-mono text-sm"
|
||||
style={{ minHeight: "140px" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -715,7 +747,9 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("omo.advanced", { defaultValue: "Advanced Settings" })}
|
||||
</Label>
|
||||
{OMO_ADVANCED_JSON_FIELDS.map((field) => (
|
||||
{OMO_ADVANCED_JSON_FIELDS.filter(
|
||||
(field) => !isSlim || OMO_SLIM_ADVANCED_KEYS.has(field.key),
|
||||
).map((field) => (
|
||||
<JsonTextareaField
|
||||
key={field.key}
|
||||
label={t(field.labelKey, { defaultValue: field.defaultLabel })}
|
||||
|
||||
@@ -78,7 +78,7 @@ import {
|
||||
useOmoDraftState,
|
||||
useOpenclawFormState,
|
||||
} from "./hooks";
|
||||
import { useOmoGlobalConfig } from "@/lib/query/omo";
|
||||
import { useOmoGlobalConfig, useOmoSlimGlobalConfig } from "@/lib/query/omo";
|
||||
import {
|
||||
CLAUDE_DEFAULT_CONFIG,
|
||||
CODEX_DEFAULT_CONFIG,
|
||||
@@ -184,7 +184,15 @@ export function ProviderForm({
|
||||
initialCategory: initialData?.category,
|
||||
});
|
||||
const isOmoCategory = appId === "opencode" && category === "omo";
|
||||
const { data: queriedOmoGlobalConfig } = useOmoGlobalConfig(isOmoCategory);
|
||||
const isOmoSlimCategory = appId === "opencode" && category === "omo-slim";
|
||||
const isAnyOmoCategory = isOmoCategory || isOmoSlimCategory;
|
||||
const { data: queriedStandardOmoGlobalConfig } =
|
||||
useOmoGlobalConfig(isOmoCategory);
|
||||
const { data: queriedSlimOmoGlobalConfig } =
|
||||
useOmoSlimGlobalConfig(isOmoSlimCategory);
|
||||
const queriedOmoGlobalConfig = isOmoSlimCategory
|
||||
? queriedSlimOmoGlobalConfig
|
||||
: queriedStandardOmoGlobalConfig;
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedPresetId(initialData ? null : "custom");
|
||||
@@ -495,7 +503,7 @@ export function ProviderForm({
|
||||
omoModelVariantsMap,
|
||||
omoPresetMetaMap,
|
||||
existingOpencodeKeys,
|
||||
} = useOmoModelSource({ isOmoCategory, providerId });
|
||||
} = useOmoModelSource({ isOmoCategory: isAnyOmoCategory, providerId });
|
||||
|
||||
const opencodeForm = useOpencodeFormState({
|
||||
initialData,
|
||||
@@ -506,7 +514,8 @@ export function ProviderForm({
|
||||
});
|
||||
|
||||
const initialOmoSettings =
|
||||
appId === "opencode" && initialData?.category === "omo"
|
||||
appId === "opencode" &&
|
||||
(initialData?.category === "omo" || initialData?.category === "omo-slim")
|
||||
? (initialData.settingsConfig as Record<string, unknown> | undefined)
|
||||
: undefined;
|
||||
|
||||
@@ -677,13 +686,19 @@ export function ProviderForm({
|
||||
} catch (err) {
|
||||
settingsConfig = values.settingsConfig.trim();
|
||||
}
|
||||
} else if (appId === "opencode" && category === "omo") {
|
||||
} else if (
|
||||
appId === "opencode" &&
|
||||
(category === "omo" || category === "omo-slim")
|
||||
) {
|
||||
const omoConfig: Record<string, unknown> = {};
|
||||
omoConfig.useCommonConfig = omoDraft.useOmoCommonConfig;
|
||||
if (Object.keys(omoDraft.omoAgents).length > 0) {
|
||||
omoConfig.agents = omoDraft.omoAgents;
|
||||
}
|
||||
if (Object.keys(omoDraft.omoCategories).length > 0) {
|
||||
if (
|
||||
category === "omo" &&
|
||||
Object.keys(omoDraft.omoCategories).length > 0
|
||||
) {
|
||||
omoConfig.categories = omoDraft.omoCategories;
|
||||
}
|
||||
if (omoDraft.omoOtherFieldsStr.trim()) {
|
||||
@@ -1334,19 +1349,25 @@ export function ProviderForm({
|
||||
/>
|
||||
)}
|
||||
|
||||
{appId === "opencode" && category === "omo" && (
|
||||
<OmoFormFields
|
||||
modelOptions={omoModelOptions}
|
||||
modelVariantsMap={omoModelVariantsMap}
|
||||
presetMetaMap={omoPresetMetaMap}
|
||||
agents={omoDraft.omoAgents}
|
||||
onAgentsChange={omoDraft.setOmoAgents}
|
||||
categories={omoDraft.omoCategories}
|
||||
onCategoriesChange={omoDraft.setOmoCategories}
|
||||
otherFieldsStr={omoDraft.omoOtherFieldsStr}
|
||||
onOtherFieldsStrChange={omoDraft.setOmoOtherFieldsStr}
|
||||
/>
|
||||
)}
|
||||
{appId === "opencode" &&
|
||||
(category === "omo" || category === "omo-slim") && (
|
||||
<OmoFormFields
|
||||
modelOptions={omoModelOptions}
|
||||
modelVariantsMap={omoModelVariantsMap}
|
||||
presetMetaMap={omoPresetMetaMap}
|
||||
agents={omoDraft.omoAgents}
|
||||
onAgentsChange={omoDraft.setOmoAgents}
|
||||
categories={
|
||||
category === "omo" ? omoDraft.omoCategories : undefined
|
||||
}
|
||||
onCategoriesChange={
|
||||
category === "omo" ? omoDraft.setOmoCategories : undefined
|
||||
}
|
||||
otherFieldsStr={omoDraft.omoOtherFieldsStr}
|
||||
onOtherFieldsStrChange={omoDraft.setOmoOtherFieldsStr}
|
||||
isSlim={category === "omo-slim"}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* OpenClaw 专属字段 */}
|
||||
{appId === "openclaw" && (
|
||||
@@ -1408,7 +1429,8 @@ export function ProviderForm({
|
||||
/>
|
||||
{settingsConfigErrorField}
|
||||
</>
|
||||
) : appId === "opencode" && category === "omo" ? (
|
||||
) : appId === "opencode" &&
|
||||
(category === "omo" || category === "omo-slim") ? (
|
||||
<OmoCommonConfigEditor
|
||||
previewValue={omoDraft.mergedOmoJsonPreview}
|
||||
useCommonConfig={omoDraft.useOmoCommonConfig}
|
||||
@@ -1421,8 +1443,11 @@ export function ProviderForm({
|
||||
onGlobalConfigStateChange={omoDraft.setOmoGlobalState}
|
||||
globalConfigRef={omoDraft.omoGlobalConfigRef}
|
||||
fieldsKey={omoDraft.omoFieldsKey}
|
||||
isSlim={category === "omo-slim"}
|
||||
/>
|
||||
) : appId === "opencode" && category !== "omo" ? (
|
||||
) : appId === "opencode" &&
|
||||
category !== "omo" &&
|
||||
category !== "omo-slim" ? (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="settingsConfig">{t("provider.configJson")}</Label>
|
||||
|
||||
@@ -2,7 +2,11 @@ import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import type { OmoGlobalConfig } from "@/types/omo";
|
||||
import { mergeOmoConfigPreview } from "@/types/omo";
|
||||
import {
|
||||
mergeOmoConfigPreview,
|
||||
mergeOmoSlimConfigPreview,
|
||||
buildOmoSlimProfilePreview,
|
||||
} from "@/types/omo";
|
||||
import { type OmoGlobalConfigFieldsRef } from "../OmoGlobalConfigFields";
|
||||
import * as configApi from "@/lib/api/config";
|
||||
import {
|
||||
@@ -54,6 +58,8 @@ export function useOmoDraftState({
|
||||
category,
|
||||
}: UseOmoDraftStateParams): OmoDraftState {
|
||||
const { t } = useTranslation();
|
||||
const isSlim = category === "omo-slim";
|
||||
const commonConfigKey = isSlim ? "omo_slim" : "omo";
|
||||
|
||||
const [omoAgents, setOmoAgents] = useState<
|
||||
Record<string, Record<string, unknown>>
|
||||
@@ -93,6 +99,14 @@ export function useOmoDraftState({
|
||||
|
||||
const mergedOmoJsonPreview = useMemo(() => {
|
||||
if (useOmoCommonConfig) {
|
||||
if (isSlim) {
|
||||
const merged = mergeOmoSlimConfigPreview(
|
||||
effectiveOmoGlobalConfig,
|
||||
omoAgents,
|
||||
omoOtherFieldsStr,
|
||||
);
|
||||
return JSON.stringify(merged, null, 2);
|
||||
}
|
||||
const merged = mergeOmoConfigPreview(
|
||||
effectiveOmoGlobalConfig,
|
||||
omoAgents,
|
||||
@@ -101,6 +115,13 @@ export function useOmoDraftState({
|
||||
);
|
||||
return JSON.stringify(merged, null, 2);
|
||||
} else {
|
||||
if (isSlim) {
|
||||
return JSON.stringify(
|
||||
buildOmoSlimProfilePreview(omoAgents, omoOtherFieldsStr),
|
||||
null,
|
||||
2,
|
||||
);
|
||||
}
|
||||
return JSON.stringify(
|
||||
buildOmoProfilePreview(omoAgents, omoCategories, omoOtherFieldsStr),
|
||||
null,
|
||||
@@ -113,16 +134,22 @@ export function useOmoDraftState({
|
||||
omoAgents,
|
||||
omoCategories,
|
||||
omoOtherFieldsStr,
|
||||
isSlim,
|
||||
]);
|
||||
|
||||
// Auto-detect whether common config has content for new OMO profiles
|
||||
// Auto-detect whether common config has content for new OMO/OMO Slim profiles
|
||||
useEffect(() => {
|
||||
if (appId !== "opencode" || category !== "omo" || isEditMode) return;
|
||||
if (
|
||||
appId !== "opencode" ||
|
||||
(category !== "omo" && category !== "omo-slim") ||
|
||||
isEditMode
|
||||
)
|
||||
return;
|
||||
let active = true;
|
||||
(async () => {
|
||||
let next = false;
|
||||
try {
|
||||
const raw = await configApi.getCommonConfigSnippet("omo");
|
||||
const raw = await configApi.getCommonConfigSnippet(commonConfigKey);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||
next = Object.keys(parsed).some(
|
||||
@@ -135,14 +162,17 @@ export function useOmoDraftState({
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [appId, category, isEditMode]);
|
||||
}, [appId, category, isEditMode, commonConfigKey]);
|
||||
|
||||
const handleOmoGlobalConfigSave = useCallback(async () => {
|
||||
if (!omoGlobalConfigRef.current) return;
|
||||
setIsOmoSaving(true);
|
||||
try {
|
||||
const config = omoGlobalConfigRef.current.buildCurrentConfigStrict();
|
||||
await configApi.setCommonConfigSnippet("omo", JSON.stringify(config));
|
||||
await configApi.setCommonConfigSnippet(
|
||||
commonConfigKey,
|
||||
JSON.stringify(config),
|
||||
);
|
||||
setIsOmoConfigModalOpen(false);
|
||||
toast.success(
|
||||
t("omo.globalConfigSaved", { defaultValue: "Global config saved" }),
|
||||
@@ -152,7 +182,7 @@ export function useOmoDraftState({
|
||||
} finally {
|
||||
setIsOmoSaving(false);
|
||||
}
|
||||
}, [t]);
|
||||
}, [t, commonConfigKey]);
|
||||
|
||||
const handleOmoEditClick = useCallback(() => {
|
||||
setOmoFieldsKey((k) => k + 1);
|
||||
|
||||
@@ -133,7 +133,7 @@ export function useOmoModelSource({
|
||||
const parseFailedProviders: string[] = [];
|
||||
|
||||
for (const [providerKey, provider] of Object.entries(allProviders)) {
|
||||
if (provider.category === "omo") {
|
||||
if (provider.category === "omo" || provider.category === "omo-slim") {
|
||||
continue;
|
||||
}
|
||||
if (liveSet && !liveSet.has(providerKey)) {
|
||||
|
||||
@@ -1129,4 +1129,17 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
|
||||
iconColor: "#8B5CF6",
|
||||
isCustomTemplate: true,
|
||||
},
|
||||
{
|
||||
name: "Oh My OpenCode Slim",
|
||||
websiteUrl: "https://github.com/alvinunreal/oh-my-opencode-slim",
|
||||
settingsConfig: {
|
||||
npm: "",
|
||||
options: {},
|
||||
models: {},
|
||||
},
|
||||
category: "omo-slim" as ProviderCategory,
|
||||
icon: "opencode",
|
||||
iconColor: "#6366F1",
|
||||
isCustomTemplate: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1858,6 +1858,22 @@
|
||||
"unspecifiedLow": "Uncategorized low-effort task category for tasks that don't fit other categories with small workload. Defaults to Claude Sonnet 4.5.",
|
||||
"unspecifiedHigh": "Uncategorized high-effort task category for tasks that don't fit other categories with large workload. Defaults to Claude Opus 4.6 max variant.",
|
||||
"writing": "Writing category for documentation, prose and technical writing. Defaults to Gemini 3 Flash fast generation model."
|
||||
},
|
||||
"slimAgentDesc": {
|
||||
"orchestrator": "Orchestrator",
|
||||
"oracle": "Oracle",
|
||||
"librarian": "Librarian",
|
||||
"explorer": "Explorer",
|
||||
"designer": "Designer",
|
||||
"fixer": "Fixer"
|
||||
},
|
||||
"slimAgentTooltip": {
|
||||
"orchestrator": "Writes executable code, orchestrates multi-agent workflow, summons experts",
|
||||
"oracle": "Root cause analysis, architecture review, debugging guidance (read-only)",
|
||||
"librarian": "Documentation lookup, GitHub code search (read-only)",
|
||||
"explorer": "Regex search, AST pattern matching, file discovery (read-only)",
|
||||
"designer": "Modern responsive design, CSS/Tailwind expertise",
|
||||
"fixer": "Code implementation, refactoring, testing, verification"
|
||||
}
|
||||
},
|
||||
"openclawConfig": {
|
||||
|
||||
@@ -1839,6 +1839,22 @@
|
||||
"unspecifiedLow": "未分類の低作業量タスクカテゴリ。他のカテゴリに該当せず作業量が小さいタスクに適用。デフォルトで Claude Sonnet 4.5 を使用。",
|
||||
"unspecifiedHigh": "未分類の高作業量タスクカテゴリ。他のカテゴリに該当せず作業量が大きいタスクに適用。デフォルトで Claude Opus 4.6 の max バリアントを使用。",
|
||||
"writing": "ライティングカテゴリ。ドキュメント、散文、技術文書に特化。デフォルトで Gemini 3 Flash 高速生成モデルを使用。"
|
||||
},
|
||||
"slimAgentDesc": {
|
||||
"orchestrator": "オーケストレーター",
|
||||
"oracle": "オラクル",
|
||||
"librarian": "ライブラリアン",
|
||||
"explorer": "エクスプローラー",
|
||||
"designer": "デザイナー",
|
||||
"fixer": "フィクサー"
|
||||
},
|
||||
"slimAgentTooltip": {
|
||||
"orchestrator": "実行コードの作成、マルチエージェントワークフローの調整、エキスパートの召喚",
|
||||
"oracle": "根本原因分析、アーキテクチャレビュー、デバッグガイダンス(読み取り専用)",
|
||||
"librarian": "ドキュメント検索、GitHubコード検索(読み取り専用)",
|
||||
"explorer": "正規表現検索、ASTパターンマッチング、ファイル検出(読み取り専用)",
|
||||
"designer": "モダンなレスポンシブデザイン、CSS/Tailwindの専門知識",
|
||||
"fixer": "コード実装、リファクタリング、テスト、検証"
|
||||
}
|
||||
},
|
||||
"openclawConfig": {
|
||||
|
||||
@@ -1858,6 +1858,22 @@
|
||||
"unspecifiedLow": "未归类低工作量任务类别,适用于不适合其他类别且工作量较小的任务,默认使用 Claude Sonnet 4.5。",
|
||||
"unspecifiedHigh": "未归类高工作量任务类别,适用于不适合其他类别且工作量较大的任务,默认使用 Claude Opus 4.6 的最大变体。",
|
||||
"writing": "写作类别,专注于文档、散文和技术写作,默认使用 Gemini 3 Flash 快速生成模型。"
|
||||
},
|
||||
"slimAgentDesc": {
|
||||
"orchestrator": "编排者",
|
||||
"oracle": "神谕者",
|
||||
"librarian": "图书管理员",
|
||||
"explorer": "探索者",
|
||||
"designer": "设计师",
|
||||
"fixer": "修复者"
|
||||
},
|
||||
"slimAgentTooltip": {
|
||||
"orchestrator": "编写执行代码,编排多代理工作流,召唤专家",
|
||||
"oracle": "根本原因分析、架构审查、调试指导(只读)",
|
||||
"librarian": "文档查询、GitHub 代码搜索(只读)",
|
||||
"explorer": "正则搜索、AST 模式匹配、文件发现(只读)",
|
||||
"designer": "现代响应式设计、CSS/Tailwind 精通",
|
||||
"fixer": "代码实现、重构、测试、验证"
|
||||
}
|
||||
},
|
||||
"openclawConfig": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 配置相关 API
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export type AppType = "claude" | "codex" | "gemini" | "omo";
|
||||
export type AppType = "claude" | "codex" | "gemini" | "omo" | "omo_slim";
|
||||
|
||||
/**
|
||||
* 获取 Claude 通用配置片段(已废弃,使用 getCommonConfigSnippet)
|
||||
|
||||
@@ -8,3 +8,13 @@ export const omoApi = {
|
||||
getOmoProviderCount: (): Promise<number> => invoke("get_omo_provider_count"),
|
||||
disableCurrentOmo: (): Promise<void> => invoke("disable_current_omo"),
|
||||
};
|
||||
|
||||
export const omoSlimApi = {
|
||||
readLocalFile: (): Promise<OmoLocalFileData> =>
|
||||
invoke("read_omo_slim_local_file"),
|
||||
getCurrentProviderId: (): Promise<string> =>
|
||||
invoke("get_current_omo_slim_provider_id"),
|
||||
getProviderCount: (): Promise<number> =>
|
||||
invoke("get_omo_slim_provider_count"),
|
||||
disableCurrent: (): Promise<void> => invoke("disable_current_omo_slim"),
|
||||
};
|
||||
|
||||
+100
-1
@@ -1,5 +1,5 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { omoApi } from "@/lib/api/omo";
|
||||
import { omoApi, omoSlimApi } from "@/lib/api/omo";
|
||||
import * as configApi from "@/lib/api/config";
|
||||
import type { OmoGlobalConfig } from "@/types/omo";
|
||||
|
||||
@@ -93,3 +93,102 @@ export function useDisableCurrentOmo() {
|
||||
onSuccess: () => invalidateOmoQueries(queryClient),
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OMO Slim query hooks
|
||||
// ============================================================================
|
||||
|
||||
export const omoSlimKeys = {
|
||||
all: ["omo-slim"] as const,
|
||||
globalConfig: () => [...omoSlimKeys.all, "global-config"] as const,
|
||||
currentProviderId: () => [...omoSlimKeys.all, "current-provider-id"] as const,
|
||||
providerCount: () => [...omoSlimKeys.all, "provider-count"] as const,
|
||||
};
|
||||
|
||||
function invalidateOmoSlimQueries(
|
||||
queryClient: ReturnType<typeof useQueryClient>,
|
||||
) {
|
||||
queryClient.invalidateQueries({ queryKey: omoSlimKeys.globalConfig() });
|
||||
queryClient.invalidateQueries({ queryKey: ["providers"] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: omoSlimKeys.currentProviderId(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: omoSlimKeys.providerCount() });
|
||||
}
|
||||
|
||||
export function useOmoSlimGlobalConfig(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: omoSlimKeys.globalConfig(),
|
||||
enabled,
|
||||
queryFn: async (): Promise<OmoGlobalConfig> => {
|
||||
const raw = await configApi.getCommonConfigSnippet("omo_slim");
|
||||
if (!raw) {
|
||||
return {
|
||||
id: "global",
|
||||
disabledAgents: [],
|
||||
disabledMcps: [],
|
||||
disabledHooks: [],
|
||||
disabledSkills: [],
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
try {
|
||||
return JSON.parse(raw) as OmoGlobalConfig;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"[omo-slim] invalid global config json, fallback to defaults",
|
||||
error,
|
||||
);
|
||||
return {
|
||||
id: "global",
|
||||
disabledAgents: [],
|
||||
disabledMcps: [],
|
||||
disabledHooks: [],
|
||||
disabledSkills: [],
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCurrentOmoSlimProviderId(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: omoSlimKeys.currentProviderId(),
|
||||
queryFn: omoSlimApi.getCurrentProviderId,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useOmoSlimProviderCount(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: omoSlimKeys.providerCount(),
|
||||
queryFn: omoSlimApi.getProviderCount,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSaveOmoSlimGlobalConfig() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (input: OmoGlobalConfig) => {
|
||||
const jsonStr = JSON.stringify(input);
|
||||
await configApi.setCommonConfigSnippet("omo_slim", jsonStr);
|
||||
},
|
||||
onSuccess: () => invalidateOmoSlimQueries(queryClient),
|
||||
});
|
||||
}
|
||||
|
||||
export function useReadOmoSlimLocalFile() {
|
||||
return useMutation({
|
||||
mutationFn: () => omoSlimApi.readLocalFile(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDisableCurrentOmoSlim() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: () => omoSlimApi.disableCurrent(),
|
||||
onSuccess: () => invalidateOmoSlimQueries(queryClient),
|
||||
});
|
||||
}
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@ export type ProviderCategory =
|
||||
| "aggregator" // 聚合网站
|
||||
| "third_party" // 第三方供应商
|
||||
| "custom" // 自定义
|
||||
| "omo"; // Oh My OpenCode
|
||||
| "omo" // Oh My OpenCode
|
||||
| "omo-slim"; // Oh My OpenCode Slim
|
||||
|
||||
export interface Provider {
|
||||
id: string;
|
||||
|
||||
@@ -327,6 +327,142 @@ export function parseOmoOtherFieldsObject(
|
||||
return parsed as Record<string, unknown>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OMO Slim (oh-my-opencode-slim) definitions
|
||||
// ============================================================================
|
||||
|
||||
export const OMO_SLIM_BUILTIN_AGENTS: OmoAgentDef[] = [
|
||||
{
|
||||
key: "orchestrator",
|
||||
display: "Orchestrator",
|
||||
descKey: "omo.slimAgentDesc.orchestrator",
|
||||
tooltipKey: "omo.slimAgentTooltip.orchestrator",
|
||||
recommended: "kimi-for-coding/k2p5",
|
||||
group: "main",
|
||||
},
|
||||
{
|
||||
key: "oracle",
|
||||
display: "Oracle",
|
||||
descKey: "omo.slimAgentDesc.oracle",
|
||||
tooltipKey: "omo.slimAgentTooltip.oracle",
|
||||
recommended: "openai/gpt-5.2-codex",
|
||||
group: "sub",
|
||||
},
|
||||
{
|
||||
key: "librarian",
|
||||
display: "Librarian",
|
||||
descKey: "omo.slimAgentDesc.librarian",
|
||||
tooltipKey: "omo.slimAgentTooltip.librarian",
|
||||
recommended: "openai/gpt-5.1-codex-mini",
|
||||
group: "sub",
|
||||
},
|
||||
{
|
||||
key: "explorer",
|
||||
display: "Explorer",
|
||||
descKey: "omo.slimAgentDesc.explorer",
|
||||
tooltipKey: "omo.slimAgentTooltip.explorer",
|
||||
recommended: "openai/gpt-5.1-codex-mini",
|
||||
group: "sub",
|
||||
},
|
||||
{
|
||||
key: "designer",
|
||||
display: "Designer",
|
||||
descKey: "omo.slimAgentDesc.designer",
|
||||
tooltipKey: "omo.slimAgentTooltip.designer",
|
||||
recommended: "kimi-for-coding/k2p5",
|
||||
group: "sub",
|
||||
},
|
||||
{
|
||||
key: "fixer",
|
||||
display: "Fixer",
|
||||
descKey: "omo.slimAgentDesc.fixer",
|
||||
tooltipKey: "omo.slimAgentTooltip.fixer",
|
||||
recommended: "openai/gpt-5.1-codex-mini",
|
||||
group: "sub",
|
||||
},
|
||||
];
|
||||
|
||||
export const OMO_SLIM_DISABLEABLE_AGENTS = [
|
||||
{ value: "orchestrator", label: "Orchestrator" },
|
||||
{ value: "oracle", label: "Oracle" },
|
||||
{ value: "librarian", label: "Librarian" },
|
||||
{ value: "explorer", label: "Explorer" },
|
||||
{ value: "designer", label: "Designer" },
|
||||
{ value: "fixer", label: "Fixer" },
|
||||
] as const;
|
||||
|
||||
export const OMO_SLIM_DISABLEABLE_MCPS = [
|
||||
{ value: "context7", label: "context7" },
|
||||
{ value: "grep_app", label: "grep_app" },
|
||||
{ value: "websearch", label: "websearch" },
|
||||
] as const;
|
||||
|
||||
export const OMO_SLIM_DISABLEABLE_HOOKS = [
|
||||
{ value: "auto-update-checker", label: "auto-update-checker" },
|
||||
{ value: "phase-reminder", label: "phase-reminder" },
|
||||
{ value: "post-read-nudge", label: "post-read-nudge" },
|
||||
] as const;
|
||||
|
||||
export const OMO_SLIM_DEFAULT_SCHEMA_URL =
|
||||
"https://raw.githubusercontent.com/alvinunreal/oh-my-opencode-slim/master/assets/oh-my-opencode-slim.schema.json";
|
||||
|
||||
export function mergeOmoSlimConfigPreview(
|
||||
global: OmoGlobalConfig | undefined,
|
||||
agents: Record<string, Record<string, unknown>>,
|
||||
otherFieldsStr: string,
|
||||
): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
if (global) {
|
||||
if (global.schemaUrl) result["$schema"] = global.schemaUrl;
|
||||
if (global.disabledAgents?.length)
|
||||
result["disabled_agents"] = global.disabledAgents;
|
||||
if (global.disabledMcps?.length)
|
||||
result["disabled_mcps"] = global.disabledMcps;
|
||||
if (global.disabledHooks?.length)
|
||||
result["disabled_hooks"] = global.disabledHooks;
|
||||
|
||||
if (global.otherFields) {
|
||||
for (const [k, v] of Object.entries(global.otherFields)) {
|
||||
result[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(agents).length > 0) result["agents"] = agents;
|
||||
|
||||
try {
|
||||
const other = parseOmoOtherFieldsObject(otherFieldsStr);
|
||||
if (other) {
|
||||
for (const [k, v] of Object.entries(other)) {
|
||||
result[k] = v;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function buildOmoSlimProfilePreview(
|
||||
agents: Record<string, Record<string, unknown>>,
|
||||
otherFieldsStr: string,
|
||||
): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
if (Object.keys(agents).length > 0) result["agents"] = agents;
|
||||
|
||||
try {
|
||||
const other = parseOmoOtherFieldsObject(otherFieldsStr);
|
||||
if (other) {
|
||||
for (const [k, v] of Object.entries(other)) {
|
||||
result[k] = v;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function mergeOmoConfigPreview(
|
||||
global: OmoGlobalConfig,
|
||||
agents: Record<string, Record<string, unknown>>,
|
||||
|
||||
Reference in New Issue
Block a user