mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 20:12:48 +05:30
minor(gui): Disable simulation button if no provider found
This commit is contained in:
@@ -25,11 +25,15 @@ export default function ConfigPage() {
|
||||
const [config, setConfig] = useState<ConfigStatus | null>(null);
|
||||
const [instances, setInstances] = useState<LLMProviderInstance[]>([]);
|
||||
const [mappings, setMappings] = useState<Record<string, string>>({});
|
||||
const [availableProviders, setAvailableProviders] = useState<LLMProviderMeta[]>([]);
|
||||
const [availableProviders, setAvailableProviders] = useState<
|
||||
LLMProviderMeta[]
|
||||
>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const [selectedInstanceId, setSelectedInstanceId] = useState<string | null>(null);
|
||||
const [selectedInstanceId, setSelectedInstanceId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [editName, setEditName] = useState("");
|
||||
const [editProvider, setEditProvider] = useState("google-genai");
|
||||
const [editKey, setEditKey] = useState("");
|
||||
@@ -57,8 +61,12 @@ export default function ConfigPage() {
|
||||
setEditName(inst.name);
|
||||
setEditProvider(inst.providerName);
|
||||
setEditKey("");
|
||||
const pMeta = availableProviders.find((p) => p.id === inst.providerName);
|
||||
setEditModel(inst.modelName || pMeta?.defaultModel || "gemini-2.5-flash");
|
||||
const pMeta = availableProviders.find(
|
||||
(p) => p.id === inst.providerName,
|
||||
);
|
||||
setEditModel(
|
||||
inst.modelName || pMeta?.defaultModel || "gemini-2.5-flash",
|
||||
);
|
||||
setEditIsActive(inst.isActive);
|
||||
}
|
||||
}
|
||||
@@ -128,13 +136,24 @@ export default function ConfigPage() {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const created = await createProviderInstance(editName, editProvider, editKey, editModel || undefined);
|
||||
const created = await createProviderInstance(
|
||||
editName,
|
||||
editProvider,
|
||||
editKey,
|
||||
editModel || undefined,
|
||||
);
|
||||
if (editIsActive) {
|
||||
await setActiveProviderInstance(created.id);
|
||||
}
|
||||
setSelectedInstanceId(created.id);
|
||||
} else {
|
||||
await updateProviderInstance(selectedInstanceId, editName, editProvider, editKey || undefined, editModel || undefined);
|
||||
await updateProviderInstance(
|
||||
selectedInstanceId,
|
||||
editName,
|
||||
editProvider,
|
||||
editKey || undefined,
|
||||
editModel || undefined,
|
||||
);
|
||||
if (editIsActive) {
|
||||
await setActiveProviderInstance(selectedInstanceId);
|
||||
}
|
||||
@@ -151,7 +170,8 @@ export default function ConfigPage() {
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (selectedInstanceId === "new" || selectedInstanceId === null) return;
|
||||
if (!confirm("Are you sure you want to delete this provider instance?")) return;
|
||||
if (!confirm("Are you sure you want to delete this provider instance?"))
|
||||
return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -167,7 +187,10 @@ export default function ConfigPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateMapping = async (task: string, providerInstanceId: string) => {
|
||||
const handleUpdateMapping = async (
|
||||
task: string,
|
||||
providerInstanceId: string,
|
||||
) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await setProviderMapping(task, providerInstanceId);
|
||||
@@ -192,13 +215,15 @@ export default function ConfigPage() {
|
||||
|
||||
{config && !loading && (
|
||||
<>
|
||||
<section className="mb-8 border-b border-gray-200 pb-6">
|
||||
<section className="mb-8 pb-6">
|
||||
<h2 className="mb-3 text-lg">LLM Provider Instances</h2>
|
||||
<div className="mt-4 grid min-h-[400px] grid-cols-1 overflow-hidden rounded-xl border border-gray-200 bg-white md:grid-cols-[30%_70%]">
|
||||
{/* 30% area */}
|
||||
<div className="flex flex-col border-r border-gray-200 bg-gray-50">
|
||||
<div className="flex items-center justify-between border-b border-gray-200 bg-gray-100 px-4 py-4">
|
||||
<h3 className="m-0 text-[0.95rem] font-semibold text-[#111]">Instances</h3>
|
||||
<h3 className="m-0 text-[0.95rem] font-semibold text-[#111]">
|
||||
Instances
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setSelectedInstanceId("new")}
|
||||
className="cursor-pointer rounded-md bg-emerald-500 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-emerald-600"
|
||||
@@ -223,7 +248,9 @@ export default function ConfigPage() {
|
||||
: "border-l-transparent"
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm font-medium text-[#111]">{inst.name}</div>
|
||||
<div className="text-sm font-medium text-[#111]">
|
||||
{inst.name}
|
||||
</div>
|
||||
<div className="mt-1 flex items-center justify-between text-xs text-gray-500">
|
||||
<span>{inst.providerName}</span>
|
||||
{inst.isActive && (
|
||||
@@ -245,7 +272,10 @@ export default function ConfigPage() {
|
||||
Press + to add or select an existing Instance to edit
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSave} className="flex h-full flex-col justify-between">
|
||||
<form
|
||||
onSubmit={handleSave}
|
||||
className="flex h-full flex-col justify-between"
|
||||
>
|
||||
<div className="flex flex-1 flex-col gap-5 p-6">
|
||||
<h3 className="m-0 mb-2 text-lg font-semibold text-[#111]">
|
||||
{selectedInstanceId === "new"
|
||||
@@ -254,7 +284,10 @@ export default function ConfigPage() {
|
||||
</h3>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="formName" className="text-xs font-medium text-gray-700">
|
||||
<label
|
||||
htmlFor="formName"
|
||||
className="text-xs font-medium text-gray-700"
|
||||
>
|
||||
Friendly Name
|
||||
</label>
|
||||
<input
|
||||
@@ -269,7 +302,10 @@ export default function ConfigPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="formProvider" className="text-xs font-medium text-gray-700">
|
||||
<label
|
||||
htmlFor="formProvider"
|
||||
className="text-xs font-medium text-gray-700"
|
||||
>
|
||||
Provider Type
|
||||
</label>
|
||||
<select
|
||||
@@ -286,13 +322,20 @@ export default function ConfigPage() {
|
||||
</select>
|
||||
{editProvider && availableProviders.length > 0 && (
|
||||
<span className="mt-1 block rounded border border-gray-200 bg-gray-100 px-3 py-2 text-xs text-gray-600">
|
||||
{availableProviders.find((p) => p.id === editProvider)?.description}
|
||||
{
|
||||
availableProviders.find(
|
||||
(p) => p.id === editProvider,
|
||||
)?.description
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="formKey" className="text-xs font-medium text-gray-700">
|
||||
<label
|
||||
htmlFor="formKey"
|
||||
className="text-xs font-medium text-gray-700"
|
||||
>
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
@@ -311,7 +354,10 @@ export default function ConfigPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="formModel" className="text-xs font-medium text-gray-700">
|
||||
<label
|
||||
htmlFor="formModel"
|
||||
className="text-xs font-medium text-gray-700"
|
||||
>
|
||||
Model Name
|
||||
</label>
|
||||
<input
|
||||
@@ -332,7 +378,10 @@ export default function ConfigPage() {
|
||||
onChange={(e) => setEditIsActive(e.target.checked)}
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
<label htmlFor="formActive" className="cursor-pointer text-xs font-medium text-gray-700">
|
||||
<label
|
||||
htmlFor="formActive"
|
||||
className="cursor-pointer text-xs font-medium text-gray-700"
|
||||
>
|
||||
Set as Active Instance
|
||||
</label>
|
||||
</div>
|
||||
@@ -367,37 +416,58 @@ export default function ConfigPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-8 border-b border-gray-200 pb-6">
|
||||
<section className="mb-8 pb-6">
|
||||
<h2 className="mb-3 text-lg">Task Provider Routing</h2>
|
||||
<p className="my-4 rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800">
|
||||
Configure which LLM Provider Key Instance should handle each specific simulation
|
||||
task. Mappings default to the currently <strong>Active</strong> instance if not
|
||||
specified.
|
||||
Configure which LLM Provider Key Instance should handle each
|
||||
specific simulation task. Mappings default to the currently{" "}
|
||||
<strong>Active</strong> instance if not specified.
|
||||
</p>
|
||||
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{[
|
||||
{ key: "actor-prose", label: "Actor Prose Generation", desc: "Generates roleplay/narrative prose for Non-Player Characters." },
|
||||
{ key: "llm-validator", label: "LLM Validator", desc: "Arbitrates and validates proposed actions against the world state rules." },
|
||||
{ key: "intent-decoder", label: "Intent Decoder", desc: "Splits raw prose actions into structured intents (Player and NPC)." },
|
||||
{ key: "timedelta", label: "TimeDelta Generator", desc: "Calculates the duration of character actions to advance the game clock." },
|
||||
{
|
||||
key: "actor-prose",
|
||||
label: "Actor Prose Generation",
|
||||
desc: "Generates roleplay/narrative prose for Non-Player Characters.",
|
||||
},
|
||||
{
|
||||
key: "llm-validator",
|
||||
label: "LLM Validator",
|
||||
desc: "Arbitrates and validates proposed actions against the world state rules.",
|
||||
},
|
||||
{
|
||||
key: "intent-decoder",
|
||||
label: "Intent Decoder",
|
||||
desc: "Splits raw prose actions into structured intents (Player and NPC).",
|
||||
},
|
||||
{
|
||||
key: "timedelta",
|
||||
label: "TimeDelta Generator",
|
||||
desc: "Calculates the duration of character actions to advance the game clock.",
|
||||
},
|
||||
].map((task) => (
|
||||
<div
|
||||
key={task.key}
|
||||
className="flex flex-col justify-between gap-3 rounded-lg border border-gray-200 bg-gray-50 p-4"
|
||||
>
|
||||
<div className="flex flex-col gap-1 text-xs">
|
||||
<strong className="text-sm text-[#111]">{task.label}</strong>
|
||||
<strong className="text-sm text-[#111]">
|
||||
{task.label}
|
||||
</strong>
|
||||
<span className="mt-0.5 text-gray-500">{task.desc}</span>
|
||||
</div>
|
||||
<select
|
||||
value={mappings[task.key] || ""}
|
||||
onChange={(e) => handleUpdateMapping(task.key, e.target.value)}
|
||||
onChange={(e) =>
|
||||
handleUpdateMapping(task.key, e.target.value)
|
||||
}
|
||||
className="w-full rounded border border-gray-300 bg-white px-2 py-1.5 text-xs"
|
||||
>
|
||||
<option value="">Use Default Provider</option>
|
||||
{instances.map((inst) => (
|
||||
<option key={inst.id} value={inst.id}>
|
||||
{inst.name} ({inst.providerName}){inst.isActive ? " [Active]" : ""}
|
||||
{inst.name} ({inst.providerName})
|
||||
{inst.isActive ? " [Active]" : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -406,35 +476,15 @@ export default function ConfigPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-8 border-b border-gray-200 pb-6">
|
||||
<h2 className="mb-3 text-lg">Environment Variables Default</h2>
|
||||
<div className="flex justify-between border-b border-gray-100 py-1.5">
|
||||
<span className="text-sm text-gray-500">Default Model</span>
|
||||
<span className="text-sm">
|
||||
<code className="font-mono text-sm">{config.model}</code>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-gray-100 py-1.5">
|
||||
<span className="text-sm text-gray-500">Default API Key (.env)</span>
|
||||
<span
|
||||
className={
|
||||
config.apiKeySet
|
||||
? "text-sm text-green-600"
|
||||
: "text-sm font-medium text-red-600"
|
||||
}
|
||||
>
|
||||
{config.apiKeySet
|
||||
? `✓ Set (${config.apiKeyPreview})`
|
||||
: "✗ NOT SET"}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-8 border-b border-gray-200 pb-6">
|
||||
<section className="mb-8 pb-6">
|
||||
<h2 className="mb-3 text-lg">Available Scenarios</h2>
|
||||
{config.availableScenarios.length === 0 ? (
|
||||
<p className="mt-3 rounded border border-amber-200 bg-amber-100 px-3 py-2 text-xs text-amber-800">
|
||||
No scenarios found in <code className="font-mono text-xs">content/demo/scenarios/</code>.
|
||||
No scenarios found in{" "}
|
||||
<code className="font-mono text-xs">
|
||||
content/demo/scenarios/
|
||||
</code>
|
||||
.
|
||||
</p>
|
||||
) : (
|
||||
<table className="w-full border-collapse text-sm">
|
||||
@@ -453,7 +503,9 @@ export default function ConfigPage() {
|
||||
<tr key={s.path}>
|
||||
<td className="border-b border-gray-100 p-2">{s.name}</td>
|
||||
<td className="border-b border-gray-100 p-2">
|
||||
<code className="font-mono text-xs text-blue-600">{s.path}</code>
|
||||
<code className="font-mono text-xs text-blue-600">
|
||||
{s.path}
|
||||
</code>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -461,17 +513,6 @@ export default function ConfigPage() {
|
||||
</table>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="mb-8 border-b border-gray-200 pb-6">
|
||||
<h2 className="mb-3 text-lg">Engine Packages</h2>
|
||||
<p className="mt-3 rounded border border-amber-200 bg-amber-100 px-3 py-2 text-xs text-amber-800">
|
||||
All <code className="font-mono text-xs">@omnia/*</code> workspace packages are
|
||||
consumed via <code className="font-mono text-xs">transpilePackages</code> in{" "}
|
||||
<code className="font-mono text-xs">next.config.ts</code>. The native{" "}
|
||||
<code className="font-mono text-xs">better-sqlite3</code> module is externalized
|
||||
via <code className="font-mono text-xs">serverExternalPackages</code>.
|
||||
</p>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -510,7 +510,7 @@ export function PlayView() {
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" disabled={loading}>
|
||||
<button type="submit" disabled={loading || providerInstances.length === 0}>
|
||||
{loading ? "Starting..." : "Start Simulation"}
|
||||
</button>
|
||||
</form>
|
||||
@@ -534,7 +534,7 @@ export function PlayView() {
|
||||
</span>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
<button onClick={() => handleResume(s.id)} disabled={loading}>
|
||||
<button onClick={() => handleResume(s.id)} disabled={loading || providerInstances.length === 0}>
|
||||
Resume
|
||||
</button>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user