dvc890 commited on
Commit
2b32ad3
·
verified ·
1 Parent(s): c5b4c4c

Upload 67 files

Browse files
components/ai/AssessmentPanel.tsx CHANGED
@@ -15,6 +15,7 @@ export const AssessmentPanel: React.FC<AssessmentPanelProps> = ({ currentUser })
15
  const [selectedImages, setSelectedImages] = useState<File[]>([]);
16
  const [isAssessmentRecording, setIsAssessmentRecording] = useState(false);
17
  const [assessmentStatus, setAssessmentStatus] = useState<'IDLE' | 'UPLOADING' | 'ANALYZING' | 'TTS' | 'COMPLETED'>('IDLE');
 
18
 
19
  // Playback state for the report card
20
  const [isPlayingResponse, setIsPlayingResponse] = useState(false);
@@ -35,11 +36,19 @@ export const AssessmentPanel: React.FC<AssessmentPanelProps> = ({ currentUser })
35
  const recordingStartTimeRef = useRef<number>(0);
36
  const cameraInputRef = useRef<HTMLInputElement>(null);
37
 
38
- // Initialize AudioContext
39
  useEffect(() => {
40
  // @ts-ignore
41
  const AudioCtor = window.AudioContext || window.webkitAudioContext;
42
  audioContextRef.current = new AudioCtor();
 
 
 
 
 
 
 
 
43
  return () => {
44
  forceStopPlayback();
45
  window.speechSynthesis.cancel();
@@ -291,7 +300,7 @@ export const AssessmentPanel: React.FC<AssessmentPanelProps> = ({ currentUser })
291
  <span className="flex items-center"><Brain className="mr-2 text-purple-600"/> 今日测评题目</span>
292
  <div className="flex bg-gray-100 p-1 rounded-lg text-xs font-medium">
293
  <button onClick={()=>setAssessmentMode('audio')} className={`px-3 py-1 rounded-md transition-all ${assessmentMode==='audio'?'bg-white shadow text-purple-600':'text-gray-500'}`}>语音回答</button>
294
- <button onClick={()=>setAssessmentMode('image')} className={`px-3 py-1 rounded-md transition-all ${assessmentMode==='image'?'bg-white shadow text-purple-600':'text-gray-500'}`}>拍照上传</button>
295
  </div>
296
  </h3>
297
  <textarea className="w-full bg-purple-50/50 border border-purple-100 rounded-xl p-4 text-gray-700 font-medium text-lg resize-none focus:ring-2 focus:ring-purple-200 outline-none" value={assessmentTopic} onChange={e => setAssessmentTopic(e.target.value)} rows={3}/>
@@ -321,15 +330,18 @@ export const AssessmentPanel: React.FC<AssessmentPanelProps> = ({ currentUser })
321
  onChange={handleImageUpload}
322
  onClick={(e) => (e.currentTarget.value = '')}
323
  />
324
- <input
325
- type="file"
326
- accept="image/*"
327
- capture="environment"
328
- className="hidden"
329
- ref={cameraInputRef}
330
- onChange={handleImageUpload}
331
- onClick={(e) => (e.currentTarget.value = '')}
332
- />
 
 
 
333
 
334
  {selectedImages.length === 0 ? (
335
  <>
@@ -337,14 +349,16 @@ export const AssessmentPanel: React.FC<AssessmentPanelProps> = ({ currentUser })
337
  <p className="text-purple-600 font-bold mb-1">上传作业图片</p>
338
  <div className="flex gap-2">
339
  <label htmlFor="assessment-file-upload" className="px-4 py-2 bg-white border border-purple-200 rounded-lg text-sm font-bold text-purple-600 hover:bg-purple-50 cursor-pointer shadow-sm">
340
- 从相册选择
341
  </label>
342
- <button
343
- onClick={() => cameraInputRef.current?.click()}
344
- className="px-4 py-2 bg-purple-600 text-white rounded-lg text-sm font-bold hover:bg-purple-700 flex items-center gap-1 shadow-sm"
345
- >
346
- <Camera size={14}/> 拍照
347
- </button>
 
 
348
  </div>
349
  <p className="text-xs text-gray-400 mt-2">支持批量上传 • 自动压缩处理</p>
350
  </>
@@ -353,10 +367,14 @@ export const AssessmentPanel: React.FC<AssessmentPanelProps> = ({ currentUser })
353
  <label htmlFor="assessment-file-upload" className="flex items-center text-purple-400 font-bold cursor-pointer hover:text-purple-600">
354
  <Plus className="mr-1" size={20}/> 继续添加
355
  </label>
356
- <span className="text-gray-300">|</span>
357
- <button onClick={() => cameraInputRef.current?.click()} className="flex items-center text-purple-400 font-bold cursor-pointer hover:text-purple-600">
358
- <Camera className="mr-1" size={20}/> 拍照
359
- </button>
 
 
 
 
360
  </div>
361
  )}
362
  </div>
 
15
  const [selectedImages, setSelectedImages] = useState<File[]>([]);
16
  const [isAssessmentRecording, setIsAssessmentRecording] = useState(false);
17
  const [assessmentStatus, setAssessmentStatus] = useState<'IDLE' | 'UPLOADING' | 'ANALYZING' | 'TTS' | 'COMPLETED'>('IDLE');
18
+ const [isMobile, setIsMobile] = useState(false);
19
 
20
  // Playback state for the report card
21
  const [isPlayingResponse, setIsPlayingResponse] = useState(false);
 
36
  const recordingStartTimeRef = useRef<number>(0);
37
  const cameraInputRef = useRef<HTMLInputElement>(null);
38
 
39
+ // Initialize AudioContext & Check Mobile
40
  useEffect(() => {
41
  // @ts-ignore
42
  const AudioCtor = window.AudioContext || window.webkitAudioContext;
43
  audioContextRef.current = new AudioCtor();
44
+
45
+ const checkMobile = () => {
46
+ const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;
47
+ const mobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent.toLowerCase());
48
+ setIsMobile(mobile);
49
+ };
50
+ checkMobile();
51
+
52
  return () => {
53
  forceStopPlayback();
54
  window.speechSynthesis.cancel();
 
300
  <span className="flex items-center"><Brain className="mr-2 text-purple-600"/> 今日测评题目</span>
301
  <div className="flex bg-gray-100 p-1 rounded-lg text-xs font-medium">
302
  <button onClick={()=>setAssessmentMode('audio')} className={`px-3 py-1 rounded-md transition-all ${assessmentMode==='audio'?'bg-white shadow text-purple-600':'text-gray-500'}`}>语音回答</button>
303
+ <button onClick={()=>setAssessmentMode('image')} className={`px-3 py-1 rounded-md transition-all ${assessmentMode==='image'?'bg-white shadow text-purple-600':'text-gray-500'}`}>{isMobile ? '拍照上传' : '图片上传'}</button>
304
  </div>
305
  </h3>
306
  <textarea className="w-full bg-purple-50/50 border border-purple-100 rounded-xl p-4 text-gray-700 font-medium text-lg resize-none focus:ring-2 focus:ring-purple-200 outline-none" value={assessmentTopic} onChange={e => setAssessmentTopic(e.target.value)} rows={3}/>
 
330
  onChange={handleImageUpload}
331
  onClick={(e) => (e.currentTarget.value = '')}
332
  />
333
+
334
+ {isMobile && (
335
+ <input
336
+ type="file"
337
+ accept="image/*"
338
+ capture="environment"
339
+ className="hidden"
340
+ ref={cameraInputRef}
341
+ onChange={handleImageUpload}
342
+ onClick={(e) => (e.currentTarget.value = '')}
343
+ />
344
+ )}
345
 
346
  {selectedImages.length === 0 ? (
347
  <>
 
349
  <p className="text-purple-600 font-bold mb-1">上传作业图片</p>
350
  <div className="flex gap-2">
351
  <label htmlFor="assessment-file-upload" className="px-4 py-2 bg-white border border-purple-200 rounded-lg text-sm font-bold text-purple-600 hover:bg-purple-50 cursor-pointer shadow-sm">
352
+ 从相册/文件选择
353
  </label>
354
+ {isMobile && (
355
+ <button
356
+ onClick={() => cameraInputRef.current?.click()}
357
+ className="px-4 py-2 bg-purple-600 text-white rounded-lg text-sm font-bold hover:bg-purple-700 flex items-center gap-1 shadow-sm"
358
+ >
359
+ <Camera size={14}/> 拍照
360
+ </button>
361
+ )}
362
  </div>
363
  <p className="text-xs text-gray-400 mt-2">支持批量上传 • 自动压缩处理</p>
364
  </>
 
367
  <label htmlFor="assessment-file-upload" className="flex items-center text-purple-400 font-bold cursor-pointer hover:text-purple-600">
368
  <Plus className="mr-1" size={20}/> 继续添加
369
  </label>
370
+ {isMobile && (
371
+ <>
372
+ <span className="text-gray-300">|</span>
373
+ <button onClick={() => cameraInputRef.current?.click()} className="flex items-center text-purple-400 font-bold cursor-pointer hover:text-purple-600">
374
+ <Camera className="mr-1" size={20}/> 拍照
375
+ </button>
376
+ </>
377
+ )}
378
  </div>
379
  )}
380
  </div>
components/ai/ChatPanel.tsx CHANGED
@@ -29,6 +29,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ currentUser }) => {
29
  // Input States
30
  const [textInput, setTextInput] = useState('');
31
  const [isRecording, setIsRecording] = useState(false);
 
32
 
33
  // Config States
34
  const [enableThinking, setEnableThinking] = useState(false);
@@ -54,11 +55,19 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ currentUser }) => {
54
 
55
  const abortControllerRef = useRef<AbortController | null>(null);
56
 
57
- // Initialize AudioContext
58
  useEffect(() => {
59
  // @ts-ignore
60
  const AudioCtor = window.AudioContext || window.webkitAudioContext;
61
  audioContextRef.current = new AudioCtor();
 
 
 
 
 
 
 
 
62
  return () => {
63
  stopPlayback();
64
  window.speechSynthesis.cancel();
@@ -463,9 +472,11 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ currentUser }) => {
463
  <button onClick={() => fileInputRef.current?.click()} className="p-2 text-gray-500 hover:bg-white hover:text-blue-600 rounded-full transition-colors shrink-0" title="相册/文件">
464
  <ImageIcon size={22}/>
465
  </button>
466
- <button onClick={() => cameraInputRef.current?.click()} className="p-2 text-gray-500 hover:bg-white hover:text-blue-600 rounded-full transition-colors shrink-0" title="拍照">
467
- <Camera size={22}/>
468
- </button>
 
 
469
  </div>
470
 
471
  <input
@@ -477,15 +488,18 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ currentUser }) => {
477
  onChange={handleImageSelect}
478
  onClick={(e) => (e.currentTarget.value = '')}
479
  />
480
- <input
481
- type="file"
482
- accept="image/*"
483
- capture="environment"
484
- ref={cameraInputRef}
485
- className="hidden"
486
- onChange={handleImageSelect}
487
- onClick={(e) => (e.currentTarget.value = '')}
488
- />
 
 
 
489
 
490
  <div className="flex-1 min-h-[40px] flex items-center">
491
  {audioAttachment ? (
 
29
  // Input States
30
  const [textInput, setTextInput] = useState('');
31
  const [isRecording, setIsRecording] = useState(false);
32
+ const [isMobile, setIsMobile] = useState(false);
33
 
34
  // Config States
35
  const [enableThinking, setEnableThinking] = useState(false);
 
55
 
56
  const abortControllerRef = useRef<AbortController | null>(null);
57
 
58
+ // Initialize AudioContext & Check Mobile
59
  useEffect(() => {
60
  // @ts-ignore
61
  const AudioCtor = window.AudioContext || window.webkitAudioContext;
62
  audioContextRef.current = new AudioCtor();
63
+
64
+ const checkMobile = () => {
65
+ const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;
66
+ const mobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent.toLowerCase());
67
+ setIsMobile(mobile);
68
+ };
69
+ checkMobile();
70
+
71
  return () => {
72
  stopPlayback();
73
  window.speechSynthesis.cancel();
 
472
  <button onClick={() => fileInputRef.current?.click()} className="p-2 text-gray-500 hover:bg-white hover:text-blue-600 rounded-full transition-colors shrink-0" title="相册/文件">
473
  <ImageIcon size={22}/>
474
  </button>
475
+ {isMobile && (
476
+ <button onClick={() => cameraInputRef.current?.click()} className="p-2 text-gray-500 hover:bg-white hover:text-blue-600 rounded-full transition-colors shrink-0" title="拍照">
477
+ <Camera size={22}/>
478
+ </button>
479
+ )}
480
  </div>
481
 
482
  <input
 
488
  onChange={handleImageSelect}
489
  onClick={(e) => (e.currentTarget.value = '')}
490
  />
491
+
492
+ {isMobile && (
493
+ <input
494
+ type="file"
495
+ accept="image/*"
496
+ capture="environment"
497
+ ref={cameraInputRef}
498
+ className="hidden"
499
+ onChange={handleImageSelect}
500
+ onClick={(e) => (e.currentTarget.value = '')}
501
+ />
502
+ )}
503
 
504
  <div className="flex-1 min-h-[40px] flex items-center">
505
  {audioAttachment ? (
components/ai/WorkAssistantPanel.tsx CHANGED
@@ -143,6 +143,7 @@ export const WorkAssistantPanel: React.FC<WorkAssistantPanelProps> = ({ currentU
143
  const [selectedRole, setSelectedRole] = useState(ROLES[0]);
144
  const [enableThinking, setEnableThinking] = useState(false);
145
  const [enableSearch, setEnableSearch] = useState(false);
 
146
 
147
  const [messages, setMessages] = useState<AIChatMessage[]>([]);
148
  const [textInput, setTextInput] = useState('');
@@ -164,8 +165,15 @@ export const WorkAssistantPanel: React.FC<WorkAssistantPanelProps> = ({ currentU
164
  const docInputRef = useRef<HTMLInputElement>(null);
165
  const abortControllerRef = useRef<AbortController | null>(null);
166
 
167
- // Smart Scroll
168
  useEffect(() => {
 
 
 
 
 
 
 
169
  if (!scrollContainerRef.current || !messagesEndRef.current) return;
170
  const container = scrollContainerRef.current;
171
  const { scrollTop, scrollHeight, clientHeight } = container;
@@ -599,13 +607,18 @@ export const WorkAssistantPanel: React.FC<WorkAssistantPanelProps> = ({ currentU
599
  <button onClick={() => fileInputRef.current?.click()} className="p-2 text-gray-500 hover:bg-white hover:text-blue-600 rounded-full transition-colors shrink-0" title="相册/文件">
600
  <ImageIcon size={22}/>
601
  </button>
602
- <button onClick={() => cameraInputRef.current?.click()} className="p-2 text-gray-500 hover:bg-white hover:text-blue-600 rounded-full transition-colors shrink-0" title="拍照">
603
- <Camera size={22}/>
604
- </button>
 
 
605
  </div>
606
 
607
  <input type="file" multiple accept="image/*" ref={fileInputRef} className="hidden" onChange={handleImageSelect} onClick={(e) => (e.currentTarget.value = '')} />
608
- <input type="file" accept="image/*" capture="environment" ref={cameraInputRef} className="hidden" onChange={handleImageSelect} onClick={(e) => (e.currentTarget.value = '')} />
 
 
 
609
 
610
  <button onClick={() => docInputRef.current?.click()} className={`p-2 rounded-full transition-colors shrink-0 ${docFile ? 'text-indigo-600 bg-indigo-50' : 'text-gray-500 hover:bg-white hover:text-indigo-600'}`} title="上传文档 (Docx, PDF, Txt)">
611
  <Paperclip size={22}/>
 
143
  const [selectedRole, setSelectedRole] = useState(ROLES[0]);
144
  const [enableThinking, setEnableThinking] = useState(false);
145
  const [enableSearch, setEnableSearch] = useState(false);
146
+ const [isMobile, setIsMobile] = useState(false);
147
 
148
  const [messages, setMessages] = useState<AIChatMessage[]>([]);
149
  const [textInput, setTextInput] = useState('');
 
165
  const docInputRef = useRef<HTMLInputElement>(null);
166
  const abortControllerRef = useRef<AbortController | null>(null);
167
 
168
+ // Smart Scroll & Check Mobile
169
  useEffect(() => {
170
+ const checkMobile = () => {
171
+ const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;
172
+ const mobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent.toLowerCase());
173
+ setIsMobile(mobile);
174
+ };
175
+ checkMobile();
176
+
177
  if (!scrollContainerRef.current || !messagesEndRef.current) return;
178
  const container = scrollContainerRef.current;
179
  const { scrollTop, scrollHeight, clientHeight } = container;
 
607
  <button onClick={() => fileInputRef.current?.click()} className="p-2 text-gray-500 hover:bg-white hover:text-blue-600 rounded-full transition-colors shrink-0" title="相册/文件">
608
  <ImageIcon size={22}/>
609
  </button>
610
+ {isMobile && (
611
+ <button onClick={() => cameraInputRef.current?.click()} className="p-2 text-gray-500 hover:bg-white hover:text-blue-600 rounded-full transition-colors shrink-0" title="拍照">
612
+ <Camera size={22}/>
613
+ </button>
614
+ )}
615
  </div>
616
 
617
  <input type="file" multiple accept="image/*" ref={fileInputRef} className="hidden" onChange={handleImageSelect} onClick={(e) => (e.currentTarget.value = '')} />
618
+
619
+ {isMobile && (
620
+ <input type="file" accept="image/*" capture="environment" ref={cameraInputRef} className="hidden" onChange={handleImageSelect} onClick={(e) => (e.currentTarget.value = '')} />
621
+ )}
622
 
623
  <button onClick={() => docInputRef.current?.click()} className={`p-2 rounded-full transition-colors shrink-0 ${docFile ? 'text-indigo-600 bg-indigo-50' : 'text-gray-500 hover:bg-white hover:text-indigo-600'}`} title="上传文档 (Docx, PDF, Txt)">
624
  <Paperclip size={22}/>