Compare commits
2 Commits
ffa7f097af
...
76a95b28c2
Author | SHA1 | Date | |
---|---|---|---|
76a95b28c2 | |||
961508449f |
6
.idea/deno.xml
generated
Normal file
6
.idea/deno.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DenoSettings">
|
||||||
|
<option name="useDeno" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ExceptionCaughtLocallyJS" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<n-config-provider :theme-overrides="theme">
|
||||||
<n-message-provider>
|
<n-message-provider>
|
||||||
<n-dialog-provider>
|
<n-dialog-provider>
|
||||||
<nuxt-layout>
|
<nuxt-layout>
|
||||||
@ -7,9 +8,19 @@
|
|||||||
</nuxt-layout>
|
</nuxt-layout>
|
||||||
</n-dialog-provider>
|
</n-dialog-provider>
|
||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
|
</n-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NMessageProvider, NDialogProvider } from "naive-ui";
|
import { NConfigProvider, NMessageProvider, NDialogProvider } from "naive-ui";
|
||||||
import "@/assets/css/index.css";
|
import "@/assets/css/index.css";
|
||||||
|
|
||||||
|
const theme = {
|
||||||
|
"common": {
|
||||||
|
"primaryColor": "#e7bf72",
|
||||||
|
"primaryColorHover": "#f1ca7a",
|
||||||
|
"primaryColorPressed": "#d6b26b",
|
||||||
|
"primaryColorSuppl": "#c49e55"
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -21,3 +21,8 @@ code, pre {
|
|||||||
pre {
|
pre {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #c49e55;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
Binary file not shown.
21
application/components/problem/preview/program.vue
Normal file
21
application/components/problem/preview/program.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<n-card>
|
||||||
|
<n-code :hljs="hljs" :language="language" :code="props.answers?.code" />
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NCard, NCode } from "naive-ui";
|
||||||
|
import hljs from "highlight.js/lib/core";
|
||||||
|
import cpp from "highlight.js/lib/languages/cpp";
|
||||||
|
|
||||||
|
hljs.registerLanguage("cpp", cpp);
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
challenge: any,
|
||||||
|
problem: any,
|
||||||
|
answers: any,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const language = computed(() => props.answers?.language ?? "text");
|
||||||
|
</script>
|
@ -26,6 +26,7 @@
|
|||||||
"@ibm/plex": "^6.3.0",
|
"@ibm/plex": "^6.3.0",
|
||||||
"@nuxtjs/mdc": "^0.2.8",
|
"@nuxtjs/mdc": "^0.2.8",
|
||||||
"@supabase/supabase-js": "^2.39.0",
|
"@supabase/supabase-js": "^2.39.0",
|
||||||
|
"highlight.js": "^11.9.0",
|
||||||
"prismjs": "^1.29.0"
|
"prismjs": "^1.29.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,52 @@
|
|||||||
</n-page-header>
|
</n-page-header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<section>
|
<div id="answering-content" v-if="challenge?.status === 'in-progress'">
|
||||||
<problem-solution-program
|
<problem-solution-program
|
||||||
v-if="problem.type === 'programming'"
|
v-if="problem.type === 'programming'"
|
||||||
v-model:answers="answers"
|
v-model:answers="answers"
|
||||||
:challenge="challenge"
|
:challenge="challenge"
|
||||||
:problem="problem"
|
:problem="problem"
|
||||||
/>
|
/>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
|
<div id="preview-challenge" class="flex flex-col gap-4" v-else>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold">状态</div>
|
||||||
|
<div class="case-capital">{{ challenge?.status?.replaceAll("-", " ") }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold">问题</div>
|
||||||
|
<div>
|
||||||
|
<nuxt-link target="_blank" :to="`/problems/${challenge.problem}`">
|
||||||
|
#{{ challenge?.problem }} | {{ problem?.title }}
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold">答案</div>
|
||||||
|
<client-only>
|
||||||
|
<div class="mt-2 mx-[-4px]">
|
||||||
|
<problem-preview-program
|
||||||
|
v-if="problem.type === 'programming'"
|
||||||
|
:answers="answers"
|
||||||
|
:challenge="challenge"
|
||||||
|
:problem="problem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</client-only>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold">结果</div>
|
||||||
|
<n-card embedded class="mt-2 mx-[-4px]">
|
||||||
|
<div v-if="challenge?.details?.cases"></div>
|
||||||
|
<n-empty v-else description="未进行判题,暂无挑战结果" />
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template #action>
|
<template #action>
|
||||||
<div class="w-full flex justify-between">
|
<div v-if="challenge?.status === 'in-progress'" id="answering-widgets" class="w-full flex justify-between">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<n-button secondary circle class="rounded-[4px]" type="warning" :disabled="submitting" @click="save">
|
<n-button secondary circle class="rounded-[4px]" type="warning" :disabled="submitting" @click="save">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
@ -41,7 +76,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NButton, NCard, NPageHeader, NTag, NIcon, useDialog, useMessage } from "naive-ui";
|
import { NButton, NCard, NPageHeader, NTag, NIcon, NEmpty, useDialog, useMessage } from "naive-ui";
|
||||||
import { Save } from "@vicons/carbon";
|
import { Save } from "@vicons/carbon";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -124,6 +159,36 @@ function abandon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
|
await save();
|
||||||
|
|
||||||
|
const instance = dialog.warning({
|
||||||
|
title: "警告",
|
||||||
|
content: "你确定要提交吗?确认提交后无法再次修改答案,等待判题可能需要一段时间。",
|
||||||
|
positiveText: "确定",
|
||||||
|
negativeText: "不确定",
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
instance.loading = true;
|
||||||
|
|
||||||
|
const inst = message.loading("正在提交中,请稍后……");
|
||||||
|
|
||||||
|
const { error } = await client
|
||||||
|
.from("challenges")
|
||||||
|
// @ts-ignore
|
||||||
|
.update<any>({ status: "submitted" })
|
||||||
|
.eq("id", challenge.id);
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
message.error(`Something went wrong... ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
inst.destroy();
|
||||||
|
message.success("提交成功!即将跳转结果页面……");
|
||||||
|
|
||||||
|
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
|
||||||
|
|
||||||
|
await delay(1850);
|
||||||
|
reloadNuxtApp();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
1
supabase/functions/.env
Normal file
1
supabase/functions/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
JUDGE0_ENDPOINT=http://192.168.50.83:2358
|
44
supabase/functions/fresh-challenges/index.ts
Normal file
44
supabase/functions/fresh-challenges/index.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { createClient } from "https://esm.sh/@supabase/supabase-js";
|
||||||
|
|
||||||
|
// http://127.0.0.1:54321/functions/v1/fresh-challenges
|
||||||
|
// Auto get judging status from remote lawyers
|
||||||
|
|
||||||
|
Deno.serve(async (req) => {
|
||||||
|
try {
|
||||||
|
const client = createClient(
|
||||||
|
Deno.env.get("SUPABASE_URL") ?? "",
|
||||||
|
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = await req.json();
|
||||||
|
|
||||||
|
const { data } = await client
|
||||||
|
.from("challenges")
|
||||||
|
.select<any, any>("*")
|
||||||
|
.eq("status", "judging")
|
||||||
|
.eq("id", body.id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (data == null || data?.details?.submissions == null) {
|
||||||
|
return new Response(String("Unable to find the challenge with the id in the request."), { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = data?.details?.submissions
|
||||||
|
.map((item: { token: string }) => item.token)
|
||||||
|
.join(",");
|
||||||
|
|
||||||
|
const resp = await fetch(
|
||||||
|
Deno.env.get("JUDGE0_ENDPOINT") + `/submissions/batch?tokens=${tokens}`,
|
||||||
|
{ method: "GET" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await resp.json();
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ result }), {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
status: 200
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return new Response(String(err?.message ?? err), { status: 500 });
|
||||||
|
}
|
||||||
|
});
|
58
supabase/functions/judge-challenges/index.ts
Normal file
58
supabase/functions/judge-challenges/index.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { createClient } from "https://esm.sh/@supabase/supabase-js";
|
||||||
|
import { runChallenge } from "./runners/index.ts";
|
||||||
|
|
||||||
|
// http://127.0.0.1:54321/functions/v1/judge-challenges
|
||||||
|
// Judge all status is submitted challenges
|
||||||
|
|
||||||
|
Deno.serve(async (_) => {
|
||||||
|
try {
|
||||||
|
const client = createClient(
|
||||||
|
Deno.env.get("SUPABASE_URL") ?? "",
|
||||||
|
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, error } = await client
|
||||||
|
.from("challenges")
|
||||||
|
.select<any, any>("*")
|
||||||
|
.eq("status", "submitted")
|
||||||
|
.limit(20);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
for (const item of data) {
|
||||||
|
const { data: problem } = await client
|
||||||
|
.from("problems")
|
||||||
|
.select<any, any>("*")
|
||||||
|
.eq("id", item.problem)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (problem == null) {
|
||||||
|
throw new Error("Problem was not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: cases } = await client
|
||||||
|
.from("problem_cases")
|
||||||
|
.select<any, any>("*")
|
||||||
|
.eq("problem", problem.id);
|
||||||
|
|
||||||
|
const result = await runChallenge(item, problem, cases);
|
||||||
|
|
||||||
|
await client
|
||||||
|
.from("challenges")
|
||||||
|
.update<any>({ status: result.status === "skipped" ? "finished" : "judging", details: result })
|
||||||
|
.eq("id", item.id);
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ judged: counter }), {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
status: 200
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return new Response(String(err?.message ?? err), { status: 500 });
|
||||||
|
}
|
||||||
|
});
|
10
supabase/functions/judge-challenges/runners/index.ts
Normal file
10
supabase/functions/judge-challenges/runners/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { runProgramChallenge } from "./programming.ts";
|
||||||
|
|
||||||
|
export async function runChallenge(challenge: any, problem: any, cases: any) {
|
||||||
|
switch (problem.type) {
|
||||||
|
case "programming":
|
||||||
|
return await runProgramChallenge(challenge, problem, cases)
|
||||||
|
default:
|
||||||
|
throw new Error("Unsupported problem type.")
|
||||||
|
}
|
||||||
|
}
|
40
supabase/functions/judge-challenges/runners/programming.ts
Normal file
40
supabase/functions/judge-challenges/runners/programming.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
export async function runProgramChallenge(challenge: any, problem: any, cases: any[]) {
|
||||||
|
const languages: { [id: string]: number } = {
|
||||||
|
"cpp": 54
|
||||||
|
};
|
||||||
|
|
||||||
|
const code = challenge.answers?.code;
|
||||||
|
const language = challenge.answers?.language;
|
||||||
|
if (!code || !language || !cases || Object.keys(languages).indexOf(language) < 0) {
|
||||||
|
return {
|
||||||
|
status: "skipped",
|
||||||
|
submissions: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx = languages[language];
|
||||||
|
const submissions = cases.map((item: any) => {
|
||||||
|
return {
|
||||||
|
"language_id": idx,
|
||||||
|
"source_code": challenge.answers?.code,
|
||||||
|
"expected_output": item?.stdout,
|
||||||
|
"stdin": code
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const resp = await fetch(
|
||||||
|
Deno.env.get("JUDGE0_ENDPOINT") + "/submissions/batch",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ submissions })
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await resp.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "judging",
|
||||||
|
submissions: result
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user