Feat : tts

This commit is contained in:
decolua
2026-04-10 10:17:53 +07:00
parent 39545cf4c8
commit 3c96e8d6d1
40 changed files with 1896 additions and 147 deletions
@@ -13,7 +13,9 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
const [showInstallGuide, setShowInstallGuide] = useState(false);
const [selectedApiKey, setSelectedApiKey] = useState("");
const [selectedModel, setSelectedModel] = useState("");
const [subagentModel, setSubagentModel] = useState("");
const [modalOpen, setModalOpen] = useState(false);
const [subagentModalOpen, setSubagentModalOpen] = useState(false);
const [modelAliases, setModelAliases] = useState({});
const [showManualConfigModal, setShowManualConfigModal] = useState(false);
const [customBaseUrl, setCustomBaseUrl] = useState("");
@@ -46,11 +48,15 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
}
};
// Parse model from config content (don't sync URL - always use baseUrl from props)
// Parse model and subagent settings from config content
useEffect(() => {
if (codexStatus?.config) {
const modelMatch = codexStatus.config.match(/^model\s*=\s*"([^"]+)"/m);
if (modelMatch) setSelectedModel(modelMatch[1]);
// Parse subagent settings
const subagentModelMatch = codexStatus.config.match(/\[agents\.subagent\]\s*\n\s*model\s*=\s*"([^"]+)"/m);
if (subagentModelMatch) setSubagentModel(subagentModelMatch[1]);
}
}, [codexStatus]);
@@ -96,7 +102,12 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
const res = await fetch("/api/cli-tools/codex-settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, model: selectedModel }),
body: JSON.stringify({
baseUrl: getEffectiveBaseUrl(),
apiKey: keyToUse,
model: selectedModel,
subagentModel: subagentModel || selectedModel
}),
});
const data = await res.json();
if (res.ok) {
@@ -121,6 +132,7 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
if (res.ok) {
setMessage({ type: "success", text: "Settings reset successfully!" });
setSelectedModel("");
setSubagentModel("");
checkCodexStatus();
} else {
setMessage({ type: "error", text: data.error || "Failed to reset settings" });
@@ -134,6 +146,10 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
const handleModelSelect = (model) => {
setSelectedModel(model.value);
// Auto-set subagent model if not set
if (!subagentModel) {
setSubagentModel(model.value);
}
setModalOpen(false);
};
@@ -142,6 +158,8 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
? selectedApiKey
: (!cloudEnabled ? "sk_9router" : "<API_KEY_FROM_DASHBOARD>");
const effectiveSubagentModel = subagentModel || selectedModel;
const configContent = `# 9Router Configuration for Codex CLI
model = "${selectedModel}"
model_provider = "9router"
@@ -150,6 +168,9 @@ model_provider = "9router"
name = "9Router"
base_url = "${getEffectiveBaseUrl()}"
wire_api = "responses"
[agents.subagent]
model = "${effectiveSubagentModel}"
`;
const authContent = JSON.stringify({
@@ -290,6 +311,35 @@ wire_api = "responses"
<button onClick={() => setModalOpen(true)} disabled={!activeProviders?.length} className={`px-2 py-1.5 rounded border text-xs transition-colors shrink-0 whitespace-nowrap ${activeProviders?.length ? "bg-surface border-border text-text-main hover:border-primary cursor-pointer" : "opacity-50 cursor-not-allowed border-border"}`}>Select Model</button>
{selectedModel && <button onClick={() => setSelectedModel("")} className="p-1 text-text-muted hover:text-red-500 rounded transition-colors" title="Clear"><span className="material-symbols-outlined text-[14px]">close</span></button>}
</div>
{/* Subagent Model */}
<div className="flex items-center gap-2">
<span className="w-32 shrink-0 text-sm font-semibold text-text-main text-right">Subagent Model</span>
<span className="material-symbols-outlined text-text-muted text-[14px]">arrow_forward</span>
<input
type="text"
value={subagentModel}
onChange={(e) => setSubagentModel(e.target.value)}
placeholder={selectedModel || "provider/model-id (defaults to main model)"}
className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50"
/>
<button
onClick={() => setSubagentModalOpen(true)}
disabled={!activeProviders?.length}
className={`px-2 py-1.5 rounded border text-xs transition-colors shrink-0 whitespace-nowrap ${activeProviders?.length ? "bg-surface border-border text-text-main hover:border-primary cursor-pointer" : "opacity-50 cursor-not-allowed border-border"}`}
>
Select Model
</button>
{subagentModel && (
<button
onClick={() => setSubagentModel("")}
className="p-1 text-text-muted hover:text-red-500 rounded transition-colors"
title="Clear (will use main model)"
>
<span className="material-symbols-outlined text-[14px]">close</span>
</button>
)}
</div>
</div>
{message && (
@@ -325,6 +375,16 @@ wire_api = "responses"
title="Select Model for Codex"
/>
<ModelSelectModal
isOpen={subagentModalOpen}
onClose={() => setSubagentModalOpen(false)}
onSelect={(model) => { setSubagentModel(model.value); setSubagentModalOpen(false); }}
selectedModel={subagentModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Subagent Model for Codex"
/>
<ManualConfigModal
isOpen={showManualConfigModal}
onClose={() => setShowManualConfigModal(false)}