sa2kit 1.6.91 → 1.6.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client-BlkUL2To.d.ts +26 -0
- package/dist/client-DpMIhrlS.d.mts +26 -0
- package/dist/index-C7yh6b5Q.d.mts +17 -0
- package/dist/index-CDapUIT5.d.mts +51 -0
- package/dist/index-Cv9jlnNz.d.ts +17 -0
- package/dist/index-D3UbkUai.d.ts +51 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1337 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1306 -61
- package/dist/index.mjs.map +1 -1
- package/dist/mikuContest/index.d.mts +13 -0
- package/dist/mikuContest/index.d.ts +13 -0
- package/dist/mikuContest/index.js +1310 -0
- package/dist/mikuContest/index.js.map +1 -0
- package/dist/mikuContest/index.mjs +1253 -0
- package/dist/mikuContest/index.mjs.map +1 -0
- package/dist/mikuContest/logic/index.d.mts +32 -0
- package/dist/mikuContest/logic/index.d.ts +32 -0
- package/dist/mikuContest/logic/index.js +511 -0
- package/dist/mikuContest/logic/index.js.map +1 -0
- package/dist/mikuContest/logic/index.mjs +483 -0
- package/dist/mikuContest/logic/index.mjs.map +1 -0
- package/dist/mikuContest/routes/index.d.mts +80 -0
- package/dist/mikuContest/routes/index.d.ts +80 -0
- package/dist/mikuContest/routes/index.js +821 -0
- package/dist/mikuContest/routes/index.js.map +1 -0
- package/dist/mikuContest/routes/index.mjs +791 -0
- package/dist/mikuContest/routes/index.mjs.map +1 -0
- package/dist/mikuContest/server/index.d.mts +766 -0
- package/dist/mikuContest/server/index.d.ts +766 -0
- package/dist/mikuContest/server/index.js +705 -0
- package/dist/mikuContest/server/index.js.map +1 -0
- package/dist/mikuContest/server/index.mjs +672 -0
- package/dist/mikuContest/server/index.mjs.map +1 -0
- package/dist/mikuContest/service/index.d.mts +30 -0
- package/dist/mikuContest/service/index.d.ts +30 -0
- package/dist/mikuContest/service/index.js +139 -0
- package/dist/mikuContest/service/index.js.map +1 -0
- package/dist/mikuContest/service/index.mjs +135 -0
- package/dist/mikuContest/service/index.mjs.map +1 -0
- package/dist/mikuContest/types/index.d.mts +179 -0
- package/dist/mikuContest/types/index.d.ts +179 -0
- package/dist/mikuContest/types/index.js +4 -0
- package/dist/mikuContest/types/index.js.map +1 -0
- package/dist/mikuContest/types/index.mjs +3 -0
- package/dist/mikuContest/types/index.mjs.map +1 -0
- package/dist/mikuContest/ui/miniapp/index.d.mts +3 -0
- package/dist/mikuContest/ui/miniapp/index.d.ts +3 -0
- package/dist/mikuContest/ui/miniapp/index.js +566 -0
- package/dist/mikuContest/ui/miniapp/index.js.map +1 -0
- package/dist/mikuContest/ui/miniapp/index.mjs +540 -0
- package/dist/mikuContest/ui/miniapp/index.mjs.map +1 -0
- package/dist/mikuContest/ui/web/index.d.mts +4 -0
- package/dist/mikuContest/ui/web/index.d.ts +4 -0
- package/dist/mikuContest/ui/web/index.js +353 -0
- package/dist/mikuContest/ui/web/index.js.map +1 -0
- package/dist/mikuContest/ui/web/index.mjs +343 -0
- package/dist/mikuContest/ui/web/index.mjs.map +1 -0
- package/dist/service-D7DM1wW-.d.ts +38 -0
- package/dist/service-DPr2rlvH.d.mts +38 -0
- package/dist/types-BS7Xz09b.d.mts +14 -0
- package/dist/types-k4koMp4m.d.ts +14 -0
- package/package.json +41 -1
package/dist/index.mjs
CHANGED
|
@@ -11,19 +11,26 @@ import Link from 'next/link';
|
|
|
11
11
|
import { useSensors, useSensor, PointerSensor, TouchSensor, KeyboardSensor, DndContext, closestCenter } from '@dnd-kit/core';
|
|
12
12
|
import { useSortable, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, arrayMove } from '@dnd-kit/sortable';
|
|
13
13
|
import { CSS } from '@dnd-kit/utilities';
|
|
14
|
-
import { pgEnum, pgTable, boolean, timestamp, jsonb, text, uniqueIndex, foreignKey } from 'drizzle-orm/pg-core';
|
|
15
|
-
import { sql, relations } from 'drizzle-orm';
|
|
14
|
+
import { pgEnum, pgTable, boolean, timestamp, jsonb, text, uniqueIndex, foreignKey, integer } from 'drizzle-orm/pg-core';
|
|
15
|
+
import { sql, relations, eq } from 'drizzle-orm';
|
|
16
16
|
import { randomUUID } from 'crypto';
|
|
17
17
|
import 'bcryptjs';
|
|
18
18
|
import 'jsonwebtoken';
|
|
19
19
|
import * as THREE2 from 'three';
|
|
20
|
+
import * as XLSX from 'xlsx';
|
|
21
|
+
import { NextResponse } from 'next/server';
|
|
20
22
|
|
|
23
|
+
var __defProp = Object.defineProperty;
|
|
21
24
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
22
25
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
23
26
|
}) : x)(function(x) {
|
|
24
27
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
25
28
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
26
29
|
});
|
|
30
|
+
var __export = (target, all) => {
|
|
31
|
+
for (var name in all)
|
|
32
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
33
|
+
};
|
|
27
34
|
|
|
28
35
|
// src/logger/types.ts
|
|
29
36
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
@@ -51,10 +58,10 @@ var ConsoleLoggerAdapter = class {
|
|
|
51
58
|
};
|
|
52
59
|
}
|
|
53
60
|
log(entry) {
|
|
54
|
-
const { level, message, timestamp:
|
|
61
|
+
const { level, message, timestamp: timestamp6, data, context, error } = entry;
|
|
55
62
|
let logMessage = "";
|
|
56
|
-
if (
|
|
57
|
-
logMessage += "[" + this.formatTimestamp(
|
|
63
|
+
if (timestamp6) {
|
|
64
|
+
logMessage += "[" + this.formatTimestamp(timestamp6) + "] ";
|
|
58
65
|
}
|
|
59
66
|
const levelName = this.getLevelName(level);
|
|
60
67
|
logMessage += levelName + ": ";
|
|
@@ -547,7 +554,7 @@ var useSentimentAnalysis = (options = {}) => {
|
|
|
547
554
|
result: null
|
|
548
555
|
});
|
|
549
556
|
const pipelineRef = useRef(null);
|
|
550
|
-
const analyze = useCallback(async (
|
|
557
|
+
const analyze = useCallback(async (text6) => {
|
|
551
558
|
setState((prev) => ({
|
|
552
559
|
...prev,
|
|
553
560
|
isProcessing: true,
|
|
@@ -594,12 +601,12 @@ var useSentimentAnalysis = (options = {}) => {
|
|
|
594
601
|
pipelineRef.current = await pipeline("sentiment-analysis", options.model || defaultModel);
|
|
595
602
|
}
|
|
596
603
|
setState((prev) => ({ ...prev, status: "analyzing" }));
|
|
597
|
-
const output = await pipelineRef.current(
|
|
604
|
+
const output = await pipelineRef.current(text6);
|
|
598
605
|
const resultData = output[0];
|
|
599
606
|
const label = resultData.label.toLowerCase();
|
|
600
607
|
let sentiment = "neutral";
|
|
601
608
|
const negativeKeywords = ["\u7D2F", "\u60E8", "\u7EDD\u671B", "\u96BE\u53D7", "\u4F24\u5FC3", "\u5DEE", "\u574F", "\u7CDF", "\u4E0D\u884C"];
|
|
602
|
-
const hasNegativeKeyword = negativeKeywords.some((k) =>
|
|
609
|
+
const hasNegativeKeyword = negativeKeywords.some((k) => text6.includes(k));
|
|
603
610
|
if (label.includes("positive") && !hasNegativeKeyword) {
|
|
604
611
|
sentiment = "positive";
|
|
605
612
|
} else if (label.includes("negative") || label.includes("0") || hasNegativeKeyword) {
|
|
@@ -631,12 +638,12 @@ var SentimentAnalyzer = ({
|
|
|
631
638
|
className = "",
|
|
632
639
|
placeholder = "\u8F93\u5165\u4E00\u6BB5\u4E2D\u6587\u6216\u82F1\u6587\uFF0C\u5206\u6790\u5176\u60C5\u611F\u503E\u5411..."
|
|
633
640
|
}) => {
|
|
634
|
-
const [
|
|
641
|
+
const [text6, setText] = useState("");
|
|
635
642
|
const { analyze, isProcessing, status, result, error } = useSentimentAnalysis();
|
|
636
643
|
const handleAnalyze = async () => {
|
|
637
|
-
if (!
|
|
644
|
+
if (!text6.trim() || isProcessing) return;
|
|
638
645
|
try {
|
|
639
|
-
const res = await analyze(
|
|
646
|
+
const res = await analyze(text6);
|
|
640
647
|
onResult?.(res);
|
|
641
648
|
} catch (err) {
|
|
642
649
|
console.error("Sentiment Analysis Error:", err);
|
|
@@ -667,7 +674,7 @@ var SentimentAnalyzer = ({
|
|
|
667
674
|
return /* @__PURE__ */ React69__default.createElement("div", { className: clsx("p-6 border rounded-xl bg-white dark:bg-gray-800 shadow-sm", className) }, /* @__PURE__ */ React69__default.createElement("div", { className: "flex items-center gap-2 mb-4 text-gray-700 dark:text-gray-300 font-medium" }, /* @__PURE__ */ React69__default.createElement(MessageSquare, { size: 20 }), /* @__PURE__ */ React69__default.createElement("span", null, "\u6587\u672C\u60C5\u611F\u5206\u6790")), /* @__PURE__ */ React69__default.createElement("div", { className: "relative" }, /* @__PURE__ */ React69__default.createElement(
|
|
668
675
|
"textarea",
|
|
669
676
|
{
|
|
670
|
-
value:
|
|
677
|
+
value: text6,
|
|
671
678
|
onChange: (e) => setText(e.target.value),
|
|
672
679
|
placeholder,
|
|
673
680
|
className: "w-full h-32 p-4 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all outline-none resize-none text-gray-800 dark:text-gray-200",
|
|
@@ -677,7 +684,7 @@ var SentimentAnalyzer = ({
|
|
|
677
684
|
"button",
|
|
678
685
|
{
|
|
679
686
|
onClick: handleAnalyze,
|
|
680
|
-
disabled: !
|
|
687
|
+
disabled: !text6.trim() || isProcessing,
|
|
681
688
|
className: "absolute bottom-3 right-3 p-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white rounded-md transition-colors shadow-sm"
|
|
682
689
|
},
|
|
683
690
|
isProcessing ? /* @__PURE__ */ React69__default.createElement(Loader2, { className: "animate-spin", size: 18 }) : /* @__PURE__ */ React69__default.createElement(Send, { size: 18 })
|
|
@@ -865,13 +872,13 @@ var requestJson = async (options) => {
|
|
|
865
872
|
body: body ? JSON.stringify(body) : void 0,
|
|
866
873
|
signal: controller?.signal
|
|
867
874
|
});
|
|
868
|
-
const
|
|
875
|
+
const text6 = await response.text();
|
|
869
876
|
let data = null;
|
|
870
|
-
if (
|
|
877
|
+
if (text6) {
|
|
871
878
|
try {
|
|
872
|
-
data = JSON.parse(
|
|
879
|
+
data = JSON.parse(text6);
|
|
873
880
|
} catch {
|
|
874
|
-
data =
|
|
881
|
+
data = text6;
|
|
875
882
|
}
|
|
876
883
|
}
|
|
877
884
|
if (!response.ok) {
|
|
@@ -1332,20 +1339,20 @@ var japaneseUtils = {
|
|
|
1332
1339
|
/**
|
|
1333
1340
|
* 提取文本中的汉字
|
|
1334
1341
|
*/
|
|
1335
|
-
extractKanji(
|
|
1336
|
-
return
|
|
1342
|
+
extractKanji(text6) {
|
|
1343
|
+
return text6.match(/[\u4E00-\u9FAF]/g) || [];
|
|
1337
1344
|
},
|
|
1338
1345
|
/**
|
|
1339
1346
|
* 提取文本中的假名
|
|
1340
1347
|
*/
|
|
1341
|
-
extractKana(
|
|
1342
|
-
return
|
|
1348
|
+
extractKana(text6) {
|
|
1349
|
+
return text6.match(/[\u3040-\u309F\u30A0-\u30FF]/g) || [];
|
|
1343
1350
|
},
|
|
1344
1351
|
/**
|
|
1345
1352
|
* 清理文本,移除特殊字符但保留日语字符
|
|
1346
1353
|
*/
|
|
1347
|
-
cleanText(
|
|
1348
|
-
return
|
|
1354
|
+
cleanText(text6) {
|
|
1355
|
+
return text6.replace(/[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\w\s]/g, "");
|
|
1349
1356
|
}
|
|
1350
1357
|
};
|
|
1351
1358
|
|
|
@@ -1434,11 +1441,11 @@ var fileUtils = {
|
|
|
1434
1441
|
* 生成唯一文件名
|
|
1435
1442
|
*/
|
|
1436
1443
|
generateUniqueFileName(originalName) {
|
|
1437
|
-
const
|
|
1444
|
+
const timestamp6 = Date.now();
|
|
1438
1445
|
const random = Math.random().toString(36).substring(2, 15);
|
|
1439
1446
|
const extension = this.getFileExtension(originalName);
|
|
1440
1447
|
const baseName = originalName.replace("." + extension, "");
|
|
1441
|
-
return extension ? baseName + "_" +
|
|
1448
|
+
return extension ? baseName + "_" + timestamp6 + "_" + random + "." + extension : baseName + "_" + timestamp6 + "_" + random;
|
|
1442
1449
|
},
|
|
1443
1450
|
/**
|
|
1444
1451
|
* 验证文件名是否有效
|
|
@@ -1508,28 +1515,28 @@ var stringUtils = {
|
|
|
1508
1515
|
/**
|
|
1509
1516
|
* 截断文本
|
|
1510
1517
|
*/
|
|
1511
|
-
truncate(
|
|
1512
|
-
if (
|
|
1513
|
-
return
|
|
1518
|
+
truncate(text6, length, suffix = "...") {
|
|
1519
|
+
if (text6.length <= length) return text6;
|
|
1520
|
+
return text6.substring(0, length - suffix.length) + suffix;
|
|
1514
1521
|
},
|
|
1515
1522
|
/**
|
|
1516
1523
|
* 首字母大写
|
|
1517
1524
|
*/
|
|
1518
|
-
capitalize(
|
|
1519
|
-
if (!
|
|
1520
|
-
return
|
|
1525
|
+
capitalize(text6) {
|
|
1526
|
+
if (!text6) return "";
|
|
1527
|
+
return text6.charAt(0).toUpperCase() + text6.slice(1).toLowerCase();
|
|
1521
1528
|
},
|
|
1522
1529
|
/**
|
|
1523
1530
|
* 驼峰转下划线
|
|
1524
1531
|
*/
|
|
1525
|
-
camelToSnake(
|
|
1526
|
-
return
|
|
1532
|
+
camelToSnake(text6) {
|
|
1533
|
+
return text6.replace(/[A-Z]/g, (letter) => "_" + letter.toLowerCase());
|
|
1527
1534
|
},
|
|
1528
1535
|
/**
|
|
1529
1536
|
* 下划线转驼峰
|
|
1530
1537
|
*/
|
|
1531
|
-
snakeToCamel(
|
|
1532
|
-
return
|
|
1538
|
+
snakeToCamel(text6) {
|
|
1539
|
+
return text6.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1533
1540
|
},
|
|
1534
1541
|
/**
|
|
1535
1542
|
* 生成随机字符串
|
|
@@ -5648,9 +5655,9 @@ var UserInfoBar = ({ apiClient }) => {
|
|
|
5648
5655
|
))));
|
|
5649
5656
|
};
|
|
5650
5657
|
function DanmakuPanel({ onSend }) {
|
|
5651
|
-
const [
|
|
5658
|
+
const [text6, setText] = useState("");
|
|
5652
5659
|
const emit = () => {
|
|
5653
|
-
const value =
|
|
5660
|
+
const value = text6.trim();
|
|
5654
5661
|
if (!value) {
|
|
5655
5662
|
return;
|
|
5656
5663
|
}
|
|
@@ -5661,7 +5668,7 @@ function DanmakuPanel({ onSend }) {
|
|
|
5661
5668
|
"input",
|
|
5662
5669
|
{
|
|
5663
5670
|
type: "text",
|
|
5664
|
-
value:
|
|
5671
|
+
value: text6,
|
|
5665
5672
|
onChange: (event) => setText(event.target.value),
|
|
5666
5673
|
onKeyDown: (event) => {
|
|
5667
5674
|
if (event.key === "Enter") {
|
|
@@ -5777,8 +5784,8 @@ function useDanmakuController(options) {
|
|
|
5777
5784
|
cursorRef.current += 1;
|
|
5778
5785
|
}, []);
|
|
5779
5786
|
const send = useCallback(
|
|
5780
|
-
(
|
|
5781
|
-
const trimmed =
|
|
5787
|
+
(text6, color, sendOptions) => {
|
|
5788
|
+
const trimmed = text6.trim();
|
|
5782
5789
|
if (!trimmed) {
|
|
5783
5790
|
return null;
|
|
5784
5791
|
}
|
|
@@ -5815,26 +5822,26 @@ function useDanmakuController(options) {
|
|
|
5815
5822
|
[addIncoming, items, removeItem, send]
|
|
5816
5823
|
);
|
|
5817
5824
|
}
|
|
5818
|
-
function parseCommand(
|
|
5819
|
-
if (
|
|
5820
|
-
return { launchKind: "miku", content:
|
|
5825
|
+
function parseCommand(text6) {
|
|
5826
|
+
if (text6.startsWith("/miku ")) {
|
|
5827
|
+
return { launchKind: "miku", content: text6.replace("/miku ", "").trim() };
|
|
5821
5828
|
}
|
|
5822
|
-
if (
|
|
5829
|
+
if (text6 === "/miku") {
|
|
5823
5830
|
return { launchKind: "miku", content: "MIKU!" };
|
|
5824
5831
|
}
|
|
5825
|
-
if (
|
|
5826
|
-
return { launchKind: "avatar", content:
|
|
5832
|
+
if (text6.startsWith("/avatar ")) {
|
|
5833
|
+
return { launchKind: "avatar", content: text6.replace("/avatar ", "").trim() };
|
|
5827
5834
|
}
|
|
5828
|
-
if (
|
|
5835
|
+
if (text6 === "/avatar") {
|
|
5829
5836
|
return { launchKind: "avatar", content: "Avatar Firework!" };
|
|
5830
5837
|
}
|
|
5831
|
-
if (
|
|
5832
|
-
return { launchKind: "normal", content:
|
|
5838
|
+
if (text6.startsWith("/normal ")) {
|
|
5839
|
+
return { launchKind: "normal", content: text6.replace("/normal ", "").trim() };
|
|
5833
5840
|
}
|
|
5834
|
-
if (
|
|
5841
|
+
if (text6 === "/normal") {
|
|
5835
5842
|
return { launchKind: "normal", content: "Fireworks!" };
|
|
5836
5843
|
}
|
|
5837
|
-
return { content:
|
|
5844
|
+
return { content: text6 };
|
|
5838
5845
|
}
|
|
5839
5846
|
function createCircularSpriteTexture() {
|
|
5840
5847
|
const size = 64;
|
|
@@ -6516,12 +6523,12 @@ var WebSocketTransport = class {
|
|
|
6516
6523
|
}
|
|
6517
6524
|
};
|
|
6518
6525
|
function parseServerMessage(raw) {
|
|
6519
|
-
const
|
|
6520
|
-
if (!
|
|
6526
|
+
const text6 = decodeMessage(raw);
|
|
6527
|
+
if (!text6) {
|
|
6521
6528
|
return null;
|
|
6522
6529
|
}
|
|
6523
6530
|
try {
|
|
6524
|
-
return JSON.parse(
|
|
6531
|
+
return JSON.parse(text6);
|
|
6525
6532
|
} catch {
|
|
6526
6533
|
return null;
|
|
6527
6534
|
}
|
|
@@ -6788,8 +6795,8 @@ function MikuFireworks3D({
|
|
|
6788
6795
|
}
|
|
6789
6796
|
launch(payload);
|
|
6790
6797
|
};
|
|
6791
|
-
const handleSendDanmaku = (
|
|
6792
|
-
const result = send(
|
|
6798
|
+
const handleSendDanmaku = (text6) => {
|
|
6799
|
+
const result = send(text6, void 0, {
|
|
6793
6800
|
optimistic: !realtimeEnabled
|
|
6794
6801
|
});
|
|
6795
6802
|
if (!result) {
|
|
@@ -6888,9 +6895,9 @@ function useScreenReceiver(options) {
|
|
|
6888
6895
|
const peerRef = useRef({ pendingCandidates: [] });
|
|
6889
6896
|
const videoRef = useRef(null);
|
|
6890
6897
|
const appendLog = useCallback(
|
|
6891
|
-
(
|
|
6898
|
+
(text6) => {
|
|
6892
6899
|
logIdRef.current += 1;
|
|
6893
|
-
setLogs((prev) => [...prev, { id: logIdRef.current, text:
|
|
6900
|
+
setLogs((prev) => [...prev, { id: logIdRef.current, text: text6 }].slice(-maxLogs));
|
|
6894
6901
|
},
|
|
6895
6902
|
[maxLogs]
|
|
6896
6903
|
);
|
|
@@ -7616,8 +7623,8 @@ var withRoundedClip = (ctx, left, top, width, height, radius, draw) => {
|
|
|
7616
7623
|
draw();
|
|
7617
7624
|
ctx.restore();
|
|
7618
7625
|
};
|
|
7619
|
-
var drawMultilineText = (ctx,
|
|
7620
|
-
const paragraphs =
|
|
7626
|
+
var drawMultilineText = (ctx, text6, left, top, maxWidth, lineHeight) => {
|
|
7627
|
+
const paragraphs = text6.split("\n");
|
|
7621
7628
|
let currentY = top;
|
|
7622
7629
|
paragraphs.forEach((paragraph, index) => {
|
|
7623
7630
|
const words = paragraph.split("");
|
|
@@ -8774,6 +8781,1244 @@ var BoothConfigPage = ({ initialConfig, onSave }) => {
|
|
|
8774
8781
|
)), /* @__PURE__ */ React69__default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React69__default.createElement("button", { className: "rounded bg-indigo-600 px-3 py-2 text-white", disabled: saving, onClick: save }, saving ? "\u4FDD\u5B58\u4E2D..." : "\u4FDD\u5B58\u914D\u7F6E"), /* @__PURE__ */ React69__default.createElement("button", { className: "rounded border px-3 py-2", onClick: reset }, "\u6062\u590D\u9ED8\u8BA4")));
|
|
8775
8782
|
};
|
|
8776
8783
|
|
|
8784
|
+
// src/mikuContest/logic/shared/defaults.ts
|
|
8785
|
+
var defaultMikuVotingRules = {
|
|
8786
|
+
maxVotesPerDay: 3,
|
|
8787
|
+
forbidDuplicateVotePerWork: true,
|
|
8788
|
+
maxVotesPerDevicePerDay: 20,
|
|
8789
|
+
maxVotesPerIpPerDay: 100
|
|
8790
|
+
};
|
|
8791
|
+
var createDefaultMikuContestConfig = (overrides) => ({
|
|
8792
|
+
id: overrides?.id || "miku-contest-default",
|
|
8793
|
+
name: overrides?.name || "\u521D\u97F3\u672A\u6765\u793E\u56E2\u5F81\u7A3F\u5927\u8D5B",
|
|
8794
|
+
theme: overrides?.theme || "\u521D\u97F3\u672A\u6765\u4E3B\u9898\u521B\u4F5C\u5F81\u7A3F",
|
|
8795
|
+
organizer: overrides?.organizer || "\u521D\u97F3\u672A\u6765\u793E\u56E2",
|
|
8796
|
+
awards: overrides?.awards || ["\u4E00\u7B49\u5956", "\u4E8C\u7B49\u5956", "\u4E09\u7B49\u5956", "\u4EBA\u6C14\u5956"],
|
|
8797
|
+
rules: overrides?.rules || "\u8BF7\u786E\u4FDD\u4F5C\u54C1\u539F\u521B\u4E14\u7B26\u5408\u793E\u56E2\u89C4\u8303\u3002",
|
|
8798
|
+
copyright: overrides?.copyright || "\u6295\u7A3F\u5373\u89C6\u4E3A\u6388\u6743\u8D5B\u4E8B\u5C55\u793A\u4E0E\u516C\u793A\u3002",
|
|
8799
|
+
timeline: overrides?.timeline || {
|
|
8800
|
+
submissionStartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8801
|
+
submissionEndAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8802
|
+
votingStartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8803
|
+
votingEndAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8804
|
+
publicResultAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8805
|
+
},
|
|
8806
|
+
votingRules: {
|
|
8807
|
+
...defaultMikuVotingRules,
|
|
8808
|
+
...overrides?.votingRules || {}
|
|
8809
|
+
},
|
|
8810
|
+
toggles: {
|
|
8811
|
+
submissionEnabled: overrides?.toggles?.submissionEnabled ?? true,
|
|
8812
|
+
votingEnabled: overrides?.toggles?.votingEnabled ?? true,
|
|
8813
|
+
resultEnabled: overrides?.toggles?.resultEnabled ?? false
|
|
8814
|
+
}
|
|
8815
|
+
});
|
|
8816
|
+
|
|
8817
|
+
// src/mikuContest/logic/shared/validators.ts
|
|
8818
|
+
var DESCRIPTION_LIMIT = 500;
|
|
8819
|
+
var MINIAPP_DESCRIPTION_LIMIT = 200;
|
|
8820
|
+
var TEXT_CONTENT_LIMIT = 2e3;
|
|
8821
|
+
var MAX_TAGS = 3;
|
|
8822
|
+
var hasValue = (value) => {
|
|
8823
|
+
return typeof value === "string" ? value.trim().length > 0 : value !== null && value !== void 0;
|
|
8824
|
+
};
|
|
8825
|
+
var validateByType = (type, input) => {
|
|
8826
|
+
const errors = [];
|
|
8827
|
+
const { content } = input;
|
|
8828
|
+
switch (type) {
|
|
8829
|
+
case "visual": {
|
|
8830
|
+
const imageCount = content.images?.length || 0;
|
|
8831
|
+
if (imageCount < 1 || imageCount > 3) {
|
|
8832
|
+
errors.push("\u89C6\u89C9\u7C7B\u4F5C\u54C1\u9700\u4E0A\u4F20 1-3 \u5F20\u56FE\u7247");
|
|
8833
|
+
}
|
|
8834
|
+
break;
|
|
8835
|
+
}
|
|
8836
|
+
case "video": {
|
|
8837
|
+
if (!hasValue(content.videoLink)) {
|
|
8838
|
+
errors.push("\u89C6\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u89C6\u9891\u94FE\u63A5");
|
|
8839
|
+
}
|
|
8840
|
+
if (!hasValue(content.coverImage)) {
|
|
8841
|
+
errors.push("\u89C6\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u5C01\u9762\u56FE");
|
|
8842
|
+
}
|
|
8843
|
+
break;
|
|
8844
|
+
}
|
|
8845
|
+
case "text": {
|
|
8846
|
+
const text6 = content.textContent || "";
|
|
8847
|
+
if (!text6.trim()) {
|
|
8848
|
+
errors.push("\u6587\u5B57\u7C7B\u4F5C\u54C1\u9700\u586B\u5199\u6B63\u6587");
|
|
8849
|
+
}
|
|
8850
|
+
if (text6.length > TEXT_CONTENT_LIMIT) {
|
|
8851
|
+
errors.push(`\u6587\u5B57\u6B63\u6587\u4E0D\u80FD\u8D85\u8FC7 ${TEXT_CONTENT_LIMIT} \u5B57`);
|
|
8852
|
+
}
|
|
8853
|
+
break;
|
|
8854
|
+
}
|
|
8855
|
+
case "audio": {
|
|
8856
|
+
if (!hasValue(content.audioLink)) {
|
|
8857
|
+
errors.push("\u97F3\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u97F3\u9891\u94FE\u63A5");
|
|
8858
|
+
}
|
|
8859
|
+
break;
|
|
8860
|
+
}
|
|
8861
|
+
}
|
|
8862
|
+
return errors;
|
|
8863
|
+
};
|
|
8864
|
+
var validateMikuSubmissionInput = (input, mode = "web") => {
|
|
8865
|
+
const errors = [];
|
|
8866
|
+
if (!input.contestId.trim()) errors.push("contestId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
8867
|
+
if (!input.authorId.trim()) errors.push("authorId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
8868
|
+
if (!input.authorNickname.trim()) errors.push("\u4F5C\u8005\u6635\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
8869
|
+
if (!input.title.trim()) errors.push("\u4F5C\u54C1\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
8870
|
+
const descriptionLimit = mode === "miniapp" ? MINIAPP_DESCRIPTION_LIMIT : DESCRIPTION_LIMIT;
|
|
8871
|
+
if (input.description.length > descriptionLimit) {
|
|
8872
|
+
errors.push(`\u4F5C\u54C1\u7B80\u4ECB\u4E0D\u80FD\u8D85\u8FC7 ${descriptionLimit} \u5B57`);
|
|
8873
|
+
}
|
|
8874
|
+
if ((input.tags?.length || 0) > MAX_TAGS) {
|
|
8875
|
+
errors.push(`\u6807\u7B7E\u6700\u591A ${MAX_TAGS} \u4E2A`);
|
|
8876
|
+
}
|
|
8877
|
+
errors.push(...validateByType(input.type, input));
|
|
8878
|
+
return errors;
|
|
8879
|
+
};
|
|
8880
|
+
|
|
8881
|
+
// src/mikuContest/logic/shared/voting.ts
|
|
8882
|
+
var toVoteDayKey = (date = /* @__PURE__ */ new Date()) => {
|
|
8883
|
+
const y = date.getUTCFullYear();
|
|
8884
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
8885
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
8886
|
+
return `${y}-${m}-${d}`;
|
|
8887
|
+
};
|
|
8888
|
+
var checkVoteEligibility = (context) => {
|
|
8889
|
+
const { existingVotes, submissionId, voterId, dayKey, rules } = context;
|
|
8890
|
+
const userTodayVotes = existingVotes.filter((vote) => vote.voterId === voterId && vote.dayKey === dayKey);
|
|
8891
|
+
if (userTodayVotes.length >= rules.maxVotesPerDay) {
|
|
8892
|
+
return { ok: false, reason: "\u5DF2\u8FBE\u5230\u4ECA\u65E5\u6295\u7968\u4E0A\u9650" };
|
|
8893
|
+
}
|
|
8894
|
+
if (rules.forbidDuplicateVotePerWork) {
|
|
8895
|
+
const duplicated = userTodayVotes.some((vote) => vote.submissionId === submissionId);
|
|
8896
|
+
if (duplicated) return { ok: false, reason: "\u4E0D\u53EF\u91CD\u590D\u6295\u540C\u4E00\u4F5C\u54C1" };
|
|
8897
|
+
}
|
|
8898
|
+
return { ok: true };
|
|
8899
|
+
};
|
|
8900
|
+
var sortByVotesDesc = (items) => {
|
|
8901
|
+
return [...items].sort((a, b) => {
|
|
8902
|
+
if (b.voteCount === a.voteCount) {
|
|
8903
|
+
return (a.createdAt || "").localeCompare(b.createdAt || "");
|
|
8904
|
+
}
|
|
8905
|
+
return b.voteCount - a.voteCount;
|
|
8906
|
+
});
|
|
8907
|
+
};
|
|
8908
|
+
var randomId = (prefix) => {
|
|
8909
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
8910
|
+
};
|
|
8911
|
+
var serialNo = () => {
|
|
8912
|
+
const now = /* @__PURE__ */ new Date();
|
|
8913
|
+
const y = now.getFullYear();
|
|
8914
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
8915
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
8916
|
+
const seq = Math.floor(Math.random() * 9e3 + 1e3);
|
|
8917
|
+
return `MIKU-${y}${m}${d}-${seq}`;
|
|
8918
|
+
};
|
|
8919
|
+
var MikuContestService = class {
|
|
8920
|
+
constructor(options = {}) {
|
|
8921
|
+
this.submissions = /* @__PURE__ */ new Map();
|
|
8922
|
+
this.votes = [];
|
|
8923
|
+
this.announcements = /* @__PURE__ */ new Map();
|
|
8924
|
+
this.voterRestrictions = /* @__PURE__ */ new Map();
|
|
8925
|
+
this.contest = createDefaultMikuContestConfig(options.contestConfig);
|
|
8926
|
+
}
|
|
8927
|
+
getContestConfig() {
|
|
8928
|
+
return this.contest;
|
|
8929
|
+
}
|
|
8930
|
+
updateContestConfig(patch) {
|
|
8931
|
+
this.contest = {
|
|
8932
|
+
...this.contest,
|
|
8933
|
+
...patch,
|
|
8934
|
+
votingRules: {
|
|
8935
|
+
...this.contest.votingRules,
|
|
8936
|
+
...patch.votingRules || {}
|
|
8937
|
+
},
|
|
8938
|
+
toggles: {
|
|
8939
|
+
...this.contest.toggles,
|
|
8940
|
+
...patch.toggles || {}
|
|
8941
|
+
},
|
|
8942
|
+
timeline: {
|
|
8943
|
+
...this.contest.timeline,
|
|
8944
|
+
...patch.timeline || {}
|
|
8945
|
+
}
|
|
8946
|
+
};
|
|
8947
|
+
return this.contest;
|
|
8948
|
+
}
|
|
8949
|
+
createSubmission(input, mode = "web") {
|
|
8950
|
+
if (!this.contest.toggles.submissionEnabled) {
|
|
8951
|
+
throw new Error("\u5F53\u524D\u672A\u5F00\u653E\u6295\u7A3F");
|
|
8952
|
+
}
|
|
8953
|
+
const errors = validateMikuSubmissionInput(input, mode);
|
|
8954
|
+
if (errors.length > 0) {
|
|
8955
|
+
throw new Error(errors.join("\uFF1B"));
|
|
8956
|
+
}
|
|
8957
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8958
|
+
const next = {
|
|
8959
|
+
id: randomId("submission"),
|
|
8960
|
+
serialNo: serialNo(),
|
|
8961
|
+
contestId: input.contestId,
|
|
8962
|
+
authorId: input.authorId,
|
|
8963
|
+
authorNickname: input.authorNickname,
|
|
8964
|
+
title: input.title,
|
|
8965
|
+
type: input.type,
|
|
8966
|
+
description: input.description,
|
|
8967
|
+
tags: input.tags || [],
|
|
8968
|
+
content: input.content,
|
|
8969
|
+
voteCount: 0,
|
|
8970
|
+
status: "pending",
|
|
8971
|
+
createdAt: now,
|
|
8972
|
+
updatedAt: now
|
|
8973
|
+
};
|
|
8974
|
+
this.submissions.set(next.id, next);
|
|
8975
|
+
return next;
|
|
8976
|
+
}
|
|
8977
|
+
listSubmissions(filter) {
|
|
8978
|
+
const authorKeyword = filter?.authorKeyword?.trim().toLowerCase();
|
|
8979
|
+
const titleKeyword = filter?.titleKeyword?.trim().toLowerCase();
|
|
8980
|
+
return [...this.submissions.values()].filter((item) => {
|
|
8981
|
+
if (filter?.status && item.status !== filter.status) return false;
|
|
8982
|
+
if (filter?.type && item.type !== filter.type) return false;
|
|
8983
|
+
if (filter?.authorId && item.authorId !== filter.authorId) return false;
|
|
8984
|
+
if (authorKeyword && !item.authorNickname.toLowerCase().includes(authorKeyword)) return false;
|
|
8985
|
+
if (titleKeyword && !item.title.toLowerCase().includes(titleKeyword)) return false;
|
|
8986
|
+
return true;
|
|
8987
|
+
});
|
|
8988
|
+
}
|
|
8989
|
+
getSubmission(submissionId) {
|
|
8990
|
+
return this.submissions.get(submissionId) || null;
|
|
8991
|
+
}
|
|
8992
|
+
reviewSubmission(input) {
|
|
8993
|
+
const current = this.submissions.get(input.submissionId);
|
|
8994
|
+
if (!current) throw new Error("\u6295\u7A3F\u4E0D\u5B58\u5728");
|
|
8995
|
+
if (input.action === "reject" && !input.rejectReason?.trim()) {
|
|
8996
|
+
throw new Error("\u9A73\u56DE\u9700\u586B\u5199\u539F\u56E0");
|
|
8997
|
+
}
|
|
8998
|
+
const reviewed = {
|
|
8999
|
+
...current,
|
|
9000
|
+
status: input.action === "approve" ? "approved" : "rejected",
|
|
9001
|
+
rejectReason: input.action === "reject" ? input.rejectReason?.trim() : void 0,
|
|
9002
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9003
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9004
|
+
};
|
|
9005
|
+
this.submissions.set(reviewed.id, reviewed);
|
|
9006
|
+
return reviewed;
|
|
9007
|
+
}
|
|
9008
|
+
vote(input) {
|
|
9009
|
+
if (!this.contest.toggles.votingEnabled) {
|
|
9010
|
+
throw new Error("\u5F53\u524D\u672A\u5F00\u653E\u6295\u7968");
|
|
9011
|
+
}
|
|
9012
|
+
const restriction = this.voterRestrictions.get(input.voterId);
|
|
9013
|
+
if (restriction?.banned) {
|
|
9014
|
+
throw new Error("\u5F53\u524D\u8D26\u53F7\u5DF2\u88AB\u9650\u5236\u6295\u7968");
|
|
9015
|
+
}
|
|
9016
|
+
const target = this.submissions.get(input.submissionId);
|
|
9017
|
+
if (!target) throw new Error("\u4F5C\u54C1\u4E0D\u5B58\u5728");
|
|
9018
|
+
if (target.status !== "approved") throw new Error("\u4EC5\u53EF\u5BF9\u5DF2\u8FC7\u5BA1\u4F5C\u54C1\u6295\u7968");
|
|
9019
|
+
const dayKey = toVoteDayKey();
|
|
9020
|
+
const eligible = checkVoteEligibility({
|
|
9021
|
+
existingVotes: this.votes,
|
|
9022
|
+
submissionId: input.submissionId,
|
|
9023
|
+
voterId: input.voterId,
|
|
9024
|
+
dayKey,
|
|
9025
|
+
rules: this.contest.votingRules
|
|
9026
|
+
});
|
|
9027
|
+
if (!eligible.ok) {
|
|
9028
|
+
throw new Error(eligible.reason || "\u6295\u7968\u5931\u8D25");
|
|
9029
|
+
}
|
|
9030
|
+
const vote = {
|
|
9031
|
+
id: randomId("vote"),
|
|
9032
|
+
contestId: input.contestId,
|
|
9033
|
+
submissionId: input.submissionId,
|
|
9034
|
+
voterId: input.voterId,
|
|
9035
|
+
votedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9036
|
+
dayKey,
|
|
9037
|
+
deviceId: input.deviceId,
|
|
9038
|
+
ip: input.ip
|
|
9039
|
+
};
|
|
9040
|
+
this.votes.push(vote);
|
|
9041
|
+
const updated = {
|
|
9042
|
+
...target,
|
|
9043
|
+
voteCount: target.voteCount + 1,
|
|
9044
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9045
|
+
};
|
|
9046
|
+
this.submissions.set(updated.id, updated);
|
|
9047
|
+
return updated;
|
|
9048
|
+
}
|
|
9049
|
+
getVoterRestriction(voterId) {
|
|
9050
|
+
return this.voterRestrictions.get(voterId) || null;
|
|
9051
|
+
}
|
|
9052
|
+
setVoterRestriction(input) {
|
|
9053
|
+
const next = {
|
|
9054
|
+
voterId: input.voterId,
|
|
9055
|
+
banned: input.banned,
|
|
9056
|
+
reason: input.reason,
|
|
9057
|
+
operatorId: input.operatorId,
|
|
9058
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9059
|
+
};
|
|
9060
|
+
this.voterRestrictions.set(input.voterId, next);
|
|
9061
|
+
return next;
|
|
9062
|
+
}
|
|
9063
|
+
resetVotes(input) {
|
|
9064
|
+
if (!input.submissionId && !input.voterId) {
|
|
9065
|
+
throw new Error("submissionId \u4E0E voterId \u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A");
|
|
9066
|
+
}
|
|
9067
|
+
const before = this.votes.length;
|
|
9068
|
+
const affected = /* @__PURE__ */ new Set();
|
|
9069
|
+
const remained = this.votes.filter((vote) => {
|
|
9070
|
+
const matchSubmission = input.submissionId ? vote.submissionId === input.submissionId : true;
|
|
9071
|
+
const matchVoter = input.voterId ? vote.voterId === input.voterId : true;
|
|
9072
|
+
const shouldRemove = matchSubmission && matchVoter;
|
|
9073
|
+
if (shouldRemove) affected.add(vote.submissionId);
|
|
9074
|
+
return !shouldRemove;
|
|
9075
|
+
});
|
|
9076
|
+
this.votes.length = 0;
|
|
9077
|
+
this.votes.push(...remained);
|
|
9078
|
+
this.recalculateVoteCounts();
|
|
9079
|
+
return {
|
|
9080
|
+
removedVotes: before - remained.length,
|
|
9081
|
+
affectedSubmissions: [...affected]
|
|
9082
|
+
};
|
|
9083
|
+
}
|
|
9084
|
+
listAnnouncements(contestId) {
|
|
9085
|
+
const all = [...this.announcements.values()];
|
|
9086
|
+
return contestId ? all.filter((item) => item.contestId === contestId) : all;
|
|
9087
|
+
}
|
|
9088
|
+
publishAnnouncement(input) {
|
|
9089
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9090
|
+
const announcement = {
|
|
9091
|
+
id: randomId("notice"),
|
|
9092
|
+
contestId: input.contestId,
|
|
9093
|
+
title: input.title,
|
|
9094
|
+
content: input.content,
|
|
9095
|
+
type: input.type,
|
|
9096
|
+
createdBy: input.createdBy,
|
|
9097
|
+
createdAt: now,
|
|
9098
|
+
updatedAt: now
|
|
9099
|
+
};
|
|
9100
|
+
this.announcements.set(announcement.id, announcement);
|
|
9101
|
+
return announcement;
|
|
9102
|
+
}
|
|
9103
|
+
getLeaderboard(limit = 10) {
|
|
9104
|
+
const ranked = sortByVotesDesc(this.listSubmissions({ status: "approved" })).slice(0, limit);
|
|
9105
|
+
return ranked.map((item, index) => ({
|
|
9106
|
+
submissionId: item.id,
|
|
9107
|
+
title: item.title,
|
|
9108
|
+
authorNickname: item.authorNickname,
|
|
9109
|
+
voteCount: item.voteCount,
|
|
9110
|
+
rank: index + 1
|
|
9111
|
+
}));
|
|
9112
|
+
}
|
|
9113
|
+
getSnapshot() {
|
|
9114
|
+
return {
|
|
9115
|
+
contest: this.contest,
|
|
9116
|
+
submissions: this.listSubmissions(),
|
|
9117
|
+
announcements: this.listAnnouncements(),
|
|
9118
|
+
leaderboard: this.getLeaderboard()
|
|
9119
|
+
};
|
|
9120
|
+
}
|
|
9121
|
+
getSubmissionExportRows(filter) {
|
|
9122
|
+
return this.listSubmissions(filter).map((item) => ({
|
|
9123
|
+
\u6295\u7A3F\u7F16\u53F7: item.serialNo,
|
|
9124
|
+
\u6295\u7A3FID: item.id,
|
|
9125
|
+
\u8D5B\u4E8BID: item.contestId,
|
|
9126
|
+
\u4F5C\u8005ID: item.authorId,
|
|
9127
|
+
\u4F5C\u8005\u6635\u79F0: item.authorNickname,
|
|
9128
|
+
\u4F5C\u54C1\u540D\u79F0: item.title,
|
|
9129
|
+
\u4F5C\u54C1\u7C7B\u578B: item.type,
|
|
9130
|
+
\u7B80\u4ECB: item.description,
|
|
9131
|
+
\u6807\u7B7E: item.tags.join(","),
|
|
9132
|
+
\u5BA1\u6838\u72B6\u6001: item.status,
|
|
9133
|
+
\u9A73\u56DE\u539F\u56E0: item.rejectReason || "",
|
|
9134
|
+
\u7968\u6570: item.voteCount,
|
|
9135
|
+
\u63D0\u4EA4\u65F6\u95F4: item.createdAt,
|
|
9136
|
+
\u66F4\u65B0\u65F6\u95F4: item.updatedAt
|
|
9137
|
+
}));
|
|
9138
|
+
}
|
|
9139
|
+
exportSubmissionExcel(filter) {
|
|
9140
|
+
const rows = this.getSubmissionExportRows(filter);
|
|
9141
|
+
const workbook = XLSX.utils.book_new();
|
|
9142
|
+
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
9143
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, "submissions");
|
|
9144
|
+
return XLSX.write(workbook, { bookType: "xlsx", type: "buffer" });
|
|
9145
|
+
}
|
|
9146
|
+
recalculateVoteCounts() {
|
|
9147
|
+
const counts = /* @__PURE__ */ new Map();
|
|
9148
|
+
for (const vote of this.votes) {
|
|
9149
|
+
counts.set(vote.submissionId, (counts.get(vote.submissionId) || 0) + 1);
|
|
9150
|
+
}
|
|
9151
|
+
for (const [id, submission] of this.submissions.entries()) {
|
|
9152
|
+
const nextCount = counts.get(id) || 0;
|
|
9153
|
+
if (submission.voteCount === nextCount) continue;
|
|
9154
|
+
this.submissions.set(id, {
|
|
9155
|
+
...submission,
|
|
9156
|
+
voteCount: nextCount,
|
|
9157
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9158
|
+
});
|
|
9159
|
+
}
|
|
9160
|
+
}
|
|
9161
|
+
exportPersistenceState() {
|
|
9162
|
+
return {
|
|
9163
|
+
contest: this.contest,
|
|
9164
|
+
submissions: [...this.submissions.values()],
|
|
9165
|
+
votes: [...this.votes],
|
|
9166
|
+
announcements: [...this.announcements.values()],
|
|
9167
|
+
voterRestrictions: [...this.voterRestrictions.values()],
|
|
9168
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9169
|
+
};
|
|
9170
|
+
}
|
|
9171
|
+
importPersistenceState(state) {
|
|
9172
|
+
this.contest = state.contest;
|
|
9173
|
+
this.submissions.clear();
|
|
9174
|
+
this.announcements.clear();
|
|
9175
|
+
this.voterRestrictions.clear();
|
|
9176
|
+
this.votes.length = 0;
|
|
9177
|
+
for (const item of state.submissions) {
|
|
9178
|
+
this.submissions.set(item.id, item);
|
|
9179
|
+
}
|
|
9180
|
+
for (const item of state.announcements) {
|
|
9181
|
+
this.announcements.set(item.id, item);
|
|
9182
|
+
}
|
|
9183
|
+
for (const item of state.voterRestrictions) {
|
|
9184
|
+
this.voterRestrictions.set(item.voterId, item);
|
|
9185
|
+
}
|
|
9186
|
+
this.votes.push(...state.votes);
|
|
9187
|
+
}
|
|
9188
|
+
};
|
|
9189
|
+
var createMikuContestService = (options) => {
|
|
9190
|
+
return new MikuContestService(options);
|
|
9191
|
+
};
|
|
9192
|
+
var mikuContestConfigs = pgTable("miku_contest_configs", {
|
|
9193
|
+
contestId: text("contest_id").primaryKey(),
|
|
9194
|
+
config: jsonb("config").$type().notNull(),
|
|
9195
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
9196
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
9197
|
+
});
|
|
9198
|
+
var mikuContestSubmissions = pgTable("miku_contest_submissions", {
|
|
9199
|
+
id: text("id").primaryKey(),
|
|
9200
|
+
contestId: text("contest_id").notNull(),
|
|
9201
|
+
serialNo: text("serial_no").notNull(),
|
|
9202
|
+
authorId: text("author_id").notNull(),
|
|
9203
|
+
authorNickname: text("author_nickname").notNull(),
|
|
9204
|
+
title: text("title").notNull(),
|
|
9205
|
+
type: text("type").notNull(),
|
|
9206
|
+
description: text("description").notNull(),
|
|
9207
|
+
tags: jsonb("tags").$type().notNull(),
|
|
9208
|
+
content: jsonb("content").$type().notNull(),
|
|
9209
|
+
voteCount: integer("vote_count").notNull().default(0),
|
|
9210
|
+
status: text("status").notNull(),
|
|
9211
|
+
rejectReason: text("reject_reason"),
|
|
9212
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
|
|
9213
|
+
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
9214
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull()
|
|
9215
|
+
});
|
|
9216
|
+
var mikuContestVotes = pgTable("miku_contest_votes", {
|
|
9217
|
+
id: text("id").primaryKey(),
|
|
9218
|
+
contestId: text("contest_id").notNull(),
|
|
9219
|
+
submissionId: text("submission_id").notNull(),
|
|
9220
|
+
voterId: text("voter_id").notNull(),
|
|
9221
|
+
votedAt: text("voted_at").notNull(),
|
|
9222
|
+
dayKey: text("day_key").notNull(),
|
|
9223
|
+
deviceId: text("device_id"),
|
|
9224
|
+
ip: text("ip")
|
|
9225
|
+
});
|
|
9226
|
+
var mikuContestNotices = pgTable("miku_contest_notices", {
|
|
9227
|
+
id: text("id").primaryKey(),
|
|
9228
|
+
contestId: text("contest_id").notNull(),
|
|
9229
|
+
title: text("title").notNull(),
|
|
9230
|
+
content: text("content").notNull(),
|
|
9231
|
+
type: text("type").notNull(),
|
|
9232
|
+
createdBy: text("created_by").notNull(),
|
|
9233
|
+
createdAt: text("created_at").notNull(),
|
|
9234
|
+
updatedAt: text("updated_at").notNull()
|
|
9235
|
+
});
|
|
9236
|
+
var mikuContestVoterRestrictions = pgTable("miku_contest_voter_restrictions", {
|
|
9237
|
+
id: text("id").primaryKey(),
|
|
9238
|
+
contestId: text("contest_id").notNull(),
|
|
9239
|
+
data: jsonb("data").$type().notNull()
|
|
9240
|
+
});
|
|
9241
|
+
var MikuContestStateDbService = class {
|
|
9242
|
+
constructor(db) {
|
|
9243
|
+
this.db = db;
|
|
9244
|
+
}
|
|
9245
|
+
async loadState(contestId) {
|
|
9246
|
+
const configRows = await this.db.select({ config: mikuContestConfigs.config }).from(mikuContestConfigs).where(eq(mikuContestConfigs.contestId, contestId)).limit(1);
|
|
9247
|
+
const config = configRows[0]?.config;
|
|
9248
|
+
if (!config) return null;
|
|
9249
|
+
const [submissions, votes, announcements, restrictions] = await Promise.all([
|
|
9250
|
+
this.db.select().from(mikuContestSubmissions).where(eq(mikuContestSubmissions.contestId, contestId)),
|
|
9251
|
+
this.db.select().from(mikuContestVotes).where(eq(mikuContestVotes.contestId, contestId)),
|
|
9252
|
+
this.db.select().from(mikuContestNotices).where(eq(mikuContestNotices.contestId, contestId)),
|
|
9253
|
+
this.db.select({ data: mikuContestVoterRestrictions.data }).from(mikuContestVoterRestrictions).where(eq(mikuContestVoterRestrictions.contestId, contestId))
|
|
9254
|
+
]);
|
|
9255
|
+
return {
|
|
9256
|
+
contest: config,
|
|
9257
|
+
submissions,
|
|
9258
|
+
votes,
|
|
9259
|
+
announcements,
|
|
9260
|
+
voterRestrictions: restrictions.map((item) => item.data),
|
|
9261
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9262
|
+
};
|
|
9263
|
+
}
|
|
9264
|
+
async saveState(state) {
|
|
9265
|
+
const contestId = state.contest.id;
|
|
9266
|
+
const exists = await this.db.select({ contestId: mikuContestConfigs.contestId }).from(mikuContestConfigs).where(eq(mikuContestConfigs.contestId, contestId)).limit(1);
|
|
9267
|
+
if (exists[0]) {
|
|
9268
|
+
await this.db.update(mikuContestConfigs).set({
|
|
9269
|
+
config: state.contest,
|
|
9270
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
9271
|
+
}).where(eq(mikuContestConfigs.contestId, contestId));
|
|
9272
|
+
} else {
|
|
9273
|
+
await this.db.insert(mikuContestConfigs).values({
|
|
9274
|
+
contestId,
|
|
9275
|
+
config: state.contest
|
|
9276
|
+
});
|
|
9277
|
+
}
|
|
9278
|
+
await this.db.delete(mikuContestSubmissions).where(eq(mikuContestSubmissions.contestId, contestId));
|
|
9279
|
+
await this.db.delete(mikuContestVotes).where(eq(mikuContestVotes.contestId, contestId));
|
|
9280
|
+
await this.db.delete(mikuContestNotices).where(eq(mikuContestNotices.contestId, contestId));
|
|
9281
|
+
await this.db.delete(mikuContestVoterRestrictions).where(eq(mikuContestVoterRestrictions.contestId, contestId));
|
|
9282
|
+
if (state.submissions.length > 0) {
|
|
9283
|
+
await this.db.insert(mikuContestSubmissions).values(
|
|
9284
|
+
state.submissions.map((item) => ({
|
|
9285
|
+
...item,
|
|
9286
|
+
contestId,
|
|
9287
|
+
createdAt: new Date(item.createdAt),
|
|
9288
|
+
reviewedAt: item.reviewedAt ? new Date(item.reviewedAt) : null,
|
|
9289
|
+
updatedAt: new Date(item.updatedAt)
|
|
9290
|
+
}))
|
|
9291
|
+
);
|
|
9292
|
+
}
|
|
9293
|
+
if (state.votes.length > 0) {
|
|
9294
|
+
await this.db.insert(mikuContestVotes).values(state.votes);
|
|
9295
|
+
}
|
|
9296
|
+
if (state.announcements.length > 0) {
|
|
9297
|
+
await this.db.insert(mikuContestNotices).values(state.announcements);
|
|
9298
|
+
}
|
|
9299
|
+
if (state.voterRestrictions.length > 0) {
|
|
9300
|
+
await this.db.insert(mikuContestVoterRestrictions).values(
|
|
9301
|
+
state.voterRestrictions.map((item) => ({
|
|
9302
|
+
id: `${contestId}:${item.voterId}`,
|
|
9303
|
+
contestId,
|
|
9304
|
+
data: item
|
|
9305
|
+
}))
|
|
9306
|
+
);
|
|
9307
|
+
}
|
|
9308
|
+
}
|
|
9309
|
+
};
|
|
9310
|
+
|
|
9311
|
+
// src/mikuContest/server/persistence/drizzle-adapter.ts
|
|
9312
|
+
var createMikuContestDrizzlePersistenceAdapter = (db) => {
|
|
9313
|
+
const service = new MikuContestStateDbService(db);
|
|
9314
|
+
return {
|
|
9315
|
+
loadState: (contestId) => service.loadState(contestId),
|
|
9316
|
+
saveState: (state) => service.saveState(state)
|
|
9317
|
+
};
|
|
9318
|
+
};
|
|
9319
|
+
|
|
9320
|
+
// src/mikuContest/server/persistence/service.ts
|
|
9321
|
+
var MikuContestPersistentService = class {
|
|
9322
|
+
constructor(options) {
|
|
9323
|
+
this.options = options;
|
|
9324
|
+
this.hydrated = false;
|
|
9325
|
+
this.hydrationPromise = null;
|
|
9326
|
+
this.engine = createMikuContestService(options);
|
|
9327
|
+
}
|
|
9328
|
+
async ensureHydrated() {
|
|
9329
|
+
if (this.hydrated) return;
|
|
9330
|
+
if (this.hydrationPromise) return this.hydrationPromise;
|
|
9331
|
+
this.hydrationPromise = (async () => {
|
|
9332
|
+
const contestId = this.engine.getContestConfig().id;
|
|
9333
|
+
const loaded = await this.options.persistenceAdapter.loadState(contestId);
|
|
9334
|
+
if (loaded) {
|
|
9335
|
+
this.engine.importPersistenceState(loaded);
|
|
9336
|
+
} else {
|
|
9337
|
+
await this.options.persistenceAdapter.saveState(this.engine.exportPersistenceState());
|
|
9338
|
+
}
|
|
9339
|
+
this.hydrated = true;
|
|
9340
|
+
})();
|
|
9341
|
+
await this.hydrationPromise;
|
|
9342
|
+
}
|
|
9343
|
+
async persist() {
|
|
9344
|
+
await this.options.persistenceAdapter.saveState(this.engine.exportPersistenceState());
|
|
9345
|
+
}
|
|
9346
|
+
async getContestConfig() {
|
|
9347
|
+
await this.ensureHydrated();
|
|
9348
|
+
return this.engine.getContestConfig();
|
|
9349
|
+
}
|
|
9350
|
+
async updateContestConfig(patch) {
|
|
9351
|
+
await this.ensureHydrated();
|
|
9352
|
+
const data = this.engine.updateContestConfig(patch);
|
|
9353
|
+
await this.persist();
|
|
9354
|
+
return data;
|
|
9355
|
+
}
|
|
9356
|
+
async createSubmission(input, mode = "web") {
|
|
9357
|
+
await this.ensureHydrated();
|
|
9358
|
+
const data = this.engine.createSubmission(input, mode);
|
|
9359
|
+
await this.persist();
|
|
9360
|
+
return data;
|
|
9361
|
+
}
|
|
9362
|
+
async listSubmissions(filter) {
|
|
9363
|
+
await this.ensureHydrated();
|
|
9364
|
+
return this.engine.listSubmissions(filter);
|
|
9365
|
+
}
|
|
9366
|
+
async getSubmission(submissionId) {
|
|
9367
|
+
await this.ensureHydrated();
|
|
9368
|
+
return this.engine.getSubmission(submissionId);
|
|
9369
|
+
}
|
|
9370
|
+
async reviewSubmission(input) {
|
|
9371
|
+
await this.ensureHydrated();
|
|
9372
|
+
const data = this.engine.reviewSubmission(input);
|
|
9373
|
+
await this.persist();
|
|
9374
|
+
return data;
|
|
9375
|
+
}
|
|
9376
|
+
async vote(input) {
|
|
9377
|
+
await this.ensureHydrated();
|
|
9378
|
+
const data = this.engine.vote(input);
|
|
9379
|
+
await this.persist();
|
|
9380
|
+
return data;
|
|
9381
|
+
}
|
|
9382
|
+
async getVoterRestriction(voterId) {
|
|
9383
|
+
await this.ensureHydrated();
|
|
9384
|
+
return this.engine.getVoterRestriction(voterId);
|
|
9385
|
+
}
|
|
9386
|
+
async setVoterRestriction(input) {
|
|
9387
|
+
await this.ensureHydrated();
|
|
9388
|
+
const data = this.engine.setVoterRestriction(input);
|
|
9389
|
+
await this.persist();
|
|
9390
|
+
return data;
|
|
9391
|
+
}
|
|
9392
|
+
async resetVotes(input) {
|
|
9393
|
+
await this.ensureHydrated();
|
|
9394
|
+
const data = this.engine.resetVotes(input);
|
|
9395
|
+
await this.persist();
|
|
9396
|
+
return data;
|
|
9397
|
+
}
|
|
9398
|
+
async listAnnouncements(contestId) {
|
|
9399
|
+
await this.ensureHydrated();
|
|
9400
|
+
return this.engine.listAnnouncements(contestId);
|
|
9401
|
+
}
|
|
9402
|
+
async publishAnnouncement(input) {
|
|
9403
|
+
await this.ensureHydrated();
|
|
9404
|
+
const data = this.engine.publishAnnouncement(input);
|
|
9405
|
+
await this.persist();
|
|
9406
|
+
return data;
|
|
9407
|
+
}
|
|
9408
|
+
async getLeaderboard(limit = 10) {
|
|
9409
|
+
await this.ensureHydrated();
|
|
9410
|
+
return this.engine.getLeaderboard(limit);
|
|
9411
|
+
}
|
|
9412
|
+
async getSnapshot() {
|
|
9413
|
+
await this.ensureHydrated();
|
|
9414
|
+
return this.engine.getSnapshot();
|
|
9415
|
+
}
|
|
9416
|
+
async getSubmissionExportRows(filter) {
|
|
9417
|
+
await this.ensureHydrated();
|
|
9418
|
+
return this.engine.getSubmissionExportRows(filter);
|
|
9419
|
+
}
|
|
9420
|
+
async exportSubmissionExcel(filter) {
|
|
9421
|
+
await this.ensureHydrated();
|
|
9422
|
+
return this.engine.exportSubmissionExcel(filter);
|
|
9423
|
+
}
|
|
9424
|
+
};
|
|
9425
|
+
var createMikuContestPersistentService = (options) => {
|
|
9426
|
+
return new MikuContestPersistentService(options);
|
|
9427
|
+
};
|
|
9428
|
+
|
|
9429
|
+
// src/mikuContest/server/db.ts
|
|
9430
|
+
var MikuContestDbService = class {
|
|
9431
|
+
constructor() {
|
|
9432
|
+
this._db = null;
|
|
9433
|
+
}
|
|
9434
|
+
setDb(db) {
|
|
9435
|
+
this._db = db;
|
|
9436
|
+
}
|
|
9437
|
+
isConfigured() {
|
|
9438
|
+
return Boolean(this._db);
|
|
9439
|
+
}
|
|
9440
|
+
get db() {
|
|
9441
|
+
if (!this._db) {
|
|
9442
|
+
throw new Error("MikuContestDbService: Database instance not set. Call setDb(db) first.");
|
|
9443
|
+
}
|
|
9444
|
+
return this._db;
|
|
9445
|
+
}
|
|
9446
|
+
};
|
|
9447
|
+
var mikuContestDbService = new MikuContestDbService();
|
|
9448
|
+
|
|
9449
|
+
// src/mikuContest/logic/hooks/useMikuContest.ts
|
|
9450
|
+
var useMikuContest = (options) => {
|
|
9451
|
+
const [service] = useState(() => createMikuContestService(options));
|
|
9452
|
+
const [version, setVersion] = useState(0);
|
|
9453
|
+
const refresh = () => setVersion((value) => value + 1);
|
|
9454
|
+
const snapshot = useMemo(() => {
|
|
9455
|
+
return service.getSnapshot();
|
|
9456
|
+
}, [service, version]);
|
|
9457
|
+
return {
|
|
9458
|
+
service,
|
|
9459
|
+
snapshot,
|
|
9460
|
+
refresh
|
|
9461
|
+
};
|
|
9462
|
+
};
|
|
9463
|
+
|
|
9464
|
+
// src/mikuContest/service/api/client.ts
|
|
9465
|
+
var toQueryString = (filter) => {
|
|
9466
|
+
if (!filter) return "";
|
|
9467
|
+
const params = new URLSearchParams();
|
|
9468
|
+
if (filter.status) params.set("status", filter.status);
|
|
9469
|
+
if (filter.type) params.set("type", filter.type);
|
|
9470
|
+
if (filter.authorId) params.set("authorId", filter.authorId);
|
|
9471
|
+
if (filter.authorKeyword) params.set("authorKeyword", filter.authorKeyword);
|
|
9472
|
+
if (filter.titleKeyword) params.set("titleKeyword", filter.titleKeyword);
|
|
9473
|
+
const query = params.toString();
|
|
9474
|
+
return query ? `?${query}` : "";
|
|
9475
|
+
};
|
|
9476
|
+
var unwrap = (result) => {
|
|
9477
|
+
if (!result.success || result.data === void 0) {
|
|
9478
|
+
throw new Error(result.error || "\u8BF7\u6C42\u5931\u8D25");
|
|
9479
|
+
}
|
|
9480
|
+
return result.data;
|
|
9481
|
+
};
|
|
9482
|
+
var createMikuContestApiClient = (basePath, requester) => {
|
|
9483
|
+
return {
|
|
9484
|
+
async getSnapshot() {
|
|
9485
|
+
const result = await requester(`${basePath}/contest`, { method: "GET" });
|
|
9486
|
+
return unwrap(result);
|
|
9487
|
+
},
|
|
9488
|
+
async updateContestConfig(patch) {
|
|
9489
|
+
const result = await requester(`${basePath}/contest`, {
|
|
9490
|
+
method: "PATCH",
|
|
9491
|
+
body: patch
|
|
9492
|
+
});
|
|
9493
|
+
return unwrap(result);
|
|
9494
|
+
},
|
|
9495
|
+
async createSubmission(input, mode = "web") {
|
|
9496
|
+
const result = await requester(`${basePath}/submissions`, {
|
|
9497
|
+
method: "POST",
|
|
9498
|
+
body: { payload: input, mode }
|
|
9499
|
+
});
|
|
9500
|
+
return unwrap(result);
|
|
9501
|
+
},
|
|
9502
|
+
async listSubmissions(filter) {
|
|
9503
|
+
const result = await requester(
|
|
9504
|
+
`${basePath}/submissions${toQueryString(filter)}`,
|
|
9505
|
+
{ method: "GET" }
|
|
9506
|
+
);
|
|
9507
|
+
return unwrap(result);
|
|
9508
|
+
},
|
|
9509
|
+
async reviewSubmission(input) {
|
|
9510
|
+
const result = await requester(`${basePath}/submissions/review`, {
|
|
9511
|
+
method: "POST",
|
|
9512
|
+
body: input
|
|
9513
|
+
});
|
|
9514
|
+
return unwrap(result);
|
|
9515
|
+
},
|
|
9516
|
+
async vote(input) {
|
|
9517
|
+
const result = await requester(`${basePath}/votes`, {
|
|
9518
|
+
method: "POST",
|
|
9519
|
+
body: input
|
|
9520
|
+
});
|
|
9521
|
+
return unwrap(result);
|
|
9522
|
+
},
|
|
9523
|
+
async setVoterRestriction(input) {
|
|
9524
|
+
const result = await requester(`${basePath}/admin/voter-restrictions`, {
|
|
9525
|
+
method: "POST",
|
|
9526
|
+
body: input
|
|
9527
|
+
});
|
|
9528
|
+
return unwrap(result);
|
|
9529
|
+
},
|
|
9530
|
+
async resetVotes(input) {
|
|
9531
|
+
const result = await requester(
|
|
9532
|
+
`${basePath}/admin/votes/reset`,
|
|
9533
|
+
{
|
|
9534
|
+
method: "POST",
|
|
9535
|
+
body: input
|
|
9536
|
+
}
|
|
9537
|
+
);
|
|
9538
|
+
return unwrap(result);
|
|
9539
|
+
},
|
|
9540
|
+
async exportSubmissions(filter) {
|
|
9541
|
+
const response = await fetch(`${basePath}/admin/submissions/export${toQueryString(filter)}`);
|
|
9542
|
+
if (!response.ok) {
|
|
9543
|
+
throw new Error(`\u5BFC\u51FA\u5931\u8D25: ${response.status}`);
|
|
9544
|
+
}
|
|
9545
|
+
return response.arrayBuffer();
|
|
9546
|
+
}
|
|
9547
|
+
};
|
|
9548
|
+
};
|
|
9549
|
+
|
|
9550
|
+
// src/mikuContest/service/web/index.ts
|
|
9551
|
+
var web_exports = {};
|
|
9552
|
+
__export(web_exports, {
|
|
9553
|
+
createMikuContestWebClient: () => createMikuContestWebClient
|
|
9554
|
+
});
|
|
9555
|
+
|
|
9556
|
+
// src/mikuContest/service/web/client.ts
|
|
9557
|
+
var defaultRequester = (options) => {
|
|
9558
|
+
const baseUrl = options.baseUrl || "";
|
|
9559
|
+
const commonHeaders = options.headers || {};
|
|
9560
|
+
return async (url, requestOptions) => {
|
|
9561
|
+
const response = await fetch(`${baseUrl}${url}`, {
|
|
9562
|
+
method: requestOptions?.method || "GET",
|
|
9563
|
+
headers: {
|
|
9564
|
+
"Content-Type": "application/json",
|
|
9565
|
+
...commonHeaders
|
|
9566
|
+
},
|
|
9567
|
+
body: requestOptions?.body ? JSON.stringify(requestOptions.body) : void 0
|
|
9568
|
+
});
|
|
9569
|
+
const json = await response.json();
|
|
9570
|
+
return json;
|
|
9571
|
+
};
|
|
9572
|
+
};
|
|
9573
|
+
var createMikuContestWebClient = (options = {}) => {
|
|
9574
|
+
const basePath = options.basePath || "/api/miku-contest";
|
|
9575
|
+
return createMikuContestApiClient(basePath, defaultRequester(options));
|
|
9576
|
+
};
|
|
9577
|
+
|
|
9578
|
+
// src/mikuContest/service/miniapp/index.ts
|
|
9579
|
+
var miniapp_exports = {};
|
|
9580
|
+
__export(miniapp_exports, {
|
|
9581
|
+
createMikuContestMiniappClient: () => createMikuContestMiniappClient
|
|
9582
|
+
});
|
|
9583
|
+
|
|
9584
|
+
// src/mikuContest/service/miniapp/client.ts
|
|
9585
|
+
var createMikuContestMiniappClient = (options) => {
|
|
9586
|
+
const basePath = options.basePath || "/api/miku-contest";
|
|
9587
|
+
return createMikuContestApiClient(basePath, options.requester);
|
|
9588
|
+
};
|
|
9589
|
+
var isDrizzleDb = (value) => {
|
|
9590
|
+
if (!value || typeof value !== "object") return false;
|
|
9591
|
+
const candidate = value;
|
|
9592
|
+
return typeof candidate.select === "function" && typeof candidate.insert === "function" && typeof candidate.update === "function" && typeof candidate.delete === "function";
|
|
9593
|
+
};
|
|
9594
|
+
var resolveService = (config) => {
|
|
9595
|
+
if (config?.service) return config.service;
|
|
9596
|
+
const adapter = config?.persistenceAdapter || (isDrizzleDb(config?.db) ? createMikuContestDrizzlePersistenceAdapter(config.db) : null);
|
|
9597
|
+
if (adapter) {
|
|
9598
|
+
return createMikuContestPersistentService({
|
|
9599
|
+
persistenceAdapter: adapter
|
|
9600
|
+
});
|
|
9601
|
+
}
|
|
9602
|
+
return createMikuContestService();
|
|
9603
|
+
};
|
|
9604
|
+
var createGetContestSnapshotHandler = (config) => {
|
|
9605
|
+
const service = resolveService(config);
|
|
9606
|
+
return async (_request) => {
|
|
9607
|
+
const data = await service.getSnapshot();
|
|
9608
|
+
return NextResponse.json({ success: true, data });
|
|
9609
|
+
};
|
|
9610
|
+
};
|
|
9611
|
+
var createUpdateContestConfigHandler = (config) => {
|
|
9612
|
+
const service = resolveService(config);
|
|
9613
|
+
return async (request) => {
|
|
9614
|
+
try {
|
|
9615
|
+
const payload = await request.json();
|
|
9616
|
+
const data = await service.updateContestConfig(payload);
|
|
9617
|
+
return NextResponse.json({ success: true, data });
|
|
9618
|
+
} catch (error) {
|
|
9619
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9620
|
+
}
|
|
9621
|
+
};
|
|
9622
|
+
};
|
|
9623
|
+
var createCreateSubmissionHandler = (config) => {
|
|
9624
|
+
const service = resolveService(config);
|
|
9625
|
+
return async (request) => {
|
|
9626
|
+
try {
|
|
9627
|
+
const body = await request.json();
|
|
9628
|
+
const mode = body.mode || "web";
|
|
9629
|
+
const payload = body.payload;
|
|
9630
|
+
if (!payload) {
|
|
9631
|
+
return NextResponse.json({ success: false, error: "payload \u4E0D\u80FD\u4E3A\u7A7A" }, { status: 400 });
|
|
9632
|
+
}
|
|
9633
|
+
const data = await service.createSubmission(payload, mode);
|
|
9634
|
+
return NextResponse.json({ success: true, data });
|
|
9635
|
+
} catch (error) {
|
|
9636
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9637
|
+
}
|
|
9638
|
+
};
|
|
9639
|
+
};
|
|
9640
|
+
var createVoteHandler = (config) => {
|
|
9641
|
+
const service = resolveService(config);
|
|
9642
|
+
return async (request) => {
|
|
9643
|
+
try {
|
|
9644
|
+
const payload = await request.json();
|
|
9645
|
+
const data = await service.vote(payload);
|
|
9646
|
+
return NextResponse.json({ success: true, data });
|
|
9647
|
+
} catch (error) {
|
|
9648
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9649
|
+
}
|
|
9650
|
+
};
|
|
9651
|
+
};
|
|
9652
|
+
var createReviewSubmissionHandler = (config) => {
|
|
9653
|
+
const service = resolveService(config);
|
|
9654
|
+
return async (request) => {
|
|
9655
|
+
try {
|
|
9656
|
+
const payload = await request.json();
|
|
9657
|
+
const data = await service.reviewSubmission(payload);
|
|
9658
|
+
return NextResponse.json({ success: true, data });
|
|
9659
|
+
} catch (error) {
|
|
9660
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9661
|
+
}
|
|
9662
|
+
};
|
|
9663
|
+
};
|
|
9664
|
+
var buildSubmissionFilterFromQuery = (request) => {
|
|
9665
|
+
const search = request.nextUrl.searchParams;
|
|
9666
|
+
const status = search.get("status");
|
|
9667
|
+
const type = search.get("type");
|
|
9668
|
+
return {
|
|
9669
|
+
status: status ? status : void 0,
|
|
9670
|
+
type: type ? type : void 0,
|
|
9671
|
+
authorId: search.get("authorId") || void 0,
|
|
9672
|
+
authorKeyword: search.get("authorKeyword") || void 0,
|
|
9673
|
+
titleKeyword: search.get("titleKeyword") || void 0
|
|
9674
|
+
};
|
|
9675
|
+
};
|
|
9676
|
+
var createListSubmissionsHandler = (config) => {
|
|
9677
|
+
const service = resolveService(config);
|
|
9678
|
+
return async (request) => {
|
|
9679
|
+
const filter = buildSubmissionFilterFromQuery(request);
|
|
9680
|
+
const data = await service.listSubmissions(filter);
|
|
9681
|
+
return NextResponse.json({ success: true, data });
|
|
9682
|
+
};
|
|
9683
|
+
};
|
|
9684
|
+
var createSetVoterRestrictionHandler = (config) => {
|
|
9685
|
+
const service = resolveService(config);
|
|
9686
|
+
return async (request) => {
|
|
9687
|
+
try {
|
|
9688
|
+
const payload = await request.json();
|
|
9689
|
+
const data = await service.setVoterRestriction(payload);
|
|
9690
|
+
return NextResponse.json({ success: true, data });
|
|
9691
|
+
} catch (error) {
|
|
9692
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9693
|
+
}
|
|
9694
|
+
};
|
|
9695
|
+
};
|
|
9696
|
+
var createResetVotesHandler = (config) => {
|
|
9697
|
+
const service = resolveService(config);
|
|
9698
|
+
return async (request) => {
|
|
9699
|
+
try {
|
|
9700
|
+
const payload = await request.json();
|
|
9701
|
+
const data = await service.resetVotes(payload);
|
|
9702
|
+
return NextResponse.json({ success: true, data });
|
|
9703
|
+
} catch (error) {
|
|
9704
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9705
|
+
}
|
|
9706
|
+
};
|
|
9707
|
+
};
|
|
9708
|
+
var createExportSubmissionsHandler = (config) => {
|
|
9709
|
+
const service = resolveService(config);
|
|
9710
|
+
return async (request) => {
|
|
9711
|
+
const filter = buildSubmissionFilterFromQuery(request);
|
|
9712
|
+
const data = await service.exportSubmissionExcel(filter);
|
|
9713
|
+
const body = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
9714
|
+
return new NextResponse(body, {
|
|
9715
|
+
status: 200,
|
|
9716
|
+
headers: {
|
|
9717
|
+
"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
9718
|
+
"Content-Disposition": 'attachment; filename="miku-submissions.xlsx"'
|
|
9719
|
+
}
|
|
9720
|
+
});
|
|
9721
|
+
};
|
|
9722
|
+
};
|
|
9723
|
+
|
|
9724
|
+
// src/mikuContest/ui/web/index.ts
|
|
9725
|
+
var web_exports2 = {};
|
|
9726
|
+
__export(web_exports2, {
|
|
9727
|
+
MikuContestAdminPage: () => MikuContestAdminPage_default,
|
|
9728
|
+
MikuContestArtistPage: () => MikuContestArtistPage_default,
|
|
9729
|
+
MikuContestAudiencePage: () => MikuContestAudiencePage_default,
|
|
9730
|
+
MikuContestDashboard: () => MikuContestDashboard_default,
|
|
9731
|
+
MikuContestPage: () => MikuContestPage_default
|
|
9732
|
+
});
|
|
9733
|
+
var MikuContestDashboard = ({ snapshot }) => {
|
|
9734
|
+
return /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h2", null, snapshot.contest.name), /* @__PURE__ */ React69__default.createElement("p", null, snapshot.contest.theme), /* @__PURE__ */ React69__default.createElement("p", null, "\u6295\u7A3F\u6570\uFF1A", snapshot.submissions.length), /* @__PURE__ */ React69__default.createElement("p", null, "\u516C\u544A\u6570\uFF1A", snapshot.announcements.length), /* @__PURE__ */ React69__default.createElement("ul", null, snapshot.leaderboard.map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.submissionId }, "#", item.rank, " ", item.title, " - ", item.voteCount, "\u7968"))));
|
|
9735
|
+
};
|
|
9736
|
+
var MikuContestDashboard_default = MikuContestDashboard;
|
|
9737
|
+
var MikuContestAudiencePage = ({
|
|
9738
|
+
client,
|
|
9739
|
+
voterId,
|
|
9740
|
+
title = "\u89C2\u4F17\u6295\u7968\u533A"
|
|
9741
|
+
}) => {
|
|
9742
|
+
const api = useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9743
|
+
const [snapshot, setSnapshot] = useState(null);
|
|
9744
|
+
const [loading, setLoading] = useState(false);
|
|
9745
|
+
const [error, setError] = useState(null);
|
|
9746
|
+
const approvedWorks = useMemo(() => {
|
|
9747
|
+
if (!snapshot) return [];
|
|
9748
|
+
return snapshot.submissions.filter((item) => item.status === "approved");
|
|
9749
|
+
}, [snapshot]);
|
|
9750
|
+
const loadSnapshot = async () => {
|
|
9751
|
+
setLoading(true);
|
|
9752
|
+
setError(null);
|
|
9753
|
+
try {
|
|
9754
|
+
const data = await api.getSnapshot();
|
|
9755
|
+
setSnapshot(data);
|
|
9756
|
+
} catch (e) {
|
|
9757
|
+
setError(e.message);
|
|
9758
|
+
} finally {
|
|
9759
|
+
setLoading(false);
|
|
9760
|
+
}
|
|
9761
|
+
};
|
|
9762
|
+
useEffect(() => {
|
|
9763
|
+
void loadSnapshot();
|
|
9764
|
+
}, []);
|
|
9765
|
+
const handleVote = async (submission) => {
|
|
9766
|
+
if (!snapshot) return;
|
|
9767
|
+
try {
|
|
9768
|
+
await api.vote({
|
|
9769
|
+
contestId: snapshot.contest.id,
|
|
9770
|
+
submissionId: submission.id,
|
|
9771
|
+
voterId
|
|
9772
|
+
});
|
|
9773
|
+
await loadSnapshot();
|
|
9774
|
+
} catch (e) {
|
|
9775
|
+
setError(e.message);
|
|
9776
|
+
}
|
|
9777
|
+
};
|
|
9778
|
+
return /* @__PURE__ */ React69__default.createElement("section", null, /* @__PURE__ */ React69__default.createElement("h2", null, title), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void loadSnapshot(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, !snapshot ? null : /* @__PURE__ */ React69__default.createElement(React69__default.Fragment, null, /* @__PURE__ */ React69__default.createElement("p", null, "\u8D5B\u4E8B\uFF1A", snapshot.contest.name, "\uFF5C\u4E3B\u9898\uFF1A", snapshot.contest.theme), /* @__PURE__ */ React69__default.createElement("p", null, "\u5DF2\u8FC7\u5BA1\u4F5C\u54C1\uFF1A", approvedWorks.length, "\uFF5C\u6BCF\u65E5\u4E0A\u9650\uFF1A", snapshot.contest.votingRules.maxVotesPerDay), /* @__PURE__ */ React69__default.createElement("ul", null, approvedWorks.map((work) => /* @__PURE__ */ React69__default.createElement("li", { key: work.id }, /* @__PURE__ */ React69__default.createElement("strong", null, work.title), "\uFF08", work.authorNickname, "\uFF09- \u5F53\u524D ", work.voteCount, " \u7968", " ", /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void handleVote(work) }, "\u6295\u7968"))))));
|
|
9779
|
+
};
|
|
9780
|
+
var MikuContestAudiencePage_default = MikuContestAudiencePage;
|
|
9781
|
+
var workTypes = ["visual", "video", "text", "audio"];
|
|
9782
|
+
var MikuContestArtistPage = ({
|
|
9783
|
+
client,
|
|
9784
|
+
authorId,
|
|
9785
|
+
authorNickname,
|
|
9786
|
+
title = "\u753B\u5E08\u6295\u7A3F\u533A"
|
|
9787
|
+
}) => {
|
|
9788
|
+
const api = useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9789
|
+
const [snapshot, setSnapshot] = useState(null);
|
|
9790
|
+
const [mySubmissions, setMySubmissions] = useState([]);
|
|
9791
|
+
const [submitting, setSubmitting] = useState(false);
|
|
9792
|
+
const [loading, setLoading] = useState(false);
|
|
9793
|
+
const [error, setError] = useState(null);
|
|
9794
|
+
const [titleInput, setTitleInput] = useState("");
|
|
9795
|
+
const [descInput, setDescInput] = useState("");
|
|
9796
|
+
const [coverImage, setCoverImage] = useState("");
|
|
9797
|
+
const [workType, setWorkType] = useState("visual");
|
|
9798
|
+
const loadData = async () => {
|
|
9799
|
+
setLoading(true);
|
|
9800
|
+
setError(null);
|
|
9801
|
+
try {
|
|
9802
|
+
const [contest, mine] = await Promise.all([
|
|
9803
|
+
api.getSnapshot(),
|
|
9804
|
+
api.listSubmissions({ authorId })
|
|
9805
|
+
]);
|
|
9806
|
+
setSnapshot(contest);
|
|
9807
|
+
setMySubmissions(mine);
|
|
9808
|
+
} catch (e) {
|
|
9809
|
+
setError(e.message);
|
|
9810
|
+
} finally {
|
|
9811
|
+
setLoading(false);
|
|
9812
|
+
}
|
|
9813
|
+
};
|
|
9814
|
+
useEffect(() => {
|
|
9815
|
+
void loadData();
|
|
9816
|
+
}, []);
|
|
9817
|
+
const submitWork = async () => {
|
|
9818
|
+
if (!snapshot) return;
|
|
9819
|
+
const payload = {
|
|
9820
|
+
contestId: snapshot.contest.id,
|
|
9821
|
+
authorId,
|
|
9822
|
+
authorNickname,
|
|
9823
|
+
title: titleInput,
|
|
9824
|
+
description: descInput,
|
|
9825
|
+
type: workType,
|
|
9826
|
+
tags: ["web"],
|
|
9827
|
+
content: {
|
|
9828
|
+
coverImage,
|
|
9829
|
+
images: coverImage ? [coverImage] : void 0
|
|
9830
|
+
}
|
|
9831
|
+
};
|
|
9832
|
+
setSubmitting(true);
|
|
9833
|
+
setError(null);
|
|
9834
|
+
try {
|
|
9835
|
+
await api.createSubmission(payload, "web");
|
|
9836
|
+
setTitleInput("");
|
|
9837
|
+
setDescInput("");
|
|
9838
|
+
setCoverImage("");
|
|
9839
|
+
await loadData();
|
|
9840
|
+
} catch (e) {
|
|
9841
|
+
setError(e.message);
|
|
9842
|
+
} finally {
|
|
9843
|
+
setSubmitting(false);
|
|
9844
|
+
}
|
|
9845
|
+
};
|
|
9846
|
+
return /* @__PURE__ */ React69__default.createElement("section", null, /* @__PURE__ */ React69__default.createElement("h2", null, title), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void loadData(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u65B0\u5EFA\u6295\u7A3F"), /* @__PURE__ */ React69__default.createElement("input", { value: titleInput, onChange: (e) => setTitleInput(e.target.value), placeholder: "\u4F5C\u54C1\u6807\u9898" }), /* @__PURE__ */ React69__default.createElement("br", null), /* @__PURE__ */ React69__default.createElement("textarea", { value: descInput, onChange: (e) => setDescInput(e.target.value), placeholder: "\u4F5C\u54C1\u7B80\u4ECB" }), /* @__PURE__ */ React69__default.createElement("br", null), /* @__PURE__ */ React69__default.createElement("input", { value: coverImage, onChange: (e) => setCoverImage(e.target.value), placeholder: "\u5C01\u9762 URL" }), /* @__PURE__ */ React69__default.createElement("br", null), /* @__PURE__ */ React69__default.createElement("select", { value: workType, onChange: (e) => setWorkType(e.target.value) }, workTypes.map((item) => /* @__PURE__ */ React69__default.createElement("option", { value: item, key: item }, item))), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void submitWork(), disabled: submitting || !snapshot }, submitting ? "\u63D0\u4EA4\u4E2D..." : "\u63D0\u4EA4\u7A3F\u4EF6")), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u6211\u7684\u6295\u7A3F\uFF08", mySubmissions.length, "\uFF09"), /* @__PURE__ */ React69__default.createElement("ul", null, mySubmissions.map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.id }, item.title, "\uFF5C\u72B6\u6001\uFF1A", item.status, "\uFF5C\u7968\u6570\uFF1A", item.voteCount, item.rejectReason ? `\uFF5C\u9A73\u56DE\uFF1A${item.rejectReason}` : "")))));
|
|
9847
|
+
};
|
|
9848
|
+
var MikuContestArtistPage_default = MikuContestArtistPage;
|
|
9849
|
+
var MikuContestAdminPage = ({
|
|
9850
|
+
client,
|
|
9851
|
+
adminId,
|
|
9852
|
+
title = "\u7BA1\u7406\u5458\u9762\u677F"
|
|
9853
|
+
}) => {
|
|
9854
|
+
const api = useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9855
|
+
const [snapshot, setSnapshot] = useState(null);
|
|
9856
|
+
const [submissions, setSubmissions] = useState([]);
|
|
9857
|
+
const [loading, setLoading] = useState(false);
|
|
9858
|
+
const [error, setError] = useState(null);
|
|
9859
|
+
const [voterId, setVoterId] = useState("");
|
|
9860
|
+
const loadData = async () => {
|
|
9861
|
+
setLoading(true);
|
|
9862
|
+
setError(null);
|
|
9863
|
+
try {
|
|
9864
|
+
const [contest, list] = await Promise.all([api.getSnapshot(), api.listSubmissions()]);
|
|
9865
|
+
setSnapshot(contest);
|
|
9866
|
+
setSubmissions(list);
|
|
9867
|
+
} catch (e) {
|
|
9868
|
+
setError(e.message);
|
|
9869
|
+
} finally {
|
|
9870
|
+
setLoading(false);
|
|
9871
|
+
}
|
|
9872
|
+
};
|
|
9873
|
+
useEffect(() => {
|
|
9874
|
+
void loadData();
|
|
9875
|
+
}, []);
|
|
9876
|
+
const review = async (item, action) => {
|
|
9877
|
+
try {
|
|
9878
|
+
await api.reviewSubmission({
|
|
9879
|
+
submissionId: item.id,
|
|
9880
|
+
reviewerId: adminId,
|
|
9881
|
+
action,
|
|
9882
|
+
rejectReason: action === "reject" ? "\u7BA1\u7406\u5458\u9A73\u56DE" : void 0
|
|
9883
|
+
});
|
|
9884
|
+
await loadData();
|
|
9885
|
+
} catch (e) {
|
|
9886
|
+
setError(e.message);
|
|
9887
|
+
}
|
|
9888
|
+
};
|
|
9889
|
+
const toggleVoting = async (enabled) => {
|
|
9890
|
+
if (!snapshot) return;
|
|
9891
|
+
try {
|
|
9892
|
+
await api.updateContestConfig({
|
|
9893
|
+
toggles: {
|
|
9894
|
+
...snapshot.contest.toggles,
|
|
9895
|
+
votingEnabled: enabled
|
|
9896
|
+
}
|
|
9897
|
+
});
|
|
9898
|
+
await loadData();
|
|
9899
|
+
} catch (e) {
|
|
9900
|
+
setError(e.message);
|
|
9901
|
+
}
|
|
9902
|
+
};
|
|
9903
|
+
const setRestriction = async (banned) => {
|
|
9904
|
+
if (!snapshot || !voterId.trim()) return;
|
|
9905
|
+
try {
|
|
9906
|
+
await api.setVoterRestriction({
|
|
9907
|
+
voterId: voterId.trim(),
|
|
9908
|
+
banned,
|
|
9909
|
+
reason: banned ? "\u7BA1\u7406\u5458\u624B\u52A8\u5C01\u7981" : "\u7BA1\u7406\u5458\u89E3\u9664\u5C01\u7981",
|
|
9910
|
+
operatorId: adminId
|
|
9911
|
+
});
|
|
9912
|
+
setVoterId("");
|
|
9913
|
+
} catch (e) {
|
|
9914
|
+
setError(e.message);
|
|
9915
|
+
}
|
|
9916
|
+
};
|
|
9917
|
+
const resetVotesByVoter = async () => {
|
|
9918
|
+
if (!voterId.trim()) return;
|
|
9919
|
+
try {
|
|
9920
|
+
await api.resetVotes({ voterId: voterId.trim() });
|
|
9921
|
+
setVoterId("");
|
|
9922
|
+
await loadData();
|
|
9923
|
+
} catch (e) {
|
|
9924
|
+
setError(e.message);
|
|
9925
|
+
}
|
|
9926
|
+
};
|
|
9927
|
+
const exportExcel = async () => {
|
|
9928
|
+
try {
|
|
9929
|
+
const data = await api.exportSubmissions();
|
|
9930
|
+
const blob = new Blob([data], {
|
|
9931
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
9932
|
+
});
|
|
9933
|
+
const url = URL.createObjectURL(blob);
|
|
9934
|
+
const a = document.createElement("a");
|
|
9935
|
+
a.href = url;
|
|
9936
|
+
a.download = "miku-submissions.xlsx";
|
|
9937
|
+
a.click();
|
|
9938
|
+
URL.revokeObjectURL(url);
|
|
9939
|
+
} catch (e) {
|
|
9940
|
+
setError(e.message);
|
|
9941
|
+
}
|
|
9942
|
+
};
|
|
9943
|
+
return /* @__PURE__ */ React69__default.createElement("section", null, /* @__PURE__ */ React69__default.createElement("h2", null, title), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void loadData(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u8D5B\u4E8B\u5F00\u5173"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void toggleVoting(true), disabled: !snapshot }, "\u5F00\u542F\u6295\u7968"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void toggleVoting(false), disabled: !snapshot }, "\u5173\u95ED\u6295\u7968")), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u6295\u7A3F\u5BA1\u6838\uFF08", submissions.length, "\uFF09"), /* @__PURE__ */ React69__default.createElement("ul", null, submissions.map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.id }, /* @__PURE__ */ React69__default.createElement("strong", null, item.title), "\uFF5C\u4F5C\u8005\uFF1A", item.authorNickname, "\uFF5C\u72B6\u6001\uFF1A", item.status, "\uFF5C\u7968\u6570\uFF1A", item.voteCount, item.status === "pending" ? /* @__PURE__ */ React69__default.createElement(React69__default.Fragment, null, " ", /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void review(item, "approve") }, "\u901A\u8FC7"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void review(item, "reject") }, "\u9A73\u56DE")) : null)))), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u6295\u7968\u98CE\u63A7"), /* @__PURE__ */ React69__default.createElement("input", { value: voterId, onChange: (e) => setVoterId(e.target.value), placeholder: "voterId" }), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void setRestriction(true), disabled: !snapshot }, "\u5C01\u7981\u6295\u7968"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void setRestriction(false), disabled: !snapshot }, "\u89E3\u9664\u5C01\u7981"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void resetVotesByVoter() }, "\u6E05\u96F6\u8BE5\u7528\u6237\u7968\u6570")), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u5BFC\u51FA"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void exportExcel() }, "\u5BFC\u51FA\u6295\u7A3F Excel")));
|
|
9944
|
+
};
|
|
9945
|
+
var MikuContestAdminPage_default = MikuContestAdminPage;
|
|
9946
|
+
|
|
9947
|
+
// src/mikuContest/ui/web/pages/MikuContestPage.tsx
|
|
9948
|
+
var MikuContestPage = ({
|
|
9949
|
+
defaultView = "audience",
|
|
9950
|
+
viewerVoterId = "viewer-demo",
|
|
9951
|
+
artistId = "artist-demo",
|
|
9952
|
+
artistNickname = "Demo \u753B\u5E08",
|
|
9953
|
+
adminId = "admin-demo"
|
|
9954
|
+
}) => {
|
|
9955
|
+
const [view, setView] = useState(defaultView);
|
|
9956
|
+
return /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h1", null, "Miku Contest"), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setView("audience") }, "\u89C2\u4F17\u7AEF"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setView("artist") }, "\u753B\u5E08\u7AEF"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setView("admin") }, "\u7BA1\u7406\u5458\u7AEF")), view === "audience" ? /* @__PURE__ */ React69__default.createElement(MikuContestAudiencePage_default, { voterId: viewerVoterId }) : null, view === "artist" ? /* @__PURE__ */ React69__default.createElement(MikuContestArtistPage_default, { authorId: artistId, authorNickname: artistNickname }) : null, view === "admin" ? /* @__PURE__ */ React69__default.createElement(MikuContestAdminPage_default, { adminId }) : null);
|
|
9957
|
+
};
|
|
9958
|
+
var MikuContestPage_default = MikuContestPage;
|
|
9959
|
+
|
|
9960
|
+
// src/mikuContest/ui/miniapp/index.ts
|
|
9961
|
+
var miniapp_exports2 = {};
|
|
9962
|
+
__export(miniapp_exports2, {
|
|
9963
|
+
MikuContestMiniappHome: () => MikuContestMiniappHome_default,
|
|
9964
|
+
MikuContestMiniappPage: () => MikuContestMiniappPage_default
|
|
9965
|
+
});
|
|
9966
|
+
var MikuContestMiniappHome = ({ snapshot }) => {
|
|
9967
|
+
return /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, snapshot.contest.name), /* @__PURE__ */ React69__default.createElement("p", null, "\u6295\u7A3F\uFF1A", snapshot.submissions.length, " | \u516C\u544A\uFF1A", snapshot.announcements.length), /* @__PURE__ */ React69__default.createElement("ol", null, snapshot.leaderboard.slice(0, 3).map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.submissionId }, item.title, "\uFF08", item.voteCount, "\u7968\uFF09"))));
|
|
9968
|
+
};
|
|
9969
|
+
var MikuContestMiniappHome_default = MikuContestMiniappHome;
|
|
9970
|
+
var MikuContestMiniappPage = () => {
|
|
9971
|
+
const { service, snapshot, refresh } = useMikuContest();
|
|
9972
|
+
const [tab, setTab] = useState("vote");
|
|
9973
|
+
const [voterId, setVoterId] = useState("miniapp-voter");
|
|
9974
|
+
const [authorId, setAuthorId] = useState("miniapp-author");
|
|
9975
|
+
const [authorNickname, setAuthorNickname] = useState("\u5C0F\u7A0B\u5E8F\u753B\u5E08");
|
|
9976
|
+
const [title, setTitle] = useState("");
|
|
9977
|
+
const [desc, setDesc] = useState("");
|
|
9978
|
+
const [type, setType] = useState("visual");
|
|
9979
|
+
const [error, setError] = useState(null);
|
|
9980
|
+
const approvedWorks = useMemo(() => {
|
|
9981
|
+
return snapshot.submissions.filter((item) => item.status === "approved");
|
|
9982
|
+
}, [snapshot.submissions]);
|
|
9983
|
+
const vote = (submissionId) => {
|
|
9984
|
+
try {
|
|
9985
|
+
service.vote({
|
|
9986
|
+
contestId: snapshot.contest.id,
|
|
9987
|
+
submissionId,
|
|
9988
|
+
voterId
|
|
9989
|
+
});
|
|
9990
|
+
refresh();
|
|
9991
|
+
setError(null);
|
|
9992
|
+
} catch (e) {
|
|
9993
|
+
setError(e.message);
|
|
9994
|
+
}
|
|
9995
|
+
};
|
|
9996
|
+
const submit = () => {
|
|
9997
|
+
try {
|
|
9998
|
+
service.createSubmission(
|
|
9999
|
+
{
|
|
10000
|
+
contestId: snapshot.contest.id,
|
|
10001
|
+
authorId,
|
|
10002
|
+
authorNickname,
|
|
10003
|
+
title,
|
|
10004
|
+
description: desc,
|
|
10005
|
+
type,
|
|
10006
|
+
content: {}
|
|
10007
|
+
},
|
|
10008
|
+
"miniapp"
|
|
10009
|
+
);
|
|
10010
|
+
setTitle("");
|
|
10011
|
+
setDesc("");
|
|
10012
|
+
refresh();
|
|
10013
|
+
setError(null);
|
|
10014
|
+
} catch (e) {
|
|
10015
|
+
setError(e.message);
|
|
10016
|
+
}
|
|
10017
|
+
};
|
|
10018
|
+
return /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, snapshot.contest.name), /* @__PURE__ */ React69__default.createElement("p", null, "\u5C0F\u7A0B\u5E8F\u7AEF\u793A\u4F8B\u9875\u9762"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setTab("vote") }, "\u89C2\u4F17\u6295\u7968"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setTab("submit") }, "\u753B\u5E08\u6295\u7A3F"), error ? /* @__PURE__ */ React69__default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, tab === "vote" ? /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("input", { value: voterId, onChange: (e) => setVoterId(e.target.value), placeholder: "voterId" }), /* @__PURE__ */ React69__default.createElement("ul", null, approvedWorks.map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.id }, item.title, "\uFF08", item.voteCount, "\u7968\uFF09", /* @__PURE__ */ React69__default.createElement("button", { onClick: () => vote(item.id) }, "\u6295\u7968"))))) : null, tab === "submit" ? /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("input", { value: authorId, onChange: (e) => setAuthorId(e.target.value), placeholder: "authorId" }), /* @__PURE__ */ React69__default.createElement("input", { value: authorNickname, onChange: (e) => setAuthorNickname(e.target.value), placeholder: "\u4F5C\u8005\u6635\u79F0" }), /* @__PURE__ */ React69__default.createElement("input", { value: title, onChange: (e) => setTitle(e.target.value), placeholder: "\u4F5C\u54C1\u6807\u9898" }), /* @__PURE__ */ React69__default.createElement("input", { value: desc, onChange: (e) => setDesc(e.target.value), placeholder: "\u4F5C\u54C1\u7B80\u4ECB" }), /* @__PURE__ */ React69__default.createElement("select", { value: type, onChange: (e) => setType(e.target.value) }, /* @__PURE__ */ React69__default.createElement("option", { value: "visual" }, "visual"), /* @__PURE__ */ React69__default.createElement("option", { value: "video" }, "video"), /* @__PURE__ */ React69__default.createElement("option", { value: "text" }, "text"), /* @__PURE__ */ React69__default.createElement("option", { value: "audio" }, "audio")), /* @__PURE__ */ React69__default.createElement("button", { onClick: submit }, "\u63D0\u4EA4")) : null);
|
|
10019
|
+
};
|
|
10020
|
+
var MikuContestMiniappPage_default = MikuContestMiniappPage;
|
|
10021
|
+
|
|
8777
10022
|
// src/storage/adapters/react-native-adapter.ts
|
|
8778
10023
|
var AsyncStorage = null;
|
|
8779
10024
|
try {
|
|
@@ -9127,6 +10372,6 @@ function useElectronStorage(key, defaultValue) {
|
|
|
9127
10372
|
return useStorage(electronStorage, key, defaultValue);
|
|
9128
10373
|
}
|
|
9129
10374
|
|
|
9130
|
-
export { About_default as About, Dialog as AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, DialogDescription as AlertDialogDescription, DialogFooter as AlertDialogFooter, DialogHeader as AlertDialogHeader, DialogOverlay as AlertDialogOverlay, DialogPortal as AlertDialogPortal, DialogTitle as AlertDialogTitle, DialogTrigger as AlertDialogTrigger, AutoOpenModal, Avatar, AvatarFallback, AvatarImage, BackButton, BackgroundRemover, Badge, BoothConfigPage, BoothRedeemPanel, BoothSuccessCard, BoothUploadPanel, BoothVaultService, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryFilter, CollisionBalls, CompletionFilterComponent, ConfirmModal, ConsoleLoggerAdapter, Contact_default as Contact, DANMAKU_MAX_LENGTH, DANMAKU_TRACK_COUNT, DEFAULT_FESTIVAL_CARD_CONFIG, DEFAULT_MAX_ACTIVE_FIREWORKS, DEFAULT_MAX_PARTICLES, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DanmakuPanel, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DraggableExperimentGrid, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, EnhancedAvatar, ExperimentCard, ExperimentGrid, ExperimentItemGrid, FIREWORK_KIND_LABELS, FestivalCardBook3D, FestivalCardConfigEditor, FestivalCardConfigPage, FestivalCardManagedPage, FestivalCardPageRenderer, FestivalCardService, FestivalCardStudio, FilterButtonGroup, FireworksCanvas, FireworksControlPanel, FloatingMenu_default as FloatingMenu, FloatingMenuExample_default as FloatingMenuExample, GenericOrderManager, Grid, Home_default as Home, ImageMappingPanel, InMemorySkillRegistry, Input, Label, LocalImageMappingPanel, LogLevel, Logger, MIKU_PALETTE, MikuFireworks3D, Modal, NORMAL_PALETTE, Navigation_default as Navigation, NavigationItem_default as NavigationItem, NavigationToggle_default as NavigationToggle, OCRScanner, PageHeader, PermissionGuard, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ProfileButton, ProfileModal, Progress, ProjectCarousel, ScreenReceiverPanel, ScrollArea, ScrollBar, SearchBox, SearchResultHint, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SentimentAnalyzer, Separator, Dialog as Sheet, DialogClose as SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, DialogOverlay as SheetOverlay, DialogPortal as SheetPortal, SheetTitle, DialogTrigger as SheetTrigger, SmartAssistant, SortControl, SortModeToggle, SortableExperimentItem, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Timeline, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserInfoBar, WebSocketTransport, applyPromptTemplate, arrayUtils, badgeVariants, buttonVariants, cn, createAiClient, createChatSession, createInMemoryFestivalCardDb, createLogger, createOpenAICompatibleProvider, createSkillRegistry, debugUtils, defaultVocaloidBoothConfig, errorUtils, fileUtils, filterExperiments, formatTime, generateMatchCode, getAllTags, getCategoryColor, getCategoryDisplayName, getCompletionFilterDisplayName, getCompletionStatusColor, getCompletionStatusText, getExperimentCounts, japaneseUtils, logger, normalizeFestivalCardConfig, normalizeMatchCode, normalizePromptVariables, normalizeVocaloidBoothConfig, resizeFestivalCardPages, resolveScreenReceiverSignalUrl, skillToToolDefinition, sortExperiments, stringUtils, useAiChat, useAsyncStorage, useBackgroundRemoval, useDanmakuController, useElectronStorage, useFestivalCardConfig, useFireworksEngine, useFireworksRealtime, useLocalStorage, useOCR, useScreenReceiver, useSentimentAnalysis, useStorage, useTaroStorage, useTextGeneration, validateExperiment, validators };
|
|
10375
|
+
export { About_default as About, Dialog as AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, DialogDescription as AlertDialogDescription, DialogFooter as AlertDialogFooter, DialogHeader as AlertDialogHeader, DialogOverlay as AlertDialogOverlay, DialogPortal as AlertDialogPortal, DialogTitle as AlertDialogTitle, DialogTrigger as AlertDialogTrigger, AutoOpenModal, Avatar, AvatarFallback, AvatarImage, BackButton, BackgroundRemover, Badge, BoothConfigPage, BoothRedeemPanel, BoothSuccessCard, BoothUploadPanel, BoothVaultService, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryFilter, CollisionBalls, CompletionFilterComponent, ConfirmModal, ConsoleLoggerAdapter, Contact_default as Contact, DANMAKU_MAX_LENGTH, DANMAKU_TRACK_COUNT, DEFAULT_FESTIVAL_CARD_CONFIG, DEFAULT_MAX_ACTIVE_FIREWORKS, DEFAULT_MAX_PARTICLES, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DanmakuPanel, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DraggableExperimentGrid, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, EnhancedAvatar, ExperimentCard, ExperimentGrid, ExperimentItemGrid, FIREWORK_KIND_LABELS, FestivalCardBook3D, FestivalCardConfigEditor, FestivalCardConfigPage, FestivalCardManagedPage, FestivalCardPageRenderer, FestivalCardService, FestivalCardStudio, FilterButtonGroup, FireworksCanvas, FireworksControlPanel, FloatingMenu_default as FloatingMenu, FloatingMenuExample_default as FloatingMenuExample, GenericOrderManager, Grid, Home_default as Home, ImageMappingPanel, InMemorySkillRegistry, Input, Label, LocalImageMappingPanel, LogLevel, Logger, MIKU_PALETTE, MikuContestPersistentService, MikuContestService, MikuContestStateDbService, MikuFireworks3D, Modal, NORMAL_PALETTE, Navigation_default as Navigation, NavigationItem_default as NavigationItem, NavigationToggle_default as NavigationToggle, OCRScanner, PageHeader, PermissionGuard, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ProfileButton, ProfileModal, Progress, ProjectCarousel, ScreenReceiverPanel, ScrollArea, ScrollBar, SearchBox, SearchResultHint, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SentimentAnalyzer, Separator, Dialog as Sheet, DialogClose as SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, DialogOverlay as SheetOverlay, DialogPortal as SheetPortal, SheetTitle, DialogTrigger as SheetTrigger, SmartAssistant, SortControl, SortModeToggle, SortableExperimentItem, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Timeline, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserInfoBar, WebSocketTransport, applyPromptTemplate, arrayUtils, badgeVariants, buttonVariants, checkVoteEligibility, cn, createAiClient, createChatSession, createCreateSubmissionHandler, createDefaultMikuContestConfig, createExportSubmissionsHandler, createGetContestSnapshotHandler, createInMemoryFestivalCardDb, createListSubmissionsHandler, createLogger, createMikuContestApiClient, createMikuContestDrizzlePersistenceAdapter, createMikuContestPersistentService, createMikuContestService, createOpenAICompatibleProvider, createResetVotesHandler, createReviewSubmissionHandler, createSetVoterRestrictionHandler, createSkillRegistry, createUpdateContestConfigHandler, createVoteHandler, debugUtils, defaultMikuVotingRules, defaultVocaloidBoothConfig, errorUtils, fileUtils, filterExperiments, formatTime, generateMatchCode, getAllTags, getCategoryColor, getCategoryDisplayName, getCompletionFilterDisplayName, getCompletionStatusColor, getCompletionStatusText, getExperimentCounts, japaneseUtils, logger, mikuContestConfigs, mikuContestDbService, mikuContestNotices, mikuContestSubmissions, mikuContestVoterRestrictions, mikuContestVotes, miniapp_exports as miniappService, miniapp_exports2 as miniappUI, normalizeFestivalCardConfig, normalizeMatchCode, normalizePromptVariables, normalizeVocaloidBoothConfig, resizeFestivalCardPages, resolveScreenReceiverSignalUrl, skillToToolDefinition, sortByVotesDesc, sortExperiments, stringUtils, toVoteDayKey, useAiChat, useAsyncStorage, useBackgroundRemoval, useDanmakuController, useElectronStorage, useFestivalCardConfig, useFireworksEngine, useFireworksRealtime, useLocalStorage, useMikuContest, useOCR, useScreenReceiver, useSentimentAnalysis, useStorage, useTaroStorage, useTextGeneration, validateExperiment, validateMikuSubmissionInput, validators, web_exports as webService, web_exports2 as webUI };
|
|
9131
10376
|
//# sourceMappingURL=index.mjs.map
|
|
9132
10377
|
//# sourceMappingURL=index.mjs.map
|