diff --git a/packages/web/public/locales/translation/en.yaml b/packages/web/public/locales/translation/en.yaml index 9ca77c541..9d843ac2c 100644 --- a/packages/web/public/locales/translation/en.yaml +++ b/packages/web/public/locales/translation/en.yaml @@ -813,9 +813,15 @@ mcp_chat: meetingMinutes: auto_generate: Auto Generate bidirectional: Bidirectional Translation - clear_minutes: Clear Minutes + both_panel: Both Panels + clear_minutes: Clear custom_prompt: Custom Prompt custom_prompt_placeholder: Enter your custom prompt for generating meeting minutes... + diagram_flowchart: Flowchart (Meeting Flow) + diagram_mindmap: Mindmap (Decisions) + diagram_options: Diagram Types to Generate + diagram_sequence: Sequence Diagram (Relationships) + diagram_timeline: Timeline (Deadlines & Schedule) frequency: Generation Frequency frequency_10min: Every 10 minutes frequency_1min: Every 1 minute @@ -827,7 +833,10 @@ meetingMinutes: generating: Generating minutes... generation_error: Failed to generate meeting minutes. Please try again. generation_frequency: Generation Frequency + generation_panel: Generate Minutes generation_success: Meeting minutes generated successfully + hide_prompt: ▼ Hide prompt + input_source: Input Source language: Transcription Language language_1: Language 1 language_2: Language 2 @@ -839,31 +848,38 @@ meetingMinutes: language_thai: Thai language_vietnamese: Vietnamese last_generated: Last generated {{time}} + microphone: Microphone minutes_placeholder: Minutes will appear here after generation model: Meeting Minutes Generation Model new_recording_session: New Recording Session next_generation: Next Generation next_generation_in: 'Next generation in : ' no_transcript: No Transcript + option: Option partial_indicator: (...) record_transcribe: Record & Transcribe screen_audio_error: Screen Audio Error + settings: Settings speech_recognition: Speech Recognition style: Minutes Style style_custom: Custom style_detail: Detail + style_diagram: Diagram style_faq: FAQ style_newspaper: Newspaper style_summary: Summary style_transcription: Transcription + style_whiteboard: Whiteboard target_language: Real-time Translation Language title: Meeting Minutes Generator transcript: Transcript transcription_language: Transcription Language + transcription_panel: Transcription translation_language: Translation Language translation_model: Real-time Translation Model translation_type: Translation Type unidirectional: Unidirectional Translation + view_prompt: ▶ View prompt model: parameters: reasoning_budget: Token budget for extended thinking diff --git a/packages/web/public/locales/translation/ja.yaml b/packages/web/public/locales/translation/ja.yaml index ecb8e83bf..8723523e1 100644 --- a/packages/web/public/locales/translation/ja.yaml +++ b/packages/web/public/locales/translation/ja.yaml @@ -658,9 +658,15 @@ mcp_chat: meetingMinutes: auto_generate: 自動生成 bidirectional: 双方向翻訳 - clear_minutes: 議事録をクリア + both_panel: 両方表示 + clear_minutes: クリア custom_prompt: カスタムプロンプト custom_prompt_placeholder: 議事録生成用のカスタムプロンプトを入力してください... + diagram_flowchart: フローチャート(会議の流れ) + diagram_mindmap: マインドマップ(決定事項) + diagram_options: 生成する図の種類 + diagram_sequence: シーケンス図(関係性) + diagram_timeline: タイムライン(期限・スケジュール) frequency: 生成頻度 frequency_10min: 10分ごと frequency_1min: 1分ごと @@ -673,7 +679,10 @@ meetingMinutes: generating: 議事録生成中... generation_error: 議事録の生成に失敗しました。再試行してください。 generation_frequency: 生成頻度 + generation_panel: 議事録生成 generation_success: 議事録が正常に生成されました + hide_prompt: ▼ プロンプトを非表示 + input_source: 入力ソース language: 文字起こし言語 language_1: 言語1 language_2: 言語2 @@ -685,31 +694,38 @@ meetingMinutes: language_thai: タイ語 language_vietnamese: ベトナム語 last_generated: 最後の生成時刻 {{time}} + microphone: マイク minutes_placeholder: 生成後にここに議事録が表示されます model: 議事録生成モデル new_recording_session: 新しい録音セッション next_generation: 次回生成 next_generation_in: '次回生成まで : ' no_transcript: 文字起こしがありません + option: オプション partial_indicator: (...) record_transcribe: 音声認識 screen_audio_error: スクリーン音声エラー + settings: 設定 speech_recognition: 音声認識 style: 議事録スタイル style_custom: カスタム style_detail: 詳細 + style_diagram: 図解 style_faq: 質疑応答 style_newspaper: 新聞 style_summary: 要約 style_transcription: 文字起こし + style_whiteboard: ホワイトボード target_language: 翻訳言語 title: 議事録生成 transcript: 文字起こし transcription_language: 文字起こし言語 + transcription_panel: 文字起こし translation_language: 翻訳言語 - translation_model: リアルタイム翻訳モデル + translation_model: 翻訳モデル translation_type: 翻訳方式 unidirectional: 片方向翻訳 + view_prompt: ▶ プロンプトを表示 model: parameters: reasoning_budget: 深く考えるトークン数上限 @@ -847,7 +863,7 @@ transcribe: result_placeholder: 音声認識結果がここに表示されます screen_audio: スクリーン音声 screen_audio_error: スクリーン音声エラー - screen_audio_notice: 'Windows の場合:「画面全体」タブより、「システム音声も共有する」を ON にしてください。
Mac の場合:「Chrome Tab」から、「Also share tab audio」を ON にしてください。
Chrome と Edge は動作します。Firefox は動作しません。' + screen_audio_notice: 'Windows の場合:「画面全体」タブより、「システム音声も共有する」を ON にしてください。Mac の場合:「Chrome Tab」から、「Also share tab audio」を ON にしてください。Chrome と Edge は動作します。Firefox は動作しません。' select_input_method: マイク入力 or ファイルアップロードから選択してください speaker_names: 話し手の名前(カンマ区切り) speaker_recognition: 話者認識 @@ -866,7 +882,7 @@ translate: detectedLanguage: 検出言語 enter_text: 入力してください realtimeTranslation: リアルタイム翻訳 - realtime_translation: リアルタイム翻訳 + realtime_translation: 翻訳 result_placeholder: 翻訳結果がここに表示されます systemGeneratedContext: システム自動コンテキスト systemGeneratedContextPlaceholder: 会議内容に基づいて自動生成されたコンテキスト diff --git a/packages/web/public/locales/translation/ko.yaml b/packages/web/public/locales/translation/ko.yaml index 1edcca5db..cd28a73ee 100644 --- a/packages/web/public/locales/translation/ko.yaml +++ b/packages/web/public/locales/translation/ko.yaml @@ -429,7 +429,9 @@ meetingMinutes: generating: 회의록 생성 중... generation_error: 회의록 생성에 실패했습니다. 다시 시도해주세요. generation_frequency: 생성 빈도 + generation_panel: 회의록 생성 generation_success: 회의록이 성공적으로 생성되었습니다 + hide_prompt: ▼ 프롬프트 숨기기 language: 전사 언어 language_auto: 자동 감지 language_chinese: 중국어 @@ -452,12 +454,16 @@ meetingMinutes: style: 회의록 스타일 style_custom: 사용자 정의 style_detail: 세부사항 + style_diagram: 다이어그램 style_faq: FAQ style_newspaper: 신문 style_summary: 요약 style_transcription: 전사 + style_whiteboard: 화이트보드 title: 회의록 생성기 transcript: 전사 + transcription_panel: 전사 + view_prompt: ▶ 프롬프트 보기 model: parameters: reasoning: 추론 diff --git a/packages/web/public/locales/translation/th.yaml b/packages/web/public/locales/translation/th.yaml index aa12ef406..3fcce27ba 100644 --- a/packages/web/public/locales/translation/th.yaml +++ b/packages/web/public/locales/translation/th.yaml @@ -579,7 +579,9 @@ meetingMinutes: generating: กำลังสร้างรายงานการประชุม... generation_error: ไม่สามารถสร้างรายงานการประชุมได้ กรุณาลองอีกครั้ง generation_frequency: ความถี่ในการสร้าง + generation_panel: สร้างรายงานการประชุม generation_success: สร้างรายงานการประชุมสำเร็จ + hide_prompt: ▼ ซ่อนพรอมต์ language: ภาษาการถอดเสียง language_auto: ตรวจจับอัตโนมัติ language_chinese: จีน @@ -602,12 +604,16 @@ meetingMinutes: style: รูปแบบรายงานการประชุม style_custom: กำหนดเอง style_detail: รายละเอียด + style_diagram: ไดอะแกรม style_faq: คำถามที่พบบ่อย style_newspaper: หนังสือพิมพ์ style_summary: สรุป style_transcription: การถอดความ + style_whiteboard: ไวท์บอร์ด title: ตัวสร้างรายงานการประชุม transcript: บันทึกการถอดความ + transcription_panel: ถอดความ + view_prompt: ▶ ดูพรอมต์ model: parameters: reasoning_budget: งบประมาณการให้เหตุผล diff --git a/packages/web/public/locales/translation/vi.yaml b/packages/web/public/locales/translation/vi.yaml index 3d6c0117b..9d4b62150 100644 --- a/packages/web/public/locales/translation/vi.yaml +++ b/packages/web/public/locales/translation/vi.yaml @@ -563,7 +563,9 @@ meetingMinutes: generating: Đang tạo biên bản... generation_error: Không thể tạo biên bản cuộc họp. Vui lòng thử lại. generation_frequency: Tần suất tạo + generation_panel: Tạo biên bản họp generation_success: Tạo biên bản cuộc họp thành công + hide_prompt: ▼ Ẩn prompt language: Ngôn ngữ phiên âm language_auto: Tự động phát hiện language_chinese: Tiếng Trung @@ -586,12 +588,16 @@ meetingMinutes: style: Kiểu biên bản style_custom: Tùy chỉnh style_detail: Chi tiết + style_diagram: Sơ đồ style_faq: FAQ style_newspaper: Báo chí style_summary: Tóm tắt style_transcription: Chuyển đổi + style_whiteboard: Bảng trắng title: Trình tạo biên bản cuộc họp transcript: Bản chuyển đổi + transcription_panel: Phiên âm + view_prompt: ▶ Xem prompt model: parameters: reasoning_budget: Token budget for extended thinking diff --git a/packages/web/public/locales/translation/zh.yaml b/packages/web/public/locales/translation/zh.yaml index 9a0e9abb2..5f27ca2fc 100644 --- a/packages/web/public/locales/translation/zh.yaml +++ b/packages/web/public/locales/translation/zh.yaml @@ -467,7 +467,9 @@ meetingMinutes: generating: 正在生成会议纪要... generation_error: 会议纪要生成失败。请重试。 generation_frequency: 生成频率 + generation_panel: 生成会议纪要 generation_success: 会议纪要生成成功 + hide_prompt: ▼ 隐藏提示词 language: 语音识别语言 language_auto: 自动检测 language_chinese: 中文 @@ -490,12 +492,16 @@ meetingMinutes: style: 会议纪要风格 style_custom: 自定义 style_detail: 详细 + style_diagram: 图解 style_faq: 常见问题 style_newspaper: 新闻报道 style_summary: 摘要 style_transcription: 转录 + style_whiteboard: 白板 title: 会议纪要生成器 transcript: 转录文本 + transcription_panel: 转录文本 + view_prompt: ▶ 查看提示词 model: parameters: reasoning_budget: 推理预算 diff --git a/packages/web/src/components/DiagramRenderer.tsx b/packages/web/src/components/DiagramRenderer.tsx deleted file mode 100644 index c4ec1dd87..000000000 --- a/packages/web/src/components/DiagramRenderer.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import React, { useEffect, useState, useCallback } from 'react'; -import { IoIosClose, IoMdDownload } from 'react-icons/io'; -import { VscCode } from 'react-icons/vsc'; -import { LuNetwork } from 'react-icons/lu'; -import EditableMarkdown from './EditableMarkdown'; -import Button from './Button'; -import mermaid, { MermaidConfig } from 'mermaid'; -import { TbSvg, TbPng } from 'react-icons/tb'; -import { useTranslation } from 'react-i18next'; - -const defaultConfig: MermaidConfig = { - // Prevent syntax error from being added to the dom node - // https://github.com/mermaid-js/mermaid/pull/4359 - suppressErrorRendering: true, - securityLevel: 'loose', // Allow SVG rendering - fontFamily: 'monospace', // Specify the font family - fontSize: 16, // Specify the font size - htmlLabels: true, // Allow HTML labels -}; -mermaid.initialize(defaultConfig); -interface MermaidProps { - code: string; - handler?: () => void; -} - -export const Mermaid: React.FC = (props) => { - const { t } = useTranslation(); - const { code } = props; - const [svgContent, setSvgContent] = useState(''); - - const render = useCallback(async () => { - if (code) { - try { - // It is necessary to specify a unique ID - const { svg } = await mermaid.render(`m${crypto.randomUUID()}`, code); - // Parse the SVG string to convert it to a DOM object - const parser = new DOMParser(); - const doc = parser.parseFromString(svg, 'image/svg+xml'); - const svgElement = doc.querySelector('svg'); - - if (svgElement) { - // Set the necessary attributes to the SVG element - svgElement.setAttribute('width', '100%'); - svgElement.setAttribute('height', '100%'); - setSvgContent(svgElement.outerHTML); - } - } catch (error) { - console.error(error); - setSvgContent(`
${t('diagram.invalid_syntax')}
`); - } - } - }, [code, t]); - - useEffect(() => { - render(); - }, [code, render]); - - return code ? ( -
-
-
- ) : null; -}; - -interface DiagramRendererProps { - code: string; - handleMarkdownChange: (markdown: string) => void; -} - -const DiagramRenderer: React.FC = ({ - code, - handleMarkdownChange, -}) => { - const { t } = useTranslation(); - const [zoom, setZoom] = useState(false); - const [viewMode, setViewMode] = useState<'diagram' | 'code'>('diagram'); - - useEffect(() => { - const handleEsc = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - setZoom(false); - } - }; - - window.addEventListener('keydown', handleEsc); - return () => { - window.removeEventListener('keydown', handleEsc); - }; - }, []); - - const downloadAsSVG = async () => { - try { - const { svg } = await mermaid.render('download-svg', code); - const blob = new Blob([svg], { type: 'image/svg+xml' }); - const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = `diagram_${new Date().getTime()}.svg`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - } catch (error) { - console.error(t('diagram.svg_error'), error); - } - }; - - const downloadAsPNG = async () => { - try { - const { svg } = await mermaid.render('download-png', code); - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - if (!ctx) return; - - const parser = new DOMParser(); - const svgDoc = parser.parseFromString(svg, 'image/svg+xml'); - const svgElement = svgDoc.querySelector('svg'); - if (!(svgElement instanceof SVGSVGElement)) return; - - const viewBox = svgElement - .getAttribute('viewBox') - ?.split(' ') - .map(Number) || [0, 0, 0, 0]; - const width = Math.max(svgElement.width.baseVal.value || 0, viewBox[2]); - const height = Math.max(svgElement.height.baseVal.value || 0, viewBox[3]); - - const scale = 2; - canvas.width = width * scale; - canvas.height = height * scale; - - const wrappedSvg = ` - - - ${svg} - - `; - - const svgBase64 = btoa(unescape(encodeURIComponent(wrappedSvg))); - const img = new Image(); - img.src = 'data:image/svg+xml;base64,' + svgBase64; - - await new Promise((resolve, reject) => { - img.onload = resolve; - img.onerror = reject; - }); - - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - ctx.scale(scale, scale); - ctx.drawImage(img, 0, 0, width, height); - - const link = document.createElement('a'); - link.download = `diagram_${new Date().getTime()}.png`; - link.href = canvas.toDataURL('image/png', 1.0); - link.click(); - } catch (error) { - console.error(t('diagram.png_error'), error); - } - }; - - const DownloadButton: React.FC<{ type: 'SVG' | 'PNG' }> = ({ type }) => { - return ( - - ); - }; - - return ( -
- {/* The header above the diagram */} -
-
- - -
-
-
setViewMode('diagram')}> - - {t('diagram.show_diagram')} -
-
setViewMode('code')}> - - {t('diagram.show_code')} -
-
-
- - {/* The drawing part of the diagram */} -
-
- setZoom(true)} /> -
-
- -
-
- - {/* When zooming */} - {zoom && ( - <> -
setZoom(false)} - /> -
e.stopPropagation()}> -
- -
-
- -
-
- - )} -
- ); -}; - -export default DiagramRenderer; diff --git a/packages/web/src/components/Markdown.tsx b/packages/web/src/components/Markdown.tsx index e99042205..b4bd63268 100644 --- a/packages/web/src/components/Markdown.tsx +++ b/packages/web/src/components/Markdown.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState, memo } from 'react'; +import React, { useEffect, useMemo, useState, memo } from 'react'; import { BaseProps } from '../@types/common'; import { default as ReactMarkdown } from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -35,6 +35,8 @@ import xmlDoc from 'react-syntax-highlighter/dist/esm/languages/prism/xml-doc'; import yaml from 'react-syntax-highlighter/dist/esm/languages/prism/yaml'; import { useLocation } from 'react-router-dom'; +import { MermaidWithToggle } from './Mermaid/MermaidWithToggle'; + SyntaxHighlighter.registerLanguage('bash', bash); SyntaxHighlighter.registerLanguage('c', c); SyntaxHighlighter.registerLanguage('cpp', cpp); @@ -58,6 +60,9 @@ SyntaxHighlighter.registerLanguage('tsx', tsx); SyntaxHighlighter.registerLanguage('xml-doc', xmlDoc); SyntaxHighlighter.registerLanguage('yaml', yaml); +// Re-export MermaidWithToggle for backward compatibility +export { MermaidWithToggle }; + type Props = BaseProps & { children: string; prefix?: string; @@ -125,12 +130,39 @@ const ImageRenderer = (props: any) => { return ; }; +// PreRenderer to skip
 tag for mermaid code blocks
+// This prevents the dark prose background from appearing around mermaid diagrams
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const PreRenderer = (props: any) => {
+  const { children } = props;
+
+  // Check if children is a code element with 'language-mermaid' class
+  if (React.isValidElement(children)) {
+    const childProps = children.props as { className?: string };
+    const className = childProps?.className || '';
+    if (className.includes('language-mermaid')) {
+      // Skip 
 tag for mermaid - return children directly
+      return <>{children};
+    }
+  }
+
+  // For other code blocks, render normal 
 tag
+  return 
{children}
; +}; + const CodeRenderer = memo( // eslint-disable-next-line @typescript-eslint/no-explicit-any (props: any) => { const language = /language-(\w+)/.exec(props.className || '')?.[1]; const codeText = String(props.children).replace(/\n$/, ''); const isCodeBlock = codeText.includes('\n'); + + // Render Mermaid diagrams with toggle + // Use not-prose to prevent prose styles from affecting the diagram container + if (language === 'mermaid') { + return ; + } + return ( <> {language ? ( @@ -189,6 +221,7 @@ const Markdown = memo(({ className, prefix, children }: Props) => { sup: ({ children }) => ( {children} ), + pre: PreRenderer, code: CodeRenderer, }} /> diff --git a/packages/web/src/components/MeetingMinutes/MeetingMinutesControlButtons.tsx b/packages/web/src/components/MeetingMinutes/MeetingMinutesControlButtons.tsx new file mode 100644 index 000000000..d90d113d7 --- /dev/null +++ b/packages/web/src/components/MeetingMinutes/MeetingMinutesControlButtons.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import Button from '../Button'; +import ButtonCopy from '../ButtonCopy'; +import ButtonSendToUseCase from '../ButtonSendToUseCase'; +import { PiStopCircleBold, PiMicrophoneBold } from 'react-icons/pi'; + +interface MeetingMinutesControlButtonsProps { + /** Whether recording is currently active */ + isRecording: boolean; + /** Whether transcript text exists */ + hasTranscriptText: boolean; + /** The transcript text for copy/send operations */ + transcriptText: string; + /** Callback when start recording button is clicked */ + onStartRecording: () => void; + /** Callback when stop recording button is clicked */ + onStopRecording: () => void; + /** Callback when clear button is clicked */ + onClear: () => void; +} + +const MeetingMinutesControlButtons: React.FC< + MeetingMinutesControlButtonsProps +> = ({ + isRecording, + hasTranscriptText, + transcriptText, + onStartRecording, + onStopRecording, + onClear, +}) => { + const { t } = useTranslation(); + + return ( +
+ {/* Copy and Send buttons - show when transcript exists */} + {hasTranscriptText && ( + <> + + + + )} + + {/* Recording control buttons */} + {!isRecording ? ( + + ) : ( + + )} + + {/* Clear button */} + +
+ ); +}; + +export default MeetingMinutesControlButtons; diff --git a/packages/web/src/components/MeetingMinutes/MeetingMinutesGeneration.tsx b/packages/web/src/components/MeetingMinutes/MeetingMinutesGeneration.tsx index 179cc0c43..bdc1ed7f7 100644 --- a/packages/web/src/components/MeetingMinutes/MeetingMinutesGeneration.tsx +++ b/packages/web/src/components/MeetingMinutes/MeetingMinutesGeneration.tsx @@ -6,43 +6,36 @@ import React, { useEffect, } from 'react'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; -import queryString from 'query-string'; +import { PiGearSix } from 'react-icons/pi'; import Button from '../Button'; import ButtonCopy from '../ButtonCopy'; import ButtonIcon from '../ButtonIcon'; -import Select from '../Select'; -import Switch from '../Switch'; -import Textarea from '../Textarea'; import Markdown from '../Markdown'; -import { PiPencilLine, PiCaretRight, PiCaretLeft } from 'react-icons/pi'; -import useMeetingMinutes, { - MeetingMinutesStyle, -} from '../../hooks/useMeetingMinutes'; +import MeetingMinutesSettingsModal from './MeetingMinutesSettingsModal'; +import useMeetingMinutes from '../../hooks/useMeetingMinutes'; import { MODELS } from '../../hooks/useModel'; +import { MeetingMinutesParams, DiagramOption } from '../../prompts'; +import { claudePrompter } from '../../prompts/claude'; interface MeetingMinutesGenerationProps { /** Current transcript text to generate minutes from */ transcriptText: string; - /** Whether the panel is collapsed */ - isCollapsed: boolean; - /** Handler for toggle collapse state */ - onToggleCollapse: () => void; } const MeetingMinutesGeneration: React.FC = ({ transcriptText, - isCollapsed, - onToggleCollapse, }) => { const { t } = useTranslation(); - const navigate = useNavigate(); const countdownIntervalRef = useRef(null); const shouldGenerateRef = useRef(false); + // Modal state + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + // Internal state management - const [minutesStyle, setMinutesStyle] = useState('faq'); + const [minutesStyle, setMinutesStyle] = + useState('summary'); const [customPrompt, setCustomPrompt] = useState(''); const [autoGenerate, setAutoGenerate] = useState(false); const [generationFrequency, setGenerationFrequency] = useState(5); @@ -50,6 +43,26 @@ const MeetingMinutesGeneration: React.FC = ({ const [generatedMinutes, setGeneratedMinutes] = useState(''); const [countdownSeconds, setCountdownSeconds] = useState(0); + // Diagram options for 'diagram' style + const [diagramOptions, setDiagramOptions] = useState([ + 'mindmap', + ]); + + // Toggle diagram option + const toggleDiagramOption = useCallback((option: DiagramOption) => { + setDiagramOptions((prev) => { + if (prev.includes(option)) { + // Don't allow removing the last option + if (prev.length === 1) { + return prev; + } + return prev.filter((o) => o !== option); + } else { + return [...prev, option]; + } + }); + }, []); + // Model selection const { modelIds: availableModels, modelDisplayName } = MODELS; const [modelId, setModelId] = useState(availableModels[0] || ''); @@ -65,7 +78,8 @@ const MeetingMinutesGeneration: React.FC = ({ autoGenerateSessionTimestamp, setGeneratedMinutes, () => {}, // Empty function for setLastProcessedTranscript - () => {} // Empty function for setLastGeneratedTime + () => {}, // Empty function for setLastGeneratedTime + minutesStyle === 'diagram' ? diagramOptions : undefined ); // Text existence check @@ -73,6 +87,21 @@ const MeetingMinutesGeneration: React.FC = ({ return transcriptText.trim() !== ''; }, [transcriptText]); + // Get style label for display + const styleLabel = useMemo(() => { + const styleOptions: Record = { + summary: t('meetingMinutes.style_summary'), + detail: t('meetingMinutes.style_detail'), + faq: t('meetingMinutes.style_faq'), + transcription: t('meetingMinutes.style_transcription'), + diagram: t('meetingMinutes.style_diagram'), + newspaper: t('meetingMinutes.style_newspaper'), + whiteboard: t('meetingMinutes.style_whiteboard'), + custom: t('meetingMinutes.style_custom'), + }; + return styleOptions[minutesStyle]; + }, [minutesStyle, t]); + // Watch for generation signal and trigger generation useEffect(() => { if ( @@ -82,13 +111,18 @@ const MeetingMinutesGeneration: React.FC = ({ ) { if (!minutesLoading) { shouldGenerateRef.current = false; - generateMinutes(transcriptText, modelId, (status) => { - if (status === 'success') { - toast.success(t('meetingMinutes.generation_success')); - } else if (status === 'error') { - toast.error(t('meetingMinutes.generation_error')); - } - }); + generateMinutes( + transcriptText, + modelId, + (status) => { + if (status === 'success') { + toast.success(t('meetingMinutes.generation_success')); + } else if (status === 'error') { + toast.error(t('meetingMinutes.generation_error')); + } + }, + generatedMinutes + ); } else { shouldGenerateRef.current = false; } @@ -101,6 +135,7 @@ const MeetingMinutesGeneration: React.FC = ({ generateMinutes, modelId, t, + generatedMinutes, ]); // Auto-generation countdown setup @@ -147,13 +182,18 @@ const MeetingMinutesGeneration: React.FC = ({ } if (hasTranscriptText && !minutesLoading) { - generateMinutes(transcriptText, modelId, (status) => { - if (status === 'success') { - toast.success(t('meetingMinutes.generation_success')); - } else if (status === 'error') { - toast.error(t('meetingMinutes.generation_error')); - } - }); + generateMinutes( + transcriptText, + modelId, + (status) => { + if (status === 'success') { + toast.success(t('meetingMinutes.generation_success')); + } else if (status === 'error') { + toast.error(t('meetingMinutes.generation_error')); + } + }, + generatedMinutes + ); } }, [ hasTranscriptText, @@ -164,6 +204,7 @@ const MeetingMinutesGeneration: React.FC = ({ t, minutesStyle, customPrompt, + generatedMinutes, ]); // Clear minutes handler @@ -171,181 +212,100 @@ const MeetingMinutesGeneration: React.FC = ({ clearMinutes(); }, [clearMinutes]); + // Get system prompt for preview + const getSystemPrompt = useCallback( + ( + style: MeetingMinutesParams['style'], + customPromptOverride?: string, + diagramOptionsOverride?: DiagramOption[] + ) => { + const params: MeetingMinutesParams = { + style, + customPrompt: customPromptOverride || customPrompt, + diagramOptions: diagramOptionsOverride || diagramOptions, + }; + return claudePrompter.meetingMinutesPrompt(params); + }, + [customPrompt, diagramOptions] + ); + return ( -
- {isCollapsed ? ( - // Collapsed UI -
-
- -
+
+ {/* Compact header with settings button and action buttons */} +
+
+ setIsSettingsOpen(true)}> + + + {/* eslint-disable-next-line @shopify/jsx-no-hardcoded-content */} + setIsSettingsOpen(true)} + className="cursor-pointer text-sm text-gray-600"> + {`${styleLabel} / ${modelDisplayName(modelId)}`} + + {autoGenerate && countdownSeconds > 0 && ( + // eslint-disable-next-line @shopify/jsx-no-hardcoded-content + + {`(${t('meetingMinutes.next_generation')}${t('common.colon')} ${Math.floor(countdownSeconds / 60)}:${(countdownSeconds % 60).toString().padStart(2, '0')})`} + + )}
- ) : ( - // Expanded UI -
- {/* Header with collapse button */} -
- -
- - {/* Meeting Minutes Configuration */} -
-
-
- - ({ - value: id, - label: modelDisplayName(id), - }))} - /> -
-
- - {minutesStyle === 'custom' && ( -
- -