Reuse status dashboard message and simplify settings actions
This commit is contained in:
@@ -9,11 +9,7 @@ import {
|
||||
updateMoaSettings,
|
||||
} from './api';
|
||||
|
||||
export function MoaSettingsPanel({
|
||||
onRestartStack,
|
||||
}: {
|
||||
onRestartStack: () => void;
|
||||
}) {
|
||||
export function MoaSettingsPanel() {
|
||||
const [config, setConfig] = useState<MoaSettingsSnapshot | null>(null);
|
||||
const [draft, setDraft] = useState<MoaSettingsSnapshot | null>(null);
|
||||
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
|
||||
@@ -135,7 +131,6 @@ export function MoaSettingsPanel({
|
||||
setApiKeys((prev) => ({ ...prev, [name]: value }))
|
||||
}
|
||||
onModelChange={setModel}
|
||||
onRestartStack={onRestartStack}
|
||||
onSave={save}
|
||||
onTest={testModel}
|
||||
onToggle={() =>
|
||||
@@ -158,7 +153,6 @@ function MoaSettingsContent({
|
||||
draft,
|
||||
onApiKeyChange,
|
||||
onModelChange,
|
||||
onRestartStack,
|
||||
onSave,
|
||||
onTest,
|
||||
onToggle,
|
||||
@@ -174,7 +168,6 @@ function MoaSettingsContent({
|
||||
name: string,
|
||||
patch: Partial<MoaModelSettingsSnapshot>,
|
||||
) => void;
|
||||
onRestartStack: () => void;
|
||||
onSave: () => Promise<void>;
|
||||
onTest: (name: string) => Promise<void>;
|
||||
onToggle: () => void;
|
||||
@@ -206,7 +199,6 @@ function MoaSettingsContent({
|
||||
busy={busy}
|
||||
checking={checking}
|
||||
dirty={dirty}
|
||||
onRestartStack={onRestartStack}
|
||||
onSave={onSave}
|
||||
savedAt={savedAt}
|
||||
/>
|
||||
@@ -256,14 +248,12 @@ function MoaSettingsActions({
|
||||
busy,
|
||||
checking,
|
||||
dirty,
|
||||
onRestartStack,
|
||||
onSave,
|
||||
savedAt,
|
||||
}: {
|
||||
busy: boolean;
|
||||
checking: string | null;
|
||||
dirty: boolean;
|
||||
onRestartStack: () => void;
|
||||
onSave: () => Promise<void>;
|
||||
savedAt: number | null;
|
||||
}) {
|
||||
@@ -275,29 +265,13 @@ function MoaSettingsActions({
|
||||
onClick={() => void onSave()}
|
||||
type="button"
|
||||
>
|
||||
{busy ? '저장 중…' : '저장'}
|
||||
{busy ? '저장 중…' : 'MoA 저장'}
|
||||
</button>
|
||||
{savedAt && !dirty ? (
|
||||
<small className="settings-hint">
|
||||
저장됨. 적용하려면 스택 재시작 필요.
|
||||
</small>
|
||||
) : null}
|
||||
<button
|
||||
className="settings-restart"
|
||||
disabled={busy || checking !== null}
|
||||
onClick={() => {
|
||||
if (
|
||||
window.confirm(
|
||||
'스택을 재시작하면 진행 중인 모든 에이전트 작업이 중단됩니다. 진행할까요?',
|
||||
)
|
||||
) {
|
||||
onRestartStack();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
스택 재시작
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,10 +35,12 @@ describe('SettingsPanel', () => {
|
||||
expect(html).toContain('MoA 참조 모델');
|
||||
expect(html).toContain('패스트 모드');
|
||||
expect(html).toContain('Codex 실험 기능');
|
||||
expect(html).toContain('변경 적용');
|
||||
expect(html).toContain('불러오는 중');
|
||||
expect(html).toContain('Claude');
|
||||
expect(html).toContain('계정');
|
||||
expect(html).toContain('전체 갱신');
|
||||
expect(html).toContain('스택 재시작');
|
||||
expect(html.match(/class="settings-restart"/g)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,20 +79,51 @@ export function SettingsPanel({
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<ModelSettings onRestartStack={onRestartStack} />
|
||||
<SettingsApplyBar onRestartStack={onRestartStack} />
|
||||
|
||||
<MoaSettingsPanel onRestartStack={onRestartStack} />
|
||||
<ModelSettings />
|
||||
|
||||
<MoaSettingsPanel />
|
||||
|
||||
<FastModeSettings />
|
||||
|
||||
<CodexFeatureSettings onRestartStack={onRestartStack} />
|
||||
<CodexFeatureSettings />
|
||||
|
||||
<AccountSettings onRestartStack={onRestartStack} />
|
||||
<AccountSettings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModelSettings({ onRestartStack }: { onRestartStack: () => void }) {
|
||||
function SettingsApplyBar({ onRestartStack }: { onRestartStack: () => void }) {
|
||||
return (
|
||||
<section className="settings-apply-bar" aria-label="변경 적용">
|
||||
<div>
|
||||
<strong>변경 적용</strong>
|
||||
<small>
|
||||
모델, MoA, Codex 실험 기능, 계정 변경은 저장 후 스택 재시작으로
|
||||
적용됩니다.
|
||||
</small>
|
||||
</div>
|
||||
<button
|
||||
className="settings-restart"
|
||||
onClick={() => {
|
||||
if (
|
||||
window.confirm(
|
||||
'스택을 재시작하면 진행 중인 모든 에이전트 작업이 중단됩니다. 진행할까요?',
|
||||
)
|
||||
) {
|
||||
onRestartStack();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
스택 재시작
|
||||
</button>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function ModelSettings() {
|
||||
const [config, setConfig] = useState<ModelConfigSnapshot | null>(null);
|
||||
const [draft, setDraft] = useState<ModelConfigSnapshot | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
@@ -193,29 +224,13 @@ function ModelSettings({ onRestartStack }: { onRestartStack: () => void }) {
|
||||
onClick={() => void save()}
|
||||
type="button"
|
||||
>
|
||||
{busy ? '저장 중…' : '저장'}
|
||||
{busy ? '저장 중…' : '모델 저장'}
|
||||
</button>
|
||||
{savedAt && !dirty ? (
|
||||
<small className="settings-hint">
|
||||
저장됨. 적용하려면 스택 재시작 필요.
|
||||
</small>
|
||||
) : null}
|
||||
<button
|
||||
className="settings-restart"
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
if (
|
||||
window.confirm(
|
||||
'스택을 재시작하면 진행 중인 모든 에이전트 작업이 중단됩니다. 진행할까요?',
|
||||
)
|
||||
) {
|
||||
onRestartStack();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
스택 재시작
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -307,11 +322,7 @@ function FastModeSettings() {
|
||||
);
|
||||
}
|
||||
|
||||
function CodexFeatureSettings({
|
||||
onRestartStack,
|
||||
}: {
|
||||
onRestartStack: () => void;
|
||||
}) {
|
||||
function CodexFeatureSettings() {
|
||||
const [state, setState] = useState<CodexFeatureSnapshot | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -376,29 +387,11 @@ function CodexFeatureSettings({
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
<div className="settings-actions">
|
||||
{savedAt ? (
|
||||
<small className="settings-hint">
|
||||
저장됨. 적용하려면 스택 재시작 필요.
|
||||
</small>
|
||||
) : null}
|
||||
<button
|
||||
className="settings-restart"
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
if (
|
||||
window.confirm(
|
||||
'스택을 재시작하면 진행 중인 모든 에이전트 작업이 중단됩니다. 진행할까요?',
|
||||
)
|
||||
) {
|
||||
onRestartStack();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
스택 재시작
|
||||
</button>
|
||||
</div>
|
||||
{savedAt ? (
|
||||
<small className="settings-hint">
|
||||
저장됨. 적용하려면 상단의 스택 재시작을 눌러 주세요.
|
||||
</small>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
@@ -436,7 +429,7 @@ function formatExpiry(
|
||||
};
|
||||
}
|
||||
|
||||
function AccountSettings({ onRestartStack }: { onRestartStack: () => void }) {
|
||||
function AccountSettings() {
|
||||
const [data, setData] = useState<AccountData | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [perRowBusy, setPerRowBusy] = useState<string | null>(null);
|
||||
@@ -561,24 +554,6 @@ function AccountSettings({ onRestartStack }: { onRestartStack: () => void }) {
|
||||
onSwitch={(index) => void handleSwitchCodex(index)}
|
||||
perRowBusy={perRowBusy}
|
||||
/>
|
||||
<div className="settings-actions">
|
||||
<button
|
||||
className="settings-restart"
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
if (
|
||||
window.confirm(
|
||||
'스택을 재시작하면 진행 중인 모든 에이전트 작업이 중단됩니다. 진행할까요?',
|
||||
)
|
||||
) {
|
||||
onRestartStack();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
스택 재시작 (변경 적용)
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -696,6 +696,39 @@ button:disabled {
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.settings-apply-bar {
|
||||
position: sticky;
|
||||
top: 72px;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(214, 130, 88, 0.32);
|
||||
border-radius: 10px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(214, 130, 88, 0.14), transparent 55%),
|
||||
rgba(19, 24, 22, 0.94);
|
||||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.settings-apply-bar > div {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-apply-bar strong {
|
||||
color: var(--bg-ink);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.settings-apply-bar small {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.settings-row-inline {
|
||||
grid-template-columns: 80px minmax(0, 1fr) 100px;
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user