번역
This commit is contained in:
parent
9f7ea1d729
commit
feb82abbf7
14 changed files with 117 additions and 41 deletions
1
.claude/scheduled_tasks.lock
Normal file
1
.claude/scheduled_tasks.lock
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"sessionId":"2ac1c12e-84cc-457a-867a-5483881cd12c","pid":79317,"acquiredAt":1775918535843}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// src/renderer/components/meeting/MeetingChatPanel.tsx
|
||||
// Phase 15: 회의 상세 페이지 하단 AI 채팅 패널
|
||||
|
||||
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { Box, TextField, IconButton, LinearProgress, Tooltip } from '@mui/material'
|
||||
import { useState, useCallback, useEffect, useRef, useMemo } from 'react'
|
||||
import { Box, TextField, IconButton, Tooltip } from '@mui/material'
|
||||
import SendIcon from '@mui/icons-material/Send'
|
||||
import DeleteSweepIcon from '@mui/icons-material/DeleteSweep'
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess'
|
||||
|
|
@ -21,16 +21,25 @@ interface MeetingChatPanelProps {
|
|||
const COLLAPSED_HEIGHT = 40
|
||||
const EXPANDED_HEIGHT = 240
|
||||
|
||||
/** LLM <think>…</think> 리즈닝 블록 제거 (스트리밍 중 열린 태그 포함) */
|
||||
function stripThinkTags(text: string): string {
|
||||
// 완성된 <think>…</think> 블록 제거
|
||||
let result = text.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
||||
// 스트리밍 중 아직 닫히지 않은 <think>… 제거
|
||||
result = result.replace(/<think>[\s\S]*$/gi, '')
|
||||
return result.trim()
|
||||
}
|
||||
|
||||
// 타이핑 인디케이터 점 3개 애니메이션
|
||||
function TypingIndicator(): React.ReactElement {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '3px', px: 1, py: 0.5 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '4px', px: 0.5, py: 0.25 }}>
|
||||
{[0, 1, 2].map((i) => (
|
||||
<Box
|
||||
key={i}
|
||||
sx={{
|
||||
width: 5,
|
||||
height: 5,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: '50%',
|
||||
bgcolor: d3roPalette.accent.amber,
|
||||
animation: 'typing-dot 1.2s infinite',
|
||||
|
|
@ -131,6 +140,14 @@ export function MeetingChatPanel({ sessionId }: MeetingChatPanelProps): React.Re
|
|||
setCollapsed((prev) => !prev)
|
||||
}, [])
|
||||
|
||||
// 스트리밍 콘텐츠에서 think 태그 제거한 표시용 텍스트
|
||||
const visibleStreamingContent = useMemo(
|
||||
() => stripThinkTags(streamingContent),
|
||||
[streamingContent],
|
||||
)
|
||||
// think 블록 안에 있는 동안(표시할 텍스트 없음) → 리즈닝 중 표시
|
||||
const isReasoning = streaming && streamingContent.length > 0 && visibleStreamingContent.length === 0
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -201,41 +218,48 @@ export function MeetingChatPanel({ sessionId }: MeetingChatPanelProps): React.Re
|
|||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
{messages.map((msg) => (
|
||||
<Box
|
||||
key={`${msg.role}-${msg.timestamp}`}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start',
|
||||
}}
|
||||
>
|
||||
{messages.map((msg) => {
|
||||
const displayContent = msg.role === 'assistant'
|
||||
? stripThinkTags(msg.content)
|
||||
: msg.content
|
||||
if (!displayContent) return null
|
||||
return (
|
||||
<Box
|
||||
key={`${msg.role}-${msg.timestamp}`}
|
||||
sx={{
|
||||
maxWidth: '80%',
|
||||
px: 1.25,
|
||||
py: 0.5,
|
||||
borderRadius: '6px',
|
||||
fontFamily: d3roFontMono,
|
||||
fontSize: d3roTypo.compact.size,
|
||||
lineHeight: 1.5,
|
||||
bgcolor:
|
||||
msg.role === 'user'
|
||||
? d3roPalette.accent.amberDim
|
||||
: d3roPalette.bg.elevated,
|
||||
color: d3roPalette.text.primary,
|
||||
border: `1px solid ${
|
||||
msg.role === 'user'
|
||||
? d3roPalette.accent.amber
|
||||
: d3roPalette.border.subtle
|
||||
}`,
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'pre-wrap',
|
||||
display: 'flex',
|
||||
justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start',
|
||||
}}
|
||||
>
|
||||
{msg.content}
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: '80%',
|
||||
px: 1.25,
|
||||
py: 0.5,
|
||||
borderRadius: '6px',
|
||||
fontFamily: d3roFontMono,
|
||||
fontSize: d3roTypo.compact.size,
|
||||
lineHeight: 1.5,
|
||||
bgcolor:
|
||||
msg.role === 'user'
|
||||
? d3roPalette.accent.amberDim
|
||||
: d3roPalette.bg.elevated,
|
||||
color: d3roPalette.text.primary,
|
||||
border: `1px solid ${
|
||||
msg.role === 'user'
|
||||
? d3roPalette.accent.amber
|
||||
: d3roPalette.border.subtle
|
||||
}`,
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'pre-wrap',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{displayContent}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
|
||||
{/* 에러 메시지 */}
|
||||
{error && (
|
||||
|
|
@ -280,24 +304,63 @@ export function MeetingChatPanel({ sessionId }: MeetingChatPanelProps): React.Re
|
|||
border: `1px solid ${d3roPalette.border.subtle}`,
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'pre-wrap',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{streamingContent || <TypingIndicator />}
|
||||
{visibleStreamingContent || (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||
<TypingIndicator />
|
||||
{isReasoning && (
|
||||
<PhosphorText
|
||||
variant="dim"
|
||||
sx={{
|
||||
fontSize: d3roTypo.micro.size,
|
||||
opacity: 0.6,
|
||||
animation: 'reasoning-pulse 2s ease-in-out infinite',
|
||||
'@keyframes reasoning-pulse': {
|
||||
'0%, 100%': { opacity: 0.4 },
|
||||
'50%': { opacity: 0.8 },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t('meeting.chatReasoning')}
|
||||
</PhosphorText>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* 스트리밍 진행 표시 */}
|
||||
{/* 스트리밍 진행 표시 — 얇은 accent 라인 */}
|
||||
{streaming && (
|
||||
<LinearProgress
|
||||
<Box
|
||||
sx={{
|
||||
height: 1,
|
||||
height: 2,
|
||||
flexShrink: 0,
|
||||
bgcolor: d3roPalette.bg.inset,
|
||||
'& .MuiLinearProgress-bar': { bgcolor: d3roPalette.accent.amber },
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
width: '40%',
|
||||
bgcolor: d3roPalette.accent.amber,
|
||||
borderRadius: 1,
|
||||
animation: 'chat-progress 1.5s ease-in-out infinite',
|
||||
'@keyframes chat-progress': {
|
||||
'0%': { left: '-40%' },
|
||||
'100%': { left: '100%' },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 입력 영역 */}
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "Kopiert",
|
||||
"meeting.chatCollapse": "Chat einklappen",
|
||||
"meeting.chatExpand": "Chat ausklappen",
|
||||
"meeting.chatReasoning": "Denkt nach…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "KI-Polierung fehlgeschlagen",
|
||||
|
|
|
|||
|
|
@ -510,6 +510,7 @@
|
|||
"meeting.copied": "Copied",
|
||||
"meeting.chatCollapse": "Collapse Chat",
|
||||
"meeting.chatExpand": "Expand Chat",
|
||||
"meeting.chatReasoning": "Reasoning…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "AI polishing failed",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "Copiado",
|
||||
"meeting.chatCollapse": "Contraer chat",
|
||||
"meeting.chatExpand": "Expandir chat",
|
||||
"meeting.chatReasoning": "Razonando…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "Error al pulir con IA",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "Copié",
|
||||
"meeting.chatCollapse": "Réduire le chat",
|
||||
"meeting.chatExpand": "Agrandir le chat",
|
||||
"meeting.chatReasoning": "Raisonnement…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "Echec du polish IA",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "コピーしました",
|
||||
"meeting.chatCollapse": "チャットを折り畳む",
|
||||
"meeting.chatExpand": "チャットを展開",
|
||||
"meeting.chatReasoning": "推論中…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "AI磨き上げに失敗しました",
|
||||
|
|
|
|||
|
|
@ -510,6 +510,7 @@
|
|||
"meeting.copied": "복사됨",
|
||||
"meeting.chatCollapse": "채팅 접기",
|
||||
"meeting.chatExpand": "채팅 펼치기",
|
||||
"meeting.chatReasoning": "추론 중…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "AI 다듬기에 실패했습니다",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "Copiado",
|
||||
"meeting.chatCollapse": "Recolher chat",
|
||||
"meeting.chatExpand": "Expandir chat",
|
||||
"meeting.chatReasoning": "Raciocinando…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "Falha no polimento de IA",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "Скопировано",
|
||||
"meeting.chatCollapse": "Свернуть чат",
|
||||
"meeting.chatExpand": "Развернуть чат",
|
||||
"meeting.chatReasoning": "Размышляет…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "Ошибка ИИ-полировки",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "คัดลอกแล้ว",
|
||||
"meeting.chatCollapse": "ยุบแชท",
|
||||
"meeting.chatExpand": "ขยายแชท",
|
||||
"meeting.chatReasoning": "กำลังคิด…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "AI ปรับแต่งล้มเหลว",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "Đã sao chép",
|
||||
"meeting.chatCollapse": "Thu gọn chat",
|
||||
"meeting.chatExpand": "Mở rộng chat",
|
||||
"meeting.chatReasoning": "Đang suy luận…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "AI đánh bóng thất bại",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "已複製",
|
||||
"meeting.chatCollapse": "收起聊天",
|
||||
"meeting.chatExpand": "展開聊天",
|
||||
"meeting.chatReasoning": "推理中…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "AI潤色失敗",
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@
|
|||
"meeting.copied": "已复制",
|
||||
"meeting.chatCollapse": "收起聊天",
|
||||
"meeting.chatExpand": "展开聊天",
|
||||
"meeting.chatReasoning": "推理中…",
|
||||
"meeting.exportMd": "Markdown (.md)",
|
||||
"meeting.exportPdfFmt": "PDF (.pdf)",
|
||||
"meeting.polishFailed": "AI润色失败",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue