feat: refine codex live chat flow

This commit is contained in:
2026-04-24 21:02:01 +09:00
parent d53532508b
commit 63e5d263a7
18 changed files with 1747 additions and 297 deletions

View File

@@ -17,7 +17,7 @@ import {
ThunderboltOutlined,
UpOutlined,
} from '@ant-design/icons';
import { Alert, Button, Checkbox, Input, Select, Spin, message } from 'antd';
import { Alert, Button, Checkbox, Input, Select, Spin, Typography, message } from 'antd';
import type { TextAreaRef } from 'antd/es/input/TextArea';
import {
useEffect,
@@ -40,6 +40,7 @@ import { copyPreviewContent, copyText } from './chatUtils';
import type { ChatComposerAttachment, ChatConversationRequest, ChatMessage } from './types';
const KST_TIME_ZONE = 'Asia/Seoul';
const { Text } = Typography;
const KST_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
const KST_DATE_TIME_FORMATTER = new Intl.DateTimeFormat('sv-SE', {
timeZone: KST_TIME_ZONE,
@@ -840,6 +841,22 @@ export function ChatConversationView({
return [...ordered, ...orphanActivityMessages];
}, [visibleMessages]);
const previewItemsByUrl = useMemo(() => new Map(previewItems.map((item) => [item.url, item])), [previewItems]);
const selectedChatTypeOption = useMemo(
() => chatTypeOptions.find((option) => option.value === selectedChatTypeId) ?? null,
[chatTypeOptions, selectedChatTypeId],
);
const normalizedSelectedChatTypeLabel = selectedChatTypeOption?.label?.trim() ?? '';
const isChatTypeReadonly = useMemo(() => {
if (isChatTypeSelectionLocked) {
return true;
}
if (typeof window === 'undefined') {
return false;
}
return Boolean(new URLSearchParams(window.location.search).get('sessionId')?.trim());
}, [isChatTypeSelectionLocked]);
const visiblePreviewItems = useMemo(() => {
if (!showLatestResourceOnly) {
return previewItems;
@@ -1524,9 +1541,14 @@ export function ChatConversationView({
),
}))}
getPopupContainer={(triggerNode) => triggerNode.closest('.app-chat-panel') ?? document.body}
disabled={chatTypeOptions.length === 0 || isChatTypeSelectionLocked}
disabled={chatTypeOptions.length === 0 || isChatTypeReadonly}
onChange={onSelectChatType}
/>
{normalizedSelectedChatTypeLabel && normalizedSelectedChatTypeLabel !== '일반 요청' ? (
<Text type="secondary" className="app-chat-panel__composer-type-note">
: {normalizedSelectedChatTypeLabel}
</Text>
) : null}
</div>
<div className="app-chat-panel__composer-actions">
<div className="app-chat-panel__composer-action-buttons">

View File

@@ -514,7 +514,11 @@ export function appendActivityEventToMessages(previous: ChatMessage[], event: Ch
}
export function createIntroMessage(chatTypeLabel?: string, chatTypeDescription?: string) {
const contextLabelLine = chatTypeLabel ? `선택 컨텍스트: ${chatTypeLabel}` : '';
const normalizedChatTypeLabel = chatTypeLabel?.trim() ?? '';
const contextLabelLine =
normalizedChatTypeLabel && normalizedChatTypeLabel !== '일반 요청'
? `선택 컨텍스트: ${normalizedChatTypeLabel}`
: '';
const contextDescriptionLine = chatTypeDescription ? `기본 문맥: ${chatTypeDescription}` : '';
return createChatMessage(
@@ -525,7 +529,11 @@ export function createIntroMessage(chatTypeLabel?: string, chatTypeDescription?:
export function buildOfflineReply(context: ChatViewContext, input: string) {
const normalized = input.toLowerCase();
const typeLine = context.chatTypeLabel ? `- 컨텍스트: ${context.chatTypeLabel}` : '';
const normalizedChatTypeLabel = context.chatTypeLabel?.trim() ?? '';
const typeLine =
normalizedChatTypeLabel && normalizedChatTypeLabel !== '일반 요청'
? `- 컨텍스트: ${normalizedChatTypeLabel}`
: '';
const descriptionLine = context.chatTypeDescription ? `- 기본 문맥: ${context.chatTypeDescription}` : '';
if (normalized.includes('preview') || normalized.includes('링크') || normalized.includes('url')) {