Youngger9765 Claude commited on
Commit
7e0455f
·
1 Parent(s): 68794df

feat: Add grading progress modal with assistant info

Browse files

- Create GradingModal component showing AI grading progress
- Display current assistant being used with details
- Show animated progress bar synced with step progression
- Progress bar moves slower (5s per step, 0.5-1% increments)
- Include 6-step process visualization with animations
- Show estimated time and helpful information
- Support both zh-TW and English languages
- Auto-show when grading starts, auto-hide when complete

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

frontend/src/App.tsx CHANGED
@@ -4,6 +4,7 @@ import type { GradeResult, Option, OptionsResponse, MatchedAssistant } from './t
4
  import { gradingApi } from './api/grading';
5
  import AssistantGuide from './AssistantGuide';
6
  import PasswordProtection from './components/PasswordProtection';
 
7
 
8
  function App() {
9
  const [isPasswordVerified, setIsPasswordVerified] = useState<boolean>(false);
@@ -205,8 +206,20 @@ function App() {
205
  return <PasswordProtection onPasswordCorrect={handlePasswordCorrect} />;
206
  }
207
 
 
 
 
 
 
208
  return (
209
  <div className="App">
 
 
 
 
 
 
 
210
  <header className="app-header">
211
  <div className="header-row-1">
212
  <h1>AI 批改平台</h1>
 
4
  import { gradingApi } from './api/grading';
5
  import AssistantGuide from './AssistantGuide';
6
  import PasswordProtection from './components/PasswordProtection';
7
+ import GradingModal from './components/GradingModal';
8
 
9
  function App() {
10
  const [isPasswordVerified, setIsPasswordVerified] = useState<boolean>(false);
 
206
  return <PasswordProtection onPasswordCorrect={handlePasswordCorrect} />;
207
  }
208
 
209
+ // 獲取當前選中的機器人資訊
210
+ const currentAssistant = matchedAssistants.find(
211
+ a => a.assistant_id === selectedAssistantId
212
+ );
213
+
214
  return (
215
  <div className="App">
216
+ {/* 批改進度 Modal */}
217
+ <GradingModal
218
+ isOpen={loading}
219
+ assistant={currentAssistant || null}
220
+ language={selectedLanguage}
221
+ />
222
+
223
  <header className="app-header">
224
  <div className="header-row-1">
225
  <h1>AI 批改平台</h1>
frontend/src/components/GradingModal.css ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .grading-modal-overlay {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ right: 0;
6
+ bottom: 0;
7
+ background-color: rgba(0, 0, 0, 0.7);
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ z-index: 1000;
12
+ animation: fadeIn 0.2s ease-in;
13
+ }
14
+
15
+ @keyframes fadeIn {
16
+ from {
17
+ opacity: 0;
18
+ }
19
+ to {
20
+ opacity: 1;
21
+ }
22
+ }
23
+
24
+ .grading-modal {
25
+ background: white;
26
+ border-radius: 16px;
27
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
28
+ max-width: 600px;
29
+ width: 90%;
30
+ max-height: 90vh;
31
+ overflow-y: auto;
32
+ animation: slideUp 0.3s ease-out;
33
+ }
34
+
35
+ @keyframes slideUp {
36
+ from {
37
+ transform: translateY(20px);
38
+ opacity: 0;
39
+ }
40
+ to {
41
+ transform: translateY(0);
42
+ opacity: 1;
43
+ }
44
+ }
45
+
46
+ .grading-modal-header {
47
+ padding: 24px 32px;
48
+ border-bottom: 2px solid #f0f0f0;
49
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
50
+ }
51
+
52
+ .grading-modal-header h2 {
53
+ margin: 0;
54
+ color: white;
55
+ font-size: 24px;
56
+ font-weight: 600;
57
+ }
58
+
59
+ .grading-modal-body {
60
+ padding: 32px;
61
+ }
62
+
63
+ /* 機器人資訊 */
64
+ .assistant-info {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 16px;
68
+ padding: 20px;
69
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
70
+ border-radius: 12px;
71
+ margin-bottom: 24px;
72
+ }
73
+
74
+ .assistant-icon {
75
+ font-size: 48px;
76
+ line-height: 1;
77
+ }
78
+
79
+ .assistant-details h3 {
80
+ margin: 0 0 4px 0;
81
+ font-size: 20px;
82
+ color: #2c3e50;
83
+ }
84
+
85
+ .assistant-subtitle {
86
+ margin: 0;
87
+ font-size: 14px;
88
+ color: #7f8c8d;
89
+ }
90
+
91
+ /* 進度條 */
92
+ .progress-section {
93
+ margin-bottom: 24px;
94
+ }
95
+
96
+ .progress-bar-container {
97
+ width: 100%;
98
+ height: 12px;
99
+ background-color: #e0e0e0;
100
+ border-radius: 6px;
101
+ overflow: hidden;
102
+ margin-bottom: 8px;
103
+ }
104
+
105
+ .progress-bar-fill {
106
+ height: 100%;
107
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
108
+ border-radius: 6px;
109
+ transition: width 0.5s ease;
110
+ animation: pulse 1.5s ease-in-out infinite;
111
+ }
112
+
113
+ @keyframes pulse {
114
+ 0%, 100% {
115
+ opacity: 1;
116
+ }
117
+ 50% {
118
+ opacity: 0.8;
119
+ }
120
+ }
121
+
122
+ .progress-text {
123
+ text-align: center;
124
+ font-size: 18px;
125
+ font-weight: 600;
126
+ color: #667eea;
127
+ }
128
+
129
+ /* 步驟列表 */
130
+ .steps-section {
131
+ background: #f8f9fa;
132
+ border-radius: 12px;
133
+ padding: 20px;
134
+ margin-bottom: 24px;
135
+ }
136
+
137
+ .step-item {
138
+ padding: 12px 16px;
139
+ margin-bottom: 8px;
140
+ border-radius: 8px;
141
+ font-size: 15px;
142
+ color: #95a5a6;
143
+ transition: all 0.3s ease;
144
+ }
145
+
146
+ .step-item.active {
147
+ background: #fff;
148
+ color: #667eea;
149
+ font-weight: 600;
150
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
151
+ animation: highlight 0.5s ease-in;
152
+ }
153
+
154
+ @keyframes highlight {
155
+ 0% {
156
+ transform: translateX(-10px);
157
+ opacity: 0;
158
+ }
159
+ 100% {
160
+ transform: translateX(0);
161
+ opacity: 1;
162
+ }
163
+ }
164
+
165
+ .step-item.completed {
166
+ color: #27ae60;
167
+ text-decoration: line-through;
168
+ opacity: 0.6;
169
+ }
170
+
171
+ /* 提示資訊 */
172
+ .grading-info {
173
+ background: #fff3cd;
174
+ border-left: 4px solid #ffc107;
175
+ border-radius: 8px;
176
+ padding: 16px;
177
+ }
178
+
179
+ .info-text {
180
+ margin: 0 0 8px 0;
181
+ font-size: 15px;
182
+ color: #856404;
183
+ font-weight: 500;
184
+ }
185
+
186
+ .info-subtitle {
187
+ margin: 0;
188
+ font-size: 13px;
189
+ color: #856404;
190
+ opacity: 0.8;
191
+ }
192
+
193
+ /* 響應式設計 */
194
+ @media (max-width: 768px) {
195
+ .grading-modal {
196
+ width: 95%;
197
+ max-height: 95vh;
198
+ }
199
+
200
+ .grading-modal-header {
201
+ padding: 20px 24px;
202
+ }
203
+
204
+ .grading-modal-header h2 {
205
+ font-size: 20px;
206
+ }
207
+
208
+ .grading-modal-body {
209
+ padding: 24px;
210
+ }
211
+
212
+ .assistant-icon {
213
+ font-size: 36px;
214
+ }
215
+
216
+ .assistant-details h3 {
217
+ font-size: 18px;
218
+ }
219
+
220
+ .step-item {
221
+ font-size: 14px;
222
+ padding: 10px 12px;
223
+ }
224
+ }
frontend/src/components/GradingModal.tsx ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useState } from 'react';
2
+ import './GradingModal.css';
3
+ import type { MatchedAssistant } from '../types/index';
4
+
5
+ interface GradingModalProps {
6
+ isOpen: boolean;
7
+ assistant: MatchedAssistant | null;
8
+ language: string;
9
+ }
10
+
11
+ const GradingModal = ({ isOpen, assistant, language }: GradingModalProps) => {
12
+ const [progress, setProgress] = useState(0);
13
+ const [currentStep, setCurrentStep] = useState(0);
14
+
15
+ const steps = language === 'zh-TW' ? [
16
+ '📖 讀取文章內容...',
17
+ '🤖 啟動 AI 批改引擎...',
18
+ '📝 分析文章結構...',
19
+ '🔍 檢查文法與用詞...',
20
+ '💡 生成改進建議...',
21
+ '✨ 整理批改結果...',
22
+ ] : [
23
+ '📖 Reading your article...',
24
+ '🤖 Starting AI grading engine...',
25
+ '📝 Analyzing article structure...',
26
+ '🔍 Checking grammar and wording...',
27
+ '💡 Generating suggestions...',
28
+ '✨ Finalizing results...',
29
+ ];
30
+
31
+ useEffect(() => {
32
+ if (!isOpen) {
33
+ setProgress(0);
34
+ setCurrentStep(0);
35
+ return;
36
+ }
37
+
38
+ // 每個步驟的目標進度
39
+ const progressPerStep = 100 / steps.length;
40
+
41
+ // 更新步驟(每 5 秒一個步驟)
42
+ const stepInterval = setInterval(() => {
43
+ setCurrentStep(prev => {
44
+ if (prev >= steps.length - 1) return prev;
45
+ return prev + 1;
46
+ });
47
+ }, 5000);
48
+
49
+ // 進度條跟隨步驟緩慢增加
50
+ const progressInterval = setInterval(() => {
51
+ setProgress(prev => {
52
+ const targetProgress = (currentStep + 1) * progressPerStep;
53
+ // 如果還沒到當前步驟的目標進度,緩慢增加
54
+ if (prev < targetProgress - 2) {
55
+ return prev + 0.5 + Math.random() * 0.5; // 每次增加 0.5-1%
56
+ }
57
+ // 接近目標時停止
58
+ return Math.min(prev, 95);
59
+ });
60
+ }, 100);
61
+
62
+ return () => {
63
+ clearInterval(progressInterval);
64
+ clearInterval(stepInterval);
65
+ };
66
+ }, [isOpen, steps.length, currentStep]);
67
+
68
+ if (!isOpen) return null;
69
+
70
+ return (
71
+ <div className="grading-modal-overlay">
72
+ <div className="grading-modal">
73
+ <div className="grading-modal-header">
74
+ <h2>{language === 'zh-TW' ? 'AI 批改中' : 'AI Grading in Progress'}</h2>
75
+ </div>
76
+
77
+ <div className="grading-modal-body">
78
+ {/* 機器人資訊 */}
79
+ {assistant && (
80
+ <div className="assistant-info">
81
+ <div className="assistant-icon">🤖</div>
82
+ <div className="assistant-details">
83
+ <h3>{assistant.name}</h3>
84
+ <p className="assistant-subtitle">
85
+ {language === 'zh-TW' ? '專業 AI 批改助手' : 'Professional AI Grading Assistant'}
86
+ </p>
87
+ </div>
88
+ </div>
89
+ )}
90
+
91
+ {/* 進度條 */}
92
+ <div className="progress-section">
93
+ <div className="progress-bar-container">
94
+ <div
95
+ className="progress-bar-fill"
96
+ style={{ width: `${Math.min(progress, 100)}%` }}
97
+ />
98
+ </div>
99
+ <div className="progress-text">
100
+ {Math.round(Math.min(progress, 100))}%
101
+ </div>
102
+ </div>
103
+
104
+ {/* 當前步驟 */}
105
+ <div className="steps-section">
106
+ {steps.map((step, index) => (
107
+ <div
108
+ key={index}
109
+ className={`step-item ${index === currentStep ? 'active' : ''} ${index < currentStep ? 'completed' : ''}`}
110
+ >
111
+ {index < currentStep ? '✓' : index === currentStep ? '⏳' : '○'} {step}
112
+ </div>
113
+ ))}
114
+ </div>
115
+
116
+ {/* 提示資訊 */}
117
+ <div className="grading-info">
118
+ <p className="info-text">
119
+ {language === 'zh-TW'
120
+ ? '⏱️ AI 批改通常需要 30-60 秒,請耐心等待...'
121
+ : '⏱️ AI grading usually takes 30-60 seconds, please wait...'}
122
+ </p>
123
+ <p className="info-subtitle">
124
+ {language === 'zh-TW'
125
+ ? '💡 批改完成後會自動顯示詳細結果'
126
+ : '💡 Results will be displayed automatically when complete'}
127
+ </p>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ );
133
+ };
134
+
135
+ export default GradingModal;