Commit remaining workspace changes

This commit is contained in:
2026-06-23 12:23:40 +08:00
parent 0ec26843d2
commit a4a10df9e3
28 changed files with 669 additions and 68 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -9,6 +9,7 @@
"lint": "eslint" "lint": "eslint"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.1075.0",
"@radix-ui/react-label": "^2.1.10", "@radix-ui/react-label": "^2.1.10",
"@radix-ui/react-select": "^2.3.1", "@radix-ui/react-select": "^2.3.1",
"@radix-ui/react-separator": "^1.1.10", "@radix-ui/react-separator": "^1.1.10",
+424
View File
@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@aws-sdk/client-s3':
specifier: ^3.1075.0
version: 3.1075.0
'@radix-ui/react-label': '@radix-ui/react-label':
specifier: ^2.1.10 specifier: ^2.1.10
version: 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -94,6 +97,109 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'} engines: {node: '>=10'}
'@aws-crypto/crc32@5.2.0':
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
engines: {node: '>=16.0.0'}
'@aws-crypto/crc32c@5.2.0':
resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==}
'@aws-crypto/sha1-browser@5.2.0':
resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==}
'@aws-crypto/sha256-browser@5.2.0':
resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
'@aws-crypto/sha256-js@5.2.0':
resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
engines: {node: '>=16.0.0'}
'@aws-crypto/supports-web-crypto@5.2.0':
resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
'@aws-crypto/util@5.2.0':
resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
'@aws-sdk/checksums@3.1000.8':
resolution: {integrity: sha512-v0U9S7gBIme3OTgt1LdbAF4RpvavCc+4GK1+1xqAcqtbrHsEhjQo6R45LKcjhs/+WrRJij1Y0Gztw7QPAIeUfA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/client-s3@3.1075.0':
resolution: {integrity: sha512-h1A6nIl1YX6Y45enGsTK7ef3ZrOnBiQJ1qF5R2K/nMWfsu6A9mc2Y5T66nxerABzyjjyyvign3MrzafnFoQKmA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/core@3.974.23':
resolution: {integrity: sha512-MiWR/uWjxjFXGzrE0Ghc5lWxUxzHsUWFhV+OX7M4cR9SrmrnZs6TXavnCWnzzdwJeFri34xQo81rvGNzK3c4BQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-env@3.972.49':
resolution: {integrity: sha512-liB3yQNHCM9k/gu/w36XHMKPluT7HTlnGUhRbBGSISDQkcr/Sy1zsZabiuvQj8WG5yW573u9RehrBvvnIQ9OEQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-http@3.972.51':
resolution: {integrity: sha512-XET0H2oofciJ5lMRWNIvRjAP7Q3wv2XT+JtJJEdhPWUMwe3TvQ9qcxonpu7vXmNngncvFpi4E2It+Tamas/naA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-ini@3.972.56':
resolution: {integrity: sha512-IAmc61hbgQiHht9U3x0tnRwz0lzdwOwD/i9voRgdJrKamF+JtmrBOsW9GwB7mfFonNWOWL4qARWYrF8veEMe3w==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-login@3.972.55':
resolution: {integrity: sha512-hBBkANo3cDn+h2qxxzER4a+J8JCO9o9Z/YYmU7iky6AcaarX5RRdRcHNC6SLdwY0vAXQygn6soUbDqPn3GghaA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-node@3.972.58':
resolution: {integrity: sha512-OyCLVmSI7pZO8hxwNVX6pXhTVlJqRBTp+ijdEfJSUj0RyjHnF602OfAarOzGq6wkGodeFkYBt8MmJ6A6ycRgWw==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-process@3.972.49':
resolution: {integrity: sha512-C8h36lBuC/RnBSsjlO+dn6xZm3KbAl5vpJaVPAfQnMmz2/OISmKOc8XZcqMQgO2ADwBYNRMM6Kf3vz9G/TulMQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-sso@3.972.55':
resolution: {integrity: sha512-1FkOz74Ea5QGS9jtIoXp55T/IkSS3spv+nLTT07fRY/+T5xmEOqaYBVIaEmX4zTNvbV6g2lrtlaVKWEoNyJt3w==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-web-identity@3.972.55':
resolution: {integrity: sha512-g2BoECD1q01kTPByi56+VLVvdWDzMkKIcr77qixpqH0okw2t0U5CoPv+6S8v/D1Y2Wa6QKKtn6XAtDzP+Kfpvg==}
engines: {node: '>=20.0.0'}
'@aws-sdk/middleware-flexible-checksums@3.974.33':
resolution: {integrity: sha512-qMgQSPemQq2/eW/e/0+SpY4kYR5L7dUgBiVdEc5bd+ztHNv07ZMYiI+sTiir3TgKndFfglSw/VFi7oZJ6bZ63g==}
engines: {node: '>=20.0.0'}
'@aws-sdk/middleware-sdk-s3@3.972.54':
resolution: {integrity: sha512-GDfDQ0gwLFRKN9gWIKcmVrHJ3e7XagnY7N1LLzMVNgnOnuY7f/ALgmy3CuBjosWD95T/Z6e+gs1IeWmLPkyLKQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/nested-clients@3.997.23':
resolution: {integrity: sha512-gO93ZPsI2bxeFZD42f1/qjDw6FAZkNZcKRO94LIiT03fzOmcJ9e/tunxjVjA1Rl69ClmVJzz8H3G9CdKef10PA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/signature-v4-multi-region@3.996.35':
resolution: {integrity: sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg==}
engines: {node: '>=20.0.0'}
'@aws-sdk/token-providers@3.1074.0':
resolution: {integrity: sha512-pv80IzgGW4RnXWtft692chZOM9i6PhebVsLCcnaM4dBEPZva2fE6FXAHs76G7Rc7s3yGyX/68G0nZMrUy+Vmpg==}
engines: {node: '>=20.0.0'}
'@aws-sdk/types@3.973.13':
resolution: {integrity: sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==}
engines: {node: '>=20.0.0'}
'@aws-sdk/util-locate-window@3.965.8':
resolution: {integrity: sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==}
engines: {node: '>=20.0.0'}
'@aws-sdk/xml-builder@3.972.31':
resolution: {integrity: sha512-SzE4Pgyl+hDF+BuyuzxUSpwnuUu9lJuO1YGgteG89/4Qv0+2IQiVQqdbPV32IozLvXWQChPQcdkk/sKvb1QHiQ==}
engines: {node: '>=20.0.0'}
'@aws/lambda-invoke-store@0.2.4':
resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==}
engines: {node: '>=18.0.0'}
'@babel/code-frame@7.29.7': '@babel/code-frame@7.29.7':
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -851,6 +957,42 @@ packages:
'@rtsao/scc@1.1.0': '@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@smithy/core@3.26.0':
resolution: {integrity: sha512-mLUktFAn+Pa2agl1J7VgtYNFWCX8/b4GMJSK1hCu4YCvtBfM6F8Os3EP4ry+DFFlXOf3wyvlgXhuUdFoy52D3g==}
engines: {node: '>=18.0.0'}
'@smithy/credential-provider-imds@4.4.2':
resolution: {integrity: sha512-18UMDMyrAbDcpmL1gLUA7ww0fRTcdCrSjSJOi2Sbld+tVjwD/pW+OAwjlScFLR7vvBnhZrIPQ7kVuTf1mnJLug==}
engines: {node: '>=18.0.0'}
'@smithy/fetch-http-handler@5.5.2':
resolution: {integrity: sha512-Ei/UK/QMhq0rKaMqGPlOAkE2yS9DZeYmZdk1RAKc3vp3zxgleZHZyBLlZv8yLsxljX4svCRuMTD6u3LLIcU4Bg==}
engines: {node: '>=18.0.0'}
'@smithy/is-array-buffer@2.2.0':
resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
engines: {node: '>=14.0.0'}
'@smithy/node-http-handler@4.8.2':
resolution: {integrity: sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ==}
engines: {node: '>=18.0.0'}
'@smithy/signature-v4@5.5.2':
resolution: {integrity: sha512-7xHpmPY4rt0IOmeAA8EfjgEH8isT+587TCdy9H6a7d4OMi5CQ0oEHhWllunvPu4j4Cq0vTFwdxXN/kABWPjdyA==}
engines: {node: '>=18.0.0'}
'@smithy/types@4.15.0':
resolution: {integrity: sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg==}
engines: {node: '>=18.0.0'}
'@smithy/util-buffer-from@2.2.0':
resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
engines: {node: '>=14.0.0'}
'@smithy/util-utf8@2.3.0':
resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
engines: {node: '>=14.0.0'}
'@swc/helpers@0.5.15': '@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
@@ -1141,6 +1283,9 @@ packages:
bl@2.2.0: bl@2.2.0:
resolution: {integrity: sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==} resolution: {integrity: sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==}
bowser@2.14.1:
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
brace-expansion@1.1.15: brace-expansion@1.1.15:
resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==}
@@ -2634,6 +2779,235 @@ snapshots:
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
'@aws-crypto/crc32@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
'@aws-sdk/types': 3.973.13
tslib: 2.8.1
'@aws-crypto/crc32c@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
'@aws-sdk/types': 3.973.13
tslib: 2.8.1
'@aws-crypto/sha1-browser@5.2.0':
dependencies:
'@aws-crypto/supports-web-crypto': 5.2.0
'@aws-crypto/util': 5.2.0
'@aws-sdk/types': 3.973.13
'@aws-sdk/util-locate-window': 3.965.8
'@smithy/util-utf8': 2.3.0
tslib: 2.8.1
'@aws-crypto/sha256-browser@5.2.0':
dependencies:
'@aws-crypto/sha256-js': 5.2.0
'@aws-crypto/supports-web-crypto': 5.2.0
'@aws-crypto/util': 5.2.0
'@aws-sdk/types': 3.973.13
'@aws-sdk/util-locate-window': 3.965.8
'@smithy/util-utf8': 2.3.0
tslib: 2.8.1
'@aws-crypto/sha256-js@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
'@aws-sdk/types': 3.973.13
tslib: 2.8.1
'@aws-crypto/supports-web-crypto@5.2.0':
dependencies:
tslib: 2.8.1
'@aws-crypto/util@5.2.0':
dependencies:
'@aws-sdk/types': 3.973.13
'@smithy/util-utf8': 2.3.0
tslib: 2.8.1
'@aws-sdk/checksums@3.1000.8':
dependencies:
'@aws-crypto/crc32': 5.2.0
'@aws-crypto/crc32c': 5.2.0
'@aws-crypto/util': 5.2.0
'@aws-sdk/core': 3.974.23
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/client-s3@3.1075.0':
dependencies:
'@aws-crypto/sha1-browser': 5.2.0
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/core': 3.974.23
'@aws-sdk/credential-provider-node': 3.972.58
'@aws-sdk/middleware-flexible-checksums': 3.974.33
'@aws-sdk/middleware-sdk-s3': 3.972.54
'@aws-sdk/signature-v4-multi-region': 3.996.35
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/fetch-http-handler': 5.5.2
'@smithy/node-http-handler': 4.8.2
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/core@3.974.23':
dependencies:
'@aws-sdk/types': 3.973.13
'@aws-sdk/xml-builder': 3.972.31
'@aws/lambda-invoke-store': 0.2.4
'@smithy/core': 3.26.0
'@smithy/signature-v4': 5.5.2
'@smithy/types': 4.15.0
bowser: 2.14.1
tslib: 2.8.1
'@aws-sdk/credential-provider-env@3.972.49':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/credential-provider-http@3.972.51':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/fetch-http-handler': 5.5.2
'@smithy/node-http-handler': 4.8.2
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/credential-provider-ini@3.972.56':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/credential-provider-env': 3.972.49
'@aws-sdk/credential-provider-http': 3.972.51
'@aws-sdk/credential-provider-login': 3.972.55
'@aws-sdk/credential-provider-process': 3.972.49
'@aws-sdk/credential-provider-sso': 3.972.55
'@aws-sdk/credential-provider-web-identity': 3.972.55
'@aws-sdk/nested-clients': 3.997.23
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/credential-provider-imds': 4.4.2
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/credential-provider-login@3.972.55':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/nested-clients': 3.997.23
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/credential-provider-node@3.972.58':
dependencies:
'@aws-sdk/credential-provider-env': 3.972.49
'@aws-sdk/credential-provider-http': 3.972.51
'@aws-sdk/credential-provider-ini': 3.972.56
'@aws-sdk/credential-provider-process': 3.972.49
'@aws-sdk/credential-provider-sso': 3.972.55
'@aws-sdk/credential-provider-web-identity': 3.972.55
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/credential-provider-imds': 4.4.2
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/credential-provider-process@3.972.49':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/credential-provider-sso@3.972.55':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/nested-clients': 3.997.23
'@aws-sdk/token-providers': 3.1074.0
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/credential-provider-web-identity@3.972.55':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/nested-clients': 3.997.23
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/middleware-flexible-checksums@3.974.33':
dependencies:
'@aws-sdk/checksums': 3.1000.8
tslib: 2.8.1
'@aws-sdk/middleware-sdk-s3@3.972.54':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/signature-v4-multi-region': 3.996.35
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/nested-clients@3.997.23':
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/core': 3.974.23
'@aws-sdk/signature-v4-multi-region': 3.996.35
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/fetch-http-handler': 5.5.2
'@smithy/node-http-handler': 4.8.2
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/signature-v4-multi-region@3.996.35':
dependencies:
'@aws-sdk/types': 3.973.13
'@smithy/signature-v4': 5.5.2
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/token-providers@3.1074.0':
dependencies:
'@aws-sdk/core': 3.974.23
'@aws-sdk/nested-clients': 3.997.23
'@aws-sdk/types': 3.973.13
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/types@3.973.13':
dependencies:
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws-sdk/util-locate-window@3.965.8':
dependencies:
tslib: 2.8.1
'@aws-sdk/xml-builder@3.972.31':
dependencies:
'@smithy/types': 4.15.0
tslib: 2.8.1
'@aws/lambda-invoke-store@0.2.4': {}
'@babel/code-frame@7.29.7': '@babel/code-frame@7.29.7':
dependencies: dependencies:
'@babel/helper-validator-identifier': 7.29.7 '@babel/helper-validator-identifier': 7.29.7
@@ -3327,6 +3701,54 @@ snapshots:
'@rtsao/scc@1.1.0': {} '@rtsao/scc@1.1.0': {}
'@smithy/core@3.26.0':
dependencies:
'@aws-crypto/crc32': 5.2.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@smithy/credential-provider-imds@4.4.2':
dependencies:
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@smithy/fetch-http-handler@5.5.2':
dependencies:
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@smithy/is-array-buffer@2.2.0':
dependencies:
tslib: 2.8.1
'@smithy/node-http-handler@4.8.2':
dependencies:
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@smithy/signature-v4@5.5.2':
dependencies:
'@smithy/core': 3.26.0
'@smithy/types': 4.15.0
tslib: 2.8.1
'@smithy/types@4.15.0':
dependencies:
tslib: 2.8.1
'@smithy/util-buffer-from@2.2.0':
dependencies:
'@smithy/is-array-buffer': 2.2.0
tslib: 2.8.1
'@smithy/util-utf8@2.3.0':
dependencies:
'@smithy/util-buffer-from': 2.2.0
tslib: 2.8.1
'@swc/helpers@0.5.15': '@swc/helpers@0.5.15':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@@ -3643,6 +4065,8 @@ snapshots:
readable-stream: 2.3.8 readable-stream: 2.3.8
safe-buffer: 5.2.1 safe-buffer: 5.2.1
bowser@2.14.1: {}
brace-expansion@1.1.15: brace-expansion@1.1.15:
dependencies: dependencies:
balanced-match: 1.0.0 balanced-match: 1.0.0
+4
View File
@@ -1,3 +1,7 @@
allowBuilds: allowBuilds:
better-sqlite3: true better-sqlite3: true
sharp: true sharp: true
onlyBuiltDependencies:
- better-sqlite3
- sharp
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

@@ -20,7 +20,7 @@ export async function GET(
try { try {
return NextResponse.json({ return NextResponse.json({
item: createCanvasProjectExport({ item: await createCanvasProjectExport({
title: project.title, title: project.title,
description: project.description, description: project.description,
data: project.data, data: project.data,
+1 -1
View File
@@ -7,7 +7,7 @@ export async function POST(request: Request) {
const payload = await request.json().catch(() => null); const payload = await request.json().catch(() => null);
try { try {
const project = importCanvasProjectExport(payload); const project = await importCanvasProjectExport(payload);
return NextResponse.json({ item: project }, { status: 201 }); return NextResponse.json({ item: project }, { status: 201 });
} catch (err) { } catch (err) {
return NextResponse.json( return NextResponse.json(
+1 -1
View File
@@ -13,7 +13,7 @@ export async function POST(request: Request) {
const buffer = Buffer.from(await image.arrayBuffer()); const buffer = Buffer.from(await image.arrayBuffer());
const id = crypto.randomUUID(); const id = crypto.randomUUID();
const persisted = persistUploadedImageFile(id, image.name || "canvas-upload.png", buffer); const persisted = await persistUploadedImageFile(id, image.name || "canvas-upload.png", buffer);
return NextResponse.json({ return NextResponse.json({
item: { item: {
+36 -34
View File
@@ -609,29 +609,30 @@ export function DirectStudio({ activeView }: { activeView: ActiveView }) {
) : history.length ? ( ) : history.length ? (
<div className="space-y-2"> <div className="space-y-2">
{history.map((item) => ( {history.map((item) => (
<button <div
key={item.id} key={item.id}
className="flex w-full items-center gap-3 rounded-md border border-zinc-200 bg-white p-2 text-left hover:bg-zinc-50" className="flex w-full items-center gap-3 rounded-md border border-zinc-200 bg-white p-2 hover:bg-zinc-50"
onClick={() => selectHistoryItem(item)}
type="button"
> >
<img alt={item.prompt} className="size-14 rounded object-cover" src={item.imageUrl} /> <button
<div className="min-w-0 flex-1"> className="flex min-w-0 flex-1 items-center gap-3 text-left"
<div className="truncate text-xs font-medium">{item.prompt}</div> onClick={() => selectHistoryItem(item)}
<div className="text-[11px] text-zinc-500">{formatDate(item.createdAt)}</div> type="button"
</div> >
<img alt={item.prompt} className="size-14 rounded object-cover" src={item.imageUrl} />
<div className="min-w-0 flex-1">
<div className="truncate text-xs font-medium">{item.prompt}</div>
<div className="text-[11px] text-zinc-500">{formatDate(item.createdAt)}</div>
</div>
</button>
<Button <Button
size="icon" size="icon"
type="button" type="button"
variant="ghost" variant="ghost"
onClick={(event) => { onClick={() => void placeImageForEdit(item)}
event.stopPropagation();
void placeImageForEdit(item);
}}
> >
<Brush /> <Brush />
</Button> </Button>
</button> </div>
))} ))}
</div> </div>
) : ( ) : (
@@ -643,35 +644,36 @@ export function DirectStudio({ activeView }: { activeView: ActiveView }) {
<div className="space-y-2"> <div className="space-y-2">
{history.length ? ( {history.length ? (
history.map((item) => ( history.map((item) => (
<button <div
key={item.id} key={item.id}
className="flex w-full items-center gap-3 rounded-md border border-zinc-200 bg-white p-2 text-left hover:bg-zinc-50" className="flex w-full items-center gap-3 rounded-md border border-zinc-200 bg-white p-2 hover:bg-zinc-50"
onClick={() =>
setPreviewImage({
title: item.prompt.slice(0, 24) || "作品",
subtitle: formatDate(item.createdAt),
url: item.imageUrl,
})
}
type="button"
> >
<img alt={item.prompt} className="size-16 rounded object-cover" src={item.imageUrl} /> <button
<div className="min-w-0 flex-1"> className="flex min-w-0 flex-1 items-center gap-3 text-left"
<div className="truncate text-sm font-medium">{item.prompt}</div> onClick={() =>
<div className="text-xs text-zinc-500">{item.model} · {item.outputFormat}</div> setPreviewImage({
</div> title: item.prompt.slice(0, 24) || "作品",
subtitle: formatDate(item.createdAt),
url: item.imageUrl,
})
}
type="button"
>
<img alt={item.prompt} className="size-16 rounded object-cover" src={item.imageUrl} />
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-medium">{item.prompt}</div>
<div className="text-xs text-zinc-500">{item.model} · {item.outputFormat}</div>
</div>
</button>
<Button <Button
size="icon" size="icon"
type="button" type="button"
variant="ghost" variant="ghost"
onClick={(event) => { onClick={() => void placeImageForEdit(item)}
event.stopPropagation();
void placeImageForEdit(item);
}}
> >
<Brush /> <Brush />
</Button> </Button>
</button> </div>
)) ))
) : ( ) : (
<div className="text-sm text-zinc-500"></div> <div className="text-sm text-zinc-500"></div>
@@ -1,4 +1,3 @@
import { existsSync, readFileSync } from "node:fs";
import { extname, basename } from "node:path"; import { extname, basename } from "node:path";
import type { CanvasImageNodeMetadata, CanvasProjectData } from "@/lib/canvas/types"; import type { CanvasImageNodeMetadata, CanvasProjectData } from "@/lib/canvas/types";
import { normalizeCanvasProjectData } from "@/lib/canvas/types"; import { normalizeCanvasProjectData } from "@/lib/canvas/types";
@@ -6,6 +5,7 @@ import { createCanvasProject } from "@/lib/server/repositories/canvas-project-re
import { import {
getGeneratedImagePathFromUrl, getGeneratedImagePathFromUrl,
persistImageBuffer, persistImageBuffer,
readPersistedImageFile,
} from "@/lib/server/storage/image-file-storage"; } from "@/lib/server/storage/image-file-storage";
const CANVAS_EXPORT_KIND = "imagegen.canvas.project"; const CANVAS_EXPORT_KIND = "imagegen.canvas.project";
@@ -29,11 +29,11 @@ export type CanvasProjectExportV1 = {
assets: CanvasProjectExportAsset[]; assets: CanvasProjectExportAsset[];
}; };
export function createCanvasProjectExport(input: { export async function createCanvasProjectExport(input: {
title: string; title: string;
description?: string; description?: string;
data: CanvasProjectData; data: CanvasProjectData;
}): CanvasProjectExportV1 { }): Promise<CanvasProjectExportV1> {
return { return {
kind: CANVAS_EXPORT_KIND, kind: CANVAS_EXPORT_KIND,
version: CANVAS_EXPORT_VERSION, version: CANVAS_EXPORT_VERSION,
@@ -41,11 +41,11 @@ export function createCanvasProjectExport(input: {
title: input.title, title: input.title,
description: input.description ?? "", description: input.description ?? "",
data: input.data, data: input.data,
assets: collectExportAssets(input.data), assets: await collectExportAssets(input.data),
}; };
} }
export function importCanvasProjectExport(value: unknown) { export async function importCanvasProjectExport(value: unknown) {
const payload = normalizeCanvasProjectExport(value); const payload = normalizeCanvasProjectExport(value);
if (!payload) { if (!payload) {
throw new Error("导入 JSON 不是有效的画布导出格式"); throw new Error("导入 JSON 不是有效的画布导出格式");
@@ -54,12 +54,12 @@ export function importCanvasProjectExport(value: unknown) {
const assetsByNodeId = new Map(payload.assets.map((asset) => [asset.nodeId, asset])); const assetsByNodeId = new Map(payload.assets.map((asset) => [asset.nodeId, asset]));
const data: CanvasProjectData = { const data: CanvasProjectData = {
...payload.data, ...payload.data,
nodes: payload.data.nodes.map((node) => { nodes: await Promise.all(payload.data.nodes.map(async (node) => {
if (node.type !== "image") return node; if (node.type !== "image") return node;
const asset = assetsByNodeId.get(node.id); const asset = assetsByNodeId.get(node.id);
if (!asset) return node; if (!asset) return node;
const persisted = persistDataUrl(asset.dataUrl, asset.fileName); const persisted = await persistDataUrl(asset.dataUrl, asset.fileName);
return { return {
...node, ...node,
metadata: { metadata: {
@@ -68,7 +68,7 @@ export function importCanvasProjectExport(value: unknown) {
filePath: persisted.filePath, filePath: persisted.filePath,
} satisfies CanvasImageNodeMetadata, } satisfies CanvasImageNodeMetadata,
}; };
}), })),
}; };
const title = payload.title.trim() const title = payload.title.trim()
@@ -82,7 +82,7 @@ export function importCanvasProjectExport(value: unknown) {
}); });
} }
function collectExportAssets(data: CanvasProjectData) { async function collectExportAssets(data: CanvasProjectData) {
const assets: CanvasProjectExportAsset[] = []; const assets: CanvasProjectExportAsset[] = [];
for (const node of data.nodes) { for (const node of data.nodes) {
@@ -91,11 +91,15 @@ function collectExportAssets(data: CanvasProjectData) {
if (!metadata.imageUrl) continue; if (!metadata.imageUrl) continue;
const filePath = metadata.filePath ?? getGeneratedImagePathFromUrl(metadata.imageUrl); const filePath = metadata.filePath ?? getGeneratedImagePathFromUrl(metadata.imageUrl);
if (!filePath || !existsSync(filePath)) { if (!filePath) {
throw new Error(`图片文件不存在:${node.title || node.id}`);
}
const buffer = await readPersistedImageFile(filePath);
if (!buffer) {
throw new Error(`图片文件不存在:${node.title || node.id}`); throw new Error(`图片文件不存在:${node.title || node.id}`);
} }
const buffer = readFileSync(filePath);
const mimeType = getMimeType(filePath); const mimeType = getMimeType(filePath);
assets.push({ assets.push({
nodeId: node.id, nodeId: node.id,
+1 -1
View File
@@ -455,7 +455,7 @@ async function runImageJob(
}); });
const id = crypto.randomUUID(); const id = crypto.randomUUID();
const persisted = persistBase64Image(id, input.outputFormat, b64); const persisted = await persistBase64Image(id, input.outputFormat, b64);
const createdAt = new Date().toISOString(); const createdAt = new Date().toISOString();
const resultPayload = { const resultPayload = {
id, id,
+185 -19
View File
@@ -1,5 +1,7 @@
import { GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { basename, join } from "node:path"; import { basename, join } from "node:path";
import { Readable } from "node:stream";
const imageDir = join( const imageDir = join(
/* turbopackIgnore: true */ process.cwd(), /* turbopackIgnore: true */ process.cwd(),
@@ -13,6 +15,23 @@ export type PersistedImageFile = {
imageUrl: string; imageUrl: string;
}; };
type S3StorageConfig = {
endpoint: string;
accessKeyId: string;
secretAccessKey: string;
bucket: string;
region: string;
publicUrl?: string;
};
const s3ObjectPrefix = "generated";
const defaultBucket = "imagegen-tools";
const defaultRegion = "us-east-1";
const globalForS3Storage = globalThis as typeof globalThis & {
imageStorageS3Client?: S3Client;
};
export function getImageDir() { export function getImageDir() {
if (!existsSync(imageDir)) { if (!existsSync(imageDir)) {
mkdirSync(imageDir, { recursive: true }); mkdirSync(imageDir, { recursive: true });
@@ -21,27 +40,54 @@ export function getImageDir() {
return imageDir; return imageDir;
} }
export function persistBase64Image( export async function persistBase64Image(
id: string, id: string,
extension: string, extension: string,
base64: string, base64: string,
): PersistedImageFile { ): Promise<PersistedImageFile> {
const normalizedExtension = extension === "jpeg" ? "jpg" : extension; const normalizedExtension = extension === "jpeg" ? "jpg" : extension;
const fileName = `${id}.${normalizedExtension}`; const fileName = `${id}.${normalizedExtension}`;
const filePath = join(getImageDir(), fileName); return persistImageBufferWithFileName(fileName, Buffer.from(base64, "base64"));
const imageUrl = `/generated/${fileName}`;
writeFileSync(filePath, Buffer.from(base64, "base64"));
return { fileName, filePath, imageUrl };
} }
export function persistUploadedImageFile( export async function persistUploadedImageFile(
id: string, id: string,
filename: string, filename: string,
buffer: Buffer, buffer: Buffer,
): PersistedImageFile { ): Promise<PersistedImageFile> {
const extension = getExtensionFromFilename(filename); const extension = getExtensionFromFilename(filename);
const fileName = `${id}.${extension}`; const fileName = `${id}.${extension}`;
return persistImageBufferWithFileName(fileName, buffer);
}
export async function persistImageBuffer(
id: string,
extension: string,
buffer: Buffer,
): Promise<PersistedImageFile> {
const normalizedExtension = normalizeExtension(extension);
const fileName = `${id}.${normalizedExtension}`;
return persistImageBufferWithFileName(fileName, buffer);
}
export async function readPersistedImageFile(filePath: string): Promise<Buffer | null> {
if (filePath.startsWith("s3://")) {
return readS3Object(filePath);
}
const { readFileSync } = await import("node:fs");
if (!existsSync(filePath)) return null;
return readFileSync(filePath);
}
export function isLocalImageFile(filePath: string) {
return !filePath.startsWith("s3://");
}
function persistImageBufferLocally(
fileName: string,
buffer: Buffer,
): PersistedImageFile {
const filePath = join(getImageDir(), fileName); const filePath = join(getImageDir(), fileName);
const imageUrl = `/generated/${fileName}`; const imageUrl = `/generated/${fileName}`;
writeFileSync(filePath, buffer); writeFileSync(filePath, buffer);
@@ -49,18 +95,33 @@ export function persistUploadedImageFile(
return { fileName, filePath, imageUrl }; return { fileName, filePath, imageUrl };
} }
export function persistImageBuffer( async function persistImageBufferWithFileName(
id: string, fileName: string,
extension: string,
buffer: Buffer, buffer: Buffer,
): PersistedImageFile { ): Promise<PersistedImageFile> {
const normalizedExtension = normalizeExtension(extension); const config = getS3StorageConfig();
const fileName = `${id}.${normalizedExtension}`; if (!config) {
const filePath = join(getImageDir(), fileName); return persistImageBufferLocally(fileName, buffer);
const imageUrl = `/generated/${fileName}`; }
writeFileSync(filePath, buffer);
return { fileName, filePath, imageUrl }; const key = `${s3ObjectPrefix}/${fileName}`;
const contentType = getMimeTypeFromFileName(fileName);
const client = getS3Client(config);
await client.send(
new PutObjectCommand({
Bucket: config.bucket,
Key: key,
Body: buffer,
ContentType: contentType,
}),
);
return {
fileName,
filePath: `s3://${config.bucket}/${key}`,
imageUrl: createPublicImageUrl(config, key),
};
} }
export function getGeneratedImagePathFromUrl(imageUrl: string) { export function getGeneratedImagePathFromUrl(imageUrl: string) {
@@ -68,6 +129,104 @@ export function getGeneratedImagePathFromUrl(imageUrl: string) {
return join(getImageDir(), basename(imageUrl)); return join(getImageDir(), basename(imageUrl));
} }
function getS3StorageConfig(): S3StorageConfig | null {
const endpoint = process.env.FILE_STORAGE_API?.trim();
const accessKeyId = process.env.FILE_STORAGE_ACCESS_KEY?.trim();
const secretAccessKey = process.env.FILE_STORAGE_SECRET_KEY?.trim();
const bucket = process.env.FILE_STORAGE_BUCKET?.trim() || defaultBucket;
// ✅ 添加调试日志
console.log('S3 Config Check:', {
hasEndpoint: !!endpoint,
hasAccessKey: !!accessKeyId,
hasSecretKey: !!secretAccessKey,
bucket,
endpoint: endpoint ? endpoint.replace(/\/+$/, "") : 'not set',
});
if (!endpoint || !accessKeyId || !secretAccessKey) {
return null;
}
return {
endpoint: endpoint.replace(/\/+$/, ""),
accessKeyId,
secretAccessKey,
bucket: process.env.FILE_STORAGE_BUCKET?.trim() || defaultBucket,
region: process.env.FILE_STORAGE_REGION?.trim() || defaultRegion,
publicUrl: process.env.FILE_STORAGE_PUBLIC_URL?.trim().replace(/\/+$/, ""),
};
}
function getS3Client(config: S3StorageConfig) {
if (!globalForS3Storage.imageStorageS3Client) {
globalForS3Storage.imageStorageS3Client = new S3Client({
endpoint: config.endpoint,
region: config.region,
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
},
forcePathStyle: true,
});
}
return globalForS3Storage.imageStorageS3Client;
}
async function readS3Object(filePath: string) {
const config = getS3StorageConfig();
if (!config) return null;
const object = parseS3FilePath(filePath);
if (!object) return null;
const response = await getS3Client(config).send(
new GetObjectCommand({
Bucket: object.bucket,
Key: object.key,
}),
);
if (!response.Body) return null;
if (response.Body instanceof Readable) {
return streamToBuffer(response.Body);
}
const body = response.Body as { transformToByteArray?: () => Promise<Uint8Array> };
if (body.transformToByteArray) {
return Buffer.from(await body.transformToByteArray());
}
return null;
}
function parseS3FilePath(filePath: string) {
const match = /^s3:\/\/([^/]+)\/(.+)$/.exec(filePath);
if (!match) return null;
return {
bucket: match[1],
key: match[2],
};
}
async function streamToBuffer(stream: Readable) {
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
return Buffer.concat(chunks);
}
function createPublicImageUrl(config: S3StorageConfig, key: string) {
const encodedKey = key.split("/").map(encodeURIComponent).join("/");
if (config.publicUrl) {
return `${config.publicUrl}/${encodedKey}`;
}
return `${config.endpoint}/${config.bucket}/${encodedKey}`;
}
function getExtensionFromFilename(filename: string) { function getExtensionFromFilename(filename: string) {
const parts = filename.split("."); const parts = filename.split(".");
const extension = parts.at(-1)?.toLowerCase(); const extension = parts.at(-1)?.toLowerCase();
@@ -78,3 +237,10 @@ function normalizeExtension(extension: string | undefined) {
if (!extension || extension.length > 5) return "png"; if (!extension || extension.length > 5) return "png";
return extension === "jpeg" ? "jpg" : extension; return extension === "jpeg" ? "jpg" : extension;
} }
function getMimeTypeFromFileName(fileName: string) {
const extension = getExtensionFromFilename(fileName);
if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
if (extension === "webp") return "image/webp";
return "image/png";
}