Compare commits
20 Commits
fba56106cc
...
b4205049cb
Author | SHA1 | Date | |
---|---|---|---|
b4205049cb | |||
509c10ded0 | |||
99c7a34833 | |||
d808f36c58 | |||
984484cc3f | |||
46578db3e6 | |||
704b5106c6 | |||
2e702f23de | |||
cb5e24e542 | |||
b1e071930c | |||
c3f13cc6e3 | |||
dd829b203d | |||
41f8b42f1c | |||
8d4edd43bf | |||
2aead46b51 | |||
79af12e526 | |||
81847cc090 | |||
0d18c921cb | |||
ba6b8bd5b3 | |||
2ed909268e |
@ -2,6 +2,11 @@
|
||||
|
||||
「中V档案馆」是一个旨在收录与展示「中文歌声合成作品」及有关信息的网站。
|
||||
|
||||
## 新闻 - 测试版本上线
|
||||
|
||||
目前,中V档案馆上线了用于测试的前端网页和API接口,它们分别位于[projectcvsa.com](https://projectcvsa.com)和[api.projectcvsa.com](https://api.projectcvsa.com)。
|
||||
API调用方法请参见[接口文档](https://docs.projectcvsa.com/api-doc/)。
|
||||
|
||||
## 创建背景与关联工作
|
||||
|
||||
纵观整个互联网,对于「中文歌声合成」或「中文虚拟歌手」(常简称为中V或VC)相关信息进行较为系统、全面地整理收集的主要有以下几个网站:
|
||||
@ -31,7 +36,7 @@
|
||||
|
||||
## 技术架构
|
||||
|
||||
参见[CVSA文档](https://cvsa.gitbook.io/)。
|
||||
参见[CVSA文档](https://docs.projectcvsa.com/)。
|
||||
|
||||
## 开放许可
|
||||
|
||||
|
@ -20,7 +20,7 @@ class VideoPlayDataset(Dataset):
|
||||
self.valid_series = [s for s in self.series_dict.values() if len(s['abs_time']) > 1]
|
||||
self.term = term
|
||||
# Set time window based on term
|
||||
self.time_window = 1000 * 24 * 3600 if term == 'long' else 7 * 24 * 3600
|
||||
self.time_window = 1000 * 24 * 3600 if term == 'long' else 3 * 24 * 3600
|
||||
MINUTE = 60
|
||||
HOUR = 3600
|
||||
DAY = 24 * HOUR
|
||||
@ -37,6 +37,7 @@ class VideoPlayDataset(Dataset):
|
||||
]
|
||||
else:
|
||||
self.feature_windows = [
|
||||
#( 5 * MINUTE, 0 * MINUTE),
|
||||
( 15 * MINUTE, 0 * MINUTE),
|
||||
( 40 * MINUTE, 0 * MINUTE),
|
||||
( 1 * HOUR, 0 * HOUR),
|
||||
@ -45,7 +46,7 @@ class VideoPlayDataset(Dataset):
|
||||
( 3 * HOUR, 0 * HOUR),
|
||||
#( 6 * HOUR, 3 * HOUR),
|
||||
( 6 * HOUR, 0 * HOUR),
|
||||
(18 * HOUR, 12 * HOUR),
|
||||
#(18 * HOUR, 12 * HOUR),
|
||||
#( 1 * DAY, 6 * HOUR),
|
||||
( 1 * DAY, 0 * DAY),
|
||||
#( 2 * DAY, 1 * DAY),
|
||||
|
@ -4,20 +4,20 @@ from model import CompactPredictor
|
||||
import torch
|
||||
|
||||
def main():
|
||||
model = CompactPredictor(10).to('cpu', dtype=torch.float32)
|
||||
model.load_state_dict(torch.load('./pred/checkpoints/long_term.pt'))
|
||||
model = CompactPredictor(15).to('cpu', dtype=torch.float32)
|
||||
model.load_state_dict(torch.load('./pred/checkpoints/model_20250320_0045.pt'))
|
||||
model.eval()
|
||||
# inference
|
||||
initial = 997029
|
||||
initial = 999704
|
||||
last = initial
|
||||
start_time = '2025-03-17 00:13:17'
|
||||
for i in range(1, 120):
|
||||
hour = i / 0.5
|
||||
start_time = '2025-03-19 22:00:42'
|
||||
for i in range(1, 48):
|
||||
hour = i / 6
|
||||
sec = hour * 3600
|
||||
time_d = np.log2(sec)
|
||||
data = [time_d, np.log2(initial+1), # time_delta, current_views
|
||||
6.111542, 8.404707, 10.071566, 11.55888, 12.457823,# grows_feat
|
||||
0.009225, 0.001318, 28.001814# time_feat
|
||||
4.857981, 6.29067, 6.869476, 6.58392, 6.523051, 8.242355, 8.841574, 10.203909, 11.449314, 12.659556, # grows_feat
|
||||
0.916956, 0.416708, 28.003162 # time_feat
|
||||
]
|
||||
np_arr = np.array([data])
|
||||
tensor = torch.from_numpy(np_arr).to('cpu', dtype=torch.float32)
|
||||
@ -25,7 +25,7 @@ def main():
|
||||
num = output.detach().numpy()[0][0]
|
||||
views_pred = int(np.exp2(num)) + initial
|
||||
current_time = datetime.datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=hour)
|
||||
print(current_time.strftime('%m-%d %H:%M:%S'), views_pred, views_pred - last)
|
||||
print(current_time.strftime('%m-%d %H:%M'), views_pred, views_pred - last)
|
||||
last = views_pred
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -38,7 +38,7 @@ def train(model, dataloader, device, epochs=100):
|
||||
scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=1e-3,
|
||||
total_steps=len(dataloader)*30)
|
||||
# Huber loss
|
||||
criterion = asymmetricHuberLoss(delta=1.0, beta=2.1)
|
||||
criterion = asymmetricHuberLoss(delta=1.0, beta=2.2)
|
||||
|
||||
model.train()
|
||||
global_step = 0
|
||||
@ -100,7 +100,7 @@ if __name__ == "__main__":
|
||||
device = 'mps'
|
||||
|
||||
# Initialize dataset and model
|
||||
dataset = VideoPlayDataset('./data/pred', './data/pred/publish_time.csv', 'short')
|
||||
dataset = VideoPlayDataset('./data/pred', './data/pred/publish_time.csv', 'short', 712)
|
||||
dataloader = DataLoader(dataset, batch_size=128, shuffle=True, collate_fn=collate_fn)
|
||||
|
||||
# Get feature dimension
|
||||
|
@ -1,27 +1,65 @@
|
||||
import { createHandlers } from "./utils.ts";
|
||||
import Argon2id from "@rabbit-company/argon2id";
|
||||
import { object, string, ValidationError } from "yup";
|
||||
import type { Context } from "hono";
|
||||
import type { Bindings, BlankEnv, BlankInput } from "hono/types";
|
||||
import type { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
|
||||
export const registerHandler = createHandlers(async (c) => {
|
||||
try {
|
||||
const client = c.get("dbCred");
|
||||
const body = await c.req.json();
|
||||
const username = body.username;
|
||||
const password = body.password;
|
||||
const hash = await Argon2id.hashEncoded(password);
|
||||
const RegistrationBodySchema = object({
|
||||
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
|
||||
password: string().required("Password is required"),
|
||||
nickname: string().optional(),
|
||||
});
|
||||
|
||||
type ContextType = Context<BlankEnv & { Bindings: Bindings }, "/user", BlankInput>;
|
||||
|
||||
export const userExists = async (username: string, client: Client) => {
|
||||
const query = `
|
||||
INSERT INTO users (username, password) VALUES ($1, $2)
|
||||
SELECT * FROM users WHERE username = $1
|
||||
`;
|
||||
await client.queryObject(query, [username, hash]);
|
||||
const result = await client.queryObject(query, [username]);
|
||||
return result.rows.length > 0;
|
||||
}
|
||||
|
||||
export const registerHandler = createHandlers(async (c: ContextType) => {
|
||||
const client = c.get("dbCred");
|
||||
|
||||
try {
|
||||
const body = await RegistrationBodySchema.validate(await c.req.json());
|
||||
const { username, password, nickname } = body;
|
||||
|
||||
if (await userExists(username, client)) {
|
||||
return c.json({
|
||||
success: true,
|
||||
message: "Registered",
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
return c.json({ error: "Invalid JSON" }, 400);
|
||||
message: `User "${username}" already exists.`,
|
||||
}, 400);
|
||||
}
|
||||
else {
|
||||
return c.json({ error: (e as Error).message }, 500);
|
||||
|
||||
const hash = await Argon2id.hashEncoded(password);
|
||||
|
||||
const query = `
|
||||
INSERT INTO users (username, password, nickname) VALUES ($1, $2, $3)
|
||||
`;
|
||||
await client.queryObject(query, [username, hash, nickname || null]);
|
||||
|
||||
return c.json({
|
||||
message: `User "${username}" registered successfully.`,
|
||||
}, 201);
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
return c.json({
|
||||
message: "Invalid registration data.",
|
||||
errors: e.errors,
|
||||
}, 400);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
return c.json({
|
||||
message: "Invalid JSON in request body.",
|
||||
}, 400);
|
||||
} else {
|
||||
console.error("Registration error:", e);
|
||||
return c.json({
|
||||
message: "An unexpected error occurred during registration.",
|
||||
error: (e as Error).message,
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -19,6 +19,6 @@ export default defineConfig({
|
||||
allow: [".", "../../"],
|
||||
},
|
||||
},
|
||||
plugins: [tsconfigPaths()],
|
||||
plugins: [tsconfigPaths()]
|
||||
},
|
||||
});
|
||||
|
@ -1 +1 @@
|
||||
export const VERSION = "1.2.6";
|
||||
export const VERSION = "1.2.7";
|
||||
|
@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/tailwind": "^6.0.2",
|
||||
"argon2id": "^1.0.1",
|
||||
"astro": "^5.5.5",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"pg": "^8.11.11",
|
||||
@ -18,6 +19,7 @@
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-wasm": "^6.2.2",
|
||||
"@types/pg": "^8.11.11"
|
||||
}
|
||||
}
|
||||
|
BIN
packages/frontend/public/no-simd.wasm
Executable file
BIN
packages/frontend/public/no-simd.wasm
Executable file
Binary file not shown.
BIN
packages/frontend/public/simd.wasm
Executable file
BIN
packages/frontend/public/simd.wasm
Executable file
Binary file not shown.
219
packages/frontend/src/components/VDFtester.svelte
Normal file
219
packages/frontend/src/components/VDFtester.svelte
Normal file
@ -0,0 +1,219 @@
|
||||
<script lang="ts">
|
||||
import { N_ARRAY } from "src/const"; // 假设你的常量文件现在导出 N_ARRAY
|
||||
|
||||
function generateRandomBigInt(min: bigint, max: bigint) {
|
||||
const range = max - min;
|
||||
const bitLength = range.toString(2).length;
|
||||
const byteLength = Math.ceil(bitLength / 8);
|
||||
const mask = (1n << BigInt(bitLength)) - 1n; // 用于截断的掩码
|
||||
let result;
|
||||
do {
|
||||
const randomBytes = new Uint8Array(byteLength);
|
||||
crypto.getRandomValues(randomBytes);
|
||||
result = 0n;
|
||||
for (let i = 0; i < byteLength; i++) {
|
||||
result = (result << 8n) | BigInt(randomBytes[i]);
|
||||
}
|
||||
result = result & mask; // 确保不超过 bitLength 位
|
||||
} while (result > range);
|
||||
return min + result;
|
||||
}
|
||||
|
||||
function generateValidG(N: bigint) {
|
||||
if (N <= 4n) throw new Error("N must be > 4");
|
||||
while (true) {
|
||||
const r = generateRandomBigInt(2n, N - 1n);
|
||||
const g = (r * r) % N;
|
||||
if (g !== 1n && g !== 0n && g !== N - 1n) {
|
||||
return g;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const workerContent = `addEventListener("message", async (event) => {
|
||||
const { g, N, difficulty } = event.data;
|
||||
function pow(base, exponent, mod) {
|
||||
let result = 1n;
|
||||
base = base % mod;
|
||||
while (exponent > 0n) {
|
||||
if (exponent % 2n === 1n) {
|
||||
result = (result * base) % mod;
|
||||
}
|
||||
base = (base * base) % mod;
|
||||
exponent = exponent / 2n;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function computeVDFWithProgress(g, N, T, postProgress) {
|
||||
let result = g;
|
||||
let latestTime = performance.now();
|
||||
for (let i = 0n; i < T; i++) {
|
||||
result = (result * result) % N;
|
||||
if (performance.now() - latestTime > 16) {
|
||||
postProgress(Number(i * 100n) / Number(T));
|
||||
latestTime = performance.now();
|
||||
}
|
||||
}
|
||||
postProgress(100);
|
||||
return result;
|
||||
}
|
||||
const startTime = performance.now();
|
||||
const result = computeVDFWithProgress(g, N, difficulty, (progress) => {
|
||||
postMessage({ type: "progress", N: N.toString(), difficulty: difficulty.toString(), progress });
|
||||
});
|
||||
const endTime = performance.now();
|
||||
const timeTaken = endTime - startTime;
|
||||
postMessage({ type: "result", N: N.toString(), difficulty: difficulty.toString(), time: timeTaken, result });
|
||||
});
|
||||
`;
|
||||
|
||||
let isBenchmarking = false;
|
||||
interface BenchmarkResult {
|
||||
N: bigint;
|
||||
difficulty: bigint;
|
||||
time: number;
|
||||
}
|
||||
let benchmarkResults: BenchmarkResult[] = [];
|
||||
let currentProgress = 0;
|
||||
let currentN: bigint | null = null;
|
||||
let currentDifficulty: bigint | null = null;
|
||||
let worker: Worker | null = null;
|
||||
let currentTestIndex = 0;
|
||||
const difficulties = [BigInt(20000), BigInt(200000)];
|
||||
const testCombinations: { N: bigint; difficulty: bigint }[] = [];
|
||||
|
||||
// 创建需要测试的 N 和难度的组合
|
||||
N_ARRAY.forEach((n) => {
|
||||
difficulties.forEach((difficulty) => {
|
||||
testCombinations.push({ N: n, difficulty });
|
||||
});
|
||||
});
|
||||
|
||||
async function startBenchmark() {
|
||||
if (testCombinations.length === 0) {
|
||||
alert("No N values provided in src/const N_ARRAY.");
|
||||
return;
|
||||
}
|
||||
isBenchmarking = true;
|
||||
benchmarkResults = [];
|
||||
currentTestIndex = 0;
|
||||
|
||||
const { N, difficulty } = testCombinations[currentTestIndex];
|
||||
const g = generateValidG(N);
|
||||
|
||||
let blob = new Blob([workerContent], { type: "text/javascript" });
|
||||
worker = new Worker(window.URL.createObjectURL(blob));
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
const { type, N: resultNStr, difficulty: resultDifficultyStr, time, progress } = event.data;
|
||||
const resultN = BigInt(resultNStr);
|
||||
const resultDifficulty = BigInt(resultDifficultyStr);
|
||||
|
||||
if (type === "progress") {
|
||||
currentProgress = progress;
|
||||
currentN = resultN;
|
||||
currentDifficulty = resultDifficulty;
|
||||
} else if (type === "result") {
|
||||
benchmarkResults = [...benchmarkResults, { N: resultN, difficulty: resultDifficulty, time }];
|
||||
currentProgress = 0;
|
||||
currentTestIndex++;
|
||||
|
||||
if (currentTestIndex < testCombinations.length) {
|
||||
// 继续下一个测试组合
|
||||
const nextTest = testCombinations[currentTestIndex];
|
||||
const nextG = generateValidG(nextTest.N);
|
||||
worker?.postMessage({ g: nextG, N: nextTest.N, difficulty: nextTest.difficulty });
|
||||
} else {
|
||||
// 所有测试完毕
|
||||
isBenchmarking = false;
|
||||
worker?.terminate();
|
||||
worker = null;
|
||||
currentN = null;
|
||||
currentDifficulty = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 开始第一个测试
|
||||
worker.postMessage({ g, N, difficulty });
|
||||
}
|
||||
|
||||
function getAccumulatedTime() {
|
||||
return benchmarkResults.reduce((acc, result) => acc + result.time, 0);
|
||||
}
|
||||
|
||||
function getAccumulatedDifficulty() {
|
||||
return benchmarkResults.reduce((acc, result) => acc + Number(result.difficulty), 0);
|
||||
}
|
||||
|
||||
function getSpeed() {
|
||||
return (getAccumulatedDifficulty() / getAccumulatedTime()) * 1000;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="md:bg-zinc-50 md:dark:bg-zinc-800 p-6 rounded-md md:border dark:border-zinc-700 mb-6 mt-8 md:w-2/3 lg:w-1/2 xl:w-[37%] md:mx-auto"
|
||||
>
|
||||
<h2 class="text-xl font-bold mb-4 text-zinc-800 dark:text-zinc-200">VDF Benchmark</h2>
|
||||
|
||||
{#if !isBenchmarking}
|
||||
<button
|
||||
class="bg-blue-500 hover:bg-blue-600 duration-100 text-white font-bold py-2 px-4 rounded"
|
||||
on:click={startBenchmark}
|
||||
>
|
||||
Start Benchmark
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if isBenchmarking}
|
||||
<p class="mb-8 text-zinc-700 dark:text-zinc-300">
|
||||
Benchmarking in progress... ({currentTestIndex + 1}/{testCombinations.length})
|
||||
</p>
|
||||
{#if currentN !== null && currentDifficulty !== null}
|
||||
<p class="mb-2 text-zinc-700 dark:text-zinc-300">N Bits: {currentN.toString(2).length}</p>
|
||||
<p class="mb-2 text-zinc-700 dark:text-zinc-300">Difficulty: {currentDifficulty}</p>
|
||||
<div class="w-full bg-zinc-300 dark:bg-neutral-700 rounded-full h-1 relative overflow-hidden">
|
||||
<div
|
||||
class="bg-black dark:bg-white h-full rounded-full relative"
|
||||
style="width: {currentProgress}%"
|
||||
></div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if benchmarkResults.length > 0 && !isBenchmarking}
|
||||
<h3 class="text-lg font-bold mt-4 mb-2 text-zinc-800 dark:text-zinc-200">Benchmark Results</h3>
|
||||
<p class="mb-4 text-zinc-700 dark:text-zinc-300 text-sm">
|
||||
<b>Summary:</b>
|
||||
{getAccumulatedDifficulty()}
|
||||
calculations done in {getAccumulatedTime().toFixed(1)}ms,
|
||||
speed: {getSpeed().toFixed(2)} op/s
|
||||
</p>
|
||||
<table class="w-full text-sm text-left rtl:text-right text-zinc-500 dark:text-zinc-400">
|
||||
<thead
|
||||
class="text-xs text-zinc-700 uppercase dark:text-zinc-400 border-b border-zinc-400 dark:border-zinc-500"
|
||||
>
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3">Time (ms)</th>
|
||||
<th scope="col" class="px-6 py-3">N (bits)</th>
|
||||
<th scope="col" class="px-6 py-3">T (log10)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each benchmarkResults as result}
|
||||
<tr class="border-b dark:border-zinc-700 border-zinc-200">
|
||||
<td class="px-6 py-4 font-medium text-zinc-900 whitespace-nowrap dark:text-white"
|
||||
>{result.time.toFixed(2)}</td
|
||||
>
|
||||
<td class="px-6 py-4 font-medium text-zinc-900 whitespace-nowrap dark:text-white"
|
||||
>{result.N.toString(2).length}</td
|
||||
>
|
||||
<td class="px-6 py-4 font-medium text-zinc-900 whitespace-nowrap dark:text-white"
|
||||
>{Math.log10(Number(result.difficulty)).toFixed(2)}</td
|
||||
>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
13
packages/frontend/src/const.ts
Normal file
13
packages/frontend/src/const.ts
Normal file
@ -0,0 +1,13 @@
|
||||
const N_1024 = BigInt("129023318876534346704360951712586568674758913224876821534686030409476129469193481910786173836188085930974906857867802234113909470848523288588793477904039083513378341278558405407018889387577114155572311708428733260891448259786041525189132461448841652472631435226032063278124857443496954605482776113964107326943")
|
||||
|
||||
const N_2048 = BigInt("23987552118069940970878653610463005981599204778388399885550631951871084945075866571231062435627294546200946516668493107358732376187241747090707087544153108117326163500579370560400058549184722138636116585329496684877258304519458316233517215780035360354808658620079068489084797380781488445517430961701007542207001544091884001098497324624368085682074645221148086075871342544591022944384890014176612259729018968864426602901247715051556212559854689574013699665035317257438297910516976812428036717668766321871780963854649899276251822244719887233041422346429752896925499321431273560130952088238625622570366815755926694833109")
|
||||
|
||||
const N_1792 = BigInt("23987552118069940970878653610463005981599204778388399885550631951871084945075866571231062435627294546200946516668493107358732376187241747090707087544153108117326163500579370560400058549184722138636116585329496684877258304519458316233517215780035360354808658620079068489084797380781488445517430961701007542207001544091884001098497324624368085682074645221148086075871342544591022944384890014176612259729018968864426602901247715051556212559854689574013699665035317257438297910516976812428036717668766321871780963854649899276251822244719887233041422346429752896925499321431273560130952088238625622570366815755926694833109")
|
||||
|
||||
const N_1536 = BigInt("1694330250214463438908848400950857073137355630337290254958754184668036770489801447652464038218330711288158361242955860326168191830448553710492926795708495297280933502917598985378231124113971732841791156356676046934277122699383776036675381503510992810963611269045078440132744168908318454891211962146563551929591147663448816841024591820348784855441153716551049843185172472891407933214238000452095646085222944171689449292644270516031799660928056315886939284985905227")
|
||||
|
||||
const N_3072 = BigInt("4432919939296042464443862503456460073874727648022810391370558006281079088795179408238989283371442564716849343712703672836423961818025813387453469700639513190304802553045342607888612037304066433501317127429264242784608682213025490491212489901736408833027611579294436675682774458141490718959615677971745638214649336218217578937534746160749039668886450447773018369168258067682196337978245372237157696236362344796867228581553446331915147012787367438751646936429739232247148712001806846526947508445039707404287951727838234648917450736371192435665040644040487427986702098273581288935278964444790007953559851323281510927332862225214878776790605026472021669614552481167977412450477230442015077669503312683966631454347169703030544483487968842349634064181183599641180349414682042575010303056241481622837185325228233789954078775053744988023738762706404546546146837242590884760044438874357295029411988267287001033032827035809135092270843")
|
||||
|
||||
const N_4096 = BigInt("703671044356805218391078271512201582198770553281951369783674142891088501340774249238173262580562112786670043634665390581120113644316651934154746357220932310140476300088580654571796404198410555061275065442553506658401183560336140989074165998202690496991174269748740565700402715364422506782445179963440819952745241176450402011121226863984008975377353558155910994380700267903933205531681076494639818328879475919332604951949178075254600102192323286738973253864238076198710173840170988339024438220034106150475640983877458155141500313471699516670799821379238743709125064098477109094533426340852518505385314780319279862586851512004686798362431227795743253799490998475141728082088984359237540124375439664236138519644100625154580910233437864328111620708697941949936338367445851449766581651338876219676721272448769082914348242483068204896479076062102236087066428603930888978596966798402915747531679758905013008059396214343112694563043918465373870648649652122703709658068801764236979191262744515840224548957285182453209028157886219424802426566456408109642062498413592155064289314088837031184200671561102160059065729282902863248815224399131391716503171191977463328439766546574118092303414702384104112719959325482439604572518549918705623086363111")
|
||||
|
||||
export const N_ARRAY = [N_1024, N_1536, N_1792, N_2048, N_3072, N_4096];
|
@ -31,7 +31,8 @@
|
||||
|
||||
## 技术架构
|
||||
|
||||
参见[CVSA文档](https://cvsa.gitbook.io/)。
|
||||
参见[CVSA文档](https://docs.projectcvsa.com/)。
|
||||
|
||||
|
||||
## 开放许可
|
||||
|
||||
|
@ -7,7 +7,7 @@ import "../styles/global.css";
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CVSA 前端</title>
|
||||
<title>中V档案馆</title>
|
||||
</head>
|
||||
<body class="dark:bg-zinc-900 dark:text-zinc-100">
|
||||
<slot />
|
||||
|
8
packages/frontend/src/pages/test-vdf.astro
Normal file
8
packages/frontend/src/pages/test-vdf.astro
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
import VDFtester from "@components/VDFtester.svelte";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<VDFtester client:load />
|
||||
</Layout>
|
Loading…
Reference in New Issue
Block a user