Add room skill setting mutations
This commit is contained in:
@@ -171,6 +171,43 @@
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.runtime-room-skill-toggles {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.runtime-room-skill-toggle {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 44px;
|
||||
border-radius: 12px;
|
||||
background: rgba(15, 23, 42, 0.38);
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.runtime-room-skill-toggle input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.runtime-room-skill-toggle > span {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.runtime-room-skill-toggle strong {
|
||||
color: var(--text);
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
.runtime-room-skill-toggle small {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.runtime-summary-card,
|
||||
.runtime-skill-grid,
|
||||
|
||||
@@ -3,11 +3,13 @@ import { useEffect, useState } from 'react';
|
||||
import {
|
||||
fetchRoomSkillSettings,
|
||||
fetchRuntimeInventory,
|
||||
updateRoomSkillSetting,
|
||||
type RuntimeAgentInventory,
|
||||
type RuntimeInventorySnapshot,
|
||||
type RuntimePathSnapshot,
|
||||
type RuntimeSkillDirSnapshot,
|
||||
type RoomSkillCatalogItem,
|
||||
type RoomSkillSettingUpdateInput,
|
||||
type RoomSkillSettingsSnapshot,
|
||||
} from './api';
|
||||
import { SettingsSectionHeading } from './SettingsPanelChrome';
|
||||
@@ -106,8 +108,12 @@ function agentLabel(agentType: 'claude-code' | 'codex') {
|
||||
}
|
||||
|
||||
function RoomSkillPolicyCard({
|
||||
onToggle,
|
||||
savingKey,
|
||||
snapshot,
|
||||
}: {
|
||||
onToggle: (input: RoomSkillSettingUpdateInput) => void;
|
||||
savingKey: string | null;
|
||||
snapshot: RoomSkillSettingsSnapshot | null;
|
||||
}) {
|
||||
if (!snapshot) {
|
||||
@@ -159,6 +165,7 @@ function RoomSkillPolicyCard({
|
||||
const disabledNames = agent.disabledSkillIds
|
||||
.map((id) => catalogById.get(id)?.displayName ?? id)
|
||||
.slice(0, 3);
|
||||
const enabledIds = new Set(agent.effectiveEnabledSkillIds);
|
||||
return (
|
||||
<div
|
||||
className="runtime-room-agent-policy"
|
||||
@@ -177,6 +184,39 @@ function RoomSkillPolicyCard({
|
||||
{disabledNames.length > 0 ? (
|
||||
<small>OFF: {disabledNames.join(', ')}</small>
|
||||
) : null}
|
||||
<div className="runtime-room-skill-toggles">
|
||||
{agent.availableSkillIds.map((skillId) => {
|
||||
const skill = catalogById.get(skillId);
|
||||
const key = `${room.jid}:${agent.agentType}:${skillId}`;
|
||||
return (
|
||||
<label
|
||||
className="runtime-room-skill-toggle"
|
||||
key={skillId}
|
||||
>
|
||||
<input
|
||||
checked={enabledIds.has(skillId)}
|
||||
disabled={savingKey !== null}
|
||||
onChange={(event) =>
|
||||
onToggle({
|
||||
roomJid: room.jid,
|
||||
agentType: agent.agentType,
|
||||
skillId,
|
||||
enabled: event.currentTarget.checked,
|
||||
})
|
||||
}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>
|
||||
<strong>{skill?.displayName ?? skillId}</strong>
|
||||
<small>
|
||||
{skill?.scope ?? 'unknown'}
|
||||
{savingKey === key ? ' · 저장 중' : ''}
|
||||
</small>
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -201,6 +241,10 @@ export function RuntimeInventorySettings() {
|
||||
const [roomSkillSnapshot, setRoomSkillSnapshot] =
|
||||
useState<RoomSkillSettingsSnapshot | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [roomSkillError, setRoomSkillError] = useState<string | null>(null);
|
||||
const [savingRoomSkillKey, setSavingRoomSkillKey] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -220,6 +264,20 @@ export function RuntimeInventorySettings() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function handleRoomSkillToggle(input: RoomSkillSettingUpdateInput) {
|
||||
const key = `${input.roomJid}:${input.agentType}:${input.skillId}`;
|
||||
setSavingRoomSkillKey(key);
|
||||
setRoomSkillError(null);
|
||||
try {
|
||||
const next = await updateRoomSkillSetting(input);
|
||||
setRoomSkillSnapshot(next);
|
||||
} catch (err) {
|
||||
setRoomSkillError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setSavingRoomSkillKey(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-labelledby="settings-runtime-tab"
|
||||
@@ -255,7 +313,16 @@ export function RuntimeInventorySettings() {
|
||||
|
||||
<AgentInventoryCard title="Codex" inventory={snapshot.codex} />
|
||||
<AgentInventoryCard title="Claude Code" inventory={snapshot.claude} />
|
||||
<RoomSkillPolicyCard snapshot={roomSkillSnapshot} />
|
||||
{roomSkillError ? (
|
||||
<p className="settings-error">{roomSkillError}</p>
|
||||
) : null}
|
||||
<RoomSkillPolicyCard
|
||||
onToggle={(input) => {
|
||||
void handleRoomSkillToggle(input);
|
||||
}}
|
||||
savingKey={savingRoomSkillKey}
|
||||
snapshot={roomSkillSnapshot}
|
||||
/>
|
||||
|
||||
<article className="runtime-agent-card">
|
||||
<header className="runtime-card-head">
|
||||
|
||||
@@ -473,6 +473,13 @@ export interface RoomSkillSettingsSnapshot {
|
||||
rooms: RoomSkillPolicyRoom[];
|
||||
}
|
||||
|
||||
export interface RoomSkillSettingUpdateInput {
|
||||
roomJid: string;
|
||||
agentType: 'claude-code' | 'codex';
|
||||
skillId: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface MoaReferenceStatus {
|
||||
model: string;
|
||||
checkedAt: string;
|
||||
@@ -635,6 +642,30 @@ export async function fetchRoomSkillSettings(): Promise<RoomSkillSettingsSnapsho
|
||||
return fetchJson('/api/settings/room-skills');
|
||||
}
|
||||
|
||||
export async function updateRoomSkillSetting(
|
||||
input: RoomSkillSettingUpdateInput,
|
||||
): Promise<RoomSkillSettingsSnapshot> {
|
||||
const response = await fetch('/api/settings/room-skills', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
if (!response.ok) {
|
||||
let msg = `update room skill failed: ${response.status}`;
|
||||
try {
|
||||
const payload = (await response.json()) as { error?: string };
|
||||
if (payload.error) msg = payload.error;
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
return (await response.json()) as RoomSkillSettingsSnapshot;
|
||||
}
|
||||
|
||||
export async function updateCodexFeatures(
|
||||
input: Partial<CodexFeatureSnapshot>,
|
||||
): Promise<CodexFeatureSnapshot> {
|
||||
|
||||
Reference in New Issue
Block a user