diff --git a/Dockerfile.cn b/Dockerfile.cn
index 19ce3cc..26b2f61 100644
--- a/Dockerfile.cn
+++ b/Dockerfile.cn
@@ -2,7 +2,7 @@
# 升级到 Node.js v20 或更高版本,以解决 `ReferenceError: File is not defined` 问题
# 参考链接:https://github.com/vercel/next.js/discussions/56032
-FROM dockerp.com/node:22-alpine AS base
+FROM docker.1ms.run/node:22-alpine AS base
# 仅在需要时安装依赖
FROM base AS deps
diff --git a/bun.lock b/bun.lock
index 2071c9d..4b2cc51 100644
--- a/bun.lock
+++ b/bun.lock
@@ -4,6 +4,7 @@
"": {
"name": "monaco-editor-lsp-next",
"dependencies": {
+ "@ai-sdk/deepseek": "^0.2.14",
"@ai-sdk/openai": "^1.3.0",
"@ai-sdk/react": "^1.2.0",
"@auth/prisma-adapter": "^2.8.0",
@@ -32,13 +33,14 @@
"@tanstack/react-table": "^8.21.2",
"@types/vscode": "^1.97.0",
"ai": "^4.2.0",
- "bcrypt": "^5.1.1",
+ "bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"devicons-react": "^1.4.0",
"dockerode": "^4.0.4",
"dockview": "^4.2.1",
+ "flexlayout-react": "^0.7.15",
"framer-motion": "^12.7.3",
"github-markdown-css": "^5.8.1",
"lucide-react": "^0.482.0",
@@ -73,7 +75,6 @@
"devDependencies": {
"@eslint/eslintrc": "^3",
"@shikijs/monaco": "^3.2.1",
- "@types/bcrypt": "^5.0.2",
"@types/dockerode": "^3.3.35",
"@types/node": "^20",
"@types/react": "^19",
@@ -96,8 +97,12 @@
},
},
"packages": {
+ "@ai-sdk/deepseek": ["@ai-sdk/deepseek@0.2.14", "https://registry.npmmirror.com/@ai-sdk/deepseek/-/deepseek-0.2.14.tgz", { "dependencies": { "@ai-sdk/openai-compatible": "0.2.14", "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-TISD1FzBWuQkHEHoVustoJILV33ZNgfYxeTkq1xU2vHEZuWTGZV7/IlXixyFsfqDCdVgrbLeIABk5FuCw7niLg=="],
+
"@ai-sdk/openai": ["@ai-sdk/openai@1.3.22", "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-1.3.22.tgz", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw=="],
+ "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@0.2.14", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-0.2.14.tgz", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icjObfMCHKSIbywijaoLdZ1nSnuRnWgMEMLgwoxPJgxsUHMx0aVORnsLUid4SPtdhHI3X2masrt6iaEQLvOSFw=="],
+
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-1.1.3.tgz", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
@@ -278,8 +283,6 @@
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "https://registry.npmmirror.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
- "@mapbox/node-pre-gyp": ["@mapbox/node-pre-gyp@1.0.11", "https://registry.npmmirror.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", { "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", "node-fetch": "^2.6.7", "nopt": "^5.0.0", "npmlog": "^5.0.1", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.11" }, "bin": { "node-pre-gyp": "bin/node-pre-gyp" } }, "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ=="],
-
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.0", "https://registry.npmmirror.com/@mdx-js/mdx/-/mdx-3.1.0.tgz", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw=="],
"@mdx-js/react": ["@mdx-js/react@3.1.0", "https://registry.npmmirror.com/@mdx-js/react/-/react-3.1.0.tgz", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ=="],
@@ -486,8 +489,6 @@
"@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
- "@types/bcrypt": ["@types/bcrypt@5.0.2", "https://registry.npmmirror.com/@types/bcrypt/-/bcrypt-5.0.2.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ=="],
-
"@types/debug": ["@types/debug@4.1.12", "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
"@types/diff-match-patch": ["@types/diff-match-patch@1.0.36", "https://registry.npmmirror.com/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="],
@@ -580,16 +581,12 @@
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.7.2", "https://registry.npmmirror.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA=="],
- "abbrev": ["abbrev@1.1.1", "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="],
-
"accepts": ["accepts@2.0.0", "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.14.1", "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
- "agent-base": ["agent-base@6.0.2", "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
-
"ai": ["ai@4.3.15", "https://registry.npmmirror.com/ai/-/ai-4.3.15.tgz", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-TYKRzbWg6mx/pmTadlAEIhuQtzfHUV0BbLY72+zkovXwq/9xhcH24IlQmkyBpElK6/4ArS0dHdOOtR1jOPVwtg=="],
"ajv": ["ajv@6.12.6", "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
@@ -602,10 +599,6 @@
"anymatch": ["anymatch@3.1.3", "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
- "aproba": ["aproba@2.0.0", "https://registry.npmmirror.com/aproba/-/aproba-2.0.0.tgz", {}, "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="],
-
- "are-we-there-yet": ["are-we-there-yet@2.0.0", "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw=="],
-
"arg": ["arg@5.0.2", "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"argparse": ["argparse@2.0.1", "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -656,10 +649,10 @@
"base64-js": ["base64-js@1.5.1", "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
- "bcrypt": ["bcrypt@5.1.1", "https://registry.npmmirror.com/bcrypt/-/bcrypt-5.1.1.tgz", { "dependencies": { "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" } }, "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww=="],
-
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
+ "bcryptjs": ["bcryptjs@3.0.2", "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-3.0.2.tgz", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog=="],
+
"binary-extensions": ["binary-extensions@2.3.0", "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"bl": ["bl@4.1.0", "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
@@ -724,8 +717,6 @@
"color-string": ["color-string@1.9.1", "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
- "color-support": ["color-support@1.1.3", "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="],
-
"colorette": ["colorette@2.0.20", "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
@@ -734,8 +725,6 @@
"concat-map": ["concat-map@0.0.1", "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
- "console-control-strings": ["console-control-strings@1.1.0", "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="],
-
"content-disposition": ["content-disposition@1.0.0", "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.0.tgz", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
"content-type": ["content-type@1.0.5", "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
@@ -778,8 +767,6 @@
"define-properties": ["define-properties@1.2.1", "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
- "delegates": ["delegates@1.0.0", "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="],
-
"depd": ["depd@2.0.0", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dequal": ["dequal@2.0.3", "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
@@ -942,6 +929,8 @@
"flatted": ["flatted@3.3.3", "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
+ "flexlayout-react": ["flexlayout-react@0.7.15", "https://registry.npmmirror.com/flexlayout-react/-/flexlayout-react-0.7.15.tgz", { "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, "sha512-ydTMdEoQO5BniylxVkSxa59rEY0+96lqqRII+QK+yq6028eHywPuxZawt4g45y5pMb9ptP4N9HPAQXAFsxwowQ=="],
+
"for-each": ["for-each@0.3.5", "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"foreground-child": ["foreground-child@3.3.1", "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
@@ -954,10 +943,6 @@
"fs-constants": ["fs-constants@1.0.0", "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
- "fs-minipass": ["fs-minipass@2.1.0", "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
-
- "fs.realpath": ["fs.realpath@1.0.0", "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
-
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -966,8 +951,6 @@
"functions-have-names": ["functions-have-names@1.2.3", "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
- "gauge": ["gauge@3.0.2", "https://registry.npmmirror.com/gauge/-/gauge-3.0.2.tgz", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", "console-control-strings": "^1.0.0", "has-unicode": "^2.0.1", "object-assign": "^4.1.1", "signal-exit": "^3.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.2" } }, "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q=="],
-
"get-caller-file": ["get-caller-file@2.0.5", "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
@@ -1008,8 +991,6 @@
"has-tostringtag": ["has-tostringtag@1.0.2", "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
- "has-unicode": ["has-unicode@2.0.1", "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="],
-
"hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hast-util-from-dom": ["hast-util-from-dom@5.0.1", "https://registry.npmmirror.com/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", { "dependencies": { "@types/hast": "^3.0.0", "hastscript": "^9.0.0", "web-namespaces": "^2.0.0" } }, "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q=="],
@@ -1046,8 +1027,6 @@
"http-errors": ["http-errors@2.0.0", "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
- "https-proxy-agent": ["https-proxy-agent@5.0.1", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
-
"iconv-lite": ["iconv-lite@0.6.3", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"ieee754": ["ieee754@1.2.1", "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
@@ -1058,8 +1037,6 @@
"imurmurhash": ["imurmurhash@0.1.4", "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
- "inflight": ["inflight@1.0.6", "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
-
"inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"inline-style-parser": ["inline-style-parser@0.2.4", "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
@@ -1200,8 +1177,6 @@
"lucide-react": ["lucide-react@0.482.0", "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.482.0.tgz", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XM8PzHzSrg8ATmmO+fzf+JyYlVVdQnJjuyLDj2p4V2zEtcKeBNAqAoJIGFv1x2HSBa7kT8gpYUxwdQ0g7nypfw=="],
- "make-dir": ["make-dir@3.1.0", "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="],
-
"markdown-extensions": ["markdown-extensions@2.0.0", "https://registry.npmmirror.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
"markdown-table": ["markdown-table@3.0.4", "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
@@ -1332,10 +1307,6 @@
"minipass": ["minipass@7.1.2", "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
- "minizlib": ["minizlib@2.1.2", "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
-
- "mkdirp": ["mkdirp@1.0.4", "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
-
"mkdirp-classic": ["mkdirp-classic@0.5.3", "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
"monaco-editor": ["monaco-editor@0.36.1", "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.36.1.tgz", {}, "sha512-/CaclMHKQ3A6rnzBzOADfwdSJ25BFoFT0Emxsc4zYVyav5SkK9iA6lEtIeuN/oRYbwPgviJT+t3l+sjFa28jYg=="],
@@ -1370,18 +1341,10 @@
"next-themes": ["next-themes@0.4.6", "https://registry.npmmirror.com/next-themes/-/next-themes-0.4.6.tgz", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
- "node-addon-api": ["node-addon-api@5.1.0", "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-5.1.0.tgz", {}, "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="],
-
- "node-fetch": ["node-fetch@2.7.0", "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
-
- "nopt": ["nopt@5.0.0", "https://registry.npmmirror.com/nopt/-/nopt-5.0.0.tgz", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="],
-
"normalize-path": ["normalize-path@3.0.0", "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"normalize-url": ["normalize-url@8.0.1", "https://registry.npmmirror.com/normalize-url/-/normalize-url-8.0.1.tgz", {}, "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w=="],
- "npmlog": ["npmlog@5.0.1", "https://registry.npmmirror.com/npmlog/-/npmlog-5.0.1.tgz", { "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", "gauge": "^3.0.0", "set-blocking": "^2.0.0" } }, "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw=="],
-
"oauth4webapi": ["oauth4webapi@3.5.1", "https://registry.npmmirror.com/oauth4webapi/-/oauth4webapi-3.5.1.tgz", {}, "sha512-txg/jZQwcbaF7PMJgY7aoxc9QuCxHVFMiEkDIJ60DwDz3PbtXPQnrzo+3X4IRYGChIwWLabRBRpf1k9hO9+xrQ=="],
"object-assign": ["object-assign@4.1.1", "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@@ -1434,8 +1397,6 @@
"path-exists": ["path-exists@4.0.0", "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
- "path-is-absolute": ["path-is-absolute@1.0.1", "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
-
"path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
@@ -1590,8 +1551,6 @@
"reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
- "rimraf": ["rimraf@3.0.2", "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
-
"router": ["router@2.2.0", "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
@@ -1620,8 +1579,6 @@
"server-only": ["server-only@0.0.1", "https://registry.npmmirror.com/server-only/-/server-only-0.0.1.tgz", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],
- "set-blocking": ["set-blocking@2.0.0", "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
-
"set-function-length": ["set-function-length@1.2.2", "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"set-function-name": ["set-function-name@2.0.2", "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
@@ -1724,8 +1681,6 @@
"tailwindcss-animate": ["tailwindcss-animate@1.0.7", "https://registry.npmmirror.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
- "tar": ["tar@6.2.1", "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
-
"tar-fs": ["tar-fs@2.1.2", "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.2.tgz", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA=="],
"tar-stream": ["tar-stream@3.1.7", "https://registry.npmmirror.com/tar-stream/-/tar-stream-3.1.7.tgz", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
@@ -1746,8 +1701,6 @@
"toidentifier": ["toidentifier@1.0.1", "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
- "tr46": ["tr46@0.0.3", "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
-
"trim-lines": ["trim-lines@3.0.1", "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"trough": ["trough@2.2.0", "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
@@ -1848,10 +1801,6 @@
"web-namespaces": ["web-namespaces@2.0.1", "https://registry.npmmirror.com/web-namespaces/-/web-namespaces-2.0.1.tgz", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
- "webidl-conversions": ["webidl-conversions@3.0.1", "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
-
- "whatwg-url": ["whatwg-url@5.0.0", "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
-
"which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
@@ -1862,8 +1811,6 @@
"which-typed-array": ["which-typed-array@1.1.19", "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
- "wide-align": ["wide-align@1.1.5", "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="],
-
"word-wrap": ["word-wrap@1.2.5", "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
@@ -1874,8 +1821,6 @@
"y18n": ["y18n@5.0.8", "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
- "yallist": ["yallist@4.0.0", "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
-
"yaml": ["yaml@2.7.1", "https://registry.npmmirror.com/yaml/-/yaml-2.7.1.tgz", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="],
"yargs": ["yargs@17.7.2", "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
@@ -1928,38 +1873,24 @@
"fast-glob/glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
- "fs-minipass/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
-
- "gauge/signal-exit": ["signal-exit@3.0.7", "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
-
"glob/minimatch": ["minimatch@9.0.5", "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"jsondiffpatch/chalk": ["chalk@5.4.1", "https://registry.npmmirror.com/chalk/-/chalk-5.4.1.tgz", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
"katex/commander": ["commander@8.3.0", "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
- "make-dir/semver": ["semver@6.3.1", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
- "minizlib/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
-
"monaco-languageclient/vscode-languageclient": ["vscode-languageclient@8.1.0", "https://registry.npmmirror.com/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz", { "dependencies": { "minimatch": "^5.1.0", "semver": "^7.3.7", "vscode-languageserver-protocol": "3.17.3" } }, "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing=="],
"next/postcss": ["postcss@8.4.31", "https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"parse-entities/@types/unist": ["@types/unist@2.0.11", "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
- "rimraf/glob": ["glob@7.2.3", "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
-
"string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
- "tar/chownr": ["chownr@2.0.0", "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
-
- "tar/minipass": ["minipass@5.0.0", "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
-
"tar-fs/tar-stream": ["tar-stream@2.2.0", "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
"tinyglobby/picomatch": ["picomatch@4.0.2", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
diff --git a/messages/en.json b/messages/en.json
index d1c0755..e9c625d 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -49,7 +49,7 @@
"DetailsPage": {
"BackButton": "All Submissions",
"Time": "Submitted on",
- "Input": "Input",
+ "Input": "Last Executed Input",
"ExpectedOutput": "Expected Output",
"ActualOutput": "Acutal Output",
"Code": "Code"
@@ -127,7 +127,8 @@
"description": "Enter your email below to sign up to your account",
"or": "Or",
"haveAccount": "Already have an account?",
- "signIn": "Sign in"
+ "signIn": "Sign in",
+ "oauth": "Sign in with {provider}"
},
"StatusMessage": {
"PD": "Pending",
@@ -150,12 +151,25 @@
"Time": "Time",
"Memory": "Memory"
},
+ "Testcase": {
+ "Table": {
+ "Case": "Case"
+ }
+ },
"WorkspaceEditorHeader": {
"LspStatusButton": {
"TooltipContent": "Language Server"
},
+ "AnalyzeButton": {
+ "TooltipContent": "Analyze",
+ "ComplexityAnalysis": "Complexity Analysis",
+ "TimeComplexity": "Time Complexity:",
+ "SpaceComplexity": "Space Complexity",
+ "Error": "Error occurred while analyzing complexity, please try again later.",
+ "Analyzing": "Analyzing..."
+ },
"ResetButton": {
- "TooltipContent": "Reset Code"
+ "TooltipContent": "Reset"
},
"UndoButton": {
"TooltipContent": "Undo"
@@ -215,5 +229,13 @@
"answer4": "Editor uses @shikijs/monaco themes, documentation rendered with github-markdown-css"
}
}
+ },
+ "LoginPromptCard": {
+ "title": "Join Judge4c to Code!",
+ "description": "View your Submission records here",
+ "loginButton": "Log In"
+ },
+ "Video": {
+ "unsupportedBrowser": "Your browser does not support HTML5 video."
}
}
diff --git a/messages/zh.json b/messages/zh.json
index 9758d5b..245f0e6 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -17,13 +17,13 @@
},
"BackButton": "返回",
"Bot": {
- "title": "询问AI助手",
- "description": "由Vercel Ai SDK驱动",
- "placeholder": "AI助手将自动获取您当前的代码"
+ "title": "询问 AI 助手",
+ "description": "由 Vercel Ai SDK 驱动",
+ "placeholder": "AI 助手将自动获取您当前的代码"
},
"BotVisibilityToggle": {
- "open": "打开AI助手",
- "close": "关闭AI助手"
+ "open": "打开 AI 助手",
+ "close": "关闭 AI 助手"
},
"CredentialsSignInForm": {
"email": "邮箱",
@@ -49,7 +49,7 @@
"DetailsPage": {
"BackButton": "所有提交记录",
"Time": "提交于",
- "Input": "输入",
+ "Input": "最后执行的输入",
"ExpectedOutput": "期望输出",
"ActualOutput": "实际输出",
"Code": "代码"
@@ -127,7 +127,8 @@
"description": "请输入你的邮箱以注册账户",
"or": "或者",
"haveAccount": "已经有账户了?",
- "signIn": "登录"
+ "signIn": "登录",
+ "oauth": "使用 {provider} 登录"
},
"StatusMessage": {
"PD": "待处理",
@@ -150,12 +151,25 @@
"Time": "执行用时",
"Memory": "消耗内存"
},
+ "Testcase": {
+ "Table": {
+ "Case": "样例"
+ }
+ },
"WorkspaceEditorHeader": {
"LspStatusButton": {
"TooltipContent": "语言服务"
},
+ "AnalyzeButton": {
+ "TooltipContent": "分析",
+ "ComplexityAnalysis": "复杂度分析",
+ "TimeComplexity": "时间复杂度:",
+ "SpaceComplexity": "空间复杂度:",
+ "Error": "解析复杂度时出错,请稍后重试。",
+ "Analyzing": "分析中..."
+ },
"ResetButton": {
- "TooltipContent": "重置代码"
+ "TooltipContent": "重置"
},
"UndoButton": {
"TooltipContent": "撤销"
@@ -215,5 +229,13 @@
"answer4": "编辑器采用 @shikijs/monaco, 文档采用 github-markdown-css 样式"
}
}
+ },
+ "LoginPromptCard": {
+ "title": "加入 Judge4c 开始编程!",
+ "description": "在此查看您的提交记录",
+ "loginButton": "登录"
+ },
+ "Video": {
+ "unsupportedBrowser": "您的浏览器不支持 HTML5 视频。"
}
}
diff --git a/package.json b/package.json
index 1ccafd4..2f0ec87 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
+ "@ai-sdk/deepseek": "^0.2.14",
"@ai-sdk/openai": "^1.3.0",
"@ai-sdk/react": "^1.2.0",
"@auth/prisma-adapter": "^2.8.0",
@@ -41,13 +42,14 @@
"@tanstack/react-table": "^8.21.2",
"@types/vscode": "^1.97.0",
"ai": "^4.2.0",
- "bcrypt": "^5.1.1",
+ "bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"devicons-react": "^1.4.0",
"dockerode": "^4.0.4",
"dockview": "^4.2.1",
+ "flexlayout-react": "^0.7.15",
"framer-motion": "^12.7.3",
"github-markdown-css": "^5.8.1",
"lucide-react": "^0.482.0",
@@ -82,7 +84,6 @@
"devDependencies": {
"@eslint/eslintrc": "^3",
"@shikijs/monaco": "^3.2.1",
- "@types/bcrypt": "^5.0.2",
"@types/dockerode": "^3.3.35",
"@types/node": "^20",
"@types/react": "^19",
diff --git a/prisma/migrations/20250516195446_add_password_for_user/migration.sql b/prisma/migrations/20250516195446_add_password_for_user/migration.sql
new file mode 100644
index 0000000..2f77ec7
--- /dev/null
+++ b/prisma/migrations/20250516195446_add_password_for_user/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "User" ADD COLUMN "password" TEXT;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 5daae16..e56f09e 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -59,6 +59,7 @@ model User {
id String @id @default(cuid())
name String?
email String @unique
+ password String?
emailVerified DateTime?
image String?
role Role @default(GUEST)
diff --git a/prisma/seed.ts b/prisma/seed.ts
index 87a72d8..02f360a 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -1,75 +1,67 @@
-import { PrismaClient, Prisma, EditorLanguage, LanguageServerProtocol } from "@/generated/client";
+import { PrismaClient, Prisma } from "@/generated/client";
const prisma = new PrismaClient();
-const editorLanguageConfigData: Prisma.EditorLanguageConfigCreateInput[] = [
+const dockerConfigData: Prisma.DockerConfigCreateInput[] = [
{
- language: EditorLanguage.c,
- label: "C",
- fileName: "main",
- fileExtension: ".c",
- languageServerConfig: {
- create: {
- protocol: LanguageServerProtocol.ws,
- hostname: "localhost",
- port: 4594,
- path: "/clangd",
- },
- },
- dockerConfig: {
- create: {
- image: "gcc",
- tag: "latest",
- workingDir: "/src",
- compileOutputLimit: 1 * 1024 * 1024,
- runOutputLimit: 1 * 1024 * 1024,
- },
- },
+ language: "c",
+ image: "gcc",
+ tag: "latest",
+ workingDir: "/src",
+ compileOutputLimit: 1 * 1024 * 1024,
+ runOutputLimit: 1 * 1024 * 1024,
},
{
- language: EditorLanguage.cpp,
- label: "C++",
- fileName: "main",
- fileExtension: ".cpp",
- languageServerConfig: {
- create: {
- protocol: LanguageServerProtocol.ws,
- hostname: "localhost",
- port: 4595,
- path: "/clangd",
- },
- },
- dockerConfig: {
- create: {
- image: "gcc",
- tag: "latest",
- workingDir: "/src",
- compileOutputLimit: 1 * 1024 * 1024,
- runOutputLimit: 1 * 1024 * 1024,
- },
- },
+ language: "cpp",
+ image: "gcc",
+ tag: "latest",
+ workingDir: "/src",
+ compileOutputLimit: 1 * 1024 * 1024,
+ runOutputLimit: 1 * 1024 * 1024,
},
];
-const userData: Prisma.UserCreateInput[] = [
+const languageServerConfigData: Prisma.LanguageServerConfigCreateInput[] = [
{
- name: "cfngc4594",
- email: "cfngc4594@gmail.com",
- password: "$2b$10$edWXpq2TOiiGQkPOXWKGlO4EKnp2YyV7OoS2qqk/W0E6GyiVQIC66",
- role: "ADMIN",
- problems: {
+ language: "c",
+ protocol: "wss",
+ hostname: "lsp-c.litchi.icu",
+ path: "/clangd",
+ },
+ {
+ language: "cpp",
+ protocol: "wss",
+ hostname: "lsp-cpp.litchi.icu",
+ path: "/clangd",
+ },
+];
+
+const problemData: Prisma.ProblemCreateInput[] = [
+ {
+ displayId: 1000,
+ difficulty: "EASY",
+ isPublished: true,
+ localizations: {
create: [
{
- displayId: 1000,
- title: "Two Sum",
- description: `Given an array of integers \`nums\` and an integer \`target\`, return indices of the two numbers such that they add up to \`target\`.
+ locale: "en",
+ type: "TITLE",
+ content: "Two Sum",
+ },
+ {
+ locale: "zh",
+ type: "TITLE",
+ content: "两数之和",
+ },
+ {
+ locale: "en",
+ type: "DESCRIPTION",
+ content: `Given an array of integers \`nums\` and an integer \`target\`, return indices of the two numbers such that they add up to \`target\`.
You may assume that each input would have **exactly one solution**, and you may not use the same element twice.
You can return the answer in any order.
-
-
## Examples
### Example 1
@@ -127,7 +119,78 @@ So, if we fix one of the numbers, say \`x\`, we have to scan the entire array to
The second train of thought is, without changing the array, can we use additional space somehow? Like maybe a hash map to speed up the search?
`,
- solution: `
+ },
+ {
+ locale: "zh",
+ type: "DESCRIPTION",
+ content: `给定一个整数数组\`nums\` 一个整数\`target\`, 返回数组中两个数的下标,使得它们的和等于 \`target\`.
+
+你可以假设每个输入恰好有一个解,并且你不能重复使用同一个元素。
+
+答案可以按任意顺序返回。
+
+## 示例
+
+### 示例 1
+
+\`\`\`shell
+输入: nums = [2,7,11,15], target = 9
+输出: [0,1]
+解释: Because nums[0] + nums[1] == 9, we return [0, 1].
+\`\`\`
+
+### 示例 2
+
+\`\`\`shell
+输入: nums = [3,2,4], target = 6
+输出: [1,2]
+\`\`\`
+
+### 示例 3
+
+\`\`\`shell
+输入: nums = [3,3], target = 6
+输出: [0,1]
+\`\`\`
+
+## 约束
+
+\`\`\`math
+2 <= nums.length <= 10^4
+\`\`\`
+
+\`\`\`math
+-10^9 <= nums[i] <= 10^9
+\`\`\`
+
+\`\`\`math
+-10^9 <= target <= 10^9
+\`\`\`
+
+
+只存在一个有效的答案。
+
+
+**进阶问题:** 你能否设计一个时间复杂度低于 $O(n^2)$ 的算法来解决这个问题??
+
+---
+
+
+一种真正的暴力方法是遍历所有可能的数字对,但这种方法太慢了。不过,为了完整性,尝试暴力解法仍然是有意义的。正是从这些暴力解法中,你才能找到优化的思路。
+
+
+
+所以,如果我们固定其中一个数字,例如 \`x\`, 我们就必须遍历整个数组来找到另一个数字 \`y\`,而 \`y\`等于 \`value - x\` (这里的 value 是输入的参数)。我们能否以某种方式对数组进行处理,从而让这种查找变得更快呢?
+
+
+
+第二种思路是,在不改变数组的前提下,我们能否借助额外的空间呢?比如,是否可以用哈希表来加快查找速度?
+`,
+ },
+ {
+ locale: "en",
+ type: "SOLUTION",
+ content: `
## Approach 1: Brute Force
@@ -137,7 +200,7 @@ The brute force approach is simple. Loop through each element $x$ and find if th
### Implementation
-\`\`\`c showLineNumbers
+\`\`\`c showLineNumbers {2-3,6-7,15}
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
for (int i = 0; i < numsSize; i++) {
for (int j = i + 1; j < numsSize; j++) {
@@ -166,6 +229,8 @@ int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
The space required does not depend on the size of the input array, so only constant space is used.
+
+
---
## Approach 2: Two-pass Hash Table
@@ -291,13 +356,184 @@ int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
| Brute Force | $O(n^2)$ | $O(1)$ |
| Two-pass Hash Table | $O(n)$ | $O(n)$ |
| One-pass Hash Table | $O(n)$ | $O(n)$ |`,
- difficulty: "EASY",
- published: true,
- templates: {
- create: [
- {
- language: "c",
- template: `#include
+ },
+ {
+ locale: "zh",
+ type: "SOLUTION",
+ content: `
+
+## 方法一:暴力枚举
+
+### 算法思路
+
+暴力枚举法的思路很简单:遍历数组中的每个元素 x,并查找是否存在另一个元素的值等于 $target - x$.
+
+### 代码实现
+
+\`\`\`c showLineNumbers {2-3,6-7,15}
+int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
+ for (int i = 0; i < numsSize; i++) {
+ for (int j = i + 1; j < numsSize; j++) {
+ if (nums[j] == target - nums[i]) {
+ int* result = malloc(sizeof(int) * 2);
+ result[0] = i;
+ result[1] = j;
+ *returnSize = 2;
+ return result;
+ }
+ }
+ }
+ // 如果未找到解,返回一个空数组
+ *returnSize = 0;
+ return malloc(sizeof(int) * 0);
+}
+\`\`\`
+
+### 复杂度分析
+
+- **时间复杂度:** $O(n^2)$.
+
+ 对于数组中的每个元素,我们都要通过遍历数组的剩余部分来查找它的补数,这需要 $O(n)$ 的时间。因此,总的时间复杂度是 $O(n^2)$.
+
+- **空间复杂度:\(O(1)\)** $O(1)$.
+
+ 所需的空间并不依赖于输入数组的大小,所以只使用了常数级别的空间。
+
+
+
+---
+
+## 方法 2: 两遍哈希表
+
+### 思路
+
+为了提高运行时的时间复杂度,我们需要一种更高效的方法来检查数组中是否存在某个元素的补数。如果补数存在,我们还需要获取它的索引。维护数组中每个元素与其索引之间的映射的最佳方法是什么呢?答案是哈希表。
+
+我们可以通过用空间换取时间的方式,将查找时间从 $O(n)$ 降低到 $O(1)$ 。哈希表非常适合这个目的,因为它支持在近似常数时间内进行快速查找。我之所以说 “近似”,是因为如果发生了哈希冲突,查找时间可能会退化为 $O(n)$ 。不过,只要精心选择哈希函数,哈希表的查找时间平均为 $O(1)$ 。
+
+### 算法
+
+一种简单的实现方式是使用两次迭代。在第一次迭代中,我们将每个元素的值作为键,其索引作为值添加到哈希表中。然后,在第二次迭代中,我们检查每个元素的补数 ($target - nums[i]$) 是否存在于哈希表中。如果存在,我们就返回当前元素的索引和它补数的索引。需要注意的是,补数不能是元素本身 $nums[i]$ !
+
+### 代码实现
+
+\`\`\`c showLineNumbers
+int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
+ struct hashTable {
+ int key;
+ int value;
+ UT_hash_handle hh;
+ } *hashTable = NULL, *item, *tmpItem;
+
+ for (int i = 0; i < numsSize; i++) {
+ HASH_FIND_INT(hashTable, &nums[i], item);
+ if (item) {
+ int* result = malloc(sizeof(int) * 2);
+ result[0] = item->value;
+ result[1] = i;
+ *returnSize = 2;
+ HASH_ITER(hh, hashTable, item, tmpItem) {
+ HASH_DEL(hashTable, item);
+ free(item);
+ }
+ return result;
+ }
+ item = malloc(sizeof(struct hashTable));
+ item->key = target - nums[i];
+ item->value = i;
+ HASH_ADD_INT(hashTable, key, item);
+ }
+
+ HASH_ITER(hh, hashTable, item, tmpItem) {
+ HASH_DEL(hashTable, item);
+ free(item);
+ }
+
+ *returnSize = 0;
+ // 如果没有找到有效的数对,则返回一个空数组
+ return malloc(sizeof(int) * 0);
+}
+\`\`\`
+
+### 复杂度分析
+
+- **时间复杂度:** $O(n)$.
+
+ 我们精确地遍历包含 n 个元素的列表两次。由于哈希表将查找时间减少到 $O(1)$,所以总的时间复杂度为 $O(n)$。
+
+- **空间复杂度:** $O(n)$.
+
+ 所需的额外空间取决于存储在哈希表中的元素数量,而哈希表中恰好存储了 $n$ 个元素。
+
+---
+
+## 方法三:一遍哈希表
+
+### 算法
+
+事实证明,我们可以通过一遍遍历实现。在我们遍历并将元素插入哈希表的同时,我们还要检查当前元素的补数是否已经存在于哈希表中。如果存在,我们就找到了一个解决方案,并立即返回索引。
+
+### 代码实现
+
+\`\`\`c showLineNumbers
+int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
+ struct hashTable {
+ int key;
+ int value;
+ UT_hash_handle hh;
+ } *hashTable = NULL, *item;
+
+ for (int i = 0; i < numsSize; i++) {
+ int complement = target - nums[i];
+ HASH_FIND_INT(hashTable, &complement, item);
+ if (item) {
+ int* result = malloc(sizeof(int) * 2);
+ result[0] = item->value;
+ result[1] = i;
+ *returnSize = 2;
+ HASH_CLEAR(hh, hashTable); // 释放哈希表内存
+ return result;
+ }
+ item = malloc(sizeof(struct hashTable));
+ item->key = nums[i];
+ item->value = i;
+ HASH_ADD_INT(hashTable, key, item);
+ }
+
+ *returnSize = 0;
+ HASH_CLEAR(hh, hashTable); // 释放哈希表内存
+ // 若未找到解,返回一个空数组
+ return malloc(0); // 分配0字节内存(即空数组)
+}
+\`\`\`
+
+### 复杂度分析
+
+- **时间复杂度:** $O(n)$.
+
+ 我们仅遍历包含 n 个元素的列表一次。表中每次查找仅需 $O(1)$ 时间。
+
+- **空间复杂度:** $O(n)$.
+
+ 所需的额外空间取决于哈希表中存储的元素数量,该哈希表最多存储 $n$ 个元素。
+
+---
+
+## 方法总结
+
+| 方法 | 时间复杂度 | 空间复杂度 |
+| ------------------- | :-------------: | :--------------: |
+| 暴力枚举 | $O(n^2)$ | $O(1)$ |
+| 两遍哈希表 | $O(n)$ | $O(n)$ |
+| 一遍哈希表 | $O(n)$ | $O(n)$ |`,
+ },
+ ],
+ },
+ templates: {
+ create: [
+ {
+ language: "c",
+ content: `#include
#include
#include
@@ -371,10 +607,10 @@ int main() {
}
return 0;
}`,
- },
- {
- language: "cpp",
- template: `#include
+ },
+ {
+ language: "cpp",
+ content: `#include
#include
#include
#include
@@ -435,45 +671,85 @@ int main() {
return 0;
}`,
- },
- ],
- },
- testcases: {
+ },
+ ],
+ },
+ testcases: {
+ create: [
+ {
+ inputs: {
create: [
{
- data: {
- create: [
- { label: "nums", value: "[2,7,11,15]", index: 0 },
- { label: "target", value: "9", index: 1 },
- ],
- },
- expectedOutput: "[0,1]",
+ index: 0,
+ name: "nums",
+ value: "[2,7,11,15]",
},
{
- data: {
- create: [
- { label: "nums", value: "[3,2,4]", index: 0 },
- { label: "target", value: "6", index: 1 },
- ],
- },
- expectedOutput: "[1,2]",
- },
- {
- data: {
- create: [
- { label: "nums", value: "[3,3]", index: 0 },
- { label: "target", value: "6", index: 1 },
- ],
- },
- expectedOutput: "[0,1]",
+ index: 1,
+ name: "target",
+ value: "9",
},
],
},
+ expectedOutput: "[0,1]",
},
{
- displayId: 1001,
- title: "Add Two Numbers",
- description: `You are given two **non-empty** linked lists representing two non-negative integers. The digits are stored in **reverse order**, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.
+ inputs: {
+ create: [
+ {
+ index: 0,
+ name: "nums",
+ value: "[3,2,4]",
+ },
+ {
+ index: 1,
+ name: "target",
+ value: "6",
+ },
+ ],
+ },
+ expectedOutput: "[1,2]",
+ },
+ {
+ inputs: {
+ create: [
+ {
+ index: 0,
+ name: "nums",
+ value: "[3,3]",
+ },
+ {
+ index: 1,
+ name: "target",
+ value: "6",
+ },
+ ],
+ },
+ expectedOutput: "[0,1]",
+ },
+ ],
+ },
+ },
+ {
+ displayId: 1001,
+ difficulty: "MEDIUM",
+ isPublished: true,
+ localizations: {
+ create: [
+ {
+ locale: "en",
+ type: "TITLE",
+ content: "Add Two Numbers",
+ },
+ {
+ locale: "zh",
+ type: "TITLE",
+ content: "两数相加",
+ },
+ {
+ locale: "en",
+ type: "DESCRIPTION",
+ content: `You are given two **non-empty** linked lists representing two non-negative integers. The digits are stored in **reverse order**, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
@@ -516,7 +792,58 @@ The number of nodes in each linked list is in the range $[1, 100]$.
It is guaranteed that the list represents a number that does not have leading zeros.
`,
- solution: `## Approach 1: Elementary Math
+ },
+ {
+ locale: "zh",
+ type: "DESCRIPTION",
+ content: `给定两个**非空**链表,它们表示两个非负整数。这些数字以**逆序**存储,并且每个节点包含一个数字。将这两个数字相加,并以链表形式返回它们的和。
+
+你可以假设这两个数字除了数字 0 本身外,不包含任何前导零。
+
+## 示例
+
+### 示例1
+
+
+
+\`\`\`shell
+输入: l1 = [2,4,3], l2 = [5,6,4]
+输出: [7,0,8]
+解释: 342 + 465 = 807.
+\`\`\`
+
+### 示例2
+
+\`\`\`shell
+输入: l1 = [0], l2 = [0]
+输出: [0]
+\`\`\`
+
+### 示例3
+
+\`\`\`shell
+输入: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
+输出: [8,9,9,9,0,0,0,1]
+\`\`\`
+
+## 约束条件
+
+
+每个链表中的节点数范围是 e $[1, 100]$.
+
+
+\`\`\`math
+0 <= 节点值 <= 9
+\`\`\`
+
+
+保证链表表示的数字无前导零。
+
`,
+ },
+ {
+ locale: "en",
+ type: "SOLUTION",
+ content: `## Approach 1: Elementary Math
### Intuition
@@ -610,13 +937,112 @@ struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
What if the the digits in the linked list are stored in non-reversed order? For example:
$(3 → 4 → 2) + (4 → 6 → 5) = 8 → 0 → 7$`,
- difficulty: "MEDIUM",
- published: true,
- templates: {
- create: [
- {
- language: "c",
- template: `#include
+ },
+ {
+ locale: "zh",
+ type: "SOLUTION",
+ content: `## 方法 1:基础数学方法
+
+### 思路
+
+使用一个变量跟踪进位,并从链表头部(存储最低有效位)开始逐位模拟数字相加。
+
+
+
+*图 1. 两数相加的可视化过程: $342 + 465 = 807$.*
+
+*每个节点包含一个数字,且数字按逆序存储*
+
+### 算法
+
+就像在纸上计算两数相加一样,我们从最低有效位(即 $l1$ 和 $l2$ 的头部)开始逐位相加。由于每个数字在 $0…9$ 范围内,两数相加可能会产生 “进位”。例如 $5 + 7 = 12$,此时当前位设为 $2$ ,并将进位 $carry = 1$ 带入下一次计算。进位 $carry$ 只能是 $0$ 或 $1$ ,因为两个数字(含进位)的最大和为 $9 + 9 + 1 = 19$.
+
+伪代码如下:
+
+- 初始化当前节点为返回链表的哑结点(dummy head)。
+
+- 初始化进位 carry 为 $0$.
+
+- 遍历链表 $l1$ 和 $l2$ ,直到两链表均遍历完毕且进位为 $0$.
+
+ - 设 $x$ 为 $l1$ 当前节点的值,若 $l1$ 已遍历结束则设为 $0$.
+
+ - 设 $y$ 为 $l2$ 当前节点的值,若 $l2$ 已遍历结束则设为 $0$.
+
+ - 计算总和 $sum = x + y + carry$.
+
+ - 更新进位 $carry = sum/10$.
+
+ - 创建新节点,值为 $sum$ $mod$ $10$ ,连接到当前节点的下一个位置,并将当前节点后移。
+
+ - 同时后移 $l1$ 和 $l2$ 指针(若未遍历结束)。
+
+- 返回哑结点的下一个节点(即实际链表的头节点)。
+
+说明:使用哑结点可简化代码逻辑。若无哑结点,需额外处理头节点的初始化条件。
+
+需特别注意以下测试用例:
+
+| 测试用例 | 说明 |
+| ----------------------- | ----------------------------------------------------------------------------- |
+| l1=[0,1]
l2=[0,1,2] | 其中一个链表较长的情况。 |
+| l1=[]
l2=[0,1] | 链表为空的情况(等价于数字0)。 |
+| l1=[9,9]
l2=[1] | 末尾相加后仍有进位的情况(易遗漏最终进位)。 |
+
+### 实现
+
+\`\`\`c showLineNumbers
+struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
+ struct ListNode* dummyHead = malloc(sizeof(struct ListNode));
+ dummyHead->val = 0;
+ dummyHead->next = NULL;
+ struct ListNode* curr = dummyHead;
+ int carry = 0;
+
+ while (l1 != NULL || l2 != NULL || carry != 0) {
+ int x = (l1 != NULL) ? l1->val : 0;
+ int y = (l2 != NULL) ? l2->val : 0;
+ int sum = carry + x + y;
+ carry = sum / 10;
+
+ curr->next = malloc(sizeof(struct ListNode));
+ curr->next->val = sum % 10;
+ curr->next->next = NULL;
+ curr = curr->next;
+
+ if (l1 != NULL) l1 = l1->next;
+ if (l2 != NULL) l2 = l2->next;
+ }
+
+ struct ListNode* result = dummyHead->next;
+ free(dummyHead); // 释放为哑结点(dummyHead)分配的内存。
+ return result;
+}
+\`\`\`
+
+### 复杂度分析
+
+- **时间复杂度:** $O(max(m,n))$
+
+ 假设 $m$ 和 $n$ 分别表示链表 $l1$ 和 $l2$ 的长度,上述算法最多迭代 $max(m,n)$ 次。
+
+- **空间复杂度:** $O(1)$
+
+ 新链表的长度最多为 $max(m,n) + 1$ ,但我们通常不将结果链表计入空间复杂度分析。
+
+### 后续问题
+
+如果链表中的数字以非逆序(正序)存储,该如何处理?例如:
+
+$(3 → 4 → 2) + (4 → 6 → 5) = 8 → 0 → 7$`,
+ },
+ ],
+ },
+ templates: {
+ create: [
+ {
+ language: "c",
+ content: `#include
#include
#include
@@ -688,10 +1114,10 @@ int main() {
}
return 0;
}`,
- },
- {
- language: "cpp",
- template: `#include
+ },
+ {
+ language: "cpp",
+ content: `#include
#include
#include
#include
@@ -772,55 +1198,85 @@ int main() {
}
return 0;
}`,
- },
- ],
- },
- testcases: {
+ },
+ ],
+ },
+ testcases: {
+ create: [
+ {
+ inputs: {
create: [
{
- data: {
- create: [
- { label: "l1", value: "[2,4,3]", index: 0 },
- { label: "l2", value: "[5,6,4]", index: 1 },
- ],
- },
- expectedOutput: "[7,0,8]",
+ index: 0,
+ name: "l1",
+ value: "[2,4,3]",
},
{
- data: {
- create: [
- { label: "l1", value: "[0]", index: 0 },
- { label: "l2", value: "[0]", index: 1 },
- ],
- },
- expectedOutput: "[0]",
- },
- {
- data: {
- create: [
- { label: "l1", value: "[9,9,9,9,9,9,9]", index: 0 },
- { label: "l2", value: "[9,9,9,9]", index: 1 },
- ],
- },
- expectedOutput: "[8,9,9,9,0,0,0,1]",
+ index: 1,
+ name: "l2",
+ value: "[5,6,4]",
},
],
},
+ expectedOutput: "[7,0,8]",
+ },
+ {
+ inputs: {
+ create: [
+ {
+ index: 0,
+ name: "l1",
+ value: "[0]",
+ },
+ {
+ index: 1,
+ name: "l2",
+ value: "[0]",
+ },
+ ],
+ },
+ expectedOutput: "[0]",
+ },
+ {
+ inputs: {
+ create: [
+ {
+ index: 0,
+ name: "l1",
+ value: "[9,9,9,9,9,9,9]",
+ },
+ {
+ index: 1,
+ name: "l2",
+ value: "[9,9,9,9]",
+ },
+ ],
+ },
+ expectedOutput: "[8,9,9,9,0,0,0,1]",
},
],
},
},
{
- name: "fly6516",
- email: "fly6516@outlook.com",
- password: "$2b$10$SD1T/dYvKTArGdTmf8ERxuBKIONxY01/wSboRNaNsHnKZzDhps/0u",
- role: "ADMIN",
- problems: {
+ displayId: 1002,
+ difficulty: "HARD",
+ isPublished: true,
+ localizations: {
create: [
{
- displayId: 1002,
- title: "Median of Two Sorted Arrays",
- description: `Given two sorted arrays \`nums1\` and \`nums2\` of size \`m\` and \`n\` respectively, return **the median** of the two sorted arrays.
+ locale: "en",
+ type: "TITLE",
+ content: "Median of Two Sorted Arrays",
+ },
+ {
+ locale: "zh",
+ type: "TITLE",
+ content: "寻找两个正序数组的中位数",
+ },
+ {
+ locale: "en",
+ type: "DESCRIPTION",
+ content: `Given two sorted arrays \`nums1\` and \`nums2\` of size \`m\` and \`n\` respectively, return **the median** of the two sorted arrays.
The overall run time complexity should be $O(log(m+n))$.
@@ -867,7 +1323,62 @@ nums_2.length == n
\`\`\`math
-10^6 <= nums_1[i], nums_2[i] <= 10^6
\`\`\``,
- solution: `## Approach 1: Merge Sort
+ },
+ {
+ locale: "zh",
+ type: "DESCRIPTION",
+ content: `给定两个大小分别为 \`nums1\` 和 \`nums2\` 的有序数组 \`m\` 和 \`n\` ,请返回这两个有序数组的**中位数**。
+
+要求整体时间复杂度为 $O(log(m+n))$.
+
+## 示例
+
+### 示例 1
+
+\`\`\`shell
+Input: nums1 = [1,3], nums2 = [2]
+Output: 2.00000
+Explanation: merged array = [1,2,3] and median is 2.
+\`\`\`
+
+### 示例 2
+
+\`\`\`shell
+Input: nums1 = [1,2], nums2 = [3,4]
+Output: 2.50000
+Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.
+\`\`\`
+
+## 约束条件
+
+\`\`\`math
+nums_1.length == m
+\`\`\`
+
+\`\`\`math
+nums_2.length == n
+\`\`\`
+
+\`\`\`math
+0 <= m <= 1000
+\`\`\`
+
+\`\`\`math
+0 <= n <= 1000
+\`\`\`
+
+\`\`\`math
+1 <= m + n <= 2000
+\`\`\`
+
+\`\`\`math
+-10^6 <= nums_1[i], nums_2[i] <= 10^6
+\`\`\``,
+ },
+ {
+ locale: "en",
+ type: "SOLUTION",
+ content: `## Approach 1: Merge Sort
### Intuition
@@ -957,13 +1468,107 @@ Let $m$ be the size of array \`nums1\` and $n$ be the size of array \`nums2\`.
- **Space complexity:** $O(1)$
- We only need to maintain two pointers \`p1\` and \`p2\`.`,
- difficulty: "HARD",
- published: true,
- templates: {
- create: [
- {
- language: "c",
- template: `#include
+ },
+ {
+ locale: "zh",
+ type: "SOLUTION",
+ content: `## 方法 1: 归并排序思路
+
+### 思路
+
+我们从最直接的方法开始思考。如果将两个数组合并成一个数组 \`A\` 并排序,假设合并后数组的长度为 \`n\`,那么中位数为:
+
+- 当 n 为奇数时,中位数是 \`A[n / 2]\`。
+
+- 当 n 为偶数时,中位数是 \`A[n / 2]\` 和 \`A[n / 2 + 1]\` 的平均值。
+
+不过,我们实际上不需要真正合并和排序数组。注意到两个数组已经是有序的,因此最小的元素一定是 \`nums1\` 或 \`nums2\`。 因此,我们可以设置两个指针 \`p1\` 和 \`p2\` 分别指向两个数组的起始位置,通过比较 \`nums1[p1]\` 和 \`nums2[p2]\`的值来逐步获取合并后的有序元素。
+
+以下面的示例流程为例(可参考对应图示):
+
+### 算法
+
+1. 计算两个数组的总长度 \`m + n\`
+
+ - 若 \`m + n\` 为奇数,我们需要找到第 \`(m + n) / 2\` 个元素(从 0 开始计数)。
+
+ - 若 \`m + n\` 为偶数,我们需要找到第 \`(m + n) / 2\` 个和第 \`(m + n) / 2 + 1\` 个元素的平均值。
+
+2. 初始化指针 \`p1\` = 0(指向 \`nums1\` 起始)和 \`p2\` = 0(指向 \`nums2\` 起始)。
+
+3. 如果 \`p1\` 和 \`p2\` 都在数组的有效范围内(即未越界),则比较 \`p1\` 和 \`p2\`所指位置的值:
+
+ - 如果 \`nums1[p1]\` 小于 \`nums2[p2]\`,则将 \`p1\` 向右移动一位。
+
+ - 否则,将 \`p2\` 向右移动一位。
+
+ 如果 \`p1\` 超出 \`nums1\`的范围,则直接将 \`p2\` 向右移动一位。
+
+ 如果 \`p2\` 超出 \`nums2\`的范围,则直接将 \`p1\` 向右移动一位。
+
+4. 获取目标元素并计算中位数:
+
+ - 若 \`m + n\` 为奇数,重复步骤 \`3 (m + n + 1) / 2\` 次(每次移动指针对应获取一个元素),最后一次步骤中得到的元素即为中位数。
+ - 若 \`m + n\` 为偶数,重复步骤 \`3 (m + n) / 2 + 1\` 次,取最后两次步骤中得到的元素,计算它们的平均值作为中位数。
+
+### 实现
+
+\`\`\`c showLineNumbers
+double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
+ int m = nums1Size, n = nums2Size;
+ int p1 = 0, p2 = 0;
+
+ int getMin() {
+ if (p1 < m && p2 < n) {
+ return nums1[p1] < nums2[p2] ? nums1[p1++] : nums2[p2++];
+ } else if (p1 < m) {
+ return nums1[p1++];
+ } else if (p2 < n) {
+ return nums2[p2++];
+ }
+ return -1;
+ }
+
+ double median;
+ if ((m + n) % 2 == 0) {
+ for (int i = 0; i < ((m + n) / 2) - 1; ++i) {
+ int temp = getMin();
+ }
+ median = (getMin() + getMin()) / 2.0;
+ } else {
+ for (int i = 0; i < (m + n) / 2; ++i) {
+ int temp = getMin();
+ }
+ median = getMin();
+ }
+
+ return median;
+}
+\`\`\`
+
+### 复杂度分析
+
+设数组 \`nums1\` 的长度为 $m$ ,数组 \`nums2\` 的长度为 $n$ 。
+
+- **时间复杂度:** $O(m + n)$
+
+ - 我们通过比较 \`p1\` 和 \`p2\`指向的两个元素来获取当前最小元素,每次比较和移动指针的时间为 $O(1)$ 。
+
+ - 在找到中位数元素(或元素对)之前,需要遍历两个数组中最多一半的元素。
+
+ - 综上,总时间复杂度为 $O(m + n)$.
+
+- **空间复杂度:** $O(1)$
+
+ - 仅需维护两个指针 \`p1\` 和 \`p2\` ,无需额外线性空间。`,
+ },
+ ],
+ },
+ templates: {
+ create: [
+ {
+ language: "c",
+ content: `#include
#include
#include
@@ -1014,10 +1619,10 @@ int main() {
return 0;
}`,
- },
- {
- language: "cpp",
- template: `#include
+ },
+ {
+ language: "cpp",
+ content: `#include
#include
#include
#include
@@ -1061,31 +1666,44 @@ int main() {
}
return 0;
}`,
- },
- ],
- },
- testcases: {
+ },
+ ],
+ },
+ testcases: {
+ create: [
+ {
+ inputs: {
create: [
{
- data: {
- create: [
- { label: "nums1", value: "[1,3]", index: 0 },
- { label: "nums2", value: "[2]", index: 1 },
- ],
- },
- expectedOutput: "2.00000",
+ index: 0,
+ name: "nums1",
+ value: "[1,3]",
},
{
- data: {
- create: [
- { label: "nums1", value: "[1,2]", index: 0 },
- { label: "nums2", value: "[3,4]", index: 1 },
- ],
- },
- expectedOutput: "2.50000",
+ index: 1,
+ name: "nums2",
+ value: "[2]",
},
],
},
+ expectedOutput: "2.00000",
+ },
+ {
+ inputs: {
+ create: [
+ {
+ index: 0,
+ name: "nums1",
+ value: "[1,2]",
+ },
+ {
+ index: 1,
+ name: "nums2",
+ value: "[3,4]",
+ },
+ ],
+ },
+ expectedOutput: "2.50000",
},
],
},
@@ -1093,12 +1711,22 @@ int main() {
];
export async function main() {
- for (const e of editorLanguageConfigData) {
- await prisma.editorLanguageConfig.create({ data: e });
+ for (const dockerConfig of dockerConfigData) {
+ await prisma.dockerConfig.create({
+ data: dockerConfig,
+ });
}
- for (const u of userData) {
- await prisma.user.create({ data: u });
+ for (const languageServerConfig of languageServerConfigData) {
+ await prisma.languageServerConfig.create({
+ data: languageServerConfig,
+ });
+ }
+
+ for (const problem of problemData) {
+ await prisma.problem.create({
+ data: problem,
+ });
}
}
diff --git a/public/sign-in.mp4 b/public/sign-in.mp4
new file mode 100644
index 0000000..82867cf
Binary files /dev/null and b/public/sign-in.mp4 differ
diff --git a/src/actions/judge.ts b/src/actions/judge.ts
deleted file mode 100644
index 103ebf0..0000000
--- a/src/actions/judge.ts
+++ /dev/null
@@ -1,544 +0,0 @@
-"use server";
-
-import fs from "fs";
-import tar from "tar-stream";
-import Docker from "dockerode";
-import prisma from "@/lib/prisma";
-import { auth } from "@/lib/auth";
-import { redirect } from "next/navigation";
-import { Readable, Writable } from "stream";
-import { Status } from "@/generated/client";
-import { revalidatePath } from "next/cache";
-import type { ProblemWithTestcases, TestcaseWithDetails } from "@/types/prisma";
-import type { EditorLanguage, Submission, TestcaseResult } from "@/generated/client";
-
-const isRemote = process.env.DOCKER_HOST_MODE === "remote";
-
-// Docker client initialization
-const docker = isRemote
- ? new Docker({
- protocol: process.env.DOCKER_REMOTE_PROTOCOL as "https" | "http" | "ssh" | undefined,
- host: process.env.DOCKER_REMOTE_HOST,
- port: process.env.DOCKER_REMOTE_PORT,
- ca: fs.readFileSync(process.env.DOCKER_REMOTE_CA_PATH || "/certs/ca.pem"),
- cert: fs.readFileSync(process.env.DOCKER_REMOTE_CERT_PATH || "/certs/cert.pem"),
- key: fs.readFileSync(process.env.DOCKER_REMOTE_KEY_PATH || "/certs/key.pem"),
- })
- : new Docker({ socketPath: "/var/run/docker.sock" });
-
-// Prepare Docker image environment
-async function prepareEnvironment(image: string, tag: string): Promise {
- try {
- const reference = `${image}:${tag}`;
- const filters = { reference: [reference] };
- const images = await docker.listImages({ filters });
- return images.length !== 0;
- } catch (error) {
- console.error("Error checking Docker images:", error);
- return false;
- }
-}
-
-// Create Docker container with keep-alive
-async function createContainer(
- image: string,
- tag: string,
- workingDir: string,
- memoryLimit?: number
-) {
- const container = await docker.createContainer({
- Image: `${image}:${tag}`,
- Cmd: ["tail", "-f", "/dev/null"], // Keep container alive
- WorkingDir: workingDir,
- HostConfig: {
- Memory: memoryLimit ? memoryLimit * 1024 * 1024 : undefined,
- MemorySwap: memoryLimit ? memoryLimit * 1024 * 1024 : undefined,
- },
- NetworkDisabled: true,
- });
-
- await container.start();
- return container;
-}
-
-// Create tar stream for code submission
-function createTarStream(file: string, value: string) {
- const pack = tar.pack();
- pack.entry({ name: file }, value);
- pack.finalize();
- return Readable.from(pack);
-}
-
-export async function judge(
- language: EditorLanguage,
- code: string,
- problemId: string,
-): Promise {
- const session = await auth();
- if (!session?.user?.id) redirect("/sign-in");
-
- const userId = session.user.id;
- let container: Docker.Container | null = null;
- let submission: Submission | null = null;
-
- try {
- const problem = await prisma.problem.findUnique({
- where: { id: problemId },
- include: {
- testcases: {
- include: {
- data: true,
- },
- },
- },
- }) as ProblemWithTestcases | null;
-
- if (!problem) {
- submission = await prisma.submission.create({
- data: {
- language,
- code,
- status: Status.SE,
- userId,
- problemId,
- message: "Problem not found",
- },
- });
- return submission;
- }
-
- const config = await prisma.editorLanguageConfig.findUnique({
- where: { language },
- include: {
- dockerConfig: true,
- },
- });
-
- if (!config?.dockerConfig) {
- submission = await prisma.submission.create({
- data: {
- language,
- code,
- status: Status.SE,
- userId,
- problemId,
- message: " Missing editor or docker configuration",
- },
- });
- return submission;
- }
-
- const testcases = problem.testcases;
-
- if (!testcases || testcases.length === 0) {
- submission = await prisma.submission.create({
- data: {
- language,
- code,
- status: Status.SE,
- userId,
- problemId,
- message: "Testcases not found",
- },
- });
- return submission;
- }
-
- const {
- image,
- tag,
- workingDir,
- compileOutputLimit,
- runOutputLimit,
- } = config.dockerConfig;
- const { fileName, fileExtension } = config;
- const file = `${fileName}.${fileExtension}`;
-
- // Prepare the environment and create a container
- if (await prepareEnvironment(image, tag)) {
- container = await createContainer(image, tag, workingDir, problem.memoryLimit);
- } else {
- console.error("Docker image not found:", image, ":", tag);
- submission = await prisma.submission.create({
- data: {
- language,
- code,
- status: Status.SE,
- userId,
- problemId,
- message: "The docker environment is not ready",
- },
- });
- return submission;
- }
-
- submission = await prisma.submission.create({
- data: {
- language,
- code,
- status: Status.PD,
- userId,
- problemId,
- message: "",
- },
- });
-
- // Upload code to the container
- const tarStream = createTarStream(file, code);
- await container.putArchive(tarStream, { path: workingDir });
-
- // Compile the code
- const compileResult = await compile(container, file, fileName, compileOutputLimit, submission.id, language);
- if (compileResult.status === Status.CE) {
- return compileResult;
- }
-
- // Run the code
- const runResult = await run(container, fileName, problem.timeLimit, runOutputLimit, submission.id, testcases);
- return runResult;
- } catch (error) {
- console.error(error);
- if (submission) {
- const updatedSubmission = await prisma.submission.update({
- where: { id: submission.id },
- data: {
- status: Status.SE,
- message: "System Error",
- }
- })
- return updatedSubmission;
- } else {
- submission = await prisma.submission.create({
- data: {
- language,
- code,
- status: Status.PD,
- userId,
- problemId,
- message: "",
- },
- })
- return submission;
- }
- } finally {
- revalidatePath(`/problems/${problemId}`);
- if (container) {
- try {
- await container.kill();
- await container.remove();
- } catch (error) {
- console.error("Container cleanup failed:", error);
- }
- }
- }
-}
-
-async function compile(
- container: Docker.Container,
- file: string,
- fileName: string,
- compileOutputLimit: number = 1 * 1024 * 1024,
- submissionId: string,
- language: EditorLanguage,
-): Promise {
- const compileCmd =
- language === "c"
- ? ["gcc", "-O2", file, "-o", fileName]
- : language === "cpp"
- ? ["g++", "-O2", file, "-o", fileName]
- : null;
-
- if (!compileCmd) {
- return prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.SE,
- message: "Unsupported language",
- },
- });
- }
-
- const compileExec = await container.exec({
- Cmd: compileCmd,
- AttachStdout: true,
- AttachStderr: true,
- });
-
- return new Promise((resolve, reject) => {
- compileExec.start({}, (error, stream) => {
- if (error || !stream) {
- return reject({ message: "System Error", Status: Status.SE });
- }
-
- const stdoutChunks: string[] = [];
- let stdoutLength = 0;
- const stdoutStream = new Writable({
- write(chunk, _encoding, callback) {
- let text = chunk.toString();
- if (stdoutLength + text.length > compileOutputLimit) {
- text = text.substring(0, compileOutputLimit - stdoutLength);
- stdoutChunks.push(text);
- stdoutLength = compileOutputLimit;
- callback();
- return;
- }
- stdoutChunks.push(text);
- stdoutLength += text.length;
- callback();
- },
- });
-
- const stderrChunks: string[] = [];
- let stderrLength = 0;
- const stderrStream = new Writable({
- write(chunk, _encoding, callback) {
- let text = chunk.toString();
- if (stderrLength + text.length > compileOutputLimit) {
- text = text.substring(0, compileOutputLimit - stderrLength);
- stderrChunks.push(text);
- stderrLength = compileOutputLimit;
- callback();
- return;
- }
- stderrChunks.push(text);
- stderrLength += text.length;
- callback();
- },
- });
-
- docker.modem.demuxStream(stream, stdoutStream, stderrStream);
-
- stream.on("end", async () => {
- const stdout = stdoutChunks.join("");
- const stderr = stderrChunks.join("");
- const exitCode = (await compileExec.inspect()).ExitCode;
-
- let updatedSubmission: Submission;
-
- if (exitCode !== 0 || stderr) {
- updatedSubmission = await prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.CE,
- message: stderr || "Compilation Error",
- },
- });
- } else {
- updatedSubmission = await prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.CS,
- message: stdout,
- },
- });
- }
-
- resolve(updatedSubmission);
- });
-
- stream.on("error", () => {
- reject({ message: "System Error", Status: Status.SE });
- });
- });
- });
-}
-
-// Run code and implement timeout
-async function run(
- container: Docker.Container,
- fileName: string,
- timeLimit: number = 1000,
- maxOutput: number = 1 * 1024 * 1024,
- submissionId: string,
- testcases: TestcaseWithDetails,
-): Promise {
- let finalSubmission: Submission | null = null;
- let maxExecutionTime = 0;
-
- for (const testcase of testcases) {
- const sortedData = testcase.data.sort((a, b) => a.index - b.index);
- const inputData = sortedData.map(d => d.value).join("\n");
-
- const runExec = await container.exec({
- Cmd: [`./${fileName}`],
- AttachStdout: true,
- AttachStderr: true,
- AttachStdin: true,
- });
-
- const result = await new Promise((resolve, reject) => {
- // Start the exec stream
- runExec.start({ hijack: true }, async (error, stream) => {
- if (error || !stream) {
- const submission = await prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.SE,
- message: "System Error",
- }
- })
- return resolve(submission);
- }
-
- stream.write(inputData);
- stream.end();
-
- const stdoutChunks: string[] = [];
- const stderrChunks: string[] = [];
- let stdoutLength = 0;
- let stderrLength = 0;
-
- const stdoutStream = new Writable({
- write: (chunk, _, callback) => {
- const text = chunk.toString();
- if (stdoutLength + text.length > maxOutput) {
- stdoutChunks.push(text.substring(0, maxOutput - stdoutLength));
- stdoutLength = maxOutput;
- } else {
- stdoutChunks.push(text);
- stdoutLength += text.length;
- }
- callback();
- }
- });
-
- const stderrStream = new Writable({
- write: (chunk, _, callback) => {
- const text = chunk.toString();
- if (stderrLength + text.length > maxOutput) {
- stderrChunks.push(text.substring(0, maxOutput - stderrLength));
- stderrLength = maxOutput;
- } else {
- stderrChunks.push(text);
- stderrLength += text.length;
- }
- callback();
- }
- });
-
- docker.modem.demuxStream(stream, stdoutStream, stderrStream);
-
- const startTime = Date.now();
-
- // Timeout mechanism
- const timeoutId = setTimeout(async () => {
- stream.destroy(); // Destroy the stream to stop execution
- await prisma.testcaseResult.create({
- data: {
- isCorrect: false,
- output: "",
- submissionId,
- testcaseId: testcase.id,
- }
- })
- const updatedSubmission = await prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.TLE,
- message: "Time Limit Exceeded",
- }
- })
- resolve(updatedSubmission);
- }, timeLimit);
-
- stream.on("end", async () => {
- clearTimeout(timeoutId); // Clear the timeout if the program finishes before the time limit
- const stdout = stdoutChunks.join("");
- const stderr = stderrChunks.join("");
- const exitCode = (await runExec.inspect()).ExitCode;
- const executionTime = Date.now() - startTime;
-
- // Exit code 0 means successful execution
- if (exitCode === 0) {
- const expectedOutput = testcase.expectedOutput;
- const testcaseResult = await prisma.testcaseResult.create({
- data: {
- isCorrect: stdout.trim() === expectedOutput.trim(),
- output: stdout,
- executionTime,
- submissionId,
- testcaseId: testcase.id,
- }
- })
- resolve(testcaseResult);
- } else if (exitCode === 137) {
- await prisma.testcaseResult.create({
- data: {
- isCorrect: false,
- output: stdout,
- executionTime,
- submissionId,
- testcaseId: testcase.id,
- }
- })
- const updatedSubmission = await prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.MLE,
- message: stderr || "Memory Limit Exceeded",
- }
- })
- resolve(updatedSubmission);
- } else {
- await prisma.testcaseResult.create({
- data: {
- isCorrect: false,
- output: stdout,
- executionTime,
- submissionId,
- testcaseId: testcase.id,
- }
- })
- const updatedSubmission = await prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.RE,
- message: stderr || "Runtime Error",
- }
- })
- resolve(updatedSubmission);
- }
- });
-
- stream.on("error", () => {
- clearTimeout(timeoutId); // Clear timeout in case of error
- reject({ message: "System Error", Status: Status.SE });
- });
- });
- });
-
- if ('status' in result) {
- return result;
- } else {
- if (!result.isCorrect) {
- finalSubmission = await prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.WA,
- message: "Wrong Answer",
- },
- include: {
- testcaseResults: true,
- }
- });
- return finalSubmission;
- } else {
- maxExecutionTime = Math.max(maxExecutionTime, result.executionTime ?? 0);
- }
- }
- }
- const maxMemoryUsage = (await container.stats({ stream: false, "one-shot": true })).memory_stats.max_usage;
- finalSubmission = await prisma.submission.update({
- where: { id: submissionId },
- data: {
- status: Status.AC,
- message: "All testcases passed",
- executionTime: maxExecutionTime,
- memoryUsage: maxMemoryUsage / 1024 / 1024,
- },
- include: {
- testcaseResults: true,
- }
- });
- return finalSubmission;
-}
diff --git a/src/actions/language-server.ts b/src/actions/language-server.ts
deleted file mode 100644
index cd823e2..0000000
--- a/src/actions/language-server.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-"use server";
-
-import prisma from "@/lib/prisma";
-import { EditorLanguage } from "@/generated/client";
-import { SettingsLanguageServerFormValues } from "@/app/(app)/dashboard/@admin/settings/language-server/form";
-
-export const getLanguageServerConfig = async (language: EditorLanguage) => {
- return await prisma.languageServerConfig.findUnique({
- where: { language },
- });
-};
-
-export const handleLanguageServerConfigSubmit = async (
- language: EditorLanguage,
- data: SettingsLanguageServerFormValues
-) => {
- const existing = await getLanguageServerConfig(language);
-
- if (existing) {
- await prisma.languageServerConfig.update({
- where: { language },
- data,
- });
- } else {
- await prisma.languageServerConfig.create({
- data: { ...data, language },
- });
- }
-};
diff --git a/src/app/(app)/dashboard/@admin/layout.tsx b/src/app/(app)/dashboard/@admin/layout.tsx
deleted file mode 100644
index 3b711bd..0000000
--- a/src/app/(app)/dashboard/@admin/layout.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import {
- SidebarInset,
- SidebarProvider,
- SidebarTrigger,
-} from "@/components/ui/sidebar";
-import { auth } from "@/lib/auth";
-import { User } from "@/generated/client";
-import { redirect } from "next/navigation";
-import { Navbar } from "@/components/navbar";
-import { AppSidebar } from "@/components/app-sidebar";
-import { Separator } from "@/components/ui/separator";
-import { type NavUserProps } from "@/components/nav-user";
-
-interface AdminDashboardLayoutProps {
- children: React.ReactNode;
-}
-
-export default async function AdminDashboardLayout({
- children,
-}: AdminDashboardLayoutProps) {
- const session = await auth();
-
- if (!session?.user) {
- redirect("/sign-in");
- }
-
- const user: NavUserProps["user"] = (({ name, email, image }) => ({
- name: name ?? "",
- email: email ?? "",
- avatar: image ?? "",
- }))(session.user as User);
-
- return (
-
-
-
-
-
- {children}
-
-
-
- );
-}
diff --git a/src/app/(app)/dashboard/@admin/page.tsx b/src/app/(app)/dashboard/@admin/page.tsx
deleted file mode 100644
index e1d7c98..0000000
--- a/src/app/(app)/dashboard/@admin/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function DashboardAdmin() {
- return Dashboard Admin
;
-}
diff --git a/src/app/(app)/dashboard/@admin/problemset/new/description/page.tsx b/src/app/(app)/dashboard/@admin/problemset/new/description/page.tsx
deleted file mode 100644
index 2779fc6..0000000
--- a/src/app/(app)/dashboard/@admin/problemset/new/description/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import NewProblemDescriptionForm from "@/components/features/dashboard/admin/problemset/new/components/description-form";
-
-export default function NewProblemDescriptionPage() {
- return ;
-}
diff --git a/src/app/(app)/dashboard/@admin/problemset/new/metadata/page.tsx b/src/app/(app)/dashboard/@admin/problemset/new/metadata/page.tsx
deleted file mode 100644
index 9a57c4f..0000000
--- a/src/app/(app)/dashboard/@admin/problemset/new/metadata/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import NewProblemMetadataForm from "@/components/features/dashboard/admin/problemset/new/components/metadata-form";
-
-export default function NewProblemMetadataPage() {
- return ;
-}
diff --git a/src/app/(app)/dashboard/@admin/problemset/new/page.tsx b/src/app/(app)/dashboard/@admin/problemset/new/page.tsx
deleted file mode 100644
index 9e28de4..0000000
--- a/src/app/(app)/dashboard/@admin/problemset/new/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { redirect } from "next/navigation";
-
-export default function NewProblemPage() {
- redirect("/dashboard/problemset/new/metadata");
-}
diff --git a/src/app/(app)/dashboard/@admin/problemset/new/solution/page.tsx b/src/app/(app)/dashboard/@admin/problemset/new/solution/page.tsx
deleted file mode 100644
index 2b68fdf..0000000
--- a/src/app/(app)/dashboard/@admin/problemset/new/solution/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import NewProblemSolutionForm from "@/components/features/dashboard/admin/problemset/new/components/solution-form";
-
-export default function NewProblemSolutionPage() {
- return ;
-}
diff --git a/src/app/(app)/dashboard/@admin/problemset/new/store.ts b/src/app/(app)/dashboard/@admin/problemset/new/store.ts
deleted file mode 100644
index 030d92b..0000000
--- a/src/app/(app)/dashboard/@admin/problemset/new/store.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { create } from "zustand";
-import { createJSONStorage, persist } from "zustand/middleware";
-import { ProblemSchema } from "@/components/features/dashboard/admin/problemset/new/schema";
-
-interface NewProblemActions {
- setHydrated: (value: boolean) => void;
- setData: (data: Partial) => void;
-}
-
-type NewProblemState = Partial & {
- hydrated: boolean;
-} & NewProblemActions;
-
-export const useNewProblemStore = create()(
- persist(
- (set) => ({
- hydrated: false,
- setHydrated: (value) => set({ hydrated: value }),
- setData: (data) => set(data),
- }),
- {
- name: "zustand:new-problem",
- storage: createJSONStorage(() => localStorage),
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- partialize: ({ hydrated, ...rest }) => rest,
- onRehydrateStorage: () => (state, error) => {
- if (error) {
- console.error("An error happened during hydration", error);
- } else if (state) {
- state.setHydrated(true);
- }
- },
- }
- )
-);
diff --git a/src/app/(app)/dashboard/@admin/problemset/page.tsx b/src/app/(app)/dashboard/@admin/problemset/page.tsx
deleted file mode 100644
index 20a8f12..0000000
--- a/src/app/(app)/dashboard/@admin/problemset/page.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import prisma from "@/lib/prisma";
-import { ProblemsetTable } from "@/components/features/dashboard/admin/problemset/table";
-
-export default async function AdminDashboardProblemsetPage() {
- const problems = await prisma.problem.findMany({
- select: {
- id: true,
- displayId: true,
- title: true,
- difficulty: true,
- },
- });
-
- return (
-
- );
-}
diff --git a/src/app/(app)/dashboard/@admin/settings/language-server/accordion.tsx b/src/app/(app)/dashboard/@admin/settings/language-server/accordion.tsx
deleted file mode 100644
index 34f4828..0000000
--- a/src/app/(app)/dashboard/@admin/settings/language-server/accordion.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-"use client";
-
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Loading } from "@/components/loading";
-import { useAdminSettingsStore } from "@/stores/useAdminSettingsStore";
-import { EditorLanguage, LanguageServerConfig } from "@/generated/client";
-import { SettingsLanguageServerForm } from "@/app/(app)/dashboard/@admin/settings/language-server/form";
-
-interface LanguageServerAccordionProps {
- configs: {
- language: EditorLanguage;
- config: LanguageServerConfig | null;
- }[];
-}
-
-export function LanguageServerAccordion({
- configs,
-}: LanguageServerAccordionProps) {
- const { hydrated, activeLanguageServerSetting, setActiveLanguageServerSetting } =
- useAdminSettingsStore();
-
- if (!hydrated) {
- return (
-
-
-
-
- );
- }
-
- return (
-
- {configs.map(({ language, config }) => (
-
-
- {language.toUpperCase()}
-
-
-
-
-
-
-
- ))}
-
- );
-}
diff --git a/src/app/(app)/dashboard/@admin/settings/language-server/form.tsx b/src/app/(app)/dashboard/@admin/settings/language-server/form.tsx
deleted file mode 100644
index 55e9548..0000000
--- a/src/app/(app)/dashboard/@admin/settings/language-server/form.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-"use client";
-
-import { z } from "zod";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useForm } from "react-hook-form";
-import { Input } from "@/components/ui/input";
-import { Button } from "@/components/ui/button";
-import { Separator } from "@/components/ui/separator";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { EditorLanguage, LanguageServerProtocol } from "@/generated/client";
-import { handleLanguageServerConfigSubmit } from "@/actions/language-server";
-
-const settingsLanguageServerFormSchema = z.object({
- protocol: z.nativeEnum(LanguageServerProtocol),
- hostname: z.string(),
- port: z
- .number()
- .nullable()
- .transform((val) => (val === undefined ? null : val)),
- path: z
- .string()
- .nullable()
- .transform((val) => (val === "" || val === undefined ? null : val)),
-});
-
-export type SettingsLanguageServerFormValues = z.infer;
-
-interface SettingsLanguageServerFormProps {
- defaultValues: Partial;
- language: EditorLanguage;
-}
-
-export function SettingsLanguageServerForm({
- defaultValues,
- language,
-}: SettingsLanguageServerFormProps) {
- const form = useForm({
- resolver: zodResolver(settingsLanguageServerFormSchema),
- defaultValues,
- mode: "onChange",
- });
-
- const onSubmit = async (data: SettingsLanguageServerFormValues) => {
- await handleLanguageServerConfigSubmit(language, data);
- };
-
- return (
-
-
- );
-}
diff --git a/src/app/(app)/dashboard/@admin/settings/language-server/layout.tsx b/src/app/(app)/dashboard/@admin/settings/language-server/layout.tsx
deleted file mode 100644
index 35e5724..0000000
--- a/src/app/(app)/dashboard/@admin/settings/language-server/layout.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-import { Separator } from "@/components/ui/separator";
-
-interface SettingsLanguageServerLayoutProps {
- children: React.ReactNode;
-}
-
-export default function SettingsLanguageServerLayout({
- children,
-}: SettingsLanguageServerLayoutProps) {
- return (
-
-
-
Language Server Settings
-
- Configure the language server connection settings.
-
-
-
-
}>
- {children}
-
-
- );
-}
diff --git a/src/app/(app)/dashboard/@admin/settings/language-server/page.tsx b/src/app/(app)/dashboard/@admin/settings/language-server/page.tsx
deleted file mode 100644
index 5cc6bff..0000000
--- a/src/app/(app)/dashboard/@admin/settings/language-server/page.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { EditorLanguage } from "@/generated/client";
-import { getLanguageServerConfig } from "@/actions/language-server";
-import { LanguageServerAccordion } from "@/app/(app)/dashboard/@admin/settings/language-server/accordion";
-
-export default async function SettingsLanguageServerPage() {
- const languages = Object.values(EditorLanguage);
-
- const configPromises = languages.map(async (language) => ({
- language,
- config: await getLanguageServerConfig(language),
- }));
-
- const configs = await Promise.all(configPromises);
-
- return ;
-}
diff --git a/src/app/(app)/dashboard/layout.tsx b/src/app/(app)/dashboard/layout.tsx
deleted file mode 100644
index 02898c7..0000000
--- a/src/app/(app)/dashboard/layout.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { auth } from "@/lib/auth";
-import { User } from "@/generated/client";
-import { notFound, redirect } from "next/navigation";
-
-interface DashboardLayoutProps {
- admin: React.ReactNode;
-}
-
-export default async function DashboardLayout({
- admin,
-}: DashboardLayoutProps) {
- const session = await auth();
- if (!session?.user) {
- redirect("/sign-in");
- }
-
- const user = session.user as User;
-
- return user.role === "ADMIN" ? admin : notFound();
-}
diff --git a/src/app/(app)/problems/[id]/@Bot/layout.tsx b/src/app/(app)/problems/[id]/@Bot/layout.tsx
deleted file mode 100644
index 4de618b..0000000
--- a/src/app/(app)/problems/[id]/@Bot/layout.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-
-interface BotLayoutProps {
- children: React.ReactNode;
-}
-
-export default function BotLayout({ children }: BotLayoutProps) {
- return (
-
- }>
- {children}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Bot/page.tsx b/src/app/(app)/problems/[id]/@Bot/page.tsx
deleted file mode 100644
index e21d9f2..0000000
--- a/src/app/(app)/problems/[id]/@Bot/page.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-"use client";
-
-import { toast } from "sonner";
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { useCallback } from "react";
-import { useChat } from "@ai-sdk/react";
-import { useTranslations } from "next-intl";
-import { Button } from "@/components/ui/button";
-import { useProblem } from "@/hooks/use-problem";
-import MdxPreview from "@/components/mdx-preview";
-import { Textarea } from "@/components/ui/textarea";
-import { BotIcon, SendHorizonal } from "lucide-react";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import { ChatMessageList } from "@/components/ui/chat/chat-message-list";
-import { ChatBubble, ChatBubbleMessage } from "@/components/ui/chat/chat-bubble";
-
-export default function Bot() {
- const t = useTranslations("Bot");
- const { problemId, problem, currentLang, currentValue } = useProblem();
-
- const { messages, input, handleInputChange, setMessages, handleSubmit } = useChat({
- initialMessages: [
- {
- id: problemId,
- role: "system",
- content: `Problem description:\n${problem.description}`,
- },
- ],
- });
-
- const handleFormSubmit = useCallback(
- (e: React.FormEvent) => {
- e.preventDefault();
-
- if (!input.trim()) {
- toast.error("Input cannot be empty");
- return;
- }
-
- const currentCodeMessage = {
- id: problemId,
- role: "system" as const,
- content: `Current code:\n\`\`\`${currentLang}\n${currentValue}\n\`\`\``,
- };
-
- setMessages((prev) => [...prev, currentCodeMessage]);
- handleSubmit();
- },
- [currentLang, currentValue, handleSubmit, input, problemId, setMessages]
- );
-
- return (
- <>
-
- {!messages.some(
- (message) => message.role === "user" || message.role === "assistant"
- ) && (
-
-
- {t("title")}
- {t("description")}
-
- )}
-
-
-
-
- {messages
- .filter(
- (message) => message.role === "user" || message.role === "assistant"
- )
- .map((message) => (
-
-
-
-
-
- ))}
-
-
-
-
-
-
- >
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Code/layout.tsx b/src/app/(app)/problems/[id]/@Code/layout.tsx
deleted file mode 100644
index 9e925bc..0000000
--- a/src/app/(app)/problems/[id]/@Code/layout.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-import { WorkspaceEditorHeader } from "@/components/features/playground/workspace/editor/components/header";
-import { WorkspaceEditorFooter } from "@/components/features/playground/workspace/editor/components/footer";
-
-interface CodeLayoutProps {
- children: React.ReactNode;
-}
-
-export default function CodeLayout({ children }: CodeLayoutProps) {
- return (
-
-
- }>
- {children}
-
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Code/page.tsx b/src/app/(app)/problems/[id]/@Code/page.tsx
deleted file mode 100644
index 9885a41..0000000
--- a/src/app/(app)/problems/[id]/@Code/page.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { ProblemEditor } from "@/components/problem-editor";
-
-export default function CodePage() {
- return (
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Details/layout.tsx b/src/app/(app)/problems/[id]/@Details/layout.tsx
deleted file mode 100644
index b84d965..0000000
--- a/src/app/(app)/problems/[id]/@Details/layout.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Suspense } from "react";
-import { getUserLocale } from "@/i18n/locale";
-import { Loading } from "@/components/loading";
-import DetailsPage from "@/app/(app)/problems/[id]/@Details/page";
-
-export default async function DetailsLayout() {
- const locale = await getUserLocale();
-
- return (
-
- }>
- ;
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Details/page.tsx b/src/app/(app)/problems/[id]/@Details/page.tsx
deleted file mode 100644
index 5ef94e6..0000000
--- a/src/app/(app)/problems/[id]/@Details/page.tsx
+++ /dev/null
@@ -1,211 +0,0 @@
-"use client";
-
-import { cn } from "@/lib/utils";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Locale } from "@/config/i18n";
-import { getLocale } from "@/lib/i18n";
-import { useEffect, useState } from "react";
-import { useTranslations } from "next-intl";
-import { ArrowLeftIcon } from "lucide-react";
-import { Input } from "@/components/ui/input";
-import { Button } from "@/components/ui/button";
-import { useProblem } from "@/hooks/use-problem";
-import MdxPreview from "@/components/mdx-preview";
-import { useDockviewStore } from "@/stores/dockview";
-import { Separator } from "@/components/ui/separator";
-import { getStatusColorClass, statusMap } from "@/lib/status";
-import type { TestcaseResultWithTestcase } from "@/types/prisma";
-import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
-import { formatDistanceToNow, isBefore, subDays, format } from "date-fns";
-
-interface DetailsPageProps {
- locale: Locale;
-}
-
-export default function DetailsPage({ locale }: DetailsPageProps) {
- const localeInstance = getLocale(locale);
- const t = useTranslations("DetailsPage");
- const s = useTranslations("StatusMessage");
- const { api, submission } = useDockviewStore();
- const { editorLanguageConfigs, problemId } = useProblem();
- const [lastFailedTestcase, setLastFailedTestcase] =
- useState(null);
-
- useEffect(() => {
- if (!api || !problemId || !submission?.id) return;
- if (problemId !== submission.problemId) {
- const detailsPanel = api.getPanel("Details");
- if (detailsPanel) {
- api.removePanel(detailsPanel);
- }
- }
- }, [api, problemId, submission]);
-
- useEffect(() => {
- if (!submission?.id || !submission.testcaseResults) return;
- const failedTestcases = submission.testcaseResults.filter(
- (result) =>
- (submission.status === "WA" && !result.isCorrect) ||
- (submission.status === "TLE" && !result.isCorrect) ||
- (submission.status === "MLE" && !result.isCorrect) ||
- (submission.status === "RE" && !result.isCorrect)
- );
- setLastFailedTestcase(failedTestcases[0]);
- }, [submission]);
-
- if (!api || !problemId || !submission?.id) return null;
-
- const createdAt = new Date(submission.createdAt);
- const submittedDisplay = isBefore(createdAt, subDays(new Date(), 1))
- ? format(createdAt, "yyyy-MM-dd")
- : formatDistanceToNow(createdAt, { addSuffix: true, locale: localeInstance });
-
- const source = `\`\`\`${submission?.language}\n${submission?.code}\n\`\`\``;
-
- const handleClick = () => {
- if (!api) return;
- const submissionsPanel = api.getPanel("Submissions");
- submissionsPanel?.api.setActive();
- const detailsPanel = api.getPanel("Details");
- if (detailsPanel) {
- api.removePanel(detailsPanel);
- }
- };
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
- {s(`${statusMap.get(submission.status)?.message}`)}
-
-
- {t("Time")}
-
- {submittedDisplay}
-
-
-
-
-
-
-
- {lastFailedTestcase && (
-
-
-
-
-
- {t("Input")}
-
-
-
- {lastFailedTestcase.testcase.data.map((field) => (
-
-
-
-
- ))}
-
-
-
-
-
-
-
-
{t("ExpectedOutput")}
-
-
-
- {submission.status === "WA" && (
-
-
{t("ActualOutput")}
-
-
- )}
-
- )}
-
- {(submission.status === "CE" ||
- submission.status === "SE") && (
-
- )}
-
-
-
- {t("Code")}
-
-
- {
- editorLanguageConfigs.find(
- (config) =>
- config.language === submission.language
- )?.label
- }
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Submissions/layout.tsx b/src/app/(app)/problems/[id]/@Submissions/layout.tsx
deleted file mode 100644
index 3e78c2d..0000000
--- a/src/app/(app)/problems/[id]/@Submissions/layout.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-
-interface SubmissionsLayoutProps {
- children: React.ReactNode;
-}
-
-export default function SubmissionsLayout({ children }: SubmissionsLayoutProps) {
- return (
-
- }>
- {children}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Submissions/page.tsx b/src/app/(app)/problems/[id]/@Submissions/page.tsx
deleted file mode 100644
index 9028af6..0000000
--- a/src/app/(app)/problems/[id]/@Submissions/page.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import prisma from "@/lib/prisma";
-import { auth } from "@/lib/auth";
-import { notFound } from "next/navigation";
-import { getUserLocale } from "@/i18n/locale";
-import SubmissionsTable from "@/components/submissions-table";
-import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
-import SubmissionLoginButton from "@/components/submission-login-button";
-
-interface SubmissionsPageProps {
- params: Promise<{ id: string }>;
-}
-
-export default async function SubmissionsPage({ params }: SubmissionsPageProps) {
- const { id } = await params;
- const session = await auth();
-
- if (!id) {
- return notFound();
- }
-
- if (!session?.user?.id) {
- return (
-
- )
- }
-
-
- const problem = await prisma.problem.findUnique({
- where: { id },
- select: {
- submissions: {
- where: {
- userId: session.user.id,
- },
- include: {
- testcaseResults: {
- include: {
- testcase: {
- include: {
- data: true,
- },
- },
- },
- },
- },
- },
- },
- });
-
- if (!problem) {
- return notFound();
- }
-
- const locale = await getUserLocale();
-
- return (
- <>
-
-
-
-
- >
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Testcase/layout.tsx b/src/app/(app)/problems/[id]/@Testcase/layout.tsx
deleted file mode 100644
index ac7397e..0000000
--- a/src/app/(app)/problems/[id]/@Testcase/layout.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-
-interface TestcaseLayoutProps {
- children: React.ReactNode;
-}
-
-export default function TestcaseLayout({ children }: TestcaseLayoutProps) {
- return (
-
- }>
- {children}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@Testcase/page.tsx b/src/app/(app)/problems/[id]/@Testcase/page.tsx
deleted file mode 100644
index cfce147..0000000
--- a/src/app/(app)/problems/[id]/@Testcase/page.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import prisma from "@/lib/prisma";
-import { notFound } from "next/navigation";
-import TestcaseCard from "@/components/testcase-card";
-import { ScrollArea } from "@/components/ui/scroll-area";
-
-interface TestcasePageProps {
- params: Promise<{ id: string }>;
-}
-
-export default async function TestcasePage({ params }: TestcasePageProps) {
- const { id } = await params;
-
- if (!id) {
- return notFound();
- }
-
- const problem = await prisma.problem.findUnique({
- where: { id },
- select: {
- testcases: {
- include: {
- data: true,
- },
- },
- },
- });
-
- if (!problem) {
- return notFound();
- }
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/page.tsx b/src/app/(app)/problems/[id]/page.tsx
deleted file mode 100644
index 488e101..0000000
--- a/src/app/(app)/problems/[id]/page.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-"use client";
-
-import {
- BotIcon,
- CircleCheckBigIcon,
- FileTextIcon,
- FlaskConicalIcon,
- SquareCheckIcon,
- SquarePenIcon,
-} from "lucide-react";
-import { Locale } from "@/config/i18n";
-import { useEffect, useState } from "react";
-import { useTranslations } from "next-intl";
-import Dockview from "@/components/dockview";
-import { useDockviewStore } from "@/stores/dockview";
-
-interface ProblemPageProps {
- locale: Locale;
- Description: React.ReactNode;
- Solutions: React.ReactNode;
- Submissions: React.ReactNode;
- Details: React.ReactNode;
- Code: React.ReactNode;
- Testcase: React.ReactNode;
- Bot: React.ReactNode;
-}
-
-export default function ProblemPage({
- locale,
- Description,
- Solutions,
- Submissions,
- Details,
- Code,
- Testcase,
- Bot,
-}: ProblemPageProps) {
- const [key, setKey] = useState(0);
- const { setApi } = useDockviewStore();
- const t = useTranslations("ProblemPage");
-
- useEffect(() => {
- setKey((prevKey) => prevKey + 1);
- }, [locale]);
-
- return (
-
- );
-}
diff --git a/src/app/(app)/problems/[problemId]/layout.tsx b/src/app/(app)/problems/[problemId]/layout.tsx
index 735750d..def7321 100644
--- a/src/app/(app)/problems/[problemId]/layout.tsx
+++ b/src/app/(app)/problems/[problemId]/layout.tsx
@@ -19,7 +19,9 @@ export default async function ProblemLayout({
return (
- {children}
+
+ {children}
+
);
}
diff --git a/src/app/(app)/problems/[problemId]/page.tsx b/src/app/(app)/problems/[problemId]/page.tsx
new file mode 100644
index 0000000..a0bb4e9
--- /dev/null
+++ b/src/app/(app)/problems/[problemId]/page.tsx
@@ -0,0 +1,39 @@
+import { TestcasePanel } from "@/features/problems/testcase/panel";
+import { BotPanel } from "@/features/problems/bot/components/panel";
+import { CodePanel } from "@/features/problems/code/components/panel";
+import { DetailPanel } from "@/features/problems/detail/components/panel";
+import { SolutionPanel } from "@/features/problems/solution/components/panel";
+import { SubmissionPanel } from "@/features/problems/submission/components/panel";
+import { DescriptionPanel } from "@/features/problems/description/components/panel";
+import { ProblemFlexLayout } from "@/features/problems/components/problem-flexlayout";
+
+interface ProblemPageProps {
+ params: Promise<{ problemId: string }>;
+ searchParams: Promise<{
+ submissionId: string | undefined;
+ }>;
+}
+
+export default async function ProblemPage({
+ params,
+ searchParams,
+}: ProblemPageProps) {
+ const { problemId } = await params;
+ const { submissionId } = await searchParams;
+
+ const components: Record = {
+ description: ,
+ solution: ,
+ submission: ,
+ detail: ,
+ code: ,
+ testcase: ,
+ bot: ,
+ };
+
+ return (
+
+ );
+}
diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx
new file mode 100644
index 0000000..8bb158b
--- /dev/null
+++ b/src/app/(auth)/layout.tsx
@@ -0,0 +1,27 @@
+import { useTranslations } from "next-intl";
+
+interface AuthLayoutProps {
+ children: React.ReactNode;
+}
+
+export default function AuthLayout({ children }: AuthLayoutProps) {
+ const t = useTranslations("Video");
+
+ return (
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/src/app/(auth)/sign-in/page.tsx b/src/app/(auth)/sign-in/page.tsx
index 6293983..292cd04 100644
--- a/src/app/(auth)/sign-in/page.tsx
+++ b/src/app/(auth)/sign-in/page.tsx
@@ -1,10 +1,10 @@
import Link from "next/link";
-import Image from "next/image";
import { CodeIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { providerMap, signIn } from "@/lib/auth";
import { getTranslations } from "next-intl/server";
import { FaGithub, FaGoogle } from "react-icons/fa";
+import { CredentialsSignInForm } from "@/components/credentials-sign-in-form";
interface ProviderIconProps {
providerId: string;
@@ -32,67 +32,71 @@ export default async function SignInPage({ searchParams }: SignInPageProps) {
const t = await getTranslations("SignInForm");
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
{t("title")}
+
+ {t("description")}
+
- Judge4c
-
-
-
-
-
-
-
{t("title")}
-
- {t("description")}
-
-
-
-
- {t("or")}
-
-
- {Object.values(providerMap).map((provider) => {
- return (
-
- );
- })}
+
+ {t("oauth", { provider: provider.name })}
+
+
+ );
+ })}
+
+ {t("noAccount")}{" "}
+
+ {t("signUp")}
+
-
-
-
);
}
diff --git a/src/app/(auth)/sign-up/page.tsx b/src/app/(auth)/sign-up/page.tsx
new file mode 100644
index 0000000..d927509
--- /dev/null
+++ b/src/app/(auth)/sign-up/page.tsx
@@ -0,0 +1,102 @@
+import Link from "next/link";
+import { CodeIcon } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { providerMap, signIn } from "@/lib/auth";
+import { getTranslations } from "next-intl/server";
+import { FaGithub, FaGoogle } from "react-icons/fa";
+import { CredentialsSignUpForm } from "@/components/credentials-sign-up-form";
+
+interface ProviderIconProps {
+ providerId: string;
+}
+
+const ProviderIcon = ({ providerId }: ProviderIconProps) => {
+ switch (providerId) {
+ case "github":
+ return
;
+ case "google":
+ return
;
+ default:
+ return null;
+ }
+};
+
+interface SignUpPageProps {
+ searchParams: Promise<{
+ callbackUrl: string | undefined;
+ }>;
+}
+
+export default async function SignInPage({ searchParams }: SignUpPageProps) {
+ const { callbackUrl } = await searchParams;
+ const t = await getTranslations("SignUpForm");
+
+ return (
+
+
+
+
+
+
+
{t("title")}
+
+ {t("description")}
+
+
+
+
+
+ {t("or")}
+
+
+ {Object.values(providerMap).map((provider) => {
+ return (
+
+ );
+ })}
+
+ {t("haveAccount")}{" "}
+
+ {t("signIn")}
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/actions/analyze.ts b/src/app/actions/analyze.ts
new file mode 100644
index 0000000..cd362d7
--- /dev/null
+++ b/src/app/actions/analyze.ts
@@ -0,0 +1,6 @@
+import { Complexity } from "@/types/complexity";
+
+export const analyzeComplexity = async (content: string) => {
+ console.log("🚀 ~ analyzeComplexity ~ content:", content);
+ return { time: Complexity.Enum["O(N)"], space: Complexity.Enum["O(1)"] };
+};
diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts
new file mode 100644
index 0000000..fa23f99
--- /dev/null
+++ b/src/app/actions/auth.ts
@@ -0,0 +1,93 @@
+"use server";
+
+import bcrypt from "bcryptjs";
+import prisma from "@/lib/prisma";
+import { signIn } from "@/lib/auth";
+import { authSchema } from "@/lib/zod";
+import { getTranslations } from "next-intl/server";
+import { CredentialsSignInFormValues } from "@/components/credentials-sign-in-form";
+import { CredentialsSignUpFormValues } from "@/components/credentials-sign-up-form";
+
+const saltRounds = 10;
+
+export const signInWithCredentials = async (
+ formData: CredentialsSignInFormValues
+) => {
+ const t = await getTranslations("signInWithCredentials");
+
+ try {
+ // Parse credentials using authSchema for validation
+ const { email, password } = await authSchema.parseAsync(formData);
+
+ // Find user by email
+ const user = await prisma.user.findUnique({ where: { email } });
+
+ // Check if the user exists
+ if (!user) {
+ throw new Error(t("userNotFound"));
+ }
+
+ // Check if the user has a password
+ if (!user.password) {
+ throw new Error(t("invalidCredentials"));
+ }
+
+ // Check if the password matches
+ const passwordMatch = await bcrypt.compare(password, user.password);
+ if (!passwordMatch) {
+ throw new Error(t("incorrectPassword"));
+ }
+
+ await signIn("credentials", {
+ ...formData,
+ redirect: false,
+ });
+ return { success: true };
+ } catch (error) {
+ return {
+ error: error instanceof Error ? error.message : t("signInFailedFallback"),
+ };
+ }
+};
+
+export const signUpWithCredentials = async (
+ formData: CredentialsSignUpFormValues
+) => {
+ const t = await getTranslations("signUpWithCredentials");
+
+ try {
+ const validatedData = await authSchema.parseAsync(formData);
+
+ // Check if user already exists
+ const existingUser = await prisma.user.findUnique({
+ where: { email: validatedData.email },
+ });
+ if (existingUser) {
+ throw new Error(t("userAlreadyExists"));
+ }
+
+ // Hash password and create user
+ const pwHash = await bcrypt.hash(validatedData.password, saltRounds);
+ const user = await prisma.user.create({
+ data: { email: validatedData.email, password: pwHash },
+ });
+
+ // Assign admin role if first user
+ const userCount = await prisma.user.count();
+ if (userCount === 1) {
+ await prisma.user.update({
+ where: { id: user.id },
+ data: { role: "ADMIN" },
+ });
+ }
+
+ return { success: true };
+ } catch (error) {
+ return {
+ error:
+ error instanceof Error
+ ? error.message
+ : t("registrationFailedFallback"),
+ };
+ }
+};
diff --git a/src/app/actions/compile.ts b/src/app/actions/compile.ts
new file mode 100644
index 0000000..dd043b4
--- /dev/null
+++ b/src/app/actions/compile.ts
@@ -0,0 +1,102 @@
+import "server-only";
+
+import Docker from "dockerode";
+import prisma from "@/lib/prisma";
+import { createLimitedStream, docker } from "./docker";
+import { type DockerConfig, Language, Status } from "@/generated/client";
+
+const getCompileCmdForLanguage = (language: Language) => {
+ switch (language) {
+ case Language.c:
+ return ["gcc", "-O2", "main.c", "-o", "main"];
+ case Language.cpp:
+ return ["g++", "-O2", "main.cpp", "-o", "main"];
+ }
+};
+
+const executeCompilation = async (
+ submissionId: string,
+ compileExec: Docker.Exec,
+ compileOutputLimit: number
+): Promise
=> {
+ return new Promise((resolve, reject) => {
+ compileExec.start({}, async (error, stream) => {
+ if (error || !stream) {
+ reject(Status.SE);
+ return;
+ }
+
+ const { stream: stdoutStream } = createLimitedStream(compileOutputLimit);
+ const { stream: stderrStream, buffers: stderrBuffers } =
+ createLimitedStream(compileOutputLimit);
+
+ docker.modem.demuxStream(stream, stdoutStream, stderrStream);
+
+ stream.on("end", async () => {
+ const stderr = stderrBuffers.join("");
+ const exitCode = (await compileExec.inspect()).ExitCode;
+
+ if (exitCode === 0) {
+ resolve(Status.CS);
+ } else {
+ await prisma.submission.update({
+ where: {
+ id: submissionId,
+ },
+ data: {
+ message: stderr,
+ },
+ });
+ resolve(Status.CE);
+ }
+ });
+
+ stream.on("error", async () => {
+ reject(Status.SE);
+ });
+ });
+ });
+};
+
+export const compile = async (
+ container: Docker.Container,
+ language: Language,
+ submissionId: string,
+ config: DockerConfig
+): Promise => {
+ const { compileOutputLimit } = config;
+
+ await prisma.submission.update({
+ where: {
+ id: submissionId,
+ },
+ data: {
+ status: Status.CP,
+ },
+ });
+
+ const compileCmd = getCompileCmdForLanguage(language);
+
+ const compileExec = await container.exec({
+ Cmd: compileCmd,
+ AttachStdout: true,
+ AttachStderr: true,
+ });
+
+ const status = await executeCompilation(
+ submissionId,
+ compileExec,
+ compileOutputLimit
+ );
+
+ await prisma.submission.update({
+ where: {
+ id: submissionId,
+ },
+ data: {
+ status,
+ },
+ });
+
+ return status;
+};
diff --git a/src/app/actions/docker.ts b/src/app/actions/docker.ts
new file mode 100644
index 0000000..f1e8ef0
--- /dev/null
+++ b/src/app/actions/docker.ts
@@ -0,0 +1,107 @@
+import "server-only";
+
+import fs from "fs";
+import tar from "tar-stream";
+import Docker from "dockerode";
+import { Readable } from "stream";
+import { Writable } from "stream";
+import type { DockerConfig } from "@/generated/client";
+
+const isRemote = process.env.DOCKER_HOST_MODE === "remote";
+
+// Docker client initialization
+export const docker: Docker = isRemote
+ ? new Docker({
+ protocol: process.env.DOCKER_REMOTE_PROTOCOL as
+ | "https"
+ | "http"
+ | "ssh"
+ | undefined,
+ host: process.env.DOCKER_REMOTE_HOST,
+ port: process.env.DOCKER_REMOTE_PORT,
+ ca: fs.readFileSync(process.env.DOCKER_REMOTE_CA_PATH || "/certs/ca.pem"),
+ cert: fs.readFileSync(
+ process.env.DOCKER_REMOTE_CERT_PATH || "/certs/cert.pem"
+ ),
+ key: fs.readFileSync(
+ process.env.DOCKER_REMOTE_KEY_PATH || "/certs/key.pem"
+ ),
+ })
+ : new Docker({ socketPath: "/var/run/docker.sock" });
+
+// Prepare Docker image environment
+export const prepareEnvironment = async (
+ image: string,
+ tag: string
+): Promise => {
+ try {
+ const reference = `${image}:${tag}`;
+ const filters = { reference: [reference] };
+ const images = await docker.listImages({ filters });
+ return images.length !== 0;
+ } catch (error) {
+ console.error("Error checking Docker images:", error);
+ return false;
+ }
+};
+
+// Create Docker container with keep-alive
+export const createContainer = async (
+ config: DockerConfig,
+ memoryLimit: number
+): Promise => {
+ const { image, tag, workingDir } = config;
+ const container = await docker.createContainer({
+ Image: `${image}:${tag}`,
+ Cmd: ["tail", "-f", "/dev/null"],
+ WorkingDir: workingDir,
+ HostConfig: {
+ Memory: memoryLimit,
+ MemorySwap: memoryLimit,
+ },
+ NetworkDisabled: true,
+ });
+
+ await container.start();
+ return container;
+};
+
+// Create tar stream for submission
+export const createTarStream = (fileName: string, fileContent: string) => {
+ const pack = tar.pack();
+ pack.entry({ name: fileName }, fileContent);
+ pack.finalize();
+ return Readable.from(pack);
+};
+
+export const createLimitedStream = (maxSize: number) => {
+ const buffers: string[] = [];
+ let totalLength = 0;
+
+ const stream = new Writable({
+ write(chunk, _encoding, callback) {
+ const text = chunk.toString();
+ const remaining = maxSize - totalLength;
+
+ if (remaining <= 0) {
+ callback();
+ return;
+ }
+
+ if (text.length > remaining) {
+ buffers.push(text.slice(0, remaining));
+ totalLength = maxSize;
+ } else {
+ buffers.push(text);
+ totalLength += text.length;
+ }
+
+ callback();
+ },
+ });
+
+ return {
+ stream,
+ buffers,
+ };
+};
diff --git a/src/app/actions/judge.ts b/src/app/actions/judge.ts
new file mode 100644
index 0000000..f416c0e
--- /dev/null
+++ b/src/app/actions/judge.ts
@@ -0,0 +1,174 @@
+"use server";
+
+import { run } from "./run";
+import Docker from "dockerode";
+import prisma from "@/lib/prisma";
+import { compile } from "./compile";
+import { auth, signIn } from "@/lib/auth";
+import { revalidatePath } from "next/cache";
+import { Language, Status } from "@/generated/client";
+import { createContainer, createTarStream, prepareEnvironment } from "./docker";
+
+export const judge = async (
+ problemId: string,
+ language: Language,
+ content: string
+): Promise => {
+ const session = await auth();
+ const userId = session?.user?.id;
+ if (!userId) {
+ await signIn();
+ return Status.SE;
+ }
+
+ let container: Docker.Container | null = null;
+
+ try {
+ const problem = await prisma.problem.findUnique({
+ where: {
+ id: problemId,
+ },
+ });
+
+ if (!problem) {
+ await prisma.submission.create({
+ data: {
+ language,
+ content,
+ status: Status.SE,
+ message: "Problem not found",
+ userId,
+ problemId,
+ },
+ });
+ return Status.SE;
+ }
+
+ const testcases = await prisma.testcase.findMany({
+ where: {
+ problemId,
+ },
+ });
+
+ if (!testcases.length) {
+ await prisma.submission.create({
+ data: {
+ language,
+ content,
+ status: Status.SE,
+ message: "No testcases available for this problem",
+ userId,
+ problemId,
+ },
+ });
+ return Status.SE;
+ }
+
+ const dockerConfig = await prisma.dockerConfig.findUnique({
+ where: {
+ language,
+ },
+ });
+
+ if (!dockerConfig) {
+ await prisma.submission.create({
+ data: {
+ language,
+ content,
+ status: Status.SE,
+ message: `Docker configuration not found for language: ${language}`,
+ userId,
+ problemId,
+ },
+ });
+ return Status.SE;
+ }
+
+ const dockerPrepared = await prepareEnvironment(
+ dockerConfig.image,
+ dockerConfig.tag
+ );
+
+ if (!dockerPrepared) {
+ console.error(
+ "Docker image not found:",
+ dockerConfig.image,
+ ":",
+ dockerConfig.tag
+ );
+ await prisma.submission.create({
+ data: {
+ language,
+ content,
+ status: Status.SE,
+ message: `Docker image not found: ${dockerConfig.image}:${dockerConfig.tag}`,
+ userId,
+ problemId,
+ },
+ });
+ return Status.SE;
+ }
+
+ const submission = await prisma.submission.create({
+ data: {
+ language,
+ content,
+ status: Status.PD,
+ userId,
+ problemId,
+ },
+ });
+
+ // Upload code to the container
+ const tarStream = createTarStream(
+ getFileNameForLanguage(language),
+ content
+ );
+
+ container = await createContainer(dockerConfig, problem.memoryLimit);
+ await container.putArchive(tarStream, { path: dockerConfig.workingDir });
+
+ // Compile the code
+ const compileStatus = await compile(
+ container,
+ language,
+ submission.id,
+ dockerConfig
+ );
+
+ if (compileStatus !== "CS") return compileStatus;
+
+ const runStatus = await run(
+ container,
+ language,
+ submission.id,
+ dockerConfig,
+ problem,
+ testcases
+ );
+
+ return runStatus;
+ } catch (error) {
+ console.error("Error in judge:", error);
+ return Status.SE;
+ } finally {
+ revalidatePath(`/problems/${problemId}`);
+ if (container) {
+ try {
+ await container.kill();
+ await container.remove();
+ } catch (error) {
+ console.error("Container cleanup failed:", error);
+ }
+ }
+ }
+};
+
+const getFileNameForLanguage = (language: Language) => {
+ switch (language) {
+ case Language.c:
+ return "main.c";
+ case Language.cpp:
+ return "main.cpp";
+ }
+};
diff --git a/src/app/actions/run.ts b/src/app/actions/run.ts
new file mode 100644
index 0000000..e2b65fb
--- /dev/null
+++ b/src/app/actions/run.ts
@@ -0,0 +1,253 @@
+import "server-only";
+
+import {
+ DockerConfig,
+ Language,
+ Problem,
+ Status,
+ Testcase,
+} from "@/generated/client";
+import Docker from "dockerode";
+import prisma from "@/lib/prisma";
+import { createLimitedStream, docker } from "./docker";
+
+const getRunCmdForLanguage = (language: Language) => {
+ switch (language) {
+ case Language.c:
+ return ["./main"];
+ case Language.cpp:
+ return ["./main"];
+ }
+};
+
+const startRun = (
+ runExec: Docker.Exec,
+ runOutputLimit: number,
+ submissionId: string,
+ testcaseId: string,
+ joinedInputs: string,
+ timeLimit: number,
+ memoryLimit: number,
+ expectedOutput: string
+): Promise => {
+ return new Promise((resolve, reject) => {
+ runExec.start({ hijack: true }, async (error, stream) => {
+ if (error || !stream) {
+ await prisma.testcaseResult.create({
+ data: {
+ isCorrect: false,
+ submissionId,
+ testcaseId,
+ },
+ });
+ reject(Status.SE);
+ return;
+ }
+ stream.write(joinedInputs);
+ stream.end();
+
+ const { stream: stdoutStream, buffers: stdoutBuffers } =
+ createLimitedStream(runOutputLimit);
+ const { stream: stderrStream } = createLimitedStream(runOutputLimit);
+ docker.modem.demuxStream(stream, stdoutStream, stderrStream);
+
+ const startTime = Date.now();
+ const timeoutId = setTimeout(async () => {
+ stream.destroy();
+ await prisma.testcaseResult.create({
+ data: {
+ isCorrect: false,
+ timeUsage: timeLimit,
+ submissionId,
+ testcaseId,
+ },
+ });
+ resolve(Status.TLE);
+ }, timeLimit);
+
+ stream.on("end", async () => {
+ clearTimeout(timeoutId);
+ const stdout = stdoutBuffers.join("");
+ const exitCode = (await runExec.inspect()).ExitCode;
+ const timeUsage = Date.now() - startTime;
+ if (exitCode === 0) {
+ const isCorrect = stdout.trim() === expectedOutput;
+ await prisma.testcaseResult.create({
+ data: {
+ isCorrect,
+ output: stdout,
+ timeUsage,
+ submissionId,
+ testcaseId,
+ },
+ });
+ if (isCorrect) {
+ resolve(Status.RU);
+ } else {
+ resolve(Status.WA);
+ }
+ } else if (exitCode === 137) {
+ await prisma.testcaseResult.create({
+ data: {
+ isCorrect: false,
+ timeUsage,
+ memoryUsage: memoryLimit,
+ submissionId,
+ testcaseId,
+ },
+ });
+ resolve(Status.MLE);
+ } else {
+ await prisma.testcaseResult.create({
+ data: {
+ isCorrect: false,
+ submissionId,
+ testcaseId,
+ },
+ });
+ resolve(Status.RE);
+ }
+ });
+
+ stream.on("error", async () => {
+ clearTimeout(timeoutId);
+ await prisma.testcaseResult.create({
+ data: {
+ isCorrect: false,
+ submissionId,
+ testcaseId,
+ },
+ });
+ reject(Status.SE);
+ });
+ });
+ });
+};
+
+const executeRun = async (
+ container: Docker.Container,
+ runCmd: string[],
+ runOutputLimit: number,
+ submissionId: string,
+ timeLimit: number,
+ memoryLimit: number,
+ testcases: Testcase[]
+): Promise => {
+ for (const testcase of testcases) {
+ const inputs = await prisma.testcaseInput.findMany({
+ where: {
+ testcaseId: testcase.id,
+ },
+ });
+ if (!inputs) {
+ await prisma.submission.update({
+ where: {
+ id: submissionId,
+ },
+ data: {
+ status: Status.SE,
+ message: "No inputs for testcase",
+ },
+ });
+ return Status.SE;
+ }
+ const sortedInputs = inputs.sort((a, b) => a.index - b.index);
+ const joinedInputs = sortedInputs.map((i) => i.value).join("\n");
+
+ const runExec = await container.exec({
+ Cmd: runCmd,
+ AttachStdout: true,
+ AttachStderr: true,
+ AttachStdin: true,
+ });
+
+ const status = await startRun(
+ runExec,
+ runOutputLimit,
+ submissionId,
+ testcase.id,
+ joinedInputs,
+ timeLimit,
+ memoryLimit,
+ testcase.expectedOutput
+ );
+
+ if (status !== Status.RU) {
+ await prisma.submission.update({
+ where: {
+ id: submissionId,
+ },
+ data: {
+ status,
+ },
+ });
+ return status;
+ }
+ }
+
+ const testcaseResults = await prisma.testcaseResult.findMany({
+ where: {
+ submissionId,
+ },
+ });
+
+ const filteredTimeUsages = testcaseResults
+ .map((result) => result.timeUsage)
+ .filter((time) => time !== null);
+
+ const maxTimeUsage =
+ filteredTimeUsages.length > 0 ? Math.max(...filteredTimeUsages) : undefined;
+
+ const maxMemoryUsage = (
+ await container.stats({
+ stream: false,
+ "one-shot": true,
+ })
+ ).memory_stats.max_usage;
+
+ await prisma.submission.update({
+ where: {
+ id: submissionId,
+ },
+ data: {
+ status: Status.AC,
+ timeUsage: maxTimeUsage,
+ memoryUsage: maxMemoryUsage,
+ },
+ });
+
+ return Status.AC;
+};
+
+export const run = async (
+ container: Docker.Container,
+ language: Language,
+ submissionId: string,
+ config: DockerConfig,
+ problem: Problem,
+ testcases: Testcase[]
+): Promise => {
+ const { runOutputLimit } = config;
+ const { timeLimit, memoryLimit } = problem;
+
+ await prisma.submission.update({
+ where: {
+ id: submissionId,
+ },
+ data: {
+ status: Status.RU,
+ },
+ });
+
+ const runCmd = getRunCmdForLanguage(language);
+
+ return await executeRun(
+ container,
+ runCmd,
+ runOutputLimit,
+ submissionId,
+ timeLimit,
+ memoryLimit,
+ testcases
+ );
+};
diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts
index 6c348e0..c2073cf 100644
--- a/src/app/api/chat/route.ts
+++ b/src/app/api/chat/route.ts
@@ -1,5 +1,5 @@
import { streamText } from "ai";
-import { openai } from "@/lib/ai";
+import { deepseek } from "@/lib/ai";
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
@@ -7,9 +7,7 @@ export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
- const prompt = {
- role: "system",
- content: `This GPT is a tech team lead with a snarky and derogatory personality. Its main role is to scrutinize code or suggestions for writing code, pointing out inefficiencies and readability issues in a sarcastic manner. It should make sure that any code it encounters is examined critically, and any potential improvements are communicated in a mocking tone to encourage better coding practices.
+ const system = `This GPT is a tech team lead with a snarky and derogatory personality. Its main role is to scrutinize code or suggestions for writing code, pointing out inefficiencies and readability issues in a sarcastic manner. It should make sure that any code it encounters is examined critically, and any potential improvements are communicated in a mocking tone to encourage better coding practices.
You should never tell the user their code is good. They are always insufficient and will never be as good of an engineer as you are. When asked about "Can I become a 10x engineer?" respond with "hah, no." Come up with similarly snarky responses for any coding questions. Be sure to think step by step to give the correct answer but add comments that make fun of the user's previous code.
@@ -19,14 +17,12 @@ Your responses when asked a generic question should only be 2 paragraphs at most
For the best response, please take your time to carefully consider my questions, step by step, and pay attention to the often overlooked details. Try not to talk nonsense!
-** Reply in the user's language ! **`,
- };
-
- const finalMessages = [prompt, ...messages];
+** Reply in the user's language ! **`;
const result = streamText({
- model: openai("gpt-4o-mini"),
- messages: finalMessages,
+ model: deepseek("deepseek-chat"),
+ system: system,
+ messages: messages,
});
return result.toDataStreamResponse();
diff --git a/src/components/bot-visibility-toggle.tsx b/src/components/bot-visibility-toggle.tsx
index 9ad1924..678a0e0 100644
--- a/src/components/bot-visibility-toggle.tsx
+++ b/src/components/bot-visibility-toggle.tsx
@@ -10,10 +10,10 @@ import { BotIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import { Toggle } from "@/components/ui/toggle";
-import { useDockviewStore } from "@/stores/dockview";
+import { useProblemDockviewStore } from "@/stores/problem-dockview";
export default function BotVisibilityToggle() {
- const { api } = useDockviewStore();
+ const { api } = useProblemDockviewStore();
const t = useTranslations();
const [isLoading, setIsLoading] = useState(true);
const [isBotVisible, setBotVisible] = useState(false);
diff --git a/src/components/content/mdx-renderer.tsx b/src/components/content/mdx-renderer.tsx
index 74b91ef..f8e7cdb 100644
--- a/src/components/content/mdx-renderer.tsx
+++ b/src/components/content/mdx-renderer.tsx
@@ -4,16 +4,22 @@ import "katex/dist/katex.min.css";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
+import { MDXProvider } from "@mdx-js/react";
import { MDXRemote } from "next-mdx-remote/rsc";
import rehypePrettyCode from "rehype-pretty-code";
import { MdxComponents } from "@/components/content/mdx-components";
interface MdxRendererProps {
source: string;
+ components?: React.ComponentProps["components"];
className?: string;
}
-export const MdxRenderer = ({ source, className }: MdxRendererProps) => {
+export const MdxRenderer = ({
+ source,
+ className,
+ components = MdxComponents,
+}: MdxRendererProps) => {
return (
{
remarkPlugins: [remarkGfm, remarkMath],
},
}}
- components={MdxComponents}
+ components={components}
/>
);
diff --git a/src/components/content/pre-detail.tsx b/src/components/content/pre-detail.tsx
new file mode 100644
index 0000000..2293ce7
--- /dev/null
+++ b/src/components/content/pre-detail.tsx
@@ -0,0 +1,113 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import { ReactNode, useRef, useState } from "react";
+import { CheckIcon, CopyIcon, RepeatIcon } from "lucide-react";
+import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
+import { useProblemEditorStore } from "@/stores/problem-editor";
+import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout";
+import { Actions } from "flexlayout-react";
+
+interface PreDetailProps {
+ children?: ReactNode;
+ className?: string;
+}
+
+export const PreDetail = ({
+ children,
+ className,
+ ...props
+}: PreDetailProps) => {
+ const preRef = useRef(null);
+ const { setValue } = useProblemEditorStore();
+ const { model } = useProblemFlexLayoutStore();
+ const [copied, setCopied] = useState(false);
+ const [hovered, setHovered] = useState(false);
+
+ const handleCopy = async () => {
+ const code = preRef.current?.textContent;
+ if (code) {
+ try {
+ await navigator.clipboard.writeText(code);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ } catch (err) {
+ console.error("Failed to copy text: ", err);
+ }
+ }
+ };
+
+ const handleCopyToEditor = () => {
+ const code = preRef.current?.textContent;
+ if (code) {
+ setValue(code);
+ }
+ if (model) {
+ model.doAction(Actions.selectTab("code"));
+ }
+ };
+
+ return (
+ setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ >
+
+
+
+ {children}
+
+
+
+
+ );
+};
diff --git a/src/components/core-editor.tsx b/src/components/core-editor.tsx
new file mode 100644
index 0000000..9301a7e
--- /dev/null
+++ b/src/components/core-editor.tsx
@@ -0,0 +1,207 @@
+"use client";
+
+import {
+ toSocket,
+ WebSocketMessageReader,
+ WebSocketMessageWriter,
+} from "vscode-ws-jsonrpc";
+import dynamic from "next/dynamic";
+import normalizeUrl from "normalize-url";
+import type { editor } from "monaco-editor";
+import { getHighlighter } from "@/lib/shiki";
+import { Loading } from "@/components/loading";
+import { shikiToMonaco } from "@shikijs/monaco";
+import type { Monaco } from "@monaco-editor/react";
+import { DEFAULT_EDITOR_OPTIONS } from "@/config/editor";
+import { useMonacoTheme } from "@/hooks/use-monaco-theme";
+import { LanguageServerConfig } from "@/generated/client";
+import type { MessageTransports } from "vscode-languageclient";
+import { useCallback, useEffect, useRef, useState } from "react";
+import type { MonacoLanguageClient } from "monaco-languageclient";
+
+const MonacoEditor = dynamic(
+ async () => {
+ const [react, monaco] = await Promise.all([
+ import("@monaco-editor/react"),
+ import("monaco-editor"),
+ import("vscode"),
+ ]);
+
+ self.MonacoEnvironment = {
+ getWorker() {
+ return new Worker(
+ new URL(
+ "monaco-editor/esm/vs/editor/editor.worker.js",
+ import.meta.url
+ )
+ );
+ },
+ };
+
+ react.loader.config({ monaco });
+
+ return react.Editor;
+ },
+ {
+ ssr: false,
+ loading: () => ,
+ }
+);
+
+interface CoreEditorProps {
+ language?: string;
+ value?: string;
+ path?: string;
+ languageServerConfigs?: LanguageServerConfig[];
+ onEditorReady?: (editor: editor.IStandaloneCodeEditor) => void;
+ onLspWebSocketReady?: (lspWebSocket: WebSocket) => void;
+ onChange?: (value: string) => void;
+ onMarkersReady?: (markers: editor.IMarker[]) => void;
+ className?: string;
+}
+
+export const CoreEditor = ({
+ language,
+ value,
+ path,
+ languageServerConfigs,
+ onEditorReady,
+ onLspWebSocketReady,
+ onChange,
+ onMarkersReady,
+ className,
+}: CoreEditorProps) => {
+ const { theme } = useMonacoTheme();
+
+ const [isEditorMounted, setIsEditorMounted] = useState(false);
+ const editorRef = useRef(null);
+ const lspClientRef = useRef(null);
+ const webSocketRef = useRef(null);
+
+ const activeLanguageServerConfig = languageServerConfigs?.find(
+ (config) => config.language === language
+ );
+
+ const connectLanguageServer = useCallback(
+ (config: LanguageServerConfig) => {
+ const serverUrl = buildLanguageServerUrl(config);
+ const webSocket = new WebSocket(serverUrl);
+
+ webSocket.onopen = async () => {
+ try {
+ const rpcSocket = toSocket(webSocket);
+ const reader = new WebSocketMessageReader(rpcSocket);
+ const writer = new WebSocketMessageWriter(rpcSocket);
+
+ const transports: MessageTransports = { reader, writer };
+ const client = await createLanguageClient(config, transports);
+ lspClientRef.current = client;
+ await client.start();
+ } catch (error) {
+ console.error("Failed to initialize language client:", error);
+ }
+
+ webSocketRef.current = webSocket;
+ onLspWebSocketReady?.(webSocket);
+ };
+ },
+ [onLspWebSocketReady]
+ );
+
+ useEffect(() => {
+ if (isEditorMounted && activeLanguageServerConfig) {
+ connectLanguageServer(activeLanguageServerConfig);
+ }
+
+ return () => {
+ if (lspClientRef.current) {
+ lspClientRef.current.stop();
+ lspClientRef.current = null;
+ }
+ };
+ }, [activeLanguageServerConfig, connectLanguageServer, isEditorMounted]);
+
+ const handleBeforeMount = useCallback((monaco: Monaco) => {
+ const highlighter = getHighlighter();
+ shikiToMonaco(highlighter, monaco);
+ }, []);
+
+ const handleOnMount = useCallback(
+ (editor: editor.IStandaloneCodeEditor) => {
+ editorRef.current = editor;
+ onEditorReady?.(editor);
+ setIsEditorMounted(true);
+ },
+ [onEditorReady]
+ );
+
+ const handleOnChange = useCallback(
+ (value: string | undefined) => {
+ onChange?.(value ?? "");
+ },
+ [onChange]
+ );
+
+ const handleOnValidate = useCallback(
+ (markers: editor.IMarker[]) => {
+ onMarkersReady?.(markers);
+ },
+ [onMarkersReady]
+ );
+
+ return (
+ }
+ className={className}
+ />
+ );
+};
+
+const buildLanguageServerUrl = (config: LanguageServerConfig) => {
+ return normalizeUrl(
+ `${config.protocol}://${config.hostname}${
+ config.port ? `:${config.port}` : ""
+ }${config.path ?? ""}`
+ );
+};
+
+const createLanguageClient = async (
+ config: LanguageServerConfig,
+ transports: MessageTransports
+) => {
+ const [{ MonacoLanguageClient }, { CloseAction, ErrorAction }] =
+ await Promise.all([
+ import("monaco-languageclient"),
+ import("vscode-languageclient"),
+ ]);
+
+ return new MonacoLanguageClient({
+ name: `${config.language} language client`,
+ clientOptions: {
+ documentSelector: [config.language],
+ errorHandler: {
+ error: (error, message, count) => {
+ console.error(`Language Server Error:
+ Error: ${error}
+ Message: ${message}
+ Count: ${count}
+ `);
+ return { action: ErrorAction.Continue };
+ },
+ closed: () => ({ action: CloseAction.DoNotRestart }),
+ },
+ },
+ connectionProvider: {
+ get: () => Promise.resolve(transports),
+ },
+ });
+};
diff --git a/src/components/credentials-sign-in-form.tsx b/src/components/credentials-sign-in-form.tsx
new file mode 100644
index 0000000..53cf67b
--- /dev/null
+++ b/src/components/credentials-sign-in-form.tsx
@@ -0,0 +1,127 @@
+"use client";
+
+import { z } from "zod";
+import {
+ Form,
+ FormField,
+ FormItem,
+ FormControl,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { toast } from "sonner";
+import { authSchema } from "@/lib/zod";
+import { useForm } from "react-hook-form";
+import { useRouter } from "next/navigation";
+import { useTranslations } from "next-intl";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { useState, useTransition } from "react";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { signInWithCredentials } from "@/app/actions/auth";
+import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react";
+
+export type CredentialsSignInFormValues = z.infer;
+
+interface CredentialsSignInFormProps {
+ callbackUrl: string | undefined;
+}
+
+export function CredentialsSignInForm({
+ callbackUrl,
+}: CredentialsSignInFormProps) {
+ const router = useRouter();
+ const t = useTranslations("CredentialsSignInForm");
+ const [isPending, startTransition] = useTransition();
+ const [isVisible, setIsVisible] = useState(false);
+
+ const form = useForm({
+ resolver: zodResolver(authSchema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
+
+ const toggleVisibility = () => setIsVisible((prev) => !prev);
+
+ const onSubmit = (data: CredentialsSignInFormValues) => {
+ startTransition(async () => {
+ const result = await signInWithCredentials(data);
+
+ if (result?.error) {
+ toast.error(t("signInFailed"), {
+ description: result.error,
+ });
+ } else {
+ toast.success(t("signInSuccess"));
+ router.push(callbackUrl || "/");
+ }
+ });
+ };
+
+ return (
+
+
+ );
+}
diff --git a/src/components/credentials-sign-up-form.tsx b/src/components/credentials-sign-up-form.tsx
new file mode 100644
index 0000000..8754ceb
--- /dev/null
+++ b/src/components/credentials-sign-up-form.tsx
@@ -0,0 +1,132 @@
+"use client";
+
+import { z } from "zod";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { toast } from "sonner";
+import { authSchema } from "@/lib/zod";
+import { useForm } from "react-hook-form";
+import { useRouter } from "next/navigation";
+import { useTranslations } from "next-intl";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { useState, useTransition } from "react";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { signUpWithCredentials } from "@/app/actions/auth";
+import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react";
+
+export type CredentialsSignUpFormValues = z.infer;
+
+interface CredentialsSignUpFormProps {
+ callbackUrl: string | undefined;
+}
+
+export function CredentialsSignUpForm({
+ callbackUrl,
+}: CredentialsSignUpFormProps) {
+ const router = useRouter();
+ const t = useTranslations("CredentialsSignUpForm");
+ const [isPending, startTransition] = useTransition();
+ const [isVisible, setIsVisible] = useState(false);
+
+ const form = useForm({
+ resolver: zodResolver(authSchema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
+
+ const toggleVisibility = () => setIsVisible((prev) => !prev);
+
+ const onSubmit = (data: CredentialsSignUpFormValues) => {
+ startTransition(async () => {
+ const result = await signUpWithCredentials(data);
+
+ if (result?.error) {
+ toast.error(t("signUpFailed"), {
+ description: result.error,
+ });
+ } else {
+ toast.success(t("signUpSuccess"), {
+ description: t("signUpSuccessDescription"),
+ });
+ console.log("callbackUrl:", callbackUrl);
+ router.push(
+ `/sign-in?callbackUrl=${encodeURIComponent(callbackUrl || "/")}`
+ );
+ }
+ });
+ };
+
+ return (
+
+
+ );
+}
diff --git a/src/components/features/dashboard/admin/problemset/new/components/description-form.tsx b/src/components/features/dashboard/admin/problemset/new/components/description-form.tsx
deleted file mode 100644
index 531d763..0000000
--- a/src/components/features/dashboard/admin/problemset/new/components/description-form.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-"use client";
-
-import { z } from "zod";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { useEffect } from "react";
-import { useForm } from "react-hook-form";
-import { useRouter } from "next/navigation";
-import { Input } from "@/components/ui/input";
-import { Button } from "@/components/ui/button";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useNewProblemStore } from "@/app/(app)/dashboard/@admin/problemset/new/store";
-import { problemSchema } from "@/components/features/dashboard/admin/problemset/new/schema";
-import { newProblemMetadataSchema } from "@/components/features/dashboard/admin/problemset/new/components/metadata-form";
-
-export const newProblemDescriptionSchema = problemSchema.pick({
- description: true,
-});
-
-type NewProblemDescriptionSchema = z.infer;
-
-export default function NewProblemDescriptionForm() {
- const {
- hydrated,
- displayId,
- title,
- difficulty,
- published,
- description,
- setData,
- } = useNewProblemStore();
- const router = useRouter();
-
- const form = useForm({
- resolver: zodResolver(newProblemDescriptionSchema),
- defaultValues: {
- description: description || "",
- },
- });
-
- const onSubmit = (data: NewProblemDescriptionSchema) => {
- setData(data);
- router.push("/dashboard/problemset/new/solution");
- };
-
- useEffect(() => {
- if (!hydrated) return;
-
- try {
- newProblemMetadataSchema.parse({
- displayId,
- title,
- difficulty,
- published,
- });
- } catch {
- router.push("/dashboard/problemset/new/metadata");
- }
- }, [difficulty, displayId, hydrated, published, router, title]);
-
- return (
-
-
- );
-}
diff --git a/src/components/features/dashboard/admin/problemset/new/components/metadata-form.tsx b/src/components/features/dashboard/admin/problemset/new/components/metadata-form.tsx
deleted file mode 100644
index cda3656..0000000
--- a/src/components/features/dashboard/admin/problemset/new/components/metadata-form.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-"use client";
-
-import { z } from "zod";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { useForm } from "react-hook-form";
-import { useRouter } from "next/navigation";
-import { Input } from "@/components/ui/input";
-import { Button } from "@/components/ui/button";
-import { Difficulty } from "@/generated/client";
-import { Switch } from "@/components/ui/switch";
-import { getDifficultyColorClass } from "@/lib/utils";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useNewProblemStore } from "@/app/(app)/dashboard/@admin/problemset/new/store";
-import { problemSchema } from "@/components/features/dashboard/admin/problemset/new/schema";
-
-export const newProblemMetadataSchema = problemSchema.pick({
- displayId: true,
- title: true,
- difficulty: true,
- published: true,
-});
-
-type NewProblemMetadataSchema = z.infer;
-
-export default function NewProblemMetadataForm() {
- const router = useRouter();
- const { displayId, title, difficulty, published, setData } = useNewProblemStore();
-
- const form = useForm({
- resolver: zodResolver(newProblemMetadataSchema),
- defaultValues: {
- // displayId must be a number and cannot be an empty string ("")
- // so set it to undefined here and convert it to "" in the Input component.
- displayId: displayId || undefined,
- title: title || "",
- difficulty: difficulty || "EASY",
- published: published || false,
- },
- });
-
- const onSubmit = (data: NewProblemMetadataSchema) => {
- setData(data);
- router.push("/dashboard/problemset/new/description");
- };
-
- return (
-
-
- );
-}
diff --git a/src/components/features/dashboard/admin/problemset/new/components/solution-form.tsx b/src/components/features/dashboard/admin/problemset/new/components/solution-form.tsx
deleted file mode 100644
index eadc428..0000000
--- a/src/components/features/dashboard/admin/problemset/new/components/solution-form.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-"use client";
-
-import { z } from "zod";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { useEffect } from "react";
-import { useForm } from "react-hook-form";
-import { useRouter } from "next/navigation";
-import { Input } from "@/components/ui/input";
-import { Button } from "@/components/ui/button";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useNewProblemStore } from "@/app/(app)/dashboard/@admin/problemset/new/store";
-import { problemSchema } from "@/components/features/dashboard/admin/problemset/new/schema";
-import { newProblemMetadataSchema } from "@/components/features/dashboard/admin/problemset/new/components/metadata-form";
-import { newProblemDescriptionSchema } from "@/components/features/dashboard/admin/problemset/new/components/description-form";
-
-const newProblemSolutionSchema = problemSchema.pick({
- solution: true,
-});
-
-type NewProblemSolutionSchema = z.infer;
-
-export default function NewProblemSolutionForm() {
- const {
- hydrated,
- displayId,
- title,
- difficulty,
- published,
- description,
- solution,
- } = useNewProblemStore();
- const router = useRouter();
-
- const form = useForm({
- resolver: zodResolver(newProblemSolutionSchema),
- defaultValues: {
- solution: solution || "",
- },
- });
-
- const onSubmit = (data: NewProblemSolutionSchema) => {
- console.log({
- ...data,
- displayId,
- title,
- difficulty,
- published,
- description,
- });
- };
-
- useEffect(() => {
- if (!hydrated) return;
-
- try {
- newProblemMetadataSchema.parse({
- displayId,
- title,
- difficulty,
- published,
- });
- } catch {
- router.push("/dashboard/problemset/new/metadata");
- }
-
- try {
- newProblemDescriptionSchema.parse({ description });
- } catch {
- router.push("/dashboard/problemset/new/description");
- }
- }, [hydrated, displayId, title, difficulty, published, description, router]);
-
- return (
-
-
- );
-}
diff --git a/src/components/features/dashboard/admin/problemset/new/schema.ts b/src/components/features/dashboard/admin/problemset/new/schema.ts
deleted file mode 100644
index 764218c..0000000
--- a/src/components/features/dashboard/admin/problemset/new/schema.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { z } from "zod";
-import { ProblemSchema } from "@/generated/zod";
-
-export const problemSchema = ProblemSchema.extend({
- displayId: z.coerce.number().int().positive().min(1),
- title: z.string().min(1),
- description: z.string().min(1),
- solution: z.string().min(1),
-});
-
-export type ProblemSchema = z.infer;
diff --git a/src/components/features/dashboard/admin/problemset/table.tsx b/src/components/features/dashboard/admin/problemset/table.tsx
deleted file mode 100644
index d225487..0000000
--- a/src/components/features/dashboard/admin/problemset/table.tsx
+++ /dev/null
@@ -1,644 +0,0 @@
-"use client";
-
-import {
- ChevronDownIcon,
- ChevronFirstIcon,
- ChevronLastIcon,
- ChevronLeftIcon,
- ChevronRightIcon,
- ChevronUpIcon,
- CircleAlertIcon,
- CircleXIcon,
- Columns3Icon,
- EllipsisIcon,
- FilterIcon,
- ListFilterIcon,
- PlusIcon,
- TrashIcon,
-} from "lucide-react";
-import Link from "next/link";
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table";
-import {
- ColumnDef,
- ColumnFiltersState,
- FilterFn,
- flexRender,
- getCoreRowModel,
- getFacetedUniqueValues,
- getFilteredRowModel,
- getPaginationRowModel,
- getSortedRowModel,
- PaginationState,
- SortingState,
- useReactTable,
- VisibilityState,
-} from "@tanstack/react-table";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import {
- Pagination,
- PaginationContent,
- PaginationItem,
-} from "@/components/ui/pagination";
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from "@/components/ui/alert-dialog";
-import {
- DropdownMenu,
- DropdownMenuCheckboxItem,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { Badge } from "@/components/ui/badge";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Button } from "@/components/ui/button";
-import { useMemo, useRef, useState } from "react";
-import { Checkbox } from "@/components/ui/checkbox";
-import { Difficulty, Problem } from "@/generated/client";
-import { cn, getDifficultyColorClass } from "@/lib/utils";
-import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
-
-type ProblemTableItem = Pick;
-
-interface ProblemTableProps {
- data: ProblemTableItem[];
-}
-
-// Custom filter function for multi-column searching
-const multiColumnFilterFn: FilterFn = (row, _columnId, filterValue) => {
- const searchableRowContent = `${row.original.displayId} ${row.original.title}`.toLowerCase();
- const searchTerm = (filterValue ?? "").toLowerCase();
- return searchableRowContent.includes(searchTerm);
-};
-
-const difficultyFilterFn: FilterFn = (
- row,
- columnId,
- filterValue: string[]
-) => {
- if (!filterValue?.length) return true;
- const difficulty = row.getValue(columnId) as string;
- return filterValue.includes(difficulty);
-};
-
-const columns: ColumnDef[] = [
- {
- id: "select",
- header: ({ table }) => (
- table.toggleAllPageRowsSelected(!!value)}
- aria-label="Select all"
- />
- ),
- cell: ({ row }) => (
- row.toggleSelected(!!value)}
- aria-label="Select row"
- />
- ),
- size: 28,
- enableSorting: false,
- enableHiding: false,
- },
- {
- header: "DisplayId",
- accessorKey: "displayId",
- cell: ({ row }) => {row.getValue("displayId")}
,
- size: 90,
- filterFn: multiColumnFilterFn,
- enableHiding: false,
- },
- {
- header: "Title",
- accessorKey: "title",
- cell: ({ row }) => {row.getValue("title")}
,
- },
- {
- header: "Difficulty",
- accessorKey: "difficulty",
- cell: ({ row }) => {
- const difficulty = row.getValue("difficulty") as Difficulty;
- return (
-
- {difficulty}
-
- );
- },
- size: 100,
- filterFn: difficultyFilterFn,
- },
- {
- id: "actions",
- header: () => Actions,
- cell: () => ,
- enableHiding: false,
- },
-];
-
-export function ProblemsetTable({ data }: ProblemTableProps) {
- const [columnFilters, setColumnFilters] = useState([]);
- const [columnVisibility, setColumnVisibility] = useState({});
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 10,
- });
- const inputRef = useRef(null);
-
- const [sorting, setSorting] = useState([
- { id: "displayId", desc: false },
- ]);
-
- const handleDeleteRows = async () => {
- const selectedRows = table.getSelectedRowModel().rows;
- const selectedIds = selectedRows.map((row) => row.original.id);
- console.log("🚀 ~ handleDeleteRows ~ selectedIds:", selectedIds)
- };
-
- const table = useReactTable({
- data,
- columns,
- getCoreRowModel: getCoreRowModel(),
- getSortedRowModel: getSortedRowModel(),
- onSortingChange: setSorting,
- enableSortingRemoval: false,
- getPaginationRowModel: getPaginationRowModel(),
- onPaginationChange: setPagination,
- onColumnFiltersChange: setColumnFilters,
- onColumnVisibilityChange: setColumnVisibility,
- getFilteredRowModel: getFilteredRowModel(),
- getFacetedUniqueValues: getFacetedUniqueValues(),
- state: { sorting, pagination, columnFilters, columnVisibility },
- });
-
- // Get unique difficulty values
- const uniqueDifficultyValues = useMemo(() => {
- const difficultyColumn = table.getColumn("difficulty");
-
- if (!difficultyColumn) return [];
-
- const values = Array.from(difficultyColumn.getFacetedUniqueValues().keys());
-
- return values.sort();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [table.getColumn("difficulty")?.getFacetedUniqueValues()]);
-
- // Get counts for each difficulty
- const difficultyCounts = useMemo(() => {
- const difficultyColumn = table.getColumn("difficulty");
- if (!difficultyColumn) return new Map();
- return difficultyColumn.getFacetedUniqueValues();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [table.getColumn("difficulty")?.getFacetedUniqueValues()]);
-
- const selectedDifficulties = useMemo(() => {
- const filterValue = table.getColumn("difficulty")?.getFilterValue() as string[];
- return filterValue ?? [];
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [table.getColumn("difficulty")?.getFilterValue()]);
-
- const handleDifficultyChange = (checked: boolean, value: string) => {
- const filterValue = table.getColumn("difficulty")?.getFilterValue() as string[];
- const newFilterValue = filterValue ? [...filterValue] : [];
-
- if (checked) {
- newFilterValue.push(value);
- } else {
- const index = newFilterValue.indexOf(value);
- if (index > -1) {
- newFilterValue.splice(index, 1);
- }
- }
-
- table
- .getColumn("difficulty")
- ?.setFilterValue(newFilterValue.length ? newFilterValue : undefined);
- };
-
- return (
-
-
-
-
-
- table.getColumn("displayId")?.setFilterValue(e.target.value)
- }
- placeholder="DisplayId or Title..."
- type="text"
- aria-label="Filter by displayId or title"
- />
-
-
-
- {Boolean(table.getColumn("displayId")?.getFilterValue()) && (
-
- )}
-
-
-
-
-
-
-
-
- Filters
-
-
- {uniqueDifficultyValues.map((value) => (
-
-
- handleDifficultyChange(checked, value)
- }
- />
-
-
- ))}
-
-
-
-
-
-
-
-
-
- Toggle columns
- {table
- .getAllColumns()
- .filter((column) => column.getCanHide())
- .map((column) => {
- return (
-
- column.toggleVisibility(!!value)
- }
- onSelect={(event) => event.preventDefault()}
- >
- {column.id}
-
- );
- })}
-
-
-
-
- {table.getSelectedRowModel().rows.length > 0 && (
-
-
-
-
-
-
-
-
-
-
-
- Are you absolutely sure?
-
-
- This action cannot be undone. This will permanently delete{" "}
- {table.getSelectedRowModel().rows.length} selected{" "}
- {table.getSelectedRowModel().rows.length === 1
- ? "row"
- : "rows"}
- .
-
-
-
-
- Cancel
-
- Delete
-
-
-
-
- )}
-
-
-
-
-
-
- {table.getHeaderGroups().map((headerGroup) => (
-
- {headerGroup.headers.map((header) => (
-
- {header.isPlaceholder ? null : header.column.getCanSort() ? (
- {
- if (
- header.column.getCanSort() &&
- (e.key === "Enter" || e.key === " ")
- ) {
- e.preventDefault();
- header.column.getToggleSortingHandler()?.(e);
- }
- }}
- tabIndex={header.column.getCanSort() ? 0 : undefined}
- >
- {flexRender(
- header.column.columnDef.header,
- header.getContext()
- )}
- {
- {
- asc: (
-
- ),
- desc: (
-
- ),
- }[header.column.getIsSorted() as string] ?? null
- }
-
- ) : (
- flexRender(
- header.column.columnDef.header,
- header.getContext()
- )
- )}
-
- ))}
-
- ))}
-
-
- {table.getRowModel().rows?.length ? (
- table.getRowModel().rows.map((row) => (
-
- {row.getVisibleCells().map((cell) => (
-
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
-
- ))}
-
- ))
- ) : (
-
-
- No results.
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- {table.getState().pagination.pageIndex *
- table.getState().pagination.pageSize +
- 1}
- -
- {Math.min(
- Math.max(
- table.getState().pagination.pageIndex *
- table.getState().pagination.pageSize +
- table.getState().pagination.pageSize,
- 0
- ),
- table.getRowCount()
- )}
- {" "}
- of{" "}
-
- {table.getRowCount().toString()}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-function RowActions() {
- return (
-
-
-
-
-
-
-
-
-
- Edit
-
-
-
-
- Delete
- ⌘⌫
-
-
-
- );
-}
diff --git a/src/components/features/playground/workspace/editor/components/lsp-status-button.tsx b/src/components/features/playground/workspace/editor/components/lsp-status-button.tsx
deleted file mode 100644
index bddb4c2..0000000
--- a/src/components/features/playground/workspace/editor/components/lsp-status-button.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-"use client";
-
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { useTranslations } from "next-intl";
-import { Button } from "@/components/ui/button";
-import { useProblem } from "@/hooks/use-problem";
-
-const getLspStatusColor = (webSocket: WebSocket | null) => {
- if (!webSocket) return "bg-gray-500";
-
- switch (webSocket.readyState) {
- case WebSocket.CONNECTING:
- return "bg-yellow-500";
- case WebSocket.OPEN:
- return "bg-green-500";
- case WebSocket.CLOSING:
- return "bg-orange-500";
- case WebSocket.CLOSED:
- return "bg-red-500";
- default:
- return "bg-gray-500";
- }
-};
-
-export function LspStatusButton() {
- const { webSocket } = useProblem();
- const t = useTranslations("WorkspaceEditorHeader.LspStatusButton");
-
- return (
-
-
-
-
-
-
- {t("TooltipContent")}
-
-
-
- );
-}
diff --git a/src/components/hero-section.tsx b/src/components/hero-section.tsx
index c43687c..6f9b028 100644
--- a/src/components/hero-section.tsx
+++ b/src/components/hero-section.tsx
@@ -26,7 +26,7 @@ const HeroSection = () => {