feat: update main chat and system chat UI

This commit is contained in:
2026-05-25 17:26:37 +09:00
parent fb5ec649cd
commit f59522ffc4
120 changed files with 43262 additions and 3325 deletions

View File

@@ -0,0 +1,252 @@
import { ReloadOutlined, SaveOutlined } from '@ant-design/icons';
import { Alert, App, Button, Card, Checkbox, Flex, Form, Input, InputNumber, Select, Space, Spin, Typography } from 'antd';
import { useCallback, useEffect, useState } from 'react';
import {
DEFAULT_APP_CONFIG,
getWeeklyScheduleOptions,
saveAppConfigToServer,
type AppConfig,
type PlanCostTimeUnit,
type WeeklyScheduleDay,
} from './appConfig';
import './SharedAppSettingsPage.css';
const { Paragraph, Text, Title } = Typography;
const PLAN_COST_TIME_UNIT_OPTIONS: Array<{ value: PlanCostTimeUnit; label: string }> = [
{ value: 'hour', label: '시간' },
{ value: 'minute', label: '분' },
{ value: 'second', label: '초' },
];
type SharedAppSettingsPageProps = {
shareToken: string;
};
type SharedAppSettingsFormValue = AppConfig;
export function SharedAppSettingsPage({ shareToken }: SharedAppSettingsPageProps) {
const { message } = App.useApp();
const [form] = Form.useForm<SharedAppSettingsFormValue>();
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [savedConfig, setSavedConfig] = useState<AppConfig>(DEFAULT_APP_CONFIG);
const loadConfig = useCallback(async () => {
setIsLoading(true);
setErrorMessage('');
try {
const response = await fetch('/api/app-config', {
headers: {
'X-Chat-Share-Token': shareToken,
},
cache: 'no-store',
});
if (!response.ok) {
const payload = (await response.json().catch(() => null)) as { message?: string } | null;
throw new Error(payload?.message || '앱 설정을 불러오지 못했습니다.');
}
const payload = (await response.json()) as { config?: AppConfig };
const nextConfig = payload.config ?? DEFAULT_APP_CONFIG;
setSavedConfig(nextConfig);
form.setFieldsValue(nextConfig);
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : '앱 설정을 불러오지 못했습니다.');
} finally {
setIsLoading(false);
}
}, [form, shareToken]);
useEffect(() => {
void loadConfig();
}, [loadConfig]);
const handleSave = useCallback(
async (values: SharedAppSettingsFormValue) => {
setIsSaving(true);
setErrorMessage('');
try {
const saved = await saveAppConfigToServer(values, {
shareToken,
skipAutomationNotifications: true,
});
setSavedConfig(saved);
form.setFieldsValue(saved);
message.success('앱 설정을 저장했습니다.');
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : '앱 설정 저장에 실패했습니다.');
} finally {
setIsSaving(false);
}
},
[form, message, shareToken],
);
if (isLoading) {
return (
<div className="shared-app-settings-page shared-app-settings-page--loading">
<Spin size="large" />
</div>
);
}
return (
<div className="shared-app-settings-page">
<Flex align="center" justify="space-between" gap={12} wrap>
<div>
<Title level={4}> </Title>
<Paragraph type="secondary">
.
</Paragraph>
</div>
<Space>
<Button icon={<ReloadOutlined />} onClick={() => void loadConfig()} disabled={isSaving}>
</Button>
<Button type="primary" icon={<SaveOutlined />} onClick={() => void form.submit()} loading={isSaving}>
</Button>
</Space>
</Flex>
{errorMessage ? <Alert showIcon type="error" message={errorMessage} /> : null}
<Form<SharedAppSettingsFormValue>
form={form}
layout="vertical"
initialValues={savedConfig}
onFinish={(values) => void handleSave(values)}
>
<div className="shared-app-settings-page__grid">
<Card size="small" title="채팅 문맥 설정">
<Form.Item label="최근 메시지 수" name={['chat', 'maxContextMessages']}>
<InputNumber min={1} max={50} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="최대 문자 수" name={['chat', 'maxContextChars']}>
<InputNumber min={500} max={20000} step={100} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="Codex Live 최대 실행 시간(초)" name={['chat', 'codexLiveMaxExecutionSeconds']}>
<InputNumber min={60} max={7200} step={30} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="무출력 실패 시간(초)" name={['chat', 'codexLiveIdleTimeoutSeconds']}>
<InputNumber min={30} max={3600} step={10} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="재기동 완료 자동 실행 대기(초)" name={['chat', 'restartReservationCompletionDelaySeconds']}>
<InputNumber min={1} max={300} style={{ width: '100%' }} />
</Form.Item>
<Form.Item name={['chat', 'receiveRoomNotifications']} valuePropName="checked">
<Checkbox> </Checkbox>
</Form.Item>
</Card>
<Card size="small" title="자동접수 / 주기">
<Form.Item name={['automation', 'autoRefreshEnabled']} valuePropName="checked">
<Checkbox> </Checkbox>
</Form.Item>
<Form.Item label="자동 새로고침 간격(초)" name={['automation', 'autoRefreshIntervalSeconds']}>
<InputNumber min={1} max={3600} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="자동접수 방식" name={['automation', 'autoReceiveScheduleType']}>
<Select
options={[
{ value: 'interval', label: '간격' },
{ value: 'daily', label: '매일' },
{ value: 'weekly', label: '매주' },
]}
/>
</Form.Item>
<Form.Item label="간격(초)" name={['automation', 'autoReceiveIntervalSeconds']}>
<InputNumber min={1} max={3600} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="매일 시각" name={['automation', 'autoReceiveDailyTime']}>
<Input placeholder="09:00" />
</Form.Item>
<Form.Item label="매주 요일" name={['automation', 'autoReceiveWeeklyDay']}>
<Select options={getWeeklyScheduleOptions()} />
</Form.Item>
<Form.Item label="매주 시각" name={['automation', 'autoReceiveWeeklyTime']}>
<Input placeholder="09:00" />
</Form.Item>
</Card>
<Card size="small" title="자동화 기본값">
<Form.Item name={['planDefaults', 'jangsingProcessingRequired']} valuePropName="checked">
<Checkbox> </Checkbox>
</Form.Item>
<Form.Item name={['planDefaults', 'autoDeployToMain']} valuePropName="checked">
<Checkbox>main </Checkbox>
</Form.Item>
<Form.Item name={['planDefaults', 'openEditorAfterCreate']} valuePropName="checked">
<Checkbox> </Checkbox>
</Form.Item>
</Card>
<Card size="small" title="업무일지 자동화">
<Form.Item name={['worklogAutomation', 'autoCreateDailyWorklog']} valuePropName="checked">
<Checkbox> </Checkbox>
</Form.Item>
<Form.Item label="생성 시각" name={['worklogAutomation', 'dailyCreateTime']}>
<Input placeholder="18:00" />
</Form.Item>
<Form.Item name={['worklogAutomation', 'includeScreenshots']} valuePropName="checked">
<Checkbox> </Checkbox>
</Form.Item>
<Form.Item name={['worklogAutomation', 'includeChangedFiles']} valuePropName="checked">
<Checkbox> </Checkbox>
</Form.Item>
<Form.Item name={['worklogAutomation', 'includeCommandLogs']} valuePropName="checked">
<Checkbox> </Checkbox>
</Form.Item>
<Form.Item label="템플릿" name={['worklogAutomation', 'template']}>
<Select
options={[
{ value: 'simple', label: '간단' },
{ value: 'detailed', label: '상세' },
]}
/>
</Form.Item>
</Card>
<Card size="small" title="비용 표시 / 단축키">
<Form.Item label="백만 토큰당 기본 비용" name={['planCost', 'baseCostPerMillionTokens']}>
<InputNumber min={100} max={1000000} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="재시도 비용 배수(%)" name={['planCost', 'retryCostMultiplierPercent']}>
<InputNumber min={0} max={500} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="시간 비용 배수(%)" name={['planCost', 'hourlyCostMultiplierPercent']}>
<InputNumber min={0} max={500} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="시간 단위" name={['planCost', 'timeCostUnit']}>
<Select options={PLAN_COST_TIME_UNIT_OPTIONS} />
</Form.Item>
<Form.Item label="주의 배수" name={['planCost', 'attentionCostThresholdMultiplier']}>
<InputNumber min={0.1} max={100} step={0.1} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="경고 배수" name={['planCost', 'warningCostThresholdMultiplier']}>
<InputNumber min={0.1} max={100} step={0.1} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="고비용 배수" name={['planCost', 'highCostThresholdMultiplier']}>
<InputNumber min={0.1} max={100} step={0.1} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="검색 단축키" name={['gestureShortcuts', 'openSearch']}>
<Input />
</Form.Item>
<Form.Item label="시스템 채팅 단축키" name={['gestureShortcuts', 'openWindowSearch']}>
<Input />
</Form.Item>
</Card>
</div>
</Form>
<Text type="secondary">
.
</Text>
</div>
);
}