Skip to content
This repository was archived by the owner on Nov 28, 2024. It is now read-only.

Commit dfe6307

Browse files
author
齐下无贰
authored
Merge pull request #28 from AIerLab/study-abroad-planning
Study abroad planning
2 parents 538a900 + a3c461c commit dfe6307

9 files changed

Lines changed: 462 additions & 189 deletions

File tree

12.5 KB
Binary file not shown.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `filePath` on the `ReviewStorage` table. All the data in the column will be lost.
5+
- You are about to drop the column `filename` on the `ReviewStorage` table. All the data in the column will be lost.
6+
- You are about to drop the column `filesize` on the `ReviewStorage` table. All the data in the column will be lost.
7+
- You are about to drop the column `filetype` on the `ReviewStorage` table. All the data in the column will be lost.
8+
- You are about to drop the column `hash` on the `ReviewStorage` table. All the data in the column will be lost.
9+
- Added the required column `fileStorageHash` to the `ReviewStorage` table without a default value. This is not possible if the table is not empty.
10+
11+
*/
12+
-- CreateTable
13+
CREATE TABLE "FileStorage" (
14+
"hash" TEXT NOT NULL PRIMARY KEY,
15+
"file" BLOB NOT NULL,
16+
"filePath" TEXT,
17+
"filename" TEXT,
18+
"filesize" INTEGER,
19+
"filetype" TEXT
20+
);
21+
22+
-- RedefineTables
23+
PRAGMA foreign_keys=OFF;
24+
CREATE TABLE "new_ReviewStorage" (
25+
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
26+
"profile_id" TEXT NOT NULL DEFAULT '',
27+
"RequestMessage" TEXT,
28+
"RequestUser" TEXT NOT NULL,
29+
"mentioned" TEXT NOT NULL DEFAULT '',
30+
"deleted" BOOLEAN NOT NULL DEFAULT false,
31+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
32+
"updatedAt" DATETIME NOT NULL,
33+
"fileStorageHash" TEXT NOT NULL,
34+
CONSTRAINT "ReviewStorage_fileStorageHash_fkey" FOREIGN KEY ("fileStorageHash") REFERENCES "FileStorage" ("hash") ON DELETE RESTRICT ON UPDATE CASCADE
35+
);
36+
INSERT INTO "new_ReviewStorage" ("RequestMessage", "RequestUser", "createdAt", "deleted", "id", "mentioned", "profile_id", "updatedAt") SELECT "RequestMessage", "RequestUser", "createdAt", "deleted", "id", "mentioned", "profile_id", "updatedAt" FROM "ReviewStorage";
37+
DROP TABLE "ReviewStorage";
38+
ALTER TABLE "new_ReviewStorage" RENAME TO "ReviewStorage";
39+
CREATE UNIQUE INDEX "ReviewStorage_id_key" ON "ReviewStorage"("id");
40+
PRAGMA foreign_key_check;
41+
PRAGMA foreign_keys=ON;
42+
43+
-- CreateIndex
44+
CREATE UNIQUE INDEX "FileStorage_hash_key" ON "FileStorage"("hash");
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// import {
2+
// ReviewStorage,
3+
// createReviewStorage,
4+
// getReviewStorage,
5+
// updateReviewStorage,
6+
// deleteReviewStorage,
7+
// } from "./review";
8+
9+
// describe("Prisma CRUD", () => {
10+
// let testReview: ReviewStorage = {
11+
// id: 1,
12+
// profile_id: "media",
13+
// RequestUser: "18330785221",
14+
// mentioned: "18330785221",
15+
// filename: "Test Filename",
16+
// filesize: 100,
17+
// filetype: "Test Type",
18+
// };
19+
20+
// it("creates a review", async () => {
21+
// const review = await createReviewStorage(testReview);
22+
23+
// expect(review.id).toBeDefined();
24+
// });
25+
26+
// it("gets the created review", async () => {
27+
// const review = await getReviewStorage(testReview.id);
28+
29+
// if (review) {
30+
// expect(review.profile_id).toBe("media");
31+
// } else {
32+
// fail("Review is null");
33+
// }
34+
// });
35+
36+
// it("updates the review", async () => {
37+
// await updateReviewStorage(testReview.id, {
38+
// ...testReview,
39+
// profile_id: "",
40+
// // 如果你更新了其他属性,也需要在这里添加
41+
// });
42+
43+
// const review = await getReviewStorage(testReview.id);
44+
// if (review) {
45+
// expect(review.profile_id).toBe("Updated Profile");
46+
// } else {
47+
// fail("Review is null");
48+
// }
49+
// });
50+
51+
// it("deletes the review", async () => {
52+
// await deleteReviewStorage(testReview.id);
53+
54+
// try {
55+
// await getReviewStorage(testReview.id);
56+
// fail("Review was not deleted");
57+
// } catch (error) {
58+
// // Review was deleted
59+
// }
60+
// });
61+
// });

react-app/src/api/db/review.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { FileCardProps } from "@/components/FileCard";
12
import prisma from "../prisma";
3+
import { FileLocalStorage } from "./file/storage";
24

35
interface ReviewStorage {
46
id?: number;
@@ -19,10 +21,15 @@ interface ReviewStorage {
1921
updatedAt?: Date;
2022
}
2123

22-
// ReviewStorage CRUD
23-
async function createReviewStorage(data: ReviewStorage) {
24+
async function createReviewStorage(
25+
payload: FormData,
26+
fileProps: FileCardProps
27+
) {
28+
FileLocalStorage(payload,fileProps);
2429
return await prisma.reviewStorage.create({
25-
data: data,
30+
data: {
31+
profile_id: "",
32+
},
2633
});
2734
}
2835

@@ -62,7 +69,7 @@ async function deleteReviewStorage(id: number) {
6269

6370
export {
6471
ReviewStorage as ReviewStorage,
65-
createReviewStorage,
72+
// createReviewStorage,
6673
getReviewStorages,
6774
getReviewStoragesDesc,
6875
getReviewStorage,

react-app/src/api/review.ts

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,70 @@
1-
import axios, { AxiosProgressEvent, AxiosRequestConfig } from 'axios'
1+
import axios, { AxiosProgressEvent, AxiosRequestConfig } from "axios";
22

3-
const API_ENDPOINT = "http://aidvisor.valmech.net:5000";
3+
// const API_ENDPOINT = "http://localhost:8000";
4+
const API_ENDPOINT = "http://aidvisor.valmech.net";
45
const api = axios.create({
56
baseURL: API_ENDPOINT,
67
headers: {
7-
'X-API-Key': 'secret_api_key',
8-
}
9-
})
8+
"X-API-Key": "secret_api_key",
9+
},
10+
});
11+
12+
export const GetOpenAIConnectionTest = async () => {
13+
return await api.post("/api/test/conn/");
14+
};
1015

1116
export interface GetFileReviewResponse {
12-
data: string
17+
data: string;
1318
}
1419

15-
export const GetFileReview = async (payload: FormData, onProgressUpdate: (p: AxiosProgressEvent) => void) => {
20+
export const GetFileReview = async (
21+
payload: FormData,
22+
onProgressUpdate: (p: AxiosProgressEvent) => void
23+
) => {
1624
const config: AxiosRequestConfig = {
17-
onUploadProgress: onProgressUpdate
18-
}
25+
onUploadProgress: onProgressUpdate,
26+
};
1927
// return await api.post('/api/file/review', payload, config)
20-
return await api.post('/api/file/rating', payload, config)
28+
return await api.post("/api/tabs/reports-review/", payload, config);
29+
};
30+
31+
interface GetStudyAboardPlanningResponse {
32+
data: {
33+
冲刺院校1: {
34+
学校名称: string;
35+
推荐专业: string;
36+
推荐原因: string;
37+
};
38+
冲刺院校2: {
39+
学校名称: string;
40+
推荐专业: string;
41+
推荐原因: string;
42+
};
43+
适中院校1: {
44+
学校名称: string;
45+
推荐专业: string;
46+
推荐原因: string;
47+
};
48+
适中院校2: {
49+
学校名称: string;
50+
推荐专业: string;
51+
推荐原因: string;
52+
};
53+
保底院校1: {
54+
学校名称: string;
55+
推荐专业: string;
56+
推荐原因: string;
57+
};
58+
保底院校2: {
59+
学校名称: string;
60+
推荐专业: string;
61+
推荐原因: string;
62+
};
63+
};
2164
}
2265

23-
export const GetOpenAIConnectionTest = async () => {
24-
return await api.post("/api/conn/test");
66+
export const GetStudyAboardPlanning = async (
67+
payload: FormData
68+
): Promise<GetStudyAboardPlanningResponse> => {
69+
return await api.post("/api/tabs/study-aboard-planning/", payload);
2570
};

react-app/src/components/FileCard/index.tsx

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,32 @@ import styles from "./index.module.css";
1313
import { humanizeFileSize } from "utils/humanize";
1414

1515
import Tooltip from "components/Tooltip/index";
16+
import { TableCellsIcon } from "@heroicons/react/24/solid";
1617

1718
// TODO: how to download? by open a link in browser
1819
// or as background job?
1920

20-
export type ValidFileType = ".txt" | ".pdf" | ".doc" | ".docx" | ".md";
21-
export function GetValidFileTypeList() {
22-
return [".txt", ".pdf", ".doc", ".docx", ".md"];
21+
export enum ValidFileTypeEnum {
22+
TXT = ".txt",
23+
PDF = ".pdf",
24+
DOC = ".doc",
25+
DOCX = ".docx",
26+
MD = ".md",
27+
XLS = ".xls",
2328
}
24-
export function IsValidFileType(ext: string) {
25-
if (!ext) return false;
26-
ext = ext.toLowerCase();
27-
return GetValidFileTypeList().includes(ext);
29+
export type ValidFileType = keyof ValidFileTypeEnum;
30+
31+
export enum TabIDsEnum {
32+
ReportsReview = "reports-review",
33+
StudyAbroadPlanning = "study-abroad-planning",
2834
}
2935

3036
export interface FileCardCommonProps {
31-
type: "review";
37+
tab: TabIDsEnum;
3238

3339
className?: string;
3440

35-
filetype: ValidFileType;
41+
filetype: ValidFileTypeEnum;
3642
filesize: number;
3743
filename: string;
3844
uploadProgress: number; // percentage, i.e. 0 - 1
@@ -51,8 +57,6 @@ export interface FileCardNotDoneProps {
5157
}
5258

5359
export interface FileCardReviewProps {
54-
type: "review";
55-
5660
done: true;
5761
overview: string;
5862
grade: number;
@@ -77,8 +81,7 @@ export function FileCard(props: FileCardProps) {
7781
<div className={s("file-info-wrap")}>
7882
<span className={s("file-name")}> {props.filename} </span>
7983
<span className={s("file-size")}>
80-
{" "}
81-
{humanizeFileSize(props.filesize)}{" "}
84+
{humanizeFileSize(props.filesize)}
8285
</span>
8386
</div>
8487
</div>
@@ -145,13 +148,13 @@ export function FileCard(props: FileCardProps) {
145148
</div>
146149
{props.done && (
147150
<div className={s("card vertical shadow")}>
148-
{props.type === "review" && (
151+
{props.tab && (
149152
<div className="w-full">
150-
<h2> 审核概述 </h2>
153+
<h2> 处理结果 </h2>
151154
<DashedSparator className={s("my-4")} />
152155
<p className={s("overview")}>{props.overview}</p>
153156
<DashedSparator className={s("my-4")} />
154-
<div className={s("card horizontal bordered")}>
157+
{/* <div className={s("card horizontal bordered")}>
155158
<div className="flex flex-row space-x-4 items-center flex-1 w-0">
156159
<DocumentIcon type={props.filetype} />
157160
<div className={s("file-info-wrap")}>
@@ -160,12 +163,10 @@ export function FileCard(props: FileCardProps) {
160163
"file-name text-indigo-500 dark:text-indigo-300"
161164
)}
162165
>
163-
{" "}
164-
{props.filename}{" "}
166+
{props.filename}
165167
</span>
166168
<span className={s("file-size")}>
167-
{" "}
168-
{humanizeFileSize(props.filesize)}{" "}
169+
{humanizeFileSize(props.filesize)}
169170
</span>
170171
</div>
171172
</div>
@@ -177,10 +178,10 @@ export function FileCard(props: FileCardProps) {
177178
mentioned={props.mentioned}
178179
mentionables={props.mentionables}
179180
onCopyOverview={handleOverviewCopy}
180-
/>
181+
/> */}
181182
</div>
182183
)}
183-
{props.type === "review" && (
184+
{props.tab === TabIDsEnum.ReportsReview && (
184185
<div className="w-full">
185186
<h2> 评分结果 </h2>
186187
<span className={s("grade")}>{props.grade}</span>
@@ -286,15 +287,16 @@ export function DocumentIcon({
286287
type,
287288
classNames,
288289
}: {
289-
type: ValidFileType;
290+
type: ValidFileTypeEnum;
290291
classNames?: string;
291292
}) {
292-
const mappings: Record<ValidFileType, ReactElement> = {
293+
const mappings: Record<ValidFileTypeEnum, ReactElement> = {
293294
".txt": <TxtIcon className={classNames} />,
294295
".pdf": <PdfIcon className={classNames} />,
295296
".doc": <DocIcon className={classNames} />,
296297
".docx": <DocIcon className={classNames} />,
297-
".md": <DocIcon className={classNames} />,
298+
".md": <TxtIcon className={classNames} />,
299+
".xls": <ExcelIcon className={classNames} />,
298300
};
299301

300302
return mappings[type];
@@ -386,3 +388,38 @@ const DocIcon = forwardRef<SVGSVGElement, React.HTMLProps<SVGSVGElement>>(
386388
);
387389
}
388390
);
391+
392+
const ExcelIcon = forwardRef<SVGSVGElement, React.HTMLProps<SVGSVGElement>>(
393+
(props, ref) => {
394+
return (
395+
<svg
396+
ref={ref}
397+
{...props}
398+
width="40"
399+
height="40"
400+
viewBox="0 0 40 40"
401+
fill="none"
402+
xmlns="http://www.w3.org/2000/svg"
403+
404+
// stroke-width="1.5"
405+
// stroke="currentColor"
406+
// class="w-6 h-6"
407+
>
408+
{/* <path
409+
d="M5 6C5 3.79086 6.79086 2 9 2H27C31.4183 2 35 5.58172 35 10V34C35 36.2091 33.2091 38 31 38H9C6.79086 38 5 36.2091 5 34V6Z"
410+
fill="#9CCC65"
411+
/>
412+
<path
413+
d="M26.5 3.5C30.366 3.5 33.5 6.63401 33.5 10.5H26.5V3.5Z"
414+
fill="white"
415+
/> */}
416+
<path
417+
stroke-linecap="round"
418+
stroke-linejoin="round"
419+
d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 01-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0112 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5"
420+
fill="#9ECC65"
421+
/>
422+
</svg>
423+
);
424+
}
425+
);

0 commit comments

Comments
 (0)