SarahXia0405 commited on
Commit
d9476d2
·
verified ·
1 Parent(s): 08e6135

Update web/src/components/ChatArea.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/ChatArea.tsx +138 -16
web/src/components/ChatArea.tsx CHANGED
@@ -133,6 +133,75 @@ interface PendingFile {
133
  type: FileType;
134
  }
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  export function ChatArea({
137
  messages,
138
  onSendMessage,
@@ -959,7 +1028,24 @@ export function ChatArea({
959
  const thumbUrl = isImage ? getOrCreate(uf.file) : null;
960
 
961
  return (
962
- <div key={key} className="flex items-center justify-between gap-2 rounded-md border px-3 py-2">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
963
  {/* ✅ Thumbnail (image preview or file icon) */}
964
  <div className="h-10 w-10 flex-shrink-0 rounded-lg overflow-hidden border border-border bg-muted">
965
  {isImage ? (
@@ -990,7 +1076,16 @@ export function ChatArea({
990
  <div className="text-xs text-muted-foreground">{uf.type}</div>
991
  </div>
992
 
993
- <Button variant="ghost" size="icon" onClick={() => onRemoveFile(i)} title="Remove">
 
 
 
 
 
 
 
 
 
994
  <Trash2 className="h-4 w-4" />
995
  </Button>
996
  </div>
@@ -1125,10 +1220,7 @@ export function ChatArea({
1125
  type="button"
1126
  size="icon"
1127
  variant="ghost"
1128
- disabled={
1129
- !isLoggedIn ||
1130
- (chatMode === "quiz" && !quizState.waitingForAnswer)
1131
- }
1132
  className="h-8 w-8 hover:bg-muted/50"
1133
  onClick={() => fileInputRef.current?.click()}
1134
  title="Upload files"
@@ -1156,22 +1248,14 @@ export function ChatArea({
1156
  ? "Ask me anything! Please provide context about your question..."
1157
  : "Ask Clare anything about the course or drag files here..."
1158
  }
1159
- disabled={
1160
- !isLoggedIn ||
1161
- (chatMode === "quiz" && !quizState.waitingForAnswer)
1162
- }
1163
  className={`min-h-[80px] pl-4 pr-20 resize-none bg-background border-2 ${
1164
  isDragging ? "border-primary border-dashed" : "border-border"
1165
  }`}
1166
  />
1167
 
1168
  <div className="absolute bottom-2 right-2 flex gap-1">
1169
- <Button
1170
- type="submit"
1171
- size="icon"
1172
- disabled={!input.trim() || !isLoggedIn}
1173
- className="h-8 w-8 rounded-full"
1174
- >
1175
  <Send className="h-4 w-4" />
1176
  </Button>
1177
  </div>
@@ -1190,6 +1274,44 @@ export function ChatArea({
1190
  </div>
1191
  </div>
1192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1193
  {/* Start New Conversation Confirmation Dialog */}
1194
  <AlertDialog open={showClearDialog} onOpenChange={onCancelClear}>
1195
  <AlertDialogContent>
 
133
  type: FileType;
134
  }
135
 
136
+ // ✅ NEW: File viewer content (image full preview + pdf iframe; others download)
137
+ function isImageFile(name: string) {
138
+ const n = name.toLowerCase();
139
+ return [".jpg", ".jpeg", ".png", ".gif", ".webp"].some((e) => n.endsWith(e));
140
+ }
141
+ function isPdfFile(name: string) {
142
+ return name.toLowerCase().endsWith(".pdf");
143
+ }
144
+ function isDocFile(name: string) {
145
+ const n = name.toLowerCase();
146
+ return n.endsWith(".doc") || n.endsWith(".docx");
147
+ }
148
+ function isPptFile(name: string) {
149
+ const n = name.toLowerCase();
150
+ return n.endsWith(".ppt") || n.endsWith(".pptx");
151
+ }
152
+
153
+ function FileViewerContent({ file }: { file: File }) {
154
+ const [url, setUrl] = React.useState<string>("");
155
+
156
+ React.useEffect(() => {
157
+ const u = URL.createObjectURL(file);
158
+ setUrl(u);
159
+ return () => URL.revokeObjectURL(u);
160
+ }, [file]);
161
+
162
+ if (isImageFile(file.name)) {
163
+ return (
164
+ <div className="w-full">
165
+ <img
166
+ src={url}
167
+ alt={file.name}
168
+ className="w-full h-auto rounded-lg border"
169
+ draggable={false}
170
+ />
171
+ </div>
172
+ );
173
+ }
174
+
175
+ if (isPdfFile(file.name)) {
176
+ return (
177
+ <div className="w-full h-[70vh] border rounded-lg overflow-hidden">
178
+ <iframe title={file.name} src={url} className="w-full h-full" />
179
+ </div>
180
+ );
181
+ }
182
+
183
+ const kind = isDocFile(file.name)
184
+ ? "Word document"
185
+ : isPptFile(file.name)
186
+ ? "PowerPoint"
187
+ : "File";
188
+
189
+ return (
190
+ <div className="space-y-3">
191
+ <div className="text-sm text-muted-foreground">
192
+ Preview is not available for this {kind} format in the browser without conversion.
193
+ </div>
194
+ <a
195
+ href={url}
196
+ download={file.name}
197
+ className="inline-flex items-center justify-center h-9 px-3 rounded-md border hover:bg-muted"
198
+ >
199
+ Download to view
200
+ </a>
201
+ </div>
202
+ );
203
+ }
204
+
205
  export function ChatArea({
206
  messages,
207
  onSendMessage,
 
1028
  const thumbUrl = isImage ? getOrCreate(uf.file) : null;
1029
 
1030
  return (
1031
+ <div
1032
+ key={key}
1033
+ role="button"
1034
+ tabIndex={0}
1035
+ onClick={() => {
1036
+ setSelectedFile({ file: uf.file, index: i });
1037
+ setShowFileViewer(true);
1038
+ }}
1039
+ onKeyDown={(e) => {
1040
+ if (e.key === "Enter" || e.key === " ") {
1041
+ e.preventDefault();
1042
+ setSelectedFile({ file: uf.file, index: i });
1043
+ setShowFileViewer(true);
1044
+ }
1045
+ }}
1046
+ className="flex items-center justify-between gap-2 rounded-md border px-3 py-2 cursor-pointer hover:bg-muted/40"
1047
+ title="Click to preview"
1048
+ >
1049
  {/* ✅ Thumbnail (image preview or file icon) */}
1050
  <div className="h-10 w-10 flex-shrink-0 rounded-lg overflow-hidden border border-border bg-muted">
1051
  {isImage ? (
 
1076
  <div className="text-xs text-muted-foreground">{uf.type}</div>
1077
  </div>
1078
 
1079
+ <Button
1080
+ variant="ghost"
1081
+ size="icon"
1082
+ onClick={(e) => {
1083
+ e.preventDefault();
1084
+ e.stopPropagation(); // ✅ don't open viewer
1085
+ onRemoveFile(i);
1086
+ }}
1087
+ title="Remove"
1088
+ >
1089
  <Trash2 className="h-4 w-4" />
1090
  </Button>
1091
  </div>
 
1220
  type="button"
1221
  size="icon"
1222
  variant="ghost"
1223
+ disabled={!isLoggedIn || (chatMode === "quiz" && !quizState.waitingForAnswer)}
 
 
 
1224
  className="h-8 w-8 hover:bg-muted/50"
1225
  onClick={() => fileInputRef.current?.click()}
1226
  title="Upload files"
 
1248
  ? "Ask me anything! Please provide context about your question..."
1249
  : "Ask Clare anything about the course or drag files here..."
1250
  }
1251
+ disabled={!isLoggedIn || (chatMode === "quiz" && !quizState.waitingForAnswer)}
 
 
 
1252
  className={`min-h-[80px] pl-4 pr-20 resize-none bg-background border-2 ${
1253
  isDragging ? "border-primary border-dashed" : "border-border"
1254
  }`}
1255
  />
1256
 
1257
  <div className="absolute bottom-2 right-2 flex gap-1">
1258
+ <Button type="submit" size="icon" disabled={!input.trim() || !isLoggedIn} className="h-8 w-8 rounded-full">
 
 
 
 
 
1259
  <Send className="h-4 w-4" />
1260
  </Button>
1261
  </div>
 
1274
  </div>
1275
  </div>
1276
 
1277
+ {/* ✅ NEW: File Viewer Dialog */}
1278
+ <Dialog
1279
+ open={showFileViewer}
1280
+ onOpenChange={(open) => {
1281
+ setShowFileViewer(open);
1282
+ if (!open) setSelectedFile(null);
1283
+ }}
1284
+ >
1285
+ <DialogContent className="max-w-4xl max-h-[85vh] flex flex-col overflow-hidden">
1286
+ <DialogHeader className="min-w-0 flex-shrink-0">
1287
+ <DialogTitle
1288
+ className="pr-8 break-words break-all overflow-wrap-anywhere leading-relaxed"
1289
+ style={{
1290
+ wordBreak: "break-all",
1291
+ overflowWrap: "anywhere",
1292
+ maxWidth: "100%",
1293
+ lineHeight: "1.6",
1294
+ }}
1295
+ >
1296
+ {selectedFile?.file.name}
1297
+ </DialogTitle>
1298
+ <DialogDescription>
1299
+ File size: {selectedFile ? formatFileSize(selectedFile.file.size) : ""}
1300
+ </DialogDescription>
1301
+ </DialogHeader>
1302
+
1303
+ <div className="flex-1 min-h-0 overflow-y-auto mt-4">
1304
+ {selectedFile && <FileViewerContent file={selectedFile.file} />}
1305
+ </div>
1306
+ </DialogContent>
1307
+ </Dialog>
1308
+
1309
+
1310
+
1311
+
1312
+
1313
+
1314
+
1315
  {/* Start New Conversation Confirmation Dialog */}
1316
  <AlertDialog open={showClearDialog} onOpenChange={onCancelClear}>
1317
  <AlertDialogContent>