1.0.1
parent
a78561edb0
commit
ff2eeee5a3
17
src/App.jsx
17
src/App.jsx
|
|
@ -1,5 +1,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import apiClient from './utils/apiClient';
|
||||
import { buildApiUrl, API_ENDPOINTS } from './config/api';
|
||||
import HomePage from './pages/HomePage';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import MeetingDetails from './pages/MeetingDetails';
|
||||
|
|
@ -33,9 +35,18 @@ function App() {
|
|||
localStorage.setItem('iMeetingUser', JSON.stringify(userData));
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
setUser(null);
|
||||
localStorage.removeItem('iMeetingUser');
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
// 调用后端登出API撤销token
|
||||
await apiClient.post(buildApiUrl(API_ENDPOINTS.AUTH.LOGOUT));
|
||||
} catch (error) {
|
||||
console.error('Logout API error:', error);
|
||||
// 即使API调用失败也继续登出流程
|
||||
} finally {
|
||||
// 清除本地状态和存储
|
||||
setUser(null);
|
||||
localStorage.removeItem('iMeetingUser');
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ const API_CONFIG = {
|
|||
BASE_URL: "",
|
||||
ENDPOINTS: {
|
||||
AUTH: {
|
||||
LOGIN: '/api/auth/login'
|
||||
LOGIN: '/api/auth/login',
|
||||
LOGOUT: '/api/auth/logout',
|
||||
LOGOUT_ALL: '/api/auth/logout-all',
|
||||
ME: '/api/auth/me',
|
||||
REFRESH: '/api/auth/refresh'
|
||||
},
|
||||
USERS: {
|
||||
LIST: '/api/users',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import apiClient from '../utils/apiClient';
|
||||
import { ArrowLeft, Upload, Users, Calendar, FileText, X, User, Plus } from 'lucide-react';
|
||||
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
|
||||
import DateTimePicker from '../components/DateTimePicker';
|
||||
|
|
@ -26,7 +26,7 @@ const CreateMeeting = ({ user }) => {
|
|||
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
const response = await axios.get(buildApiUrl(API_ENDPOINTS.USERS.LIST));
|
||||
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.USERS.LIST));
|
||||
setAvailableUsers(response.data.filter(u => u.user_id !== user.user_id));
|
||||
} catch (err) {
|
||||
console.error('Error fetching users:', err);
|
||||
|
|
@ -100,7 +100,7 @@ const CreateMeeting = ({ user }) => {
|
|||
attendee_ids: formData.attendees.map(a => a.user_id)
|
||||
};
|
||||
|
||||
const response = await axios.post(buildApiUrl(API_ENDPOINTS.MEETINGS.CREATE), meetingData);
|
||||
const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.MEETINGS.CREATE), meetingData);
|
||||
const meetingId = response.data.meeting_id;
|
||||
|
||||
// Upload audio file if provided
|
||||
|
|
@ -109,7 +109,7 @@ const CreateMeeting = ({ user }) => {
|
|||
formDataUpload.append('audio_file', audioFile);
|
||||
formDataUpload.append('meeting_id', meetingId);
|
||||
|
||||
await axios.post(buildApiUrl(API_ENDPOINTS.MEETINGS.UPLOAD_AUDIO), formDataUpload, {
|
||||
await apiClient.post(buildApiUrl(API_ENDPOINTS.MEETINGS.UPLOAD_AUDIO), formDataUpload, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { LogOut, User, Calendar, Users, TrendingUp, Clock, MessageSquare, Plus } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
import apiClient from '../utils/apiClient';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
|
||||
import MeetingTimeline from '../components/MeetingTimeline';
|
||||
|
|
@ -21,11 +21,11 @@ const Dashboard = ({ user, onLogout }) => {
|
|||
setLoading(true);
|
||||
console.log('Fetching user data for user_id:', user.user_id);
|
||||
|
||||
const userResponse = await axios.get(buildApiUrl(API_ENDPOINTS.USERS.DETAIL(user.user_id)));
|
||||
const userResponse = await apiClient.get(buildApiUrl(API_ENDPOINTS.USERS.DETAIL(user.user_id)));
|
||||
console.log('User response:', userResponse.data);
|
||||
setUserInfo(userResponse.data);
|
||||
|
||||
const meetingsResponse = await axios.get(buildApiUrl(`${API_ENDPOINTS.MEETINGS.LIST}?user_id=${user.user_id}`));
|
||||
const meetingsResponse = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.MEETINGS.LIST}?user_id=${user.user_id}`));
|
||||
console.log('Meetings response:', meetingsResponse.data);
|
||||
setMeetings(meetingsResponse.data);
|
||||
|
||||
|
|
@ -39,9 +39,9 @@ const Dashboard = ({ user, onLogout }) => {
|
|||
|
||||
const handleDeleteMeeting = async (meetingId) => {
|
||||
try {
|
||||
await axios.delete(buildApiUrl(API_ENDPOINTS.MEETINGS.DELETE(meetingId)));
|
||||
await apiClient.delete(buildApiUrl(API_ENDPOINTS.MEETINGS.DELETE(meetingId)));
|
||||
// Refresh meetings list
|
||||
const meetingsResponse = await axios.get(buildApiUrl(`${API_ENDPOINTS.MEETINGS.LIST}?user_id=${user.user_id}`));
|
||||
const meetingsResponse = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.MEETINGS.LIST}?user_id=${user.user_id}`));
|
||||
setMeetings(meetingsResponse.data);
|
||||
} catch (err) {
|
||||
console.error('Error deleting meeting:', err);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import apiClient from '../utils/apiClient';
|
||||
import { ArrowLeft, Users, Calendar, FileText, X, User, Save, Upload, Plus, Image } from 'lucide-react';
|
||||
import MDEditor, * as commands from '@uiw/react-md-editor';
|
||||
import '@uiw/react-md-editor/markdown-editor.css';
|
||||
|
|
@ -42,7 +42,7 @@ const EditMeeting = ({ user }) => {
|
|||
|
||||
const fetchMeetingData = async () => {
|
||||
try {
|
||||
const response = await axios.get(buildApiUrl(API_ENDPOINTS.MEETINGS.EDIT(meeting_id)));
|
||||
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.EDIT(meeting_id)));
|
||||
const meetingData = response.data;
|
||||
|
||||
// Check if current user is the creator
|
||||
|
|
@ -68,7 +68,7 @@ const EditMeeting = ({ user }) => {
|
|||
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
const response = await axios.get(buildApiUrl(API_ENDPOINTS.USERS.LIST));
|
||||
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.USERS.LIST));
|
||||
setAvailableUsers(response.data.filter(u => u.user_id !== user.user_id));
|
||||
} catch (err) {
|
||||
console.error('Error fetching users:', err);
|
||||
|
|
@ -141,7 +141,7 @@ const EditMeeting = ({ user }) => {
|
|||
attendee_ids: formData.attendees.map(a => a.user_id)
|
||||
};
|
||||
|
||||
await axios.put(buildApiUrl(API_ENDPOINTS.MEETINGS.UPDATE(meeting_id)), updateData);
|
||||
await apiClient.put(buildApiUrl(API_ENDPOINTS.MEETINGS.UPDATE(meeting_id)), updateData);
|
||||
navigate(`/meetings/${meeting_id}`);
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.detail || '更新会议失败,请重试');
|
||||
|
|
@ -165,7 +165,7 @@ const EditMeeting = ({ user }) => {
|
|||
formDataUpload.append('meeting_id', meeting_id);
|
||||
formDataUpload.append('force_replace', 'true'); // Always force replace in edit mode
|
||||
|
||||
const response = await axios.post(buildApiUrl(API_ENDPOINTS.MEETINGS.UPLOAD_AUDIO), formDataUpload, {
|
||||
const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.MEETINGS.UPLOAD_AUDIO), formDataUpload, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
|
|
@ -209,7 +209,7 @@ const EditMeeting = ({ user }) => {
|
|||
const formData = new FormData();
|
||||
formData.append('image_file', file);
|
||||
|
||||
const response = await axios.post(
|
||||
const response = await apiClient.post(
|
||||
buildApiUrl(API_ENDPOINTS.MEETINGS.UPLOAD_IMAGE(meeting_id)),
|
||||
formData,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Brain, Users, Calendar, TrendingUp, X, User, Lock } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
import apiClient from '../utils/apiClient';
|
||||
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
|
||||
import './HomePage.css';
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ const HomePage = ({ onLogin }) => {
|
|||
setLoginError('');
|
||||
|
||||
try {
|
||||
const response = await axios.post(buildApiUrl(API_ENDPOINTS.AUTH.LOGIN), loginForm);
|
||||
const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.AUTH.LOGIN), loginForm);
|
||||
onLogin(response.data);
|
||||
setShowLoginModal(false);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -738,6 +738,22 @@
|
|||
gap: 12px;
|
||||
}
|
||||
|
||||
.sync-scroll-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.sync-scroll-icon:hover {
|
||||
color: #3b82f6;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.auto-scroll-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import { ArrowLeft, Clock, Users, FileText, User, Calendar, Play, Pause, Volume2, MessageCircle, Edit, Trash2, Settings, Save, X, Edit3, Brain, Sparkles, Download, ArrowDown, Lock, Unlock } from 'lucide-react';
|
||||
import apiClient from '../utils/apiClient';
|
||||
import { ArrowLeft, Clock, Users, FileText, User, Calendar, Play, Pause, Volume2, MessageCircle, Edit, Trash2, Settings, Save, X, Edit3, Brain, Sparkles, Download, ArrowDown, RefreshCw, RefreshCwOff } from 'lucide-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
|
|
@ -51,32 +51,70 @@ const MeetingDetails = ({ user }) => {
|
|||
// Cleanup interval on unmount
|
||||
return () => {
|
||||
if (statusCheckInterval) {
|
||||
console.log('组件卸载,清理转录状态轮询定时器');
|
||||
clearInterval(statusCheckInterval);
|
||||
setStatusCheckInterval(null);
|
||||
}
|
||||
};
|
||||
}, [meeting_id]);
|
||||
|
||||
// Cleanup interval when status changes
|
||||
useEffect(() => {
|
||||
if (transcriptionStatus && !['pending', 'processing'].includes(transcriptionStatus.status)) {
|
||||
if (statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
setStatusCheckInterval(null);
|
||||
if (transcriptionStatus) {
|
||||
// 如果转录已完成、失败或取消,清除轮询
|
||||
if (['completed', 'failed', 'error', 'cancelled'].includes(transcriptionStatus.status)) {
|
||||
if (statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
setStatusCheckInterval(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [transcriptionStatus, statusCheckInterval]);
|
||||
|
||||
const refreshTranscriptData = async () => {
|
||||
try {
|
||||
const baseUrl = "";
|
||||
const transcriptEndpoint = API_ENDPOINTS?.MEETINGS?.TRANSCRIPT?.(meeting_id) || `/api/meetings/${meeting_id}/transcript`;
|
||||
|
||||
// 只刷新转录数据,不显示loading
|
||||
const transcriptResponse = await apiClient.get(`${baseUrl}${transcriptEndpoint}`);
|
||||
setTranscript(transcriptResponse.data);
|
||||
|
||||
// 更新发言人列表
|
||||
const allSpeakerIds = transcriptResponse.data
|
||||
.map(item => item.speaker_id)
|
||||
.filter(speakerId => speakerId !== null && speakerId !== undefined);
|
||||
|
||||
const uniqueSpeakers = [...new Set(allSpeakerIds)]
|
||||
.map(speakerId => {
|
||||
const segment = transcriptResponse.data.find(item => item.speaker_id === speakerId);
|
||||
return {
|
||||
speaker_id: speakerId,
|
||||
speaker_tag: segment ? (segment.speaker_tag || `发言人 ${speakerId}`) : `发言人 ${speakerId}`
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.speaker_id - b.speaker_id);
|
||||
|
||||
setSpeakerList(uniqueSpeakers);
|
||||
console.log('转录数据已刷新,无loading状态');
|
||||
|
||||
} catch (error) {
|
||||
console.error('刷新转录数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const startStatusPolling = (taskId) => {
|
||||
// Clear existing interval
|
||||
if (statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
setStatusCheckInterval(null);
|
||||
}
|
||||
|
||||
// Poll every 3 seconds
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const baseUrl = "";
|
||||
const statusResponse = await axios.get(`${baseUrl}/api/transcription/tasks/${taskId}/status`);
|
||||
const statusResponse = await apiClient.get(`${baseUrl}/api/transcription/tasks/${taskId}/status`);
|
||||
const status = statusResponse.data;
|
||||
|
||||
setTranscriptionStatus(status);
|
||||
|
|
@ -87,10 +125,16 @@ const MeetingDetails = ({ user }) => {
|
|||
clearInterval(interval);
|
||||
setStatusCheckInterval(null);
|
||||
|
||||
// Refresh meeting details to get updated transcript
|
||||
// Refresh transcript data only if completed successfully
|
||||
if (status.status === 'completed') {
|
||||
fetchMeetingDetails();
|
||||
console.log('转录完成,刷新转录数据(无loading)');
|
||||
await refreshTranscriptData();
|
||||
} else {
|
||||
console.log('转录失败或取消,状态:', status.status);
|
||||
}
|
||||
|
||||
// 再次确保清除状态
|
||||
setTranscriptionStatus(status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch transcription status:', error);
|
||||
|
|
@ -113,26 +157,43 @@ const MeetingDetails = ({ user }) => {
|
|||
const audioEndpoint = API_ENDPOINTS?.MEETINGS?.AUDIO?.(meeting_id) || `/api/meetings/${meeting_id}/audio`;
|
||||
const transcriptEndpoint = API_ENDPOINTS?.MEETINGS?.TRANSCRIPT?.(meeting_id) || `/api/meetings/${meeting_id}/transcript`;
|
||||
|
||||
const response = await axios.get(`${baseUrl}${detailEndpoint}`);
|
||||
const response = await apiClient.get(`${baseUrl}${detailEndpoint}`);
|
||||
setMeeting(response.data);
|
||||
|
||||
// Handle transcription status from meeting details
|
||||
if (response.data.transcription_status) {
|
||||
setTranscriptionStatus(response.data.transcription_status);
|
||||
setTranscriptionProgress(response.data.transcription_status.progress || 0);
|
||||
const newStatus = response.data.transcription_status;
|
||||
setTranscriptionStatus(newStatus);
|
||||
setTranscriptionProgress(newStatus.progress || 0);
|
||||
|
||||
// If transcription is in progress, start polling for updates
|
||||
if (['pending', 'processing'].includes(response.data.transcription_status.status)) {
|
||||
startStatusPolling(response.data.transcription_status.task_id);
|
||||
// 但只有当前没有在轮询时才启动新的轮询
|
||||
if (['pending', 'processing'].includes(newStatus.status)) {
|
||||
if (!statusCheckInterval) {
|
||||
console.log('转录进行中,开始轮询状态');
|
||||
startStatusPolling(newStatus.task_id);
|
||||
}
|
||||
} else {
|
||||
// 如果转录已完成,确保清除任何现有的轮询
|
||||
console.log('转录已完成或失败,状态:', newStatus.status);
|
||||
if (statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
setStatusCheckInterval(null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setTranscriptionStatus(null);
|
||||
setTranscriptionProgress(0);
|
||||
// 清除轮询
|
||||
if (statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
setStatusCheckInterval(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch audio file if available
|
||||
try {
|
||||
const audioResponse = await axios.get(`${baseUrl}${audioEndpoint}`);
|
||||
const audioResponse = await apiClient.get(`${baseUrl}${audioEndpoint}`);
|
||||
// Construct URL using uploads path and relative path from database
|
||||
setAudioUrl(`${baseUrl}${audioResponse.data.file_path}`);
|
||||
setAudioFileName(audioResponse.data.file_name);
|
||||
|
|
@ -144,7 +205,7 @@ const MeetingDetails = ({ user }) => {
|
|||
|
||||
// Fetch transcript segments from database
|
||||
try {
|
||||
const transcriptResponse = await axios.get(`${baseUrl}${transcriptEndpoint}`);
|
||||
const transcriptResponse = await apiClient.get(`${baseUrl}${transcriptEndpoint}`);
|
||||
setTranscript(transcriptResponse.data);
|
||||
|
||||
console.log('First transcript item:', transcriptResponse.data[0]);
|
||||
|
|
@ -307,7 +368,7 @@ const MeetingDetails = ({ user }) => {
|
|||
|
||||
const handleDeleteMeeting = async () => {
|
||||
try {
|
||||
await axios.delete(buildApiUrl(API_ENDPOINTS.MEETINGS.DELETE(meeting_id)));
|
||||
await apiClient.delete(buildApiUrl(API_ENDPOINTS.MEETINGS.DELETE(meeting_id)));
|
||||
navigate('/dashboard');
|
||||
} catch (err) {
|
||||
console.error('Error deleting meeting:', err);
|
||||
|
|
@ -318,7 +379,7 @@ const MeetingDetails = ({ user }) => {
|
|||
const handleSpeakerTagUpdate = async (speakerId, newTag) => {
|
||||
try {
|
||||
const baseUrl = "";
|
||||
await axios.put(`${baseUrl}/api/meetings/${meeting_id}/speaker-tags`, {
|
||||
await apiClient.put(`${baseUrl}/api/meetings/${meeting_id}/speaker-tags`, {
|
||||
speaker_id: speakerId,
|
||||
new_tag: newTag
|
||||
});
|
||||
|
|
@ -350,7 +411,7 @@ const MeetingDetails = ({ user }) => {
|
|||
new_tag: newTag
|
||||
}));
|
||||
|
||||
await axios.put(`${baseUrl}/api/meetings/${meeting_id}/speaker-tags/batch`, {
|
||||
await apiClient.put(`${baseUrl}/api/meetings/${meeting_id}/speaker-tags/batch`, {
|
||||
updates: updates
|
||||
});
|
||||
|
||||
|
|
@ -421,7 +482,7 @@ const MeetingDetails = ({ user }) => {
|
|||
text_content: text_content
|
||||
}));
|
||||
|
||||
await axios.put(`${baseUrl}/api/meetings/${meeting_id}/transcript/batch`, {
|
||||
await apiClient.put(`${baseUrl}/api/meetings/${meeting_id}/transcript/batch`, {
|
||||
updates: updates
|
||||
});
|
||||
|
||||
|
|
@ -454,6 +515,27 @@ const MeetingDetails = ({ user }) => {
|
|||
return items;
|
||||
};
|
||||
|
||||
const refreshMeetingSummary = async () => {
|
||||
try {
|
||||
const baseUrl = "";
|
||||
const detailEndpoint = API_ENDPOINTS?.MEETINGS?.DETAIL?.(meeting_id) || `/api/meetings/${meeting_id}`;
|
||||
|
||||
// 只获取会议详情中的summary字段,不显示loading
|
||||
const response = await apiClient.get(`${baseUrl}${detailEndpoint}`);
|
||||
|
||||
// 只更新summary相关的字段,保持其他数据不变
|
||||
setMeeting(prevMeeting => ({
|
||||
...prevMeeting,
|
||||
summary: response.data.summary
|
||||
}));
|
||||
|
||||
console.log('会议摘要已更新,无loading状态');
|
||||
|
||||
} catch (error) {
|
||||
console.error('刷新会议摘要失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// AI总结相关函数
|
||||
const generateSummary = async () => {
|
||||
if (summaryLoading) return;
|
||||
|
|
@ -461,7 +543,7 @@ const MeetingDetails = ({ user }) => {
|
|||
setSummaryLoading(true);
|
||||
try {
|
||||
const baseUrl = "";
|
||||
const response = await axios.post(`${baseUrl}/api/meetings/${meeting_id}/generate-summary`, {
|
||||
const response = await apiClient.post(`${baseUrl}/api/meetings/${meeting_id}/generate-summary`, {
|
||||
user_prompt: userPrompt
|
||||
});
|
||||
|
||||
|
|
@ -470,6 +552,9 @@ const MeetingDetails = ({ user }) => {
|
|||
// 刷新总结历史
|
||||
await fetchSummaryHistory();
|
||||
|
||||
// 只刷新会议摘要部分,避免整页刷新
|
||||
await refreshMeetingSummary();
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error generating summary:', err);
|
||||
setError('生成AI总结失败,请重试');
|
||||
|
|
@ -481,7 +566,7 @@ const MeetingDetails = ({ user }) => {
|
|||
const fetchSummaryHistory = async () => {
|
||||
try {
|
||||
const baseUrl = "";
|
||||
const response = await axios.get(`${baseUrl}/api/meetings/${meeting_id}/summaries`);
|
||||
const response = await apiClient.get(`${baseUrl}/api/meetings/${meeting_id}/summaries`);
|
||||
setSummaryHistory(response.data.summaries);
|
||||
} catch (err) {
|
||||
console.error('Error fetching summary history:', err);
|
||||
|
|
@ -494,6 +579,14 @@ const MeetingDetails = ({ user }) => {
|
|||
setSummaryResult(null);
|
||||
await fetchSummaryHistory();
|
||||
};
|
||||
|
||||
const closeSummaryModal = async () => {
|
||||
setShowSummaryModal(false);
|
||||
// 关闭弹窗时只刷新摘要部分,避免整页刷新
|
||||
if (summaryResult) {
|
||||
await refreshMeetingSummary();
|
||||
}
|
||||
};
|
||||
|
||||
const exportToPDF = async () => {
|
||||
try {
|
||||
|
|
@ -900,13 +993,13 @@ const MeetingDetails = ({ user }) => {
|
|||
<h3>
|
||||
<MessageCircle size={20} />
|
||||
对话转录
|
||||
<button
|
||||
className="auto-scroll-btn"
|
||||
<span
|
||||
className="sync-scroll-icon"
|
||||
onClick={() => setAutoScrollEnabled(!autoScrollEnabled)}
|
||||
title={autoScrollEnabled ? "关闭自动滚动" : "开启自动滚动"}
|
||||
title={autoScrollEnabled ? "关闭同步滚动" : "开启同步滚动"}
|
||||
>
|
||||
{autoScrollEnabled ? <Lock size={16} /> : <Unlock size={16} />}
|
||||
</button>
|
||||
{autoScrollEnabled ? <RefreshCw size={16} /> : <RefreshCwOff size={16} />}
|
||||
</span>
|
||||
</h3>
|
||||
<div className="transcript-controls">
|
||||
{isCreator && (
|
||||
|
|
@ -1127,13 +1220,13 @@ const MeetingDetails = ({ user }) => {
|
|||
|
||||
{/* AI Summary Modal */}
|
||||
{showSummaryModal && (
|
||||
<div className="summary-modal-overlay" onClick={() => setShowSummaryModal(false)}>
|
||||
<div className="summary-modal-overlay" onClick={closeSummaryModal}>
|
||||
<div className="summary-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-header">
|
||||
<h3><Brain size={20} /> AI会议总结</h3>
|
||||
<button
|
||||
className="close-btn"
|
||||
onClick={() => setShowSummaryModal(false)}
|
||||
onClick={closeSummaryModal}
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import axios from 'axios';
|
||||
|
||||
// 创建axios实例
|
||||
const apiClient = axios.create();
|
||||
|
||||
// 请求拦截器 - 自动添加Authorization头
|
||||
apiClient.interceptors.request.use(
|
||||
(config) => {
|
||||
const savedUser = localStorage.getItem('iMeetingUser');
|
||||
if (savedUser) {
|
||||
try {
|
||||
const user = JSON.parse(savedUser);
|
||||
if (user.token) {
|
||||
config.headers.Authorization = `Bearer ${user.token}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse user from localStorage:', error);
|
||||
localStorage.removeItem('iMeetingUser');
|
||||
}
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器 - 处理认证错误
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token过期或无效,清除本地存储并跳转登录
|
||||
localStorage.removeItem('iMeetingUser');
|
||||
window.location.href = '/';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default apiClient;
|
||||
Loading…
Reference in New Issue