Compare commits

...

28 Commits

Author SHA1 Message Date
757cbbab7e
merge: branch 'main' into gitbook 2025-04-05 06:15:44 +08:00
b4205049cb
merge: branch 'main' into lab/pred 2025-04-05 06:13:23 +08:00
509c10ded0
merge: branch 'feat/frontend' into feat/backend 2025-04-05 06:11:45 +08:00
99c7a34833
add: speed in benchmark result 2025-04-02 23:37:43 +08:00
d808f36c58
improve: number display in benchmark result 2025-04-02 23:32:26 +08:00
984484cc3f
improve: eliminate UI flickering between tests 2025-04-02 23:30:49 +08:00
46578db3e6
update: lower the difficulty of benchmark 2025-04-02 23:24:52 +08:00
704b5106c6
fix: difficulty too low in benchmark 2025-04-02 23:15:45 +08:00
2e702f23de
improve: UI of showing benchmark result 2025-04-02 23:13:53 +08:00
cb5e24e542
improve: UI of VDF benchmark 2025-04-02 22:55:55 +08:00
b1e071930c
fix: incorrect build config 2025-04-02 22:48:19 +08:00
c3f13cc6e3
update: VDF benchmark 2025-04-02 22:14:32 +08:00
dd829b203d
test: argon2id tester 2025-04-02 21:28:23 +08:00
41f8b42f1c
improve: the registration API 2025-04-01 10:30:44 +08:00
fba56106cc
add: user registration API 2025-03-31 20:07:58 +08:00
8d4edd43bf
update: link in about content 2025-03-31 06:39:52 +08:00
2aead46b51
update: title for frontend page 2025-03-31 06:36:36 +08:00
79af12e526
update: README 2025-03-31 06:35:44 +08:00
cfd4fc3d21
fix: incorrect API used in validation 2025-03-31 06:24:54 +08:00
b53366dbab
doc: GitBook - No subject 2025-03-30 22:20:54 +00:00
7a46f31d7f
version: backend/0.2.3 2025-03-31 06:09:49 +08:00
cf33c4922d
fix: incorrect parsing for id in API route /video/:id/snapshots 2025-03-31 06:09:28 +08:00
aa75fdd63e
fix: incorrect schema check for numbers 2025-03-31 06:05:59 +08:00
9c0783c607
fix: incorrect type check for number in request params 2025-03-31 06:02:07 +08:00
81847cc090
merge: branch 'main' into lab/pred 2025-03-31 05:38:17 +08:00
0d18c921cb
merge: branch 'main' into lab/pred 2025-03-29 18:59:09 +08:00
ba6b8bd5b3
temp: try to modify some features for the pred model 2025-03-20 00:53:59 +08:00
2ed909268e
add: a short-term feature in pred model 2025-03-16 15:10:32 +08:00
24 changed files with 502 additions and 60 deletions

View File

@ -2,6 +2,11 @@
「中V档案馆」是一个旨在收录与展示「中文歌声合成作品」及有关信息的网站。 「中V档案馆」是一个旨在收录与展示「中文歌声合成作品」及有关信息的网站。
## 新闻 - 测试版本上线
目前中V档案馆上线了用于测试的前端网页和API接口它们分别位于[projectcvsa.com](https://projectcvsa.com)和[api.projectcvsa.com](https://api.projectcvsa.com)。
API调用方法请参见[接口文档](https://docs.projectcvsa.com/api-doc/)。
## 创建背景与关联工作 ## 创建背景与关联工作
纵观整个互联网对于「中文歌声合成」或「中文虚拟歌手」常简称为中V或VC相关信息进行较为系统、全面地整理收集的主要有以下几个网站 纵观整个互联网对于「中文歌声合成」或「中文虚拟歌手」常简称为中V或VC相关信息进行较为系统、全面地整理收集的主要有以下几个网站
@ -31,7 +36,7 @@
## 技术架构 ## 技术架构
参见[CVSA文档](https://cvsa.gitbook.io/)。 参见[CVSA文档](https://docs.projectcvsa.com/)。
## 开放许可 ## 开放许可

View File

@ -0,0 +1,106 @@
openapi: 3.0.0
info:
title: CVSA API
version: v1
servers:
- url: https://api.projectcvsa.com
paths:
/video/{id}/snapshots:
get:
summary: 获取视频快照列表
description: 根据视频 ID 获取视频的快照列表。视频 ID 可以是以 "av" 开头的数字,以 "BV" 开头的 12 位字母数字,或者一个正整数。
parameters:
- in: path
name: id
required: true
schema:
type: string
description: "视频 ID (如: av78977256, BV1KJ411C7CW, 78977256)"
- in: query
name: ps
schema:
type: integer
minimum: 1
description: 每页返回的快照数量 (pageSize),默认为 1000。
- in: query
name: pn
schema:
type: integer
minimum: 1
description: 页码 (pageNumber)用于分页查询。offset 与 pn 只能选择一个。
- in: query
name: offset
schema:
type: integer
minimum: 1
description: 偏移量用于基于偏移量的查询。offset 与 pn 只能选择一个。
- in: query
name: reverse
schema:
type: boolean
description: 是否反向排序(从旧到新),默认为 false。
responses:
'200':
description: 成功获取快照列表
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: integer
description: 快照 ID
aid:
type: integer
description: 视频的 av 号
views:
type: integer
description: 视频播放量
coins:
type: integer
description: 视频投币数
likes:
type: integer
description: 视频点赞数
favorites:
type: integer
description: 视频收藏数
shares:
type: integer
description: 视频分享数
danmakus:
type: integer
description: 视频弹幕数
replies:
type: integer
description: 视频评论数
'400':
description: 无效的查询参数
content:
application/json:
schema:
type: object
properties:
message:
type: string
description: 错误消息
errors:
type: object
description: 详细的错误信息
'500':
description: 服务器内部错误
content:
application/json:
schema:
type: object
properties:
message:
type: string
description: 错误消息
error:
type: object
description: 详细的错误信息

View File

@ -1,11 +1,11 @@
# Table of contents # Table of contents
- [欢迎](README.md) * [欢迎](README.md)
## 关于 <a href="#about" id="about"></a> ## 关于 <a href="#about" id="about"></a>
- [关于本项目](about/this-project.md) * [关于本项目](about/this-project.md)
- [收录范围](about/scope-of-inclusion.md) * [收录范围](about/scope-of-inclusion.md)
## 技术架构 <a href="#architecture" id="architecture"></a> ## 技术架构 <a href="#architecture" id="architecture"></a>
@ -18,5 +18,5 @@
## API 文档 <a href="#api-doc" id="api-doc"></a> ## API 文档 <a href="#api-doc" id="api-doc"></a>
- [目录](api-doc/catalog.md) * [目录](api-doc/catalog.md)
- [歌曲](api-doc/songs.md) * [视频快照](api-doc/video-snapshot.md)

View File

@ -1,3 +1,4 @@
# 目录 # 目录
- [歌曲](songs.md) * [视频快照](video-snapshot.md)

View File

@ -1,3 +0,0 @@
# 歌曲
暂未实现。

View File

@ -0,0 +1,6 @@
# 视频快照
{% openapi src="../.gitbook/assets/1.yaml" path="/video/{id}/snapshots" method="get" %}
[1.yaml](../.gitbook/assets/1.yaml)
{% endopenapi %}

View File

@ -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.valid_series = [s for s in self.series_dict.values() if len(s['abs_time']) > 1]
self.term = term self.term = term
# Set time window based on 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 MINUTE = 60
HOUR = 3600 HOUR = 3600
DAY = 24 * HOUR DAY = 24 * HOUR
@ -37,6 +37,7 @@ class VideoPlayDataset(Dataset):
] ]
else: else:
self.feature_windows = [ self.feature_windows = [
#( 5 * MINUTE, 0 * MINUTE),
( 15 * MINUTE, 0 * MINUTE), ( 15 * MINUTE, 0 * MINUTE),
( 40 * MINUTE, 0 * MINUTE), ( 40 * MINUTE, 0 * MINUTE),
( 1 * HOUR, 0 * HOUR), ( 1 * HOUR, 0 * HOUR),
@ -45,7 +46,7 @@ class VideoPlayDataset(Dataset):
( 3 * HOUR, 0 * HOUR), ( 3 * HOUR, 0 * HOUR),
#( 6 * HOUR, 3 * HOUR), #( 6 * HOUR, 3 * HOUR),
( 6 * HOUR, 0 * HOUR), ( 6 * HOUR, 0 * HOUR),
(18 * HOUR, 12 * HOUR), #(18 * HOUR, 12 * HOUR),
#( 1 * DAY, 6 * HOUR), #( 1 * DAY, 6 * HOUR),
( 1 * DAY, 0 * DAY), ( 1 * DAY, 0 * DAY),
#( 2 * DAY, 1 * DAY), #( 2 * DAY, 1 * DAY),

View File

@ -4,20 +4,20 @@ from model import CompactPredictor
import torch import torch
def main(): def main():
model = CompactPredictor(10).to('cpu', dtype=torch.float32) model = CompactPredictor(15).to('cpu', dtype=torch.float32)
model.load_state_dict(torch.load('./pred/checkpoints/long_term.pt')) model.load_state_dict(torch.load('./pred/checkpoints/model_20250320_0045.pt'))
model.eval() model.eval()
# inference # inference
initial = 997029 initial = 999704
last = initial last = initial
start_time = '2025-03-17 00:13:17' start_time = '2025-03-19 22:00:42'
for i in range(1, 120): for i in range(1, 48):
hour = i / 0.5 hour = i / 6
sec = hour * 3600 sec = hour * 3600
time_d = np.log2(sec) time_d = np.log2(sec)
data = [time_d, np.log2(initial+1), # time_delta, current_views data = [time_d, np.log2(initial+1), # time_delta, current_views
6.111542, 8.404707, 10.071566, 11.55888, 12.457823,# grows_feat 4.857981, 6.29067, 6.869476, 6.58392, 6.523051, 8.242355, 8.841574, 10.203909, 11.449314, 12.659556, # grows_feat
0.009225, 0.001318, 28.001814# time_feat 0.916956, 0.416708, 28.003162 # time_feat
] ]
np_arr = np.array([data]) np_arr = np.array([data])
tensor = torch.from_numpy(np_arr).to('cpu', dtype=torch.float32) tensor = torch.from_numpy(np_arr).to('cpu', dtype=torch.float32)
@ -25,7 +25,7 @@ def main():
num = output.detach().numpy()[0][0] num = output.detach().numpy()[0][0]
views_pred = int(np.exp2(num)) + initial views_pred = int(np.exp2(num)) + initial
current_time = datetime.datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=hour) 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 last = views_pred
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -38,7 +38,7 @@ def train(model, dataloader, device, epochs=100):
scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=1e-3, scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=1e-3,
total_steps=len(dataloader)*30) total_steps=len(dataloader)*30)
# Huber loss # Huber loss
criterion = asymmetricHuberLoss(delta=1.0, beta=2.1) criterion = asymmetricHuberLoss(delta=1.0, beta=2.2)
model.train() model.train()
global_step = 0 global_step = 0
@ -100,7 +100,7 @@ if __name__ == "__main__":
device = 'mps' device = 'mps'
# Initialize dataset and model # 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) dataloader = DataLoader(dataset, batch_size=128, shuffle=True, collate_fn=collate_fn)
# Get feature dimension # Get feature dimension

View File

@ -1,10 +1,12 @@
import { type Client, Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts"; import { type Client, Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
import { postgresConfig } from "@core/db/pgConfig.ts"; import { postgresConfig, postgresConfigCred } from "@core/db/pgConfig.ts";
import { createMiddleware } from "hono/factory"; import { createMiddleware } from "hono/factory";
const pool = new Pool(postgresConfig, 4); const pool = new Pool(postgresConfig, 4);
const poolCred = new Pool(postgresConfigCred, 2);
export const db = pool; export const db = pool;
export const dbCred = poolCred;
export const dbMiddleware = createMiddleware(async (c, next) => { export const dbMiddleware = createMiddleware(async (c, next) => {
const connection = await pool.connect(); const connection = await pool.connect();
@ -13,8 +15,16 @@ export const dbMiddleware = createMiddleware(async (c, next) => {
connection.release(); connection.release();
}); });
export const dbCredMiddleware = createMiddleware(async (c, next) => {
const connection = await poolCred.connect();
c.set("dbCred", connection);
await next();
connection.release();
})
declare module "hono" { declare module "hono" {
interface ContextVariableMap { interface ContextVariableMap {
db: Client; db: Client;
dbCred: Client;
} }
} }

View File

@ -1,6 +1,7 @@
{ {
"name": "@cvsa/backend", "name": "@cvsa/backend",
"imports": { "imports": {
"@rabbit-company/argon2id": "jsr:@rabbit-company/argon2id@^2.1.0",
"hono": "jsr:@hono/hono@^4.7.5", "hono": "jsr:@hono/hono@^4.7.5",
"zod": "npm:zod", "zod": "npm:zod",
"yup": "npm:yup" "yup": "npm:yup"

View File

@ -1,15 +1,18 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { dbMiddleware } from "./database.ts"; import { dbCredMiddleware, dbMiddleware } from "./database.ts";
import { rootHandler } from "./root.ts"; import { rootHandler } from "./root.ts";
import { getSnapshotsHanlder } from "./snapshots.ts"; import { getSnapshotsHanlder } from "./snapshots.ts";
import { registerHandler } from "./register.ts";
export const app = new Hono(); export const app = new Hono();
app.use('/video/*', dbMiddleware); app.use('/video/*', dbMiddleware);
app.use('/user', dbCredMiddleware);
app.get("/", ...rootHandler); app.get("/", ...rootHandler);
app.get('/video/:id/snapshots', ...getSnapshotsHanlder); app.get('/video/:id/snapshots', ...getSnapshotsHanlder);
app.post('/user', ...registerHandler);
const fetch = app.fetch; const fetch = app.fetch;
@ -17,4 +20,4 @@ export default {
fetch, fetch,
} satisfies Deno.ServeDefaultExport; } satisfies Deno.ServeDefaultExport;
export const VERSION = "0.2.0"; export const VERSION = "0.3.0";

View File

@ -0,0 +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";
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 = `
SELECT * FROM users WHERE username = $1
`;
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({
message: `User "${username}" already exists.`,
}, 400);
}
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);
}
}
});

View File

@ -6,18 +6,19 @@ import type { VideoSnapshotType } from "@core/db/schema.d.ts";
import { boolean, mixed, number, object, ValidationError } from "yup"; import { boolean, mixed, number, object, ValidationError } from "yup";
const SnapshotQueryParamsSchema = object({ const SnapshotQueryParamsSchema = object({
ps: number().optional().positive(), ps: number().integer().optional().positive(),
pn: number().optional().positive(), pn: number().integer().optional().positive(),
offset: number().optional().positive(), offset: number().integer().optional().positive(),
reverse: boolean().optional(), reverse: boolean().optional(),
}); });
const idSchema = mixed().test( const idSchema = mixed().test(
"is-valid-id", "is-valid-id",
'id must be a string starting with "av" followed by digits, or "BV" followed by 10 alphanumeric characters, or a positive integer', 'id must be a string starting with "av" followed by digits, or "BV" followed by 10 alphanumeric characters, or a positive integer',
(value) => { async (value) => {
if (typeof value === "number") { if (value && await number().integer().isValid(value)) {
return Number.isInteger(value) && value > 0; const v = parseInt(value as string);
return Number.isInteger(v) && v > 0;
} }
if (typeof value === "string") { if (typeof value === "string") {
@ -42,9 +43,12 @@ export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
try { try {
const idParam = await idSchema.validate(c.req.param("id")); const idParam = await idSchema.validate(c.req.param("id"));
let videoId: number | string = idParam as string | number; let videoId: string | number = idParam as string;
if (typeof videoId === "string" && videoId.startsWith("av")) { if (videoId.startsWith("av")) {
videoId = videoId.slice(2); videoId = parseInt(videoId.slice(2));
}
else if (await number().isValid(videoId)) {
videoId = parseInt(videoId);
} }
const queryParams = await SnapshotQueryParamsSchema.validate(c.req.query()); const queryParams = await SnapshotQueryParamsSchema.validate(c.req.query());
const { ps, pn, offset, reverse = false } = queryParams; const { ps, pn, offset, reverse = false } = queryParams;

View File

@ -19,6 +19,6 @@ export default defineConfig({
allow: [".", "../../"], allow: [".", "../../"],
}, },
}, },
plugins: [tsconfigPaths()], plugins: [tsconfigPaths()]
}, },
}); });

View File

@ -1 +1 @@
export const VERSION = "1.2.6"; export const VERSION = "1.2.7";

View File

@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@astrojs/tailwind": "^6.0.2", "@astrojs/tailwind": "^6.0.2",
"argon2id": "^1.0.1",
"astro": "^5.5.5", "astro": "^5.5.5",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"pg": "^8.11.11", "pg": "^8.11.11",
@ -18,6 +19,7 @@
"vite-tsconfig-paths": "^5.1.4" "vite-tsconfig-paths": "^5.1.4"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-wasm": "^6.2.2",
"@types/pg": "^8.11.11" "@types/pg": "^8.11.11"
} }
} }

Binary file not shown.

Binary file not shown.

View 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>

View 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];

View File

@ -31,7 +31,8 @@
## 技术架构 ## 技术架构
参见[CVSA文档](https://cvsa.gitbook.io/)。 参见[CVSA文档](https://docs.projectcvsa.com/)。
## 开放许可 ## 开放许可

View File

@ -7,7 +7,7 @@ import "../styles/global.css";
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CVSA 前端</title> <title>中V档案馆</title>
</head> </head>
<body class="dark:bg-zinc-900 dark:text-zinc-100"> <body class="dark:bg-zinc-900 dark:text-zinc-100">
<slot /> <slot />

View File

@ -0,0 +1,8 @@
---
import VDFtester from "@components/VDFtester.svelte";
import Layout from "@layouts/Layout.astro";
---
<Layout>
<VDFtester client:load />
</Layout>