dmeo-app
21
next.config.js
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
productionBrowserSourceMaps: false, // enable browser source map generation during the production build
|
||||||
|
// Configure pageExtensions to include md and mdx
|
||||||
|
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
|
||||||
|
experimental: {
|
||||||
|
// appDir: true,
|
||||||
|
},
|
||||||
|
// fix all before production. Now it slow the develop speed.
|
||||||
|
eslint: {
|
||||||
|
// Warning: This allows production builds to successfully complete even if
|
||||||
|
// your project has ESLint errors.
|
||||||
|
ignoreDuringBuilds: true
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
// https://nextjs.org/docs/api-reference/next.config.js/ignoring-typescript-errors
|
||||||
|
ignoreBuildErrors: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import type { NextConfig } from "next";
|
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
|
||||||
/* config options here */
|
|
||||||
};
|
|
||||||
|
|
||||||
export default nextConfig;
|
|
||||||
15106
package-lock.json
generated
100
package.json
|
|
@ -1,27 +1,77 @@
|
||||||
{
|
{
|
||||||
"name": "demo-app",
|
"name": "demo-app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "19.1.0",
|
"@floating-ui/react": "^0.26.2",
|
||||||
"react-dom": "19.1.0",
|
"@formatjs/intl-localematcher": "^0.2.32",
|
||||||
"next": "15.4.6"
|
"@headlessui/react": "^1.7.13",
|
||||||
},
|
"@heroicons/react": "^2.0.16",
|
||||||
"devDependencies": {
|
"@mdx-js/loader": "^2.3.0",
|
||||||
"typescript": "^5",
|
"@mdx-js/react": "^2.3.0",
|
||||||
"@types/node": "^20",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@types/react": "^19",
|
"@remixicon/react": "^4.6.0",
|
||||||
"@types/react-dom": "^19",
|
"@supabase/ssr": "^0.6.1",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@supabase/supabase-js": "^2.55.0",
|
||||||
"tailwindcss": "^4",
|
"@tailwindcss/line-clamp": "^0.4.2",
|
||||||
"eslint": "^9",
|
"@types/react-syntax-highlighter": "^15.5.6",
|
||||||
"eslint-config-next": "15.4.6",
|
"ahooks": "^3.7.5",
|
||||||
"@eslint/eslintrc": "^3"
|
"axios": "^1.3.5",
|
||||||
}
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
|
"copy-to-clipboard": "^3.3.3",
|
||||||
|
"dify-client": "^2.3.1",
|
||||||
|
"eventsource-parser": "^1.0.0",
|
||||||
|
"husky": "^8.0.3",
|
||||||
|
"i18next": "^22.4.13",
|
||||||
|
"i18next-resources-to-backend": "^1.1.3",
|
||||||
|
"immer": "^9.0.19",
|
||||||
|
"js-cookie": "^3.0.1",
|
||||||
|
"katex": "^0.16.7",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"mime": "^4.0.7",
|
||||||
|
"negotiator": "^0.6.3",
|
||||||
|
"next": "^14.0.4",
|
||||||
|
"rc-textarea": "^1.5.3",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-error-boundary": "^4.0.2",
|
||||||
|
"react-headless-pagination": "^1.1.4",
|
||||||
|
"react-i18next": "^12.2.0",
|
||||||
|
"react-markdown": "^8.0.6",
|
||||||
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
"react-tooltip": "^5.8.3",
|
||||||
|
"rehype-katex": "^6.0.2",
|
||||||
|
"remark-breaks": "^3.0.2",
|
||||||
|
"remark-gfm": "^3.0.1",
|
||||||
|
"remark-math": "^5.1.1",
|
||||||
|
"scheduler": "^0.23.0",
|
||||||
|
"server-only": "^0.0.1",
|
||||||
|
"swr": "^2.1.0",
|
||||||
|
"tailwind-merge": "^3.2.0",
|
||||||
|
"use-context-selector": "^1.4.1",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
|
"zustand": "^4.5.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/negotiator": "^0.6.4",
|
||||||
|
"@types/node": "^18.15.0",
|
||||||
|
"@types/react": "^18.0.28",
|
||||||
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"@types/uuid": "^10.0.0",
|
||||||
|
"eslint": "^8.36.0",
|
||||||
|
"eslint-config-next": "^13.4.0",
|
||||||
|
"sass": "^1.61.0",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 391 B |
|
|
@ -1 +0,0 @@
|
||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1 KiB |
6
public/icons/close.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Close">
|
||||||
|
<path id="Vector" d="M10.3121 0.687887L0.6875 10.3125" stroke="#D9D9D9" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_2" d="M0.6875 0.687887L10.3121 10.3125" stroke="#D9D9D9" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 405 B |
3
public/icons/facebook.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="Facebook" d="M4.24864 12V6.52624H6.17843L6.46736 4.393H4.24858V3.03102C4.24858 2.4134 4.4287 1.99252 5.359 1.99252L6.54546 1.99199V0.0840478C6.34025 0.0580973 5.63591 0 4.81659 0C3.1059 0 1.93474 0.994117 1.93474 2.81982V4.393H0V6.52624H1.93474V11.9999H4.24864V12Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 392 B |
7
public/icons/img.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="gallery">
|
||||||
|
<path id="Vector" d="M7.93023 21.2326H14.0698C19.186 21.2326 21.2326 19.186 21.2326 14.0698V7.93023C21.2326 2.81395 19.186 0.767442 14.0698 0.767442H7.93023C2.81395 0.767442 0.767442 2.81395 0.767442 7.93023V14.0698C0.767442 19.186 2.81395 21.2326 7.93023 21.2326Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_2" d="M7.93023 8.95349C9.06049 8.95349 9.97674 8.03723 9.97674 6.90698C9.97674 5.77672 9.06049 4.86047 7.93023 4.86047C6.79998 4.86047 5.88372 5.77672 5.88372 6.90698C5.88372 8.03723 6.79998 8.95349 7.93023 8.95349Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_3" d="M1.45304 18.1122L6.49769 14.7252C7.30606 14.1829 8.47257 14.2443 9.19908 14.8685L9.53676 15.1652C10.3349 15.8508 11.6242 15.8508 12.4223 15.1652L16.6791 11.5122C17.4772 10.8266 18.7665 10.8266 19.5647 11.5122L21.2326 12.9447" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
6
public/icons/instagram.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Group 39463">
|
||||||
|
<path id="Ellipse 140 (Stroke)" fill-rule="evenodd" clip-rule="evenodd" d="M7.00044 4.49939C7.00044 6.43251 5.43334 7.99961 3.50022 7.99961C1.5671 7.99961 0 6.43251 0 4.49939C0 2.56628 1.5671 0.999175 3.50022 0.999175C5.43334 0.999175 7.00044 2.56628 7.00044 4.49939ZM3.50022 6.81539C4.77931 6.81539 5.81622 5.77848 5.81622 4.49939C5.81622 3.2203 4.77931 2.18339 3.50022 2.18339C2.22113 2.18339 1.18422 3.2203 1.18422 4.49939C1.18422 5.77848 2.22113 6.81539 3.50022 6.81539Z" fill="white"/>
|
||||||
|
<ellipse id="Ellipse 141" cx="7.13868" cy="0.822448" rx="0.822447" ry="0.822448" fill="white"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 712 B |
6
public/icons/left.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="undo">
|
||||||
|
<path id="Vector" d="M4.0642 13.5518H12.1723C14.9696 13.5518 17.2399 11.2815 17.2399 8.48421C17.2399 5.68692 14.9696 3.41665 12.1723 3.41665H1.02366" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_2" d="M3.35473 5.94932L0.760135 3.35473L3.35473 0.760135" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 536 B |
6
public/icons/link.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="link-2">
|
||||||
|
<path id="Vector" d="M11.0659 8.93266C13.4228 11.2896 13.4228 15.1026 11.0659 17.4491C8.70892 19.7956 4.8959 19.806 2.54943 17.4491C0.202961 15.0922 0.192486 11.2791 2.54943 8.93266" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_2" d="M8.47846 11.5215C6.02723 9.07032 6.02723 5.0897 8.47846 2.628C10.9297 0.166298 14.9103 0.176773 17.372 2.628C19.8337 5.07922 19.8232 9.05984 17.372 11.5215" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 653 B |
3
public/icons/linkedIn.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="LinkedIn" d="M0 1.32756C0 0.942753 0.135139 0.625293 0.405405 0.37518C0.675672 0.125056 1.02703 0 1.45946 0C1.88417 0 2.2278 0.123128 2.49035 0.369408C2.76061 0.623376 2.89575 0.954297 2.89575 1.36219C2.89575 1.7316 2.76448 2.03943 2.50193 2.28571C2.23166 2.53968 1.87645 2.66667 1.43629 2.66667H1.42471C0.999996 2.66667 0.656375 2.53968 0.393822 2.28571C0.13127 2.03175 0 1.71236 0 1.32756ZM0.150579 11.4286V3.71717H2.72201V11.4286H0.150579ZM4.14672 11.4286H6.71815V7.12265C6.71815 6.85328 6.74904 6.64549 6.81081 6.49928C6.91891 6.23761 7.08301 6.01634 7.30309 5.8355C7.52317 5.65464 7.79922 5.56421 8.13127 5.56421C8.99614 5.56421 9.42857 6.14526 9.42857 7.30736V11.4286H12V7.00721C12 5.8682 11.7297 5.00433 11.1892 4.41558C10.6486 3.82684 9.93437 3.53247 9.04633 3.53247C8.05019 3.53247 7.27413 3.95959 6.71815 4.81385V4.83694H6.70656L6.71815 4.81385V3.71717H4.14672C4.16216 3.96344 4.16988 4.72919 4.16988 6.01443C4.16988 7.29966 4.16216 9.10437 4.14672 11.4286Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
9
public/icons/mic.svg
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<svg width="17" height="22" viewBox="0 0 17 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="microphone-2">
|
||||||
|
<path id="Vector" d="M8.50003 14.4197C10.7363 14.4197 12.5477 12.6083 12.5477 10.372V4.80655C12.5477 2.57024 10.7363 0.758929 8.50003 0.758929C6.26372 0.758929 4.45241 2.57024 4.45241 4.80655V10.372C4.45241 12.6083 6.26372 14.4197 8.50003 14.4197Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_2" d="M0.758929 8.5004V10.2206C0.758929 14.4909 4.22977 17.9617 8.50001 17.9617C12.7702 17.9617 16.2411 14.4909 16.2411 10.2206V8.5004" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_3" d="M7.09347 5.2408C8.00418 4.90687 8.99585 4.90687 9.90657 5.2408" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_4" d="M7.69052 7.38723C8.22683 7.24556 8.78338 7.24556 9.31969 7.38723" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_5" d="M8.50003 17.9613V20.997" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
6
public/icons/plus.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Plus">
|
||||||
|
<path id="Vector" d="M6.99962 0.69569V13.3043" stroke="#D9D9D9" stroke-width="1.32353" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_2" d="M13.3039 7H0.695306" stroke="#D9D9D9" stroke-width="1.32353" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
6
public/icons/right.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="redo">
|
||||||
|
<path id="Vector" d="M13.9358 13.5518H5.8277C3.0304 13.5518 0.760135 11.2815 0.760135 8.48421C0.760135 5.68692 3.0304 3.41665 5.8277 3.41665H16.9763" stroke="white" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path id="Vector_2" d="M14.6453 5.94932L17.2399 3.35473L14.6453 0.760135" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 535 B |
3
public/icons/twitter.svg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
3
public/icons/youtube.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="YouTube" d="M7.31019 9.88362L4.43792 9.82994C3.50794 9.81124 2.57565 9.84858 1.66391 9.65478C0.276941 9.36523 0.178683 7.94549 0.0758663 6.75461C-0.0658044 5.08037 -0.0109594 3.37575 0.256391 1.71549C0.40732 0.783904 1.00128 0.228023 1.91983 0.167533C5.02059 -0.0519955 8.14198 -0.0259791 11.2359 0.0764389C11.5626 0.0858278 11.8916 0.137144 12.2138 0.195556C13.8042 0.480449 13.843 2.08932 13.9461 3.44369C14.0489 4.81203 14.0055 6.1874 13.809 7.54643C13.6514 8.67167 13.3497 9.61529 12.0767 9.70638C10.4817 9.8255 8.92334 9.92139 7.32386 9.89086C7.32393 9.88362 7.31475 9.88362 7.31019 9.88362ZM5.62157 7.03484C6.82353 6.3296 8.00255 5.63611 9.19763 4.9356C7.99343 4.23035 6.81666 3.53686 5.62157 2.83635V7.03484Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 845 B |
BIN
public/images/logo.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -1 +0,0 @@
|
||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 128 B |
|
|
@ -1 +0,0 @@
|
||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 385 B |
10
src/app/api/chat-messages/route.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
import { client, getInfo } from '@/app/api/utils/common';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const body = await request.json();
|
||||||
|
const { inputs, query, files, conversation_id: conversationId, response_mode: responseMode } = body;
|
||||||
|
const { user } = getInfo(request);
|
||||||
|
const res = await client.createChatMessage(inputs, query, user, responseMode, conversationId, files);
|
||||||
|
return new Response(res.data as any);
|
||||||
|
}
|
||||||
21
src/app/api/conversations/[conversationId]/name/route.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { client, getInfo } from '@/app/api/utils/common';
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
request: NextRequest,
|
||||||
|
{
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: { conversationId: string };
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const body = await request.json();
|
||||||
|
const { auto_generate, name } = body;
|
||||||
|
const { conversationId } = params;
|
||||||
|
const { user } = getInfo(request);
|
||||||
|
|
||||||
|
// auto generate name
|
||||||
|
const { data } = await client.renameConversation(conversationId, name, user, auto_generate);
|
||||||
|
return NextResponse.json(data);
|
||||||
|
}
|
||||||
18
src/app/api/conversations/route.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { client, getInfo, setSession } from '@/app/api/utils/common';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const { sessionId, user } = getInfo(request);
|
||||||
|
try {
|
||||||
|
const { data }: any = await client.getConversations(user);
|
||||||
|
return NextResponse.json(data, {
|
||||||
|
headers: setSession(sessionId)
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return NextResponse.json({
|
||||||
|
data: [],
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/app/api/file-upload/route.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
import { client, getInfo } from '@/app/api/utils/common';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const { user } = getInfo(request);
|
||||||
|
formData.append('user', user);
|
||||||
|
const res = await client.fileUpload(formData);
|
||||||
|
return new Response(res.data.id as any);
|
||||||
|
} catch (e: any) {
|
||||||
|
return new Response(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/app/api/messages/[messageId]/feedbacks/route.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { client, getInfo } from '@/app/api/utils/common';
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
request: NextRequest,
|
||||||
|
{
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: { messageId: string };
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const body = await request.json();
|
||||||
|
const { rating } = body;
|
||||||
|
const { messageId } = params;
|
||||||
|
const { user } = getInfo(request);
|
||||||
|
const { data } = await client.messageFeedback(messageId, rating, user);
|
||||||
|
return NextResponse.json(data);
|
||||||
|
}
|
||||||
13
src/app/api/messages/route.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { client, getInfo, setSession } from '@/app/api/utils/common';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const { sessionId, user } = getInfo(request);
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const conversationId = searchParams.get('conversation_id');
|
||||||
|
const { data }: any = await client.getConversationMessages(user, conversationId as string);
|
||||||
|
return NextResponse.json(data, {
|
||||||
|
headers: setSession(sessionId)
|
||||||
|
});
|
||||||
|
}
|
||||||
15
src/app/api/parameters/route.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { client, getInfo, setSession } from '@/app/api/utils/common';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const { sessionId, user } = getInfo(request);
|
||||||
|
try {
|
||||||
|
const { data } = await client.getApplicationParameters(user);
|
||||||
|
return NextResponse.json(data as object, {
|
||||||
|
headers: setSession(sessionId)
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/app/api/utils/common.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
import { ChatClient } from 'dify-client';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import { API_KEY, API_URL, APP_ID } from '@/config';
|
||||||
|
|
||||||
|
const userPrefix = `user_${APP_ID}:`;
|
||||||
|
|
||||||
|
export const getInfo = (request: NextRequest) => {
|
||||||
|
const sessionId = request.cookies.get('session_id')?.value || v4();
|
||||||
|
const user = userPrefix + sessionId;
|
||||||
|
return {
|
||||||
|
sessionId,
|
||||||
|
user
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setSession = (sessionId: string) => {
|
||||||
|
return { 'Set-Cookie': `session_id=${sessionId}` };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const client = new ChatClient(API_KEY, API_URL || undefined);
|
||||||
28
src/app/auth/confirm/route.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { type EmailOtpType } from '@supabase/supabase-js';
|
||||||
|
import { type NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
import { createClient } from '@/utils/supabase/server';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const token_hash = searchParams.get('token_hash');
|
||||||
|
const type = searchParams.get('type') as EmailOtpType | null;
|
||||||
|
const next = searchParams.get('next') ?? '/';
|
||||||
|
|
||||||
|
if (token_hash && type) {
|
||||||
|
const supabase = await createClient();
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.verifyOtp({
|
||||||
|
type,
|
||||||
|
token_hash
|
||||||
|
});
|
||||||
|
if (!error) {
|
||||||
|
// redirect user to specified redirect URL or root of app
|
||||||
|
redirect(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect the user to an error page with some instructions
|
||||||
|
redirect('/error');
|
||||||
|
}
|
||||||
5
src/app/error/page.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
export default function ErrorPage() {
|
||||||
|
return <p>Sorry, something went wrong</p>;
|
||||||
|
}
|
||||||
2
src/app/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
declare module 'dify-client';
|
||||||
|
declare module 'uuid';
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: #ffffff;
|
|
||||||
--foreground: #171717;
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--font-sans: var(--font-geist-sans);
|
|
||||||
--font-mono: var(--font-geist-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +1,32 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from 'next';
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Roboto } from 'next/font/google';
|
||||||
import "./globals.css";
|
import '@/styles/globals.css';
|
||||||
|
import Header from '@/components/layouts/header/header';
|
||||||
|
import Footer from '@/components/layouts/footer/footer';
|
||||||
|
|
||||||
const geistSans = Geist({
|
const roboto = Roboto({
|
||||||
variable: "--font-geist-sans",
|
subsets: ['vietnamese'],
|
||||||
subsets: ["latin"],
|
weight: ['100', '300', '400', '500', '700', '900'],
|
||||||
});
|
display: 'swap'
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: 'HỎI ĐÁP - TRA CỨU CHÍNH SÁCH KHCN',
|
||||||
description: "Generated by create next app",
|
description: 'Generated by create next app'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="vi">
|
||||||
<body
|
<body className={`${roboto.className} antialiased`}>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
<Header />
|
||||||
>
|
<div>{children}</div>
|
||||||
{children}
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
src/app/login/actions.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
import { revalidatePath } from 'next/cache';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
import { createClient } from '@/utils/supabase/server';
|
||||||
|
|
||||||
|
export async function login(formData: FormData) {
|
||||||
|
const supabase = await createClient();
|
||||||
|
// type-casting here for convenience
|
||||||
|
// in practice, you should validate your inputs
|
||||||
|
const data = {
|
||||||
|
email: formData.get('email') as string,
|
||||||
|
password: formData.get('password') as string
|
||||||
|
};
|
||||||
|
|
||||||
|
const { error, data: rs } = await supabase.auth.signInWithPassword(data);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
redirect('/error');
|
||||||
|
}
|
||||||
|
revalidatePath('/', 'layout');
|
||||||
|
return rs.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signup(formData: FormData) {
|
||||||
|
const supabase = await createClient();
|
||||||
|
// type-casting here for convenience
|
||||||
|
// in practice, you should validate your inputs
|
||||||
|
const data = {
|
||||||
|
email: formData.get('email') as string,
|
||||||
|
password: formData.get('password') as string
|
||||||
|
};
|
||||||
|
const { error, data: rs } = await supabase.auth.signUp(data);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
redirect('/error');
|
||||||
|
}
|
||||||
|
revalidatePath('/', 'layout');
|
||||||
|
return rs.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signout() {
|
||||||
|
const supabase = await createClient();
|
||||||
|
const { error } = await supabase.auth.signOut();
|
||||||
|
if (error) {
|
||||||
|
redirect('/error');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
57
src/app/login/page.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { login, signup } from './actions';
|
||||||
|
import style from './style.module.scss';
|
||||||
|
import { useAuthStore } from '@/stores/useAuthStore';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { setLogin } = useAuthStore();
|
||||||
|
const [data, setData] = useState<{ email: string; password: string }>({ email: '', password: '' });
|
||||||
|
// handle change data
|
||||||
|
const handleData = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setData({ ...data, [name]: value });
|
||||||
|
};
|
||||||
|
// handle login
|
||||||
|
const handleLogin = async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('email', data.email);
|
||||||
|
formData.append('password', data.password);
|
||||||
|
const user = await login(formData);
|
||||||
|
if (user) {
|
||||||
|
setLogin(true);
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// handle signup
|
||||||
|
const handleSignup = async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('email', data.email);
|
||||||
|
formData.append('password', data.password);
|
||||||
|
const user = await signup(formData);
|
||||||
|
if (user) {
|
||||||
|
setLogin(true);
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className={`${style.login}`} method="post" action="">
|
||||||
|
<label htmlFor="email">Email:</label>
|
||||||
|
<input id="email" name="email" type="email" required onChange={handleData} />
|
||||||
|
<label htmlFor="password">Password:</label>
|
||||||
|
<input id="password" name="password" type="password" required onChange={handleData} />
|
||||||
|
<div>
|
||||||
|
<button type="button" onClick={handleLogin}>
|
||||||
|
Log in
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={handleSignup}>
|
||||||
|
Sign up
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
25
src/app/login/style.module.scss
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
@use '@/styles/mixins.scss' as *;
|
||||||
|
@use '@/styles/variables.scss' as *;
|
||||||
|
@use '@/styles/responsive.scss' as *;
|
||||||
|
|
||||||
|
.login {
|
||||||
|
height: 50vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 20px 45px;
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
outline: none;
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
@include button(14px, 400, 12px 16px, 8px, $primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/app/page.tsx
|
|
@ -1,103 +1,9 @@
|
||||||
import Image from "next/image";
|
import Main from '@/components/layouts/main/main';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
<>
|
||||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
<Main />
|
||||||
<Image
|
</>
|
||||||
className="dark:invert"
|
);
|
||||||
src="/next.svg"
|
|
||||||
alt="Next.js logo"
|
|
||||||
width={180}
|
|
||||||
height={38}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
|
|
||||||
<li className="mb-2 tracking-[-.01em]">
|
|
||||||
Get started by editing{" "}
|
|
||||||
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
|
|
||||||
src/app/page.tsx
|
|
||||||
</code>
|
|
||||||
.
|
|
||||||
</li>
|
|
||||||
<li className="tracking-[-.01em]">
|
|
||||||
Save and see your changes instantly.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
Deploy now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Read our docs
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="/file.svg"
|
|
||||||
alt="File icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Learn
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="/window.svg"
|
|
||||||
alt="Window icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Examples
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="/globe.svg"
|
|
||||||
alt="Globe icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Go to nextjs.org →
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/app/private/page.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
import { createClient } from '@/utils/supabase/server';
|
||||||
|
|
||||||
|
export default async function PrivatePage() {
|
||||||
|
const supabase = await createClient();
|
||||||
|
|
||||||
|
const { data, error } = await supabase.auth.getUser();
|
||||||
|
if (error || !data?.user) {
|
||||||
|
redirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
31
src/components/app-unavailable.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
'use client';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
type IAppUnavailableProps = {
|
||||||
|
isUnknownReason: boolean;
|
||||||
|
errMessage?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AppUnavailable: FC<IAppUnavailableProps> = ({ isUnknownReason, errMessage }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
let message = errMessage;
|
||||||
|
if (!errMessage)
|
||||||
|
message = (isUnknownReason ? t('app.common.appUnkonwError') : t('app.common.appUnavailable')) as string;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center w-screen h-screen">
|
||||||
|
<h1
|
||||||
|
className="mr-5 h-[50px] leading-[50px] pr-5 text-[24px] font-medium"
|
||||||
|
style={{
|
||||||
|
borderRight: '1px solid rgba(0,0,0,.3)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{errMessage || isUnknownReason ? 500 : 404}
|
||||||
|
</h1>
|
||||||
|
<div className="text-sm">{message}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default React.memo(AppUnavailable);
|
||||||
44
src/components/base/action-button/index.css
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.action-btn {
|
||||||
|
@apply inline-flex justify-center items-center cursor-pointer text-text-tertiary hover:text-text-secondary hover:bg-state-base-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn-hover {
|
||||||
|
@apply bg-state-base-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn-disabled {
|
||||||
|
@apply cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn-xl {
|
||||||
|
@apply p-2 w-9 h-9 rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn-l {
|
||||||
|
@apply p-1.5 w-8 h-8 rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* m is for the regular button */
|
||||||
|
.action-btn-m {
|
||||||
|
@apply p-0.5 w-6 h-6 rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn-xs {
|
||||||
|
@apply p-0 w-4 h-4 rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.action-btn-active {
|
||||||
|
@apply text-text-accent bg-state-accent-active hover:bg-state-accent-active-alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.action-btn-disabled {
|
||||||
|
@apply text-text-disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.action-btn-destructive {
|
||||||
|
@apply text-text-destructive bg-state-destructive-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/components/base/action-button/index.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import { type VariantProps, cva } from 'class-variance-authority';
|
||||||
|
import classNames from '@/utils/classnames';
|
||||||
|
|
||||||
|
enum ActionButtonState {
|
||||||
|
Destructive = 'destructive',
|
||||||
|
Active = 'active',
|
||||||
|
Disabled = 'disabled',
|
||||||
|
Default = '',
|
||||||
|
Hover = 'hover'
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionButtonVariants = cva('action-btn', {
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
xs: 'action-btn-xs',
|
||||||
|
m: 'action-btn-m',
|
||||||
|
l: 'action-btn-l',
|
||||||
|
xl: 'action-btn-xl'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'm'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ActionButtonProps = {
|
||||||
|
size?: 'xs' | 's' | 'm' | 'l' | 'xl';
|
||||||
|
state?: ActionButtonState;
|
||||||
|
styleCss?: CSSProperties;
|
||||||
|
} & React.ButtonHTMLAttributes<HTMLButtonElement> &
|
||||||
|
VariantProps<typeof actionButtonVariants>;
|
||||||
|
|
||||||
|
function getActionButtonState(state: ActionButtonState) {
|
||||||
|
switch (state) {
|
||||||
|
case ActionButtonState.Destructive:
|
||||||
|
return 'action-btn-destructive';
|
||||||
|
case ActionButtonState.Active:
|
||||||
|
return 'action-btn-active';
|
||||||
|
case ActionButtonState.Disabled:
|
||||||
|
return 'action-btn-disabled';
|
||||||
|
case ActionButtonState.Hover:
|
||||||
|
return 'action-btn-hover';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProps>(
|
||||||
|
({ className, size, state = ActionButtonState.Default, styleCss, children, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames(actionButtonVariants({ className, size }), getActionButtonState(state))}
|
||||||
|
ref={ref}
|
||||||
|
style={styleCss}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ActionButton.displayName = 'ActionButton';
|
||||||
|
|
||||||
|
export default ActionButton;
|
||||||
|
export { ActionButton, ActionButtonState, actionButtonVariants };
|
||||||
31
src/components/base/app-icon/index.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import style from './style.module.css';
|
||||||
|
|
||||||
|
export type AppIconProps = {
|
||||||
|
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large';
|
||||||
|
rounded?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
background?: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AppIcon: FC<AppIconProps> = ({ size = 'medium', rounded = false, background, className }) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
style.appIcon,
|
||||||
|
size !== 'medium' && style[size],
|
||||||
|
rounded && style.rounded,
|
||||||
|
className ?? ''
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
background
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🤖
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppIcon;
|
||||||
23
src/components/base/app-icon/style.module.css
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
.appIcon {
|
||||||
|
@apply flex items-center justify-center relative w-9 h-9 text-lg bg-teal-100 rounded-lg grow-0 shrink-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.large {
|
||||||
|
@apply w-10 h-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.small {
|
||||||
|
@apply w-8 h-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.xs {
|
||||||
|
@apply w-3 h-3 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.tiny {
|
||||||
|
@apply w-6 h-6 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.rounded {
|
||||||
|
@apply rounded-full;
|
||||||
|
}
|
||||||
85
src/components/base/auto-height-textarea/index.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { forwardRef, useEffect, useRef } from 'react';
|
||||||
|
import cn from 'classnames';
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
placeholder?: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
|
className?: string;
|
||||||
|
minHeight?: number;
|
||||||
|
maxHeight?: number;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
controlFocus?: number;
|
||||||
|
onKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||||
|
onKeyUp?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AutoHeightTextarea = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder,
|
||||||
|
className,
|
||||||
|
minHeight = 36,
|
||||||
|
maxHeight = 96,
|
||||||
|
autoFocus,
|
||||||
|
controlFocus,
|
||||||
|
onKeyDown,
|
||||||
|
onKeyUp
|
||||||
|
}: IProps,
|
||||||
|
outerRef: any
|
||||||
|
) => {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const ref = outerRef || useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
|
const doFocus = () => {
|
||||||
|
if (ref.current) {
|
||||||
|
ref.current.setSelectionRange(value.length, value.length);
|
||||||
|
ref.current.focus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const focus = () => {
|
||||||
|
if (!doFocus()) {
|
||||||
|
let hasFocus = false;
|
||||||
|
const runId = setInterval(() => {
|
||||||
|
hasFocus = doFocus();
|
||||||
|
if (hasFocus) clearInterval(runId);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (autoFocus) focus();
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
if (controlFocus) focus();
|
||||||
|
}, [controlFocus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
className={cn(className, 'invisible whitespace-pre-wrap break-all overflow-y-auto')}
|
||||||
|
style={{ minHeight, maxHeight }}
|
||||||
|
>
|
||||||
|
{!value ? placeholder : value.replace(/\n$/, '\n ')}
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
ref={ref}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
className={cn(className, 'absolute inset-0 resize-none overflow-hidden')}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onKeyUp={onKeyUp}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AutoHeightTextarea;
|
||||||
49
src/components/base/button/index.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import type { FC, MouseEventHandler } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import Spinner from '@/components/base/spinner';
|
||||||
|
|
||||||
|
export type IButtonProps = {
|
||||||
|
type?: string;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Button: FC<IButtonProps> = ({ type, disabled, children, className, onClick, loading = false }) => {
|
||||||
|
let style = 'cursor-pointer';
|
||||||
|
switch (type) {
|
||||||
|
case 'link':
|
||||||
|
style = disabled
|
||||||
|
? 'border-solid border border-gray-200 bg-gray-200 cursor-not-allowed text-gray-800'
|
||||||
|
: 'border-solid border border-gray-200 cursor-pointer text-blue-600 bg-white hover:shadow-sm hover:border-gray-300';
|
||||||
|
break;
|
||||||
|
case 'primary':
|
||||||
|
style =
|
||||||
|
disabled || loading
|
||||||
|
? 'bg-primary-600/75 cursor-not-allowed text-white'
|
||||||
|
: 'bg-primary-600 hover:bg-primary-600/75 hover:shadow-md cursor-pointer text-white hover:shadow-sm';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
style = disabled
|
||||||
|
? 'border-solid border border-gray-200 bg-gray-200 cursor-not-allowed text-gray-800'
|
||||||
|
: 'border-solid border border-gray-200 cursor-pointer text-gray-500 hover:bg-white hover:shadow-sm hover:border-gray-300';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flex justify-center items-center content-center h-9 leading-5 rounded-lg px-4 py-2 text-base ${style} ${
|
||||||
|
className && className
|
||||||
|
}`}
|
||||||
|
onClick={disabled ? undefined : onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{/* Spinner is hidden when loading is false */}
|
||||||
|
<Spinner loading={loading} className="!text-white !h-3 !w-3 !border-2 !ml-1" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(Button);
|
||||||
34
src/components/base/file-uploader-in-attachment/constants.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { SupportUploadFileTypes } from './types';
|
||||||
|
// fallback for file size limit of dify_config
|
||||||
|
export const IMG_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||||
|
export const FILE_SIZE_LIMIT = 15 * 1024 * 1024;
|
||||||
|
export const AUDIO_SIZE_LIMIT = 50 * 1024 * 1024;
|
||||||
|
export const VIDEO_SIZE_LIMIT = 100 * 1024 * 1024;
|
||||||
|
export const MAX_FILE_UPLOAD_LIMIT = 10;
|
||||||
|
|
||||||
|
export const FILE_URL_REGEX = /^(https?|ftp):\/\//;
|
||||||
|
|
||||||
|
export const FILE_EXTS: Record<string, string[]> = {
|
||||||
|
[SupportUploadFileTypes.image]: ['JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG'],
|
||||||
|
[SupportUploadFileTypes.document]: [
|
||||||
|
'TXT',
|
||||||
|
'MD',
|
||||||
|
'MDX',
|
||||||
|
'MARKDOWN',
|
||||||
|
'PDF',
|
||||||
|
'HTML',
|
||||||
|
'XLSX',
|
||||||
|
'XLS',
|
||||||
|
'DOC',
|
||||||
|
'DOCX',
|
||||||
|
'CSV',
|
||||||
|
'EML',
|
||||||
|
'MSG',
|
||||||
|
'PPTX',
|
||||||
|
'PPT',
|
||||||
|
'XML',
|
||||||
|
'EPUB'
|
||||||
|
],
|
||||||
|
[SupportUploadFileTypes.audio]: ['MP3', 'M4A', 'WAV', 'AMR', 'MPGA'],
|
||||||
|
[SupportUploadFileTypes.video]: ['MP4', 'MOV', 'MPEG', 'WEBM']
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
import { memo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { RiUploadCloud2Line } from '@remixicon/react';
|
||||||
|
import FileInput from '../file-input';
|
||||||
|
import { useFile } from '../hooks';
|
||||||
|
import { useStore } from '../store';
|
||||||
|
import { FILE_URL_REGEX } from '../constants';
|
||||||
|
import type { FileUpload } from '../types';
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
PortalToFollowElemTrigger
|
||||||
|
} from '@/components/base/portal-to-follow-elem';
|
||||||
|
import Button from '@/components/base/button';
|
||||||
|
import cn from '@/utils/classnames';
|
||||||
|
|
||||||
|
type FileFromLinkOrLocalProps = {
|
||||||
|
showFromLink?: boolean;
|
||||||
|
showFromLocal?: boolean;
|
||||||
|
trigger: (open: boolean) => React.ReactNode;
|
||||||
|
fileConfig: FileUpload;
|
||||||
|
};
|
||||||
|
const FileFromLinkOrLocal = ({
|
||||||
|
showFromLink = true,
|
||||||
|
showFromLocal = true,
|
||||||
|
trigger,
|
||||||
|
fileConfig
|
||||||
|
}: FileFromLinkOrLocalProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const files = useStore((s) => s.files);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [url, setUrl] = useState('');
|
||||||
|
const [showError, setShowError] = useState(false);
|
||||||
|
const { handleLoadFileFromLink } = useFile(fileConfig);
|
||||||
|
const disabled = !!fileConfig.number_limits && files.length >= fileConfig.number_limits;
|
||||||
|
|
||||||
|
const handleSaveUrl = () => {
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
if (!FILE_URL_REGEX.test(url)) {
|
||||||
|
setShowError(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleLoadFileFromLink(url);
|
||||||
|
setUrl('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PortalToFollowElem placement="top" offset={4} open={open} onOpenChange={setOpen}>
|
||||||
|
<PortalToFollowElemTrigger onClick={() => setOpen((v) => !v)} asChild>
|
||||||
|
{trigger(open)}
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className="z-[1001]">
|
||||||
|
<div className="w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-3 shadow-lg">
|
||||||
|
{showFromLink && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex h-8 items-center rounded-lg border border-components-input-border-active bg-components-input-bg-active p-1 shadow-xs',
|
||||||
|
showError && 'border-components-input-border-destructive'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="system-sm-regular mr-0.5 block grow appearance-none bg-transparent px-1 outline-none"
|
||||||
|
placeholder={t('common.fileUploader.pasteFileLinkInputPlaceholder') || ''}
|
||||||
|
value={url}
|
||||||
|
onChange={(e) => {
|
||||||
|
setShowError(false);
|
||||||
|
setUrl(e.target.value.trim());
|
||||||
|
}}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className="shrink-0"
|
||||||
|
// size='small'
|
||||||
|
// variant='primary'
|
||||||
|
type="primary"
|
||||||
|
disabled={!url || disabled}
|
||||||
|
onClick={handleSaveUrl}
|
||||||
|
>
|
||||||
|
{t('common.operation.ok')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{showError && (
|
||||||
|
<div className="body-xs-regular mt-0.5 text-text-destructive">
|
||||||
|
{t('common.fileUploader.pasteFileLinkInvalid')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{showFromLink && showFromLocal && (
|
||||||
|
<div className="system-2xs-medium-uppercase flex h-7 items-center p-2 text-text-quaternary">
|
||||||
|
<div className="mr-2 h-[1px] w-[93px] bg-gradient-to-l from-[rgba(16,24,40,0.08)]" />
|
||||||
|
OR
|
||||||
|
<div className="ml-2 h-[1px] w-[93px] bg-gradient-to-r from-[rgba(16,24,40,0.08)]" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showFromLocal && (
|
||||||
|
<Button
|
||||||
|
className="relative w-full"
|
||||||
|
// variant='secondary-accent'
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<RiUploadCloud2Line className="mr-1 h-4 w-4" />
|
||||||
|
{t('common.fileUploader.uploadFromComputer')}
|
||||||
|
<FileInput fileConfig={fileConfig} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(FileFromLinkOrLocal);
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import cn from '@/utils/classnames';
|
||||||
|
|
||||||
|
type FileImageRenderProps = {
|
||||||
|
imageUrl: string;
|
||||||
|
className?: string;
|
||||||
|
alt?: string;
|
||||||
|
onLoad?: () => void;
|
||||||
|
onError?: () => void;
|
||||||
|
showDownloadAction?: boolean;
|
||||||
|
};
|
||||||
|
const FileImageRender = ({ imageUrl, className, alt, onLoad, onError, showDownloadAction }: FileImageRenderProps) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('border-[2px] border-effects-image-frame shadow-xs', className)}>
|
||||||
|
<img
|
||||||
|
className={cn('h-full w-full object-cover', showDownloadAction && 'cursor-pointer')}
|
||||||
|
alt={alt || 'Preview'}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onError={onError}
|
||||||
|
src={imageUrl}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileImageRender;
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { useFile } from './hooks';
|
||||||
|
import { useStore } from './store';
|
||||||
|
import type { FileUpload } from './types';
|
||||||
|
import { FILE_EXTS } from './constants';
|
||||||
|
import { SupportUploadFileTypes } from './types';
|
||||||
|
|
||||||
|
type FileInputProps = {
|
||||||
|
fileConfig: FileUpload;
|
||||||
|
};
|
||||||
|
const FileInput = ({ fileConfig }: FileInputProps) => {
|
||||||
|
const files = useStore((s) => s.files);
|
||||||
|
const { handleLocalFileUpload } = useFile(fileConfig);
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const targetFiles = e.target.files;
|
||||||
|
|
||||||
|
if (targetFiles) {
|
||||||
|
if (fileConfig.number_limits) {
|
||||||
|
for (let i = 0; i < targetFiles.length; i++) {
|
||||||
|
if (i + 1 + files.length <= fileConfig.number_limits) handleLocalFileUpload(targetFiles[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleLocalFileUpload(targetFiles[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const allowedFileTypes = fileConfig.allowed_file_types;
|
||||||
|
const isCustom = allowedFileTypes?.includes(SupportUploadFileTypes.custom);
|
||||||
|
const exts = isCustom
|
||||||
|
? fileConfig.allowed_file_extensions || []
|
||||||
|
: (allowedFileTypes?.map((type) => FILE_EXTS[type]) || []).flat().map((item) => `.${item}`);
|
||||||
|
const accept = exts.join(',');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className="absolute inset-0 block w-full cursor-pointer text-[0] opacity-0 disabled:cursor-not-allowed"
|
||||||
|
onClick={(e) => ((e.target as HTMLInputElement).value = '')}
|
||||||
|
type="file"
|
||||||
|
onChange={handleChange}
|
||||||
|
accept={accept}
|
||||||
|
disabled={!!(fileConfig.number_limits && files.length >= fileConfig?.number_limits)}
|
||||||
|
multiple={!!fileConfig.number_limits && fileConfig.number_limits > 1}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileInput;
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import { memo, useState } from 'react';
|
||||||
|
import { RiDeleteBinLine, RiDownloadLine, RiEyeLine } from '@remixicon/react';
|
||||||
|
import FileTypeIcon from './file-type-icon';
|
||||||
|
import FileImageRender from './file-image-render';
|
||||||
|
import type { FileEntity } from './types';
|
||||||
|
import { downloadFile, fileIsUploaded, getFileAppearanceType, getFileExtension } from './utils';
|
||||||
|
import { SupportUploadFileTypes } from './types';
|
||||||
|
import ActionButton from '@/components/base/action-button';
|
||||||
|
import ProgressCircle from '@/components/base/progress-bar/progress-circle';
|
||||||
|
import { formatFileSize } from '@/utils/format';
|
||||||
|
import cn from '@/utils/classnames';
|
||||||
|
import ReplayLine from '@/components/base/icons/other/ReplayLine';
|
||||||
|
import ImagePreview from '@/components/base/image-uploader/image-preview';
|
||||||
|
|
||||||
|
type FileInAttachmentItemProps = {
|
||||||
|
file: FileEntity;
|
||||||
|
showDeleteAction?: boolean;
|
||||||
|
showDownloadAction?: boolean;
|
||||||
|
onRemove?: (fileId: string) => void;
|
||||||
|
onReUpload?: (fileId: string) => void;
|
||||||
|
canPreview?: boolean;
|
||||||
|
};
|
||||||
|
const FileInAttachmentItem = ({
|
||||||
|
file,
|
||||||
|
showDeleteAction,
|
||||||
|
showDownloadAction = true,
|
||||||
|
onRemove,
|
||||||
|
onReUpload,
|
||||||
|
canPreview
|
||||||
|
}: FileInAttachmentItemProps) => {
|
||||||
|
const { id, name, type, progress, supportFileType, base64Url, url, isRemote } = file;
|
||||||
|
const ext = getFileExtension(name, type, isRemote);
|
||||||
|
const isImageFile = supportFileType === SupportUploadFileTypes.image;
|
||||||
|
const [imagePreviewUrl, setImagePreviewUrl] = useState('');
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex h-12 items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg pr-3 shadow-xs',
|
||||||
|
progress === -1 && 'border-state-destructive-border bg-state-destructive-hover'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex h-12 w-12 items-center justify-center">
|
||||||
|
{isImageFile && <FileImageRender className="h-8 w-8" imageUrl={base64Url || url || ''} />}
|
||||||
|
{!isImageFile && <FileTypeIcon type={getFileAppearanceType(name, type)} size="lg" />}
|
||||||
|
</div>
|
||||||
|
<div className="mr-1 w-0 grow">
|
||||||
|
<div
|
||||||
|
className="system-xs-medium mb-0.5 flex items-center truncate text-text-secondary"
|
||||||
|
title={file.name}
|
||||||
|
>
|
||||||
|
<div className="truncate">{name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="system-2xs-medium-uppercase flex items-center text-text-tertiary">
|
||||||
|
{ext && <span>{ext.toLowerCase()}</span>}
|
||||||
|
{ext && <span className="system-2xs-medium mx-1">•</span>}
|
||||||
|
{!!file.size && <span>{formatFileSize(file.size)}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 items-center">
|
||||||
|
{progress >= 0 && !fileIsUploaded(file) && (
|
||||||
|
<ProgressCircle className="mr-2.5" percentage={progress} />
|
||||||
|
)}
|
||||||
|
{progress === -1 && (
|
||||||
|
<ActionButton className="mr-1" onClick={() => onReUpload?.(id)}>
|
||||||
|
<ReplayLine className="h-4 w-4 text-text-tertiary" />
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
{showDeleteAction && (
|
||||||
|
<ActionButton onClick={() => onRemove?.(id)}>
|
||||||
|
<RiDeleteBinLine className="h-4 w-4" />
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
{canPreview && isImageFile && (
|
||||||
|
<ActionButton className="mr-1" onClick={() => setImagePreviewUrl(url || '')}>
|
||||||
|
<RiEyeLine className="h-4 w-4" />
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
{showDownloadAction && (
|
||||||
|
<ActionButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
downloadFile(url || base64Url || '', name);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RiDownloadLine className="h-4 w-4" />
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{imagePreviewUrl && canPreview && (
|
||||||
|
<ImagePreview title={name} url={imagePreviewUrl} onCancel={() => setImagePreviewUrl('')} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(FileInAttachmentItem);
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { memo } from 'react';
|
||||||
|
import {
|
||||||
|
RiFile3Fill,
|
||||||
|
RiFileCodeFill,
|
||||||
|
RiFileExcelFill,
|
||||||
|
RiFileGifFill,
|
||||||
|
RiFileImageFill,
|
||||||
|
RiFileMusicFill,
|
||||||
|
RiFilePdf2Fill,
|
||||||
|
RiFilePpt2Fill,
|
||||||
|
RiFileTextFill,
|
||||||
|
RiFileVideoFill,
|
||||||
|
RiFileWordFill,
|
||||||
|
RiMarkdownFill
|
||||||
|
} from '@remixicon/react';
|
||||||
|
import { FileAppearanceTypeEnum } from './types';
|
||||||
|
import type { FileAppearanceType } from './types';
|
||||||
|
import cn from '@/utils/classnames';
|
||||||
|
|
||||||
|
const FILE_TYPE_ICON_MAP = {
|
||||||
|
[FileAppearanceTypeEnum.pdf]: {
|
||||||
|
component: RiFilePdf2Fill,
|
||||||
|
color: 'text-[#EA3434]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.image]: {
|
||||||
|
component: RiFileImageFill,
|
||||||
|
color: 'text-[#00B2EA]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.video]: {
|
||||||
|
component: RiFileVideoFill,
|
||||||
|
color: 'text-[#844FDA]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.audio]: {
|
||||||
|
component: RiFileMusicFill,
|
||||||
|
color: 'text-[#FF3093]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.document]: {
|
||||||
|
component: RiFileTextFill,
|
||||||
|
color: 'text-[#6F8BB5]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.code]: {
|
||||||
|
component: RiFileCodeFill,
|
||||||
|
color: 'text-[#BCC0D1]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.markdown]: {
|
||||||
|
component: RiMarkdownFill,
|
||||||
|
color: 'text-[#309BEC]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.custom]: {
|
||||||
|
component: RiFile3Fill,
|
||||||
|
color: 'text-[#BCC0D1]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.excel]: {
|
||||||
|
component: RiFileExcelFill,
|
||||||
|
color: 'text-[#01AC49]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.word]: {
|
||||||
|
component: RiFileWordFill,
|
||||||
|
color: 'text-[#2684FF]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.ppt]: {
|
||||||
|
component: RiFilePpt2Fill,
|
||||||
|
color: 'text-[#FF650F]'
|
||||||
|
},
|
||||||
|
[FileAppearanceTypeEnum.gif]: {
|
||||||
|
component: RiFileGifFill,
|
||||||
|
color: 'text-[#00B2EA]'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
type FileTypeIconProps = {
|
||||||
|
type: FileAppearanceType;
|
||||||
|
size?: 'sm' | 'lg' | 'md';
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
const SizeMap = {
|
||||||
|
sm: 'w-4 h-4',
|
||||||
|
md: 'w-5 h-5',
|
||||||
|
lg: 'w-6 h-6'
|
||||||
|
};
|
||||||
|
const FileTypeIcon = ({ type, size = 'sm', className }: FileTypeIconProps) => {
|
||||||
|
const Icon = FILE_TYPE_ICON_MAP[type]?.component || FILE_TYPE_ICON_MAP[FileAppearanceTypeEnum.document].component;
|
||||||
|
const color = FILE_TYPE_ICON_MAP[type]?.color || FILE_TYPE_ICON_MAP[FileAppearanceTypeEnum.document].color;
|
||||||
|
|
||||||
|
return <Icon className={cn('shrink-0', SizeMap[size], color, className)} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(FileTypeIcon);
|
||||||
447
src/components/base/file-uploader-in-attachment/hooks.ts
Normal file
|
|
@ -0,0 +1,447 @@
|
||||||
|
import type { ClipboardEvent } from 'react';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
import produce from 'immer';
|
||||||
|
import { v4 as uuid4 } from 'uuid';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { noop } from 'lodash-es';
|
||||||
|
import type { FileEntity, FileUpload, FileUploadConfigResponse } from './types';
|
||||||
|
import { useFileStore } from './store';
|
||||||
|
import { fileUpload, getSupportFileType, isAllowedFileExtension } from './utils';
|
||||||
|
import {
|
||||||
|
AUDIO_SIZE_LIMIT,
|
||||||
|
FILE_SIZE_LIMIT,
|
||||||
|
IMG_SIZE_LIMIT,
|
||||||
|
MAX_FILE_UPLOAD_LIMIT,
|
||||||
|
VIDEO_SIZE_LIMIT
|
||||||
|
} from './constants';
|
||||||
|
import { SupportUploadFileTypes } from './types';
|
||||||
|
import { useToastContext } from '@/components/base/toast';
|
||||||
|
import { TransferMethod } from '@/types/app';
|
||||||
|
import { formatFileSize } from '@/utils/format';
|
||||||
|
|
||||||
|
const uploadRemoteFileInfo = () => {
|
||||||
|
console.log('TODO');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFileSizeLimit = (fileUploadConfig?: FileUploadConfigResponse) => {
|
||||||
|
const imgSizeLimit = Number(fileUploadConfig?.image_file_size_limit) * 1024 * 1024 || IMG_SIZE_LIMIT;
|
||||||
|
const docSizeLimit = Number(fileUploadConfig?.file_size_limit) * 1024 * 1024 || FILE_SIZE_LIMIT;
|
||||||
|
const audioSizeLimit = Number(fileUploadConfig?.audio_file_size_limit) * 1024 * 1024 || AUDIO_SIZE_LIMIT;
|
||||||
|
const videoSizeLimit = Number(fileUploadConfig?.video_file_size_limit) * 1024 * 1024 || VIDEO_SIZE_LIMIT;
|
||||||
|
const maxFileUploadLimit = Number(fileUploadConfig?.workflow_file_upload_limit) || MAX_FILE_UPLOAD_LIMIT;
|
||||||
|
|
||||||
|
return {
|
||||||
|
imgSizeLimit,
|
||||||
|
docSizeLimit,
|
||||||
|
audioSizeLimit,
|
||||||
|
videoSizeLimit,
|
||||||
|
maxFileUploadLimit
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFile = (fileConfig: FileUpload) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { notify } = useToastContext();
|
||||||
|
const fileStore = useFileStore();
|
||||||
|
const params = useParams();
|
||||||
|
const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit(
|
||||||
|
fileConfig.fileUploadConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkSizeLimit = useCallback(
|
||||||
|
(fileType: string, fileSize: number) => {
|
||||||
|
switch (fileType) {
|
||||||
|
case SupportUploadFileTypes.image: {
|
||||||
|
if (fileSize > imgSizeLimit) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.uploadFromComputerLimit', {
|
||||||
|
type: SupportUploadFileTypes.image,
|
||||||
|
size: formatFileSize(imgSizeLimit)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case SupportUploadFileTypes.document: {
|
||||||
|
if (fileSize > docSizeLimit) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.uploadFromComputerLimit', {
|
||||||
|
type: SupportUploadFileTypes.document,
|
||||||
|
size: formatFileSize(docSizeLimit)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case SupportUploadFileTypes.audio: {
|
||||||
|
if (fileSize > audioSizeLimit) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.uploadFromComputerLimit', {
|
||||||
|
type: SupportUploadFileTypes.audio,
|
||||||
|
size: formatFileSize(audioSizeLimit)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case SupportUploadFileTypes.video: {
|
||||||
|
if (fileSize > videoSizeLimit) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.uploadFromComputerLimit', {
|
||||||
|
type: SupportUploadFileTypes.video,
|
||||||
|
size: formatFileSize(videoSizeLimit)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case SupportUploadFileTypes.custom: {
|
||||||
|
if (fileSize > docSizeLimit) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.uploadFromComputerLimit', {
|
||||||
|
type: SupportUploadFileTypes.document,
|
||||||
|
size: formatFileSize(docSizeLimit)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[audioSizeLimit, docSizeLimit, imgSizeLimit, notify, t, videoSizeLimit]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddFile = useCallback(
|
||||||
|
(newFile: FileEntity) => {
|
||||||
|
const { files, setFiles } = fileStore.getState();
|
||||||
|
|
||||||
|
const newFiles = produce(files, (draft) => {
|
||||||
|
draft.push(newFile);
|
||||||
|
});
|
||||||
|
setFiles(newFiles);
|
||||||
|
},
|
||||||
|
[fileStore]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleUpdateFile = useCallback(
|
||||||
|
(newFile: FileEntity) => {
|
||||||
|
const { files, setFiles } = fileStore.getState();
|
||||||
|
|
||||||
|
const newFiles = produce(files, (draft) => {
|
||||||
|
const index = draft.findIndex((file) => file.id === newFile.id);
|
||||||
|
|
||||||
|
if (index > -1) draft[index] = newFile;
|
||||||
|
});
|
||||||
|
setFiles(newFiles);
|
||||||
|
},
|
||||||
|
[fileStore]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemoveFile = useCallback(
|
||||||
|
(fileId: string) => {
|
||||||
|
const { files, setFiles } = fileStore.getState();
|
||||||
|
|
||||||
|
const newFiles = files.filter((file) => file.id !== fileId);
|
||||||
|
setFiles(newFiles);
|
||||||
|
},
|
||||||
|
[fileStore]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleReUploadFile = useCallback(
|
||||||
|
(fileId: string) => {
|
||||||
|
const { files, setFiles } = fileStore.getState();
|
||||||
|
const index = files.findIndex((file) => file.id === fileId);
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
const uploadingFile = files[index];
|
||||||
|
const newFiles = produce(files, (draft) => {
|
||||||
|
draft[index].progress = 0;
|
||||||
|
});
|
||||||
|
setFiles(newFiles);
|
||||||
|
fileUpload({
|
||||||
|
file: uploadingFile.originalFile!,
|
||||||
|
onProgressCallback: (progress) => {
|
||||||
|
handleUpdateFile({ ...uploadingFile, progress });
|
||||||
|
},
|
||||||
|
onSuccessCallback: (res) => {
|
||||||
|
handleUpdateFile({
|
||||||
|
...uploadingFile,
|
||||||
|
uploadedId: res.id,
|
||||||
|
progress: 100
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onErrorCallback: () => {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.uploadFromComputerUploadError')
|
||||||
|
});
|
||||||
|
handleUpdateFile({ ...uploadingFile, progress: -1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fileStore, notify, t, handleUpdateFile]
|
||||||
|
);
|
||||||
|
|
||||||
|
const startProgressTimer = useCallback(
|
||||||
|
(fileId: string) => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
const files = fileStore.getState().files;
|
||||||
|
const file = files.find((file) => file.id === fileId);
|
||||||
|
|
||||||
|
if (file && file.progress < 80 && file.progress >= 0)
|
||||||
|
handleUpdateFile({ ...file, progress: file.progress + 20 });
|
||||||
|
else clearTimeout(timer);
|
||||||
|
}, 200);
|
||||||
|
},
|
||||||
|
[fileStore, handleUpdateFile]
|
||||||
|
);
|
||||||
|
const handleLoadFileFromLink = useCallback(
|
||||||
|
(url: string) => {
|
||||||
|
const allowedFileTypes = fileConfig.allowed_file_types;
|
||||||
|
|
||||||
|
const uploadingFile = {
|
||||||
|
id: uuid4(),
|
||||||
|
name: url,
|
||||||
|
type: '',
|
||||||
|
size: 0,
|
||||||
|
progress: 0,
|
||||||
|
transferMethod: TransferMethod.remote_url,
|
||||||
|
supportFileType: '',
|
||||||
|
url,
|
||||||
|
isRemote: true
|
||||||
|
};
|
||||||
|
handleAddFile(uploadingFile);
|
||||||
|
startProgressTimer(uploadingFile.id);
|
||||||
|
|
||||||
|
uploadRemoteFileInfo(url, !!params.token)
|
||||||
|
.then((res) => {
|
||||||
|
const newFile = {
|
||||||
|
...uploadingFile,
|
||||||
|
type: res.mime_type,
|
||||||
|
size: res.size,
|
||||||
|
progress: 100,
|
||||||
|
supportFileType: getSupportFileType(
|
||||||
|
res.name,
|
||||||
|
res.mime_type,
|
||||||
|
allowedFileTypes?.includes(SupportUploadFileTypes.custom)
|
||||||
|
),
|
||||||
|
uploadedId: res.id,
|
||||||
|
url: res.url
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
!isAllowedFileExtension(
|
||||||
|
res.name,
|
||||||
|
res.mime_type,
|
||||||
|
fileConfig.allowed_file_types || [],
|
||||||
|
fileConfig.allowed_file_extensions || []
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.fileExtensionNotSupport')
|
||||||
|
});
|
||||||
|
handleRemoveFile(uploadingFile.id);
|
||||||
|
}
|
||||||
|
if (!checkSizeLimit(newFile.supportFileType, newFile.size)) handleRemoveFile(uploadingFile.id);
|
||||||
|
else handleUpdateFile(newFile);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.pasteFileLinkInvalid')
|
||||||
|
});
|
||||||
|
handleRemoveFile(uploadingFile.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[
|
||||||
|
checkSizeLimit,
|
||||||
|
handleAddFile,
|
||||||
|
handleUpdateFile,
|
||||||
|
notify,
|
||||||
|
t,
|
||||||
|
handleRemoveFile,
|
||||||
|
fileConfig?.allowed_file_types,
|
||||||
|
fileConfig.allowed_file_extensions,
|
||||||
|
startProgressTimer,
|
||||||
|
params.token
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleLoadFileFromLinkSuccess = useCallback(noop, []);
|
||||||
|
|
||||||
|
const handleLoadFileFromLinkError = useCallback(noop, []);
|
||||||
|
|
||||||
|
const handleClearFiles = useCallback(() => {
|
||||||
|
const { setFiles } = fileStore.getState();
|
||||||
|
setFiles([]);
|
||||||
|
}, [fileStore]);
|
||||||
|
|
||||||
|
const handleLocalFileUpload = useCallback(
|
||||||
|
(file: File) => {
|
||||||
|
if (
|
||||||
|
!isAllowedFileExtension(
|
||||||
|
file.name,
|
||||||
|
file.type,
|
||||||
|
fileConfig.allowed_file_types || [],
|
||||||
|
fileConfig.allowed_file_extensions || []
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.fileExtensionNotSupport')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const allowedFileTypes = fileConfig.allowed_file_types;
|
||||||
|
const fileType = getSupportFileType(
|
||||||
|
file.name,
|
||||||
|
file.type,
|
||||||
|
allowedFileTypes?.includes(SupportUploadFileTypes.custom)
|
||||||
|
);
|
||||||
|
if (!checkSizeLimit(fileType, file.size)) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
const isImage = file.type.startsWith('image');
|
||||||
|
|
||||||
|
reader.addEventListener(
|
||||||
|
'load',
|
||||||
|
() => {
|
||||||
|
const uploadingFile = {
|
||||||
|
id: uuid4(),
|
||||||
|
name: file.name,
|
||||||
|
type: file.type,
|
||||||
|
size: file.size,
|
||||||
|
progress: 0,
|
||||||
|
transferMethod: TransferMethod.local_file,
|
||||||
|
supportFileType: getSupportFileType(
|
||||||
|
file.name,
|
||||||
|
file.type,
|
||||||
|
allowedFileTypes?.includes(SupportUploadFileTypes.custom)
|
||||||
|
),
|
||||||
|
originalFile: file,
|
||||||
|
base64Url: isImage ? (reader.result as string) : ''
|
||||||
|
};
|
||||||
|
handleAddFile(uploadingFile);
|
||||||
|
fileUpload({
|
||||||
|
file: uploadingFile.originalFile,
|
||||||
|
onProgressCallback: (progress) => {
|
||||||
|
handleUpdateFile({ ...uploadingFile, progress });
|
||||||
|
},
|
||||||
|
onSuccessCallback: (res) => {
|
||||||
|
handleUpdateFile({
|
||||||
|
...uploadingFile,
|
||||||
|
uploadedId: res.id,
|
||||||
|
progress: 100
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onErrorCallback: () => {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.uploadFromComputerUploadError')
|
||||||
|
});
|
||||||
|
handleUpdateFile({ ...uploadingFile, progress: -1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
reader.addEventListener(
|
||||||
|
'error',
|
||||||
|
() => {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.fileUploader.uploadFromComputerReadError')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
checkSizeLimit,
|
||||||
|
notify,
|
||||||
|
t,
|
||||||
|
handleAddFile,
|
||||||
|
handleUpdateFile,
|
||||||
|
params.token,
|
||||||
|
fileConfig?.allowed_file_types,
|
||||||
|
fileConfig?.allowed_file_extensions
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClipboardPasteFile = useCallback(
|
||||||
|
(e: ClipboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
const file = e.clipboardData?.files[0];
|
||||||
|
const text = e.clipboardData?.getData('text/plain');
|
||||||
|
if (file && !text) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleLocalFileUpload(file);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[handleLocalFileUpload]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [isDragActive, setIsDragActive] = useState(false);
|
||||||
|
const handleDragFileEnter = useCallback((e: React.DragEvent<HTMLElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragActive(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDragFileOver = useCallback((e: React.DragEvent<HTMLElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDragFileLeave = useCallback((e: React.DragEvent<HTMLElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragActive(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDropFile = useCallback(
|
||||||
|
(e: React.DragEvent<HTMLElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragActive(false);
|
||||||
|
|
||||||
|
const file = e.dataTransfer.files[0];
|
||||||
|
|
||||||
|
if (file) handleLocalFileUpload(file);
|
||||||
|
},
|
||||||
|
[handleLocalFileUpload]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleAddFile,
|
||||||
|
handleUpdateFile,
|
||||||
|
handleRemoveFile,
|
||||||
|
handleReUploadFile,
|
||||||
|
handleLoadFileFromLink,
|
||||||
|
handleLoadFileFromLinkSuccess,
|
||||||
|
handleLoadFileFromLinkError,
|
||||||
|
handleClearFiles,
|
||||||
|
handleLocalFileUpload,
|
||||||
|
handleClipboardPasteFile,
|
||||||
|
isDragActive,
|
||||||
|
handleDragFileEnter,
|
||||||
|
handleDragFileOver,
|
||||||
|
handleDragFileLeave,
|
||||||
|
handleDropFile
|
||||||
|
};
|
||||||
|
};
|
||||||
119
src/components/base/file-uploader-in-attachment/index.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { RiLink, RiUploadCloud2Line } from '@remixicon/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useFile } from './hooks';
|
||||||
|
import type { FileEntity, FileUpload } from './types';
|
||||||
|
import FileFromLinkOrLocal from './file-from-link-or-local';
|
||||||
|
import { FileContextProvider, useStore } from './store';
|
||||||
|
import FileInput from './file-input';
|
||||||
|
import FileItem from './file-item';
|
||||||
|
import Button from '@/components/base/button';
|
||||||
|
import cn from '@/utils/classnames';
|
||||||
|
import { TransferMethod } from '@/types/app';
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
};
|
||||||
|
type FileUploaderInAttachmentProps = {
|
||||||
|
fileConfig: FileUpload;
|
||||||
|
};
|
||||||
|
const FileUploaderInAttachment = ({ fileConfig }: FileUploaderInAttachmentProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const files = useStore((s) => s.files);
|
||||||
|
const { handleRemoveFile, handleReUploadFile } = useFile(fileConfig);
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: TransferMethod.local_file,
|
||||||
|
label: t('common.fileUploader.uploadFromComputer'),
|
||||||
|
icon: <RiUploadCloud2Line className="h-4 w-4" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: TransferMethod.remote_url,
|
||||||
|
label: t('common.fileUploader.pasteFileLink'),
|
||||||
|
icon: <RiLink className="h-4 w-4" />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderButton = useCallback(
|
||||||
|
(option: Option, open?: boolean) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={option.value}
|
||||||
|
// variant='tertiary'
|
||||||
|
className={cn('relative grow', open && 'bg-components-button-tertiary-bg-hover')}
|
||||||
|
disabled={!!(fileConfig.number_limits && files.length >= fileConfig.number_limits)}
|
||||||
|
>
|
||||||
|
{option.icon}
|
||||||
|
<span className="ml-1">{option.label}</span>
|
||||||
|
{option.value === TransferMethod.local_file && <FileInput fileConfig={fileConfig} />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[fileConfig, files.length]
|
||||||
|
);
|
||||||
|
const renderTrigger = useCallback(
|
||||||
|
(option: Option) => {
|
||||||
|
return (open: boolean) => renderButton(option, open);
|
||||||
|
},
|
||||||
|
[renderButton]
|
||||||
|
);
|
||||||
|
const renderOption = useCallback(
|
||||||
|
(option: Option) => {
|
||||||
|
if (
|
||||||
|
option.value === TransferMethod.local_file &&
|
||||||
|
fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.local_file)
|
||||||
|
)
|
||||||
|
return renderButton(option);
|
||||||
|
|
||||||
|
if (
|
||||||
|
option.value === TransferMethod.remote_url &&
|
||||||
|
fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.remote_url)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<FileFromLinkOrLocal
|
||||||
|
key={option.value}
|
||||||
|
showFromLocal={false}
|
||||||
|
trigger={renderTrigger(option)}
|
||||||
|
fileConfig={fileConfig}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[renderButton, renderTrigger, fileConfig]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center space-x-1">{options.map(renderOption)}</div>
|
||||||
|
<div className="mt-1 space-y-1">
|
||||||
|
{files.map((file) => (
|
||||||
|
<FileItem
|
||||||
|
key={file.id}
|
||||||
|
file={file}
|
||||||
|
showDeleteAction
|
||||||
|
showDownloadAction={false}
|
||||||
|
onRemove={() => handleRemoveFile(file.id)}
|
||||||
|
onReUpload={() => handleReUploadFile(file.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type FileUploaderInAttachmentWrapperProps = {
|
||||||
|
value?: FileEntity[];
|
||||||
|
onChange: (files: FileEntity[]) => void;
|
||||||
|
fileConfig: FileUpload;
|
||||||
|
};
|
||||||
|
const FileUploaderInAttachmentWrapper = ({ value, onChange, fileConfig }: FileUploaderInAttachmentWrapperProps) => {
|
||||||
|
return (
|
||||||
|
<FileContextProvider value={value} onChange={onChange}>
|
||||||
|
<FileUploaderInAttachment fileConfig={fileConfig} />
|
||||||
|
</FileContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileUploaderInAttachmentWrapper;
|
||||||
45
src/components/base/file-uploader-in-attachment/store.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { createContext, useContext, useRef } from 'react';
|
||||||
|
import { create, useStore as useZustandStore } from 'zustand';
|
||||||
|
import type { FileEntity } from './types';
|
||||||
|
|
||||||
|
type Shape = {
|
||||||
|
files: FileEntity[];
|
||||||
|
setFiles: (files: FileEntity[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFileStore = (value: FileEntity[] = [], onChange?: (files: FileEntity[]) => void) => {
|
||||||
|
return create<Shape>((set) => ({
|
||||||
|
files: value ? [...value] : [],
|
||||||
|
setFiles: (files) => {
|
||||||
|
set({ files });
|
||||||
|
onChange?.(files);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
type FileStore = ReturnType<typeof createFileStore>;
|
||||||
|
export const FileContext = createContext<FileStore | null>(null);
|
||||||
|
|
||||||
|
export function useStore<T>(selector: (state: Shape) => T): T {
|
||||||
|
const store = useContext(FileContext);
|
||||||
|
if (!store) throw new Error('Missing FileContext.Provider in the tree');
|
||||||
|
|
||||||
|
return useZustandStore(store, selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFileStore = () => {
|
||||||
|
return useContext(FileContext)!;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FileProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
value?: FileEntity[];
|
||||||
|
onChange?: (files: FileEntity[]) => void;
|
||||||
|
};
|
||||||
|
export const FileContextProvider = ({ children, value, onChange }: FileProviderProps) => {
|
||||||
|
const storeRef = useRef<FileStore | undefined>(undefined);
|
||||||
|
|
||||||
|
if (!storeRef.current) storeRef.current = createFileStore(value, onChange);
|
||||||
|
|
||||||
|
return <FileContext.Provider value={storeRef.current}>{children}</FileContext.Provider>;
|
||||||
|
};
|
||||||
83
src/components/base/file-uploader-in-attachment/types.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import type { TransferMethod } from '@/types/app';
|
||||||
|
|
||||||
|
export enum FileAppearanceTypeEnum {
|
||||||
|
image = 'image',
|
||||||
|
video = 'video',
|
||||||
|
audio = 'audio',
|
||||||
|
document = 'document',
|
||||||
|
code = 'code',
|
||||||
|
pdf = 'pdf',
|
||||||
|
markdown = 'markdown',
|
||||||
|
excel = 'excel',
|
||||||
|
word = 'word',
|
||||||
|
ppt = 'ppt',
|
||||||
|
gif = 'gif',
|
||||||
|
custom = 'custom'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FileAppearanceType = keyof typeof FileAppearanceTypeEnum;
|
||||||
|
|
||||||
|
export type FileEntity = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
type: string;
|
||||||
|
progress: number;
|
||||||
|
transferMethod: TransferMethod;
|
||||||
|
supportFileType: string;
|
||||||
|
originalFile?: File;
|
||||||
|
uploadedId?: string;
|
||||||
|
base64Url?: string;
|
||||||
|
url?: string;
|
||||||
|
isRemote?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EnabledOrDisabled = {
|
||||||
|
enabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum Resolution {
|
||||||
|
low = 'low',
|
||||||
|
high = 'high'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FileUploadConfigResponse = {
|
||||||
|
batch_count_limit: number;
|
||||||
|
image_file_size_limit?: number | string; // default is 10MB
|
||||||
|
file_size_limit: number; // default is 15MB
|
||||||
|
audio_file_size_limit?: number; // default is 50MB
|
||||||
|
video_file_size_limit?: number; // default is 100MB
|
||||||
|
workflow_file_upload_limit?: number; // default is 10
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FileUpload = {
|
||||||
|
image?: EnabledOrDisabled & {
|
||||||
|
detail?: Resolution;
|
||||||
|
number_limits?: number;
|
||||||
|
transfer_methods?: TransferMethod[];
|
||||||
|
};
|
||||||
|
allowed_file_types?: string[];
|
||||||
|
allowed_file_extensions?: string[];
|
||||||
|
allowed_file_upload_methods?: TransferMethod[];
|
||||||
|
number_limits?: number;
|
||||||
|
fileUploadConfig?: FileUploadConfigResponse;
|
||||||
|
} & EnabledOrDisabled;
|
||||||
|
|
||||||
|
export enum SupportUploadFileTypes {
|
||||||
|
image = 'image',
|
||||||
|
document = 'document',
|
||||||
|
audio = 'audio',
|
||||||
|
video = 'video',
|
||||||
|
custom = 'custom'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FileResponse = {
|
||||||
|
related_id: string;
|
||||||
|
extension: string;
|
||||||
|
filename: string;
|
||||||
|
size: number;
|
||||||
|
mime_type: string;
|
||||||
|
transfer_method: TransferMethod;
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
184
src/components/base/file-uploader-in-attachment/utils.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
import mime from 'mime';
|
||||||
|
import { FileAppearanceTypeEnum, SupportUploadFileTypes } from './types';
|
||||||
|
import type { FileEntity, FileResponse } from './types';
|
||||||
|
import { FILE_EXTS } from './constants';
|
||||||
|
import { upload } from '@/service/base';
|
||||||
|
import { TransferMethod } from '@/types/app';
|
||||||
|
|
||||||
|
type FileUploadParams = {
|
||||||
|
file: File;
|
||||||
|
onProgressCallback: (progress: number) => void;
|
||||||
|
onSuccessCallback: (res: { id: string }) => void;
|
||||||
|
onErrorCallback: () => void;
|
||||||
|
};
|
||||||
|
type FileUpload = (v: FileUploadParams, isPublic?: boolean, url?: string) => void;
|
||||||
|
export const fileUpload: FileUpload = ({ file, onProgressCallback, onSuccessCallback, onErrorCallback }) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
const onProgress = (e: ProgressEvent) => {
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
const percent = Math.floor((e.loaded / e.total) * 100);
|
||||||
|
onProgressCallback(percent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
upload({
|
||||||
|
xhr: new XMLHttpRequest(),
|
||||||
|
data: formData,
|
||||||
|
onprogress: onProgress
|
||||||
|
})
|
||||||
|
.then((res: { id: string }) => {
|
||||||
|
onSuccessCallback(res);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
onErrorCallback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
|
||||||
|
let extension = '';
|
||||||
|
if (fileMimetype) extension = mime.getExtension(fileMimetype) || '';
|
||||||
|
|
||||||
|
if (fileName && !extension) {
|
||||||
|
const fileNamePair = fileName.split('.');
|
||||||
|
const fileNamePairLength = fileNamePair.length;
|
||||||
|
|
||||||
|
if (fileNamePairLength > 1) extension = fileNamePair[fileNamePairLength - 1];
|
||||||
|
else extension = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRemote) extension = '';
|
||||||
|
|
||||||
|
return extension;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileAppearanceType = (fileName: string, fileMimetype: string) => {
|
||||||
|
const extension = getFileExtension(fileName, fileMimetype);
|
||||||
|
|
||||||
|
if (extension === 'gif') return FileAppearanceTypeEnum.gif;
|
||||||
|
|
||||||
|
if (FILE_EXTS.image.includes(extension.toUpperCase())) return FileAppearanceTypeEnum.image;
|
||||||
|
|
||||||
|
if (FILE_EXTS.video.includes(extension.toUpperCase())) return FileAppearanceTypeEnum.video;
|
||||||
|
|
||||||
|
if (FILE_EXTS.audio.includes(extension.toUpperCase())) return FileAppearanceTypeEnum.audio;
|
||||||
|
|
||||||
|
if (extension === 'html') return FileAppearanceTypeEnum.code;
|
||||||
|
|
||||||
|
if (extension === 'pdf') return FileAppearanceTypeEnum.pdf;
|
||||||
|
|
||||||
|
if (extension === 'md' || extension === 'markdown' || extension === 'mdx') return FileAppearanceTypeEnum.markdown;
|
||||||
|
|
||||||
|
if (extension === 'xlsx' || extension === 'xls') return FileAppearanceTypeEnum.excel;
|
||||||
|
|
||||||
|
if (extension === 'docx' || extension === 'doc') return FileAppearanceTypeEnum.word;
|
||||||
|
|
||||||
|
if (extension === 'pptx' || extension === 'ppt') return FileAppearanceTypeEnum.ppt;
|
||||||
|
|
||||||
|
if (FILE_EXTS.document.includes(extension.toUpperCase())) return FileAppearanceTypeEnum.document;
|
||||||
|
|
||||||
|
return FileAppearanceTypeEnum.custom;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSupportFileType = (fileName: string, fileMimetype: string, isCustom?: boolean) => {
|
||||||
|
if (isCustom) return SupportUploadFileTypes.custom;
|
||||||
|
|
||||||
|
const extension = getFileExtension(fileName, fileMimetype);
|
||||||
|
for (const key in FILE_EXTS) {
|
||||||
|
if (FILE_EXTS[key].includes(extension.toUpperCase())) return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProcessedFiles = (files: FileEntity[]) => {
|
||||||
|
return files
|
||||||
|
.filter((file) => file.progress !== -1)
|
||||||
|
.map((fileItem) => ({
|
||||||
|
type: fileItem.supportFileType,
|
||||||
|
transfer_method: fileItem.transferMethod,
|
||||||
|
url: fileItem.url || '',
|
||||||
|
upload_file_id: fileItem.uploadedId || ''
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProcessedFilesFromResponse = (files: FileResponse[]) => {
|
||||||
|
return files.map((fileItem) => {
|
||||||
|
return {
|
||||||
|
id: fileItem.related_id,
|
||||||
|
name: fileItem.filename,
|
||||||
|
size: fileItem.size || 0,
|
||||||
|
type: fileItem.mime_type,
|
||||||
|
progress: 100,
|
||||||
|
transferMethod: fileItem.transfer_method,
|
||||||
|
supportFileType: fileItem.type,
|
||||||
|
uploadedId: fileItem.related_id,
|
||||||
|
url: fileItem.url
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileNameFromUrl = (url: string) => {
|
||||||
|
const urlParts = url.split('/');
|
||||||
|
return urlParts[urlParts.length - 1] || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSupportFileExtensionList = (allowFileTypes: string[], allowFileExtensions: string[]) => {
|
||||||
|
if (allowFileTypes.includes(SupportUploadFileTypes.custom))
|
||||||
|
return allowFileExtensions.map((item) => item.slice(1).toUpperCase());
|
||||||
|
|
||||||
|
return allowFileTypes.map((type) => FILE_EXTS[type]).flat();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isAllowedFileExtension = (
|
||||||
|
fileName: string,
|
||||||
|
fileMimetype: string,
|
||||||
|
allowFileTypes: string[],
|
||||||
|
allowFileExtensions: string[]
|
||||||
|
) => {
|
||||||
|
return getSupportFileExtensionList(allowFileTypes, allowFileExtensions).includes(
|
||||||
|
getFileExtension(fileName, fileMimetype).toUpperCase()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFilesInLogs = (rawData: any) => {
|
||||||
|
const result = Object.keys(rawData || {})
|
||||||
|
.map((key) => {
|
||||||
|
if (typeof rawData[key] === 'object' && rawData[key]?.dify_model_identity === '__dify__file__') {
|
||||||
|
return {
|
||||||
|
varName: key,
|
||||||
|
list: getProcessedFilesFromResponse([rawData[key]])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Array.isArray(rawData[key]) &&
|
||||||
|
rawData[key].some((item) => item?.dify_model_identity === '__dify__file__')
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
varName: key,
|
||||||
|
list: getProcessedFilesFromResponse(rawData[key])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fileIsUploaded = (file: FileEntity) => {
|
||||||
|
if (file.uploadedId) return true;
|
||||||
|
|
||||||
|
if (file.transferMethod === TransferMethod.remote_url && file.progress === 100) return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const downloadFile = (url: string, filename: string) => {
|
||||||
|
const anchor = document.createElement('a');
|
||||||
|
anchor.href = url;
|
||||||
|
anchor.download = filename;
|
||||||
|
anchor.style.display = 'none';
|
||||||
|
anchor.target = '_blank';
|
||||||
|
anchor.title = filename;
|
||||||
|
document.body.appendChild(anchor);
|
||||||
|
anchor.click();
|
||||||
|
document.body.removeChild(anchor);
|
||||||
|
};
|
||||||
31
src/components/base/icons/IconBase.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import { generate } from './utils';
|
||||||
|
import type { AbstractNode } from './utils';
|
||||||
|
|
||||||
|
export type IconData = {
|
||||||
|
name: string;
|
||||||
|
icon: AbstractNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IconBaseProps = {
|
||||||
|
data: IconData;
|
||||||
|
className?: string;
|
||||||
|
onClick?: React.MouseEventHandler<SVGElement>;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IconBase = forwardRef<React.MutableRefObject<HTMLOrSVGElement>, IconBaseProps>((props, ref) => {
|
||||||
|
const { data, className, onClick, style, ...restProps } = props;
|
||||||
|
|
||||||
|
return generate(data.icon, `svg-${data.name}`, {
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
style,
|
||||||
|
'data-icon': data.name,
|
||||||
|
'aria-hidden': 'true',
|
||||||
|
...restProps,
|
||||||
|
ref: ref
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IconBase;
|
||||||
39
src/components/base/icons/line/alert-circle/AlertCircle.json
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "17",
|
||||||
|
"viewBox": "0 0 16 17",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Error"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M7.99992 5.83337V8.50004M7.99992 11.1667H8.00659M14.6666 8.50004C14.6666 12.1819 11.6818 15.1667 7.99992 15.1667C4.31802 15.1667 1.33325 12.1819 1.33325 8.50004C1.33325 4.81814 4.31802 1.83337 7.99992 1.83337C11.6818 1.83337 14.6666 4.81814 14.6666 8.50004Z",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.5",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "AlertCircle"
|
||||||
|
}
|
||||||
15
src/components/base/icons/line/alert-circle/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './AlertCircle.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'AlertCircle';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "alert-triangle"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M7.99977 5.33314V7.99981M7.99977 10.6665H8.00644M6.85977 1.90648L1.2131 11.3331C1.09668 11.5348 1.03508 11.7633 1.03443 11.9962C1.03378 12.229 1.0941 12.4579 1.20939 12.6602C1.32468 12.8624 1.49092 13.031 1.69157 13.149C1.89223 13.2671 2.1203 13.3306 2.3531 13.3331H13.6464C13.8792 13.3306 14.1073 13.2671 14.308 13.149C14.5086 13.031 14.6749 12.8624 14.7902 12.6602C14.9054 12.4579 14.9658 12.229 14.9651 11.9962C14.9645 11.7633 14.9029 11.5348 14.7864 11.3331L9.13977 1.90648C9.02092 1.71055 8.85358 1.54856 8.6539 1.43613C8.45422 1.32371 8.22893 1.26465 7.99977 1.26465C7.77061 1.26465 7.54532 1.32371 7.34564 1.43613C7.14596 1.54856 6.97862 1.71055 6.85977 1.90648Z",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.25",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "AlertTriangle"
|
||||||
|
}
|
||||||
15
src/components/base/icons/line/alert-triangle/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './AlertTriangle.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'AlertTriangle';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
39
src/components/base/icons/line/arrows/chevron-down/data.json
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "12",
|
||||||
|
"height": "12",
|
||||||
|
"viewBox": "0 0 12 12",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "chevron-down"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M3 4.5L6 7.5L9 4.5",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.5",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "ChevronDown"
|
||||||
|
}
|
||||||
15
src/components/base/icons/line/arrows/chevron-down/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'ChevronDown';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "24",
|
||||||
|
"height": "24",
|
||||||
|
"viewBox": "0 0 24 24",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M4 14H10M10 14V20M10 14L3 21M20 10H14M14 10V4M14 10L21 3M20 14V16.8C20 17.9201 20 18.4802 19.782 18.908C19.5903 19.2843 19.2843 19.5903 18.908 19.782C18.4802 20 17.9201 20 16.8 20H14M10 4H7.2C6.0799 4 5.51984 4 5.09202 4.21799C4.71569 4.40973 4.40973 4.71569 4.21799 5.09202C4 5.51984 4 6.07989 4 7.2V10",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Collapse04"
|
||||||
|
}
|
||||||
15
src/components/base/icons/line/arrows/collapse-04/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './Collapse04.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'Collapse04';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
66
src/components/base/icons/line/check-circle/CheckCircle.json
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "14",
|
||||||
|
"height": "14",
|
||||||
|
"viewBox": "0 0 14 14",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "check-circle",
|
||||||
|
"clip-path": "url(#clip0_465_21765)"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M4.37533 6.99984L6.12533 8.74984L9.62533 5.24984M12.8337 6.99984C12.8337 10.2215 10.222 12.8332 7.00033 12.8332C3.77866 12.8332 1.16699 10.2215 1.16699 6.99984C1.16699 3.77818 3.77866 1.1665 7.00033 1.1665C10.222 1.1665 12.8337 3.77818 12.8337 6.99984Z",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.5",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "defs",
|
||||||
|
"attributes": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "clipPath",
|
||||||
|
"attributes": {
|
||||||
|
"id": "clip0_465_21765"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "rect",
|
||||||
|
"attributes": {
|
||||||
|
"width": "14",
|
||||||
|
"height": "14",
|
||||||
|
"fill": "white"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "CheckCircle"
|
||||||
|
}
|
||||||
15
src/components/base/icons/line/check-circle/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './CheckCircle.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'CheckCircle';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "14",
|
||||||
|
"height": "14",
|
||||||
|
"viewBox": "0 0 14 14",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "chevron-right"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M5.25 10.5L8.75 7L5.25 3.5",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.25",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "ChevronRight"
|
||||||
|
}
|
||||||
15
src/components/base/icons/line/chevron-right/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './ChevronRight.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'ChevronRight';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
29
src/components/base/icons/line/files/Clipboard.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "24",
|
||||||
|
"height": "24",
|
||||||
|
"viewBox": "0 0 24 24",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M16 4C16.93 4 17.395 4 17.7765 4.10222C18.8117 4.37962 19.6204 5.18827 19.8978 6.22354C20 6.60504 20 7.07003 20 8V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V8C4 7.07003 4 6.60504 4.10222 6.22354C4.37962 5.18827 5.18827 4.37962 6.22354 4.10222C6.60504 4 7.07003 4 8 4M9.6 6H14.4C14.9601 6 15.2401 6 15.454 5.89101C15.6422 5.79513 15.7951 5.64215 15.891 5.45399C16 5.24008 16 4.96005 16 4.4V3.6C16 3.03995 16 2.75992 15.891 2.54601C15.7951 2.35785 15.6422 2.20487 15.454 2.10899C15.2401 2 14.9601 2 14.4 2H9.6C9.03995 2 8.75992 2 8.54601 2.10899C8.35785 2.20487 8.20487 2.35785 8.10899 2.54601C8 2.75992 8 3.03995 8 3.6V4.4C8 4.96005 8 5.24008 8.10899 5.45399C8.20487 5.64215 8.35785 5.79513 8.54601 5.89101C8.75992 6 9.03995 6 9.6 6Z",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Clipboard"
|
||||||
|
}
|
||||||
15
src/components/base/icons/line/files/Clipboard.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './Clipboard.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'Clipboard';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
29
src/components/base/icons/line/files/ClipboardCheck.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "24",
|
||||||
|
"height": "24",
|
||||||
|
"viewBox": "0 0 24 24",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M16 4C16.93 4 17.395 4 17.7765 4.10222C18.8117 4.37962 19.6204 5.18827 19.8978 6.22354C20 6.60504 20 7.07003 20 8V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V8C4 7.07003 4 6.60504 4.10222 6.22354C4.37962 5.18827 5.18827 4.37962 6.22354 4.10222C6.60504 4 7.07003 4 8 4M9 15L11 17L15.5 12.5M9.6 6H14.4C14.9601 6 15.2401 6 15.454 5.89101C15.6422 5.79513 15.7951 5.64215 15.891 5.45399C16 5.24008 16 4.96005 16 4.4V3.6C16 3.03995 16 2.75992 15.891 2.54601C15.7951 2.35785 15.6422 2.20487 15.454 2.10899C15.2401 2 14.9601 2 14.4 2H9.6C9.03995 2 8.75992 2 8.54601 2.10899C8.35785 2.20487 8.20487 2.35785 8.10899 2.54601C8 2.75992 8 3.03995 8 3.6V4.4C8 4.96005 8 5.24008 8.10899 5.45399C8.20487 5.64215 8.35785 5.79513 8.54601 5.89101C8.75992 6 9.03995 6 9.6 6Z",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "ClipboardCheck"
|
||||||
|
}
|
||||||
15
src/components/base/icons/line/files/ClipboardCheck.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './ClipboardCheck.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'ClipboardCheck';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
2
src/components/base/icons/line/files/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as ClipboardCheck } from './ClipboardCheck';
|
||||||
|
export { default as Clipboard } from './Clipboard';
|
||||||
39
src/components/base/icons/line/image-plus/data.json
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "image-plus"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M8.33333 2.00016H5.2C4.0799 2.00016 3.51984 2.00016 3.09202 2.21815C2.71569 2.4099 2.40973 2.71586 2.21799 3.09218C2 3.52001 2 4.08006 2 5.20016V10.8002C2 11.9203 2 12.4803 2.21799 12.9081C2.40973 13.2845 2.71569 13.5904 3.09202 13.7822C3.51984 14.0002 4.07989 14.0002 5.2 14.0002H11.3333C11.9533 14.0002 12.2633 14.0002 12.5176 13.932C13.2078 13.7471 13.7469 13.208 13.9319 12.5178C14 12.2635 14 11.9535 14 11.3335M12.6667 5.3335V1.3335M10.6667 3.3335H14.6667M7 5.66683C7 6.40321 6.40305 7.00016 5.66667 7.00016C4.93029 7.00016 4.33333 6.40321 4.33333 5.66683C4.33333 4.93045 4.93029 4.3335 5.66667 4.3335C6.40305 4.3335 7 4.93045 7 5.66683ZM9.99336 7.94559L4.3541 13.0722C4.03691 13.3605 3.87831 13.5047 3.86429 13.6296C3.85213 13.7379 3.89364 13.8453 3.97546 13.9172C4.06985 14.0002 4.28419 14.0002 4.71286 14.0002H10.9707C11.9301 14.0002 12.4098 14.0002 12.7866 13.839C13.2596 13.6366 13.6365 13.2598 13.8388 12.7868C14 12.41 14 11.9303 14 10.9708C14 10.648 14 10.4866 13.9647 10.3363C13.9204 10.1474 13.8353 9.9704 13.7155 9.81776C13.6202 9.6963 13.4941 9.59546 13.242 9.3938L11.3772 7.90194C11.1249 7.7001 10.9988 7.59919 10.8599 7.56357C10.7374 7.53218 10.6086 7.53624 10.4884 7.57529C10.352 7.61959 10.2324 7.72826 9.99336 7.94559Z",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.25",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "ImagePlus"
|
||||||
|
}
|
||||||
12
src/components/base/icons/line/image-plus/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'ImagePlus';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
57
src/components/base/icons/line/link-03/data.json
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "17",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 17 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "link-03"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Solid"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"fill-rule": "evenodd",
|
||||||
|
"clip-rule": "evenodd",
|
||||||
|
"d": "M9.01569 1.83378C9.7701 1.10515 10.7805 0.701975 11.8293 0.711089C12.8781 0.720202 13.8813 1.14088 14.623 1.88251C15.3646 2.62414 15.7853 3.62739 15.7944 4.67618C15.8035 5.72497 15.4003 6.73538 14.6717 7.48979L14.6636 7.49805L12.6637 9.49796C12.2581 9.90362 11.7701 10.2173 11.2327 10.4178C10.6953 10.6183 10.1211 10.7008 9.54897 10.6598C8.97686 10.6189 8.42025 10.4553 7.91689 10.1803C7.41354 9.90531 6.97522 9.52527 6.63165 9.06596C6.41112 8.77113 6.47134 8.35334 6.76618 8.1328C7.06101 7.91226 7.4788 7.97249 7.69934 8.26732C7.92838 8.57353 8.2206 8.82689 8.55617 9.01023C8.89174 9.19356 9.26281 9.30259 9.64422 9.3299C10.0256 9.35722 10.4085 9.30219 10.7667 9.16854C11.125 9.0349 11.4503 8.82576 11.7207 8.55532L13.7164 6.55956C14.1998 6.05705 14.4672 5.38513 14.4611 4.68777C14.455 3.98857 14.1746 3.31974 13.6802 2.82532C13.1857 2.3309 12.5169 2.05045 11.8177 2.04437C11.12 2.03831 10.4478 2.30591 9.94526 2.78967L8.80219 3.92609C8.54108 4.18568 8.11898 4.18445 7.85939 3.92334C7.5998 3.66223 7.60103 3.24012 7.86214 2.98053L9.0088 1.84053L9.01569 1.83378Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"fill-rule": "evenodd",
|
||||||
|
"clip-rule": "evenodd",
|
||||||
|
"d": "M5.76493 5.58217C6.30234 5.3817 6.87657 5.29915 7.44869 5.34012C8.0208 5.3811 8.57741 5.54463 9.08077 5.81964C9.58412 6.09465 10.0224 6.47469 10.366 6.93399C10.5865 7.22882 10.5263 7.64662 10.2315 7.86715C9.93665 8.08769 9.51886 8.02746 9.29832 7.73263C9.06928 7.42643 8.77706 7.17307 8.44149 6.98973C8.10592 6.80639 7.73485 6.69737 7.35344 6.67005C6.97203 6.64274 6.58921 6.69777 6.23094 6.83141C5.87266 6.96506 5.54733 7.17419 5.27699 7.44463L3.28123 9.44039C2.79787 9.94291 2.5305 10.6148 2.53656 11.3122C2.54263 12.0114 2.82309 12.6802 3.31751 13.1746C3.81193 13.6691 4.48076 13.9495 5.17995 13.9556C5.87732 13.9616 6.54923 13.6943 7.05174 13.2109L8.18743 12.0752C8.44777 11.8149 8.86988 11.8149 9.13023 12.0752C9.39058 12.3356 9.39058 12.7577 9.13023 13.018L7.99023 14.158L7.98197 14.1662C7.22756 14.8948 6.21715 15.298 5.16837 15.2889C4.11958 15.2798 3.11633 14.8591 2.3747 14.1174C1.63307 13.3758 1.21239 12.3726 1.20328 11.3238C1.19416 10.275 1.59734 9.26458 2.32597 8.51017L2.33409 8.50191L4.33401 6.50199C4.33398 6.50202 4.33404 6.50196 4.33401 6.50199C4.7395 6.09638 5.22756 5.78262 5.76493 5.58217Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Link03"
|
||||||
|
}
|
||||||
12
src/components/base/icons/line/link-03/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'Link03';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
64
src/components/base/icons/line/loading-02/data.json
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"clip-path": "url(#clip0_6037_51601)"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M7.99992 1.33398V4.00065M7.99992 12.0007V14.6673M3.99992 8.00065H1.33325M14.6666 8.00065H11.9999M12.7189 12.7196L10.8333 10.834M12.7189 3.33395L10.8333 5.21956M3.28097 12.7196L5.16659 10.834M3.28097 3.33395L5.16659 5.21956",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.25",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "defs",
|
||||||
|
"attributes": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "clipPath",
|
||||||
|
"attributes": {
|
||||||
|
"id": "clip0_6037_51601"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "rect",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"fill": "white"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Loading02"
|
||||||
|
}
|
||||||
12
src/components/base/icons/line/loading-02/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'Loading02';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
29
src/components/base/icons/line/refresh-ccw-01/data.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "24",
|
||||||
|
"height": "24",
|
||||||
|
"viewBox": "0 0 24 24",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M2 10C2 10 4.00498 7.26822 5.63384 5.63824C7.26269 4.00827 9.5136 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.89691 21 4.43511 18.2543 3.35177 14.5M2 10V4M2 10H8",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "RefreshCcw01"
|
||||||
|
}
|
||||||
12
src/components/base/icons/line/refresh-ccw-01/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'RefreshCcw01';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
66
src/components/base/icons/line/upload-03/data.json
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Left Icon",
|
||||||
|
"clip-path": "url(#clip0_12728_40636)"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M10.6654 8.00016L7.9987 5.3335M7.9987 5.3335L5.33203 8.00016M7.9987 5.3335V10.6668M14.6654 8.00016C14.6654 11.6821 11.6806 14.6668 7.9987 14.6668C4.3168 14.6668 1.33203 11.6821 1.33203 8.00016C1.33203 4.31826 4.3168 1.3335 7.9987 1.3335C11.6806 1.3335 14.6654 4.31826 14.6654 8.00016Z",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.5",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "defs",
|
||||||
|
"attributes": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "clipPath",
|
||||||
|
"attributes": {
|
||||||
|
"id": "clip0_12728_40636"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "rect",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"fill": "white"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Upload03"
|
||||||
|
}
|
||||||
12
src/components/base/icons/line/upload-03/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'Upload03';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
39
src/components/base/icons/line/x-close/data.json
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "x-close"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M12 4L4 12M4 4L12 12",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.25",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "XClose"
|
||||||
|
}
|
||||||
12
src/components/base/icons/line/x-close/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'XClose';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
36
src/components/base/icons/other/ReplayLine.json
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "20",
|
||||||
|
"height": "20",
|
||||||
|
"viewBox": "0 0 20 20",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Retry"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector",
|
||||||
|
"d": "M9.99996 1.66669C14.6023 1.66669 18.3333 5.39765 18.3333 10C18.3333 14.6024 14.6023 18.3334 9.99996 18.3334C5.39758 18.3334 1.66663 14.6024 1.66663 10H3.33329C3.33329 13.6819 6.31806 16.6667 9.99996 16.6667C13.6819 16.6667 16.6666 13.6819 16.6666 10C16.6666 6.31812 13.6819 3.33335 9.99996 3.33335C7.70848 3.33335 5.68702 4.48947 4.48705 6.25022L6.66663 6.25002V7.91669H1.66663V2.91669H3.33329L3.3332 4.99934C4.85358 2.97565 7.2739 1.66669 9.99996 1.66669Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "ReplayLine"
|
||||||
|
}
|
||||||
18
src/components/base/icons/other/ReplayLine.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './ReplayLine.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = ({
|
||||||
|
ref,
|
||||||
|
...props
|
||||||
|
}: React.SVGProps<SVGSVGElement> & {
|
||||||
|
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
|
||||||
|
}) => <IconBase {...props} ref={ref} data={data as IconData} />;
|
||||||
|
|
||||||
|
Icon.displayName = 'ReplayLine';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
64
src/components/base/icons/public/data-set/data.json
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "12",
|
||||||
|
"height": "12",
|
||||||
|
"viewBox": "0 0 12 12",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"clip-path": "url(#clip0_7847_32895)"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M10.5 2.5C10.5 3.32843 8.48528 4 6 4C3.51472 4 1.5 3.32843 1.5 2.5M10.5 2.5C10.5 1.67157 8.48528 1 6 1C3.51472 1 1.5 1.67157 1.5 2.5M10.5 2.5V9.5C10.5 10.33 8.5 11 6 11C3.5 11 1.5 10.33 1.5 9.5V2.5M10.5 6C10.5 6.83 8.5 7.5 6 7.5C3.5 7.5 1.5 6.83 1.5 6",
|
||||||
|
"stroke": "#667085",
|
||||||
|
"stroke-width": "1.25",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "defs",
|
||||||
|
"attributes": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "clipPath",
|
||||||
|
"attributes": {
|
||||||
|
"id": "clip0_7847_32895"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "rect",
|
||||||
|
"attributes": {
|
||||||
|
"width": "12",
|
||||||
|
"height": "12",
|
||||||
|
"fill": "white"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "DataSet"
|
||||||
|
}
|
||||||
15
src/components/base/icons/public/data-set/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'DataSet';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "alert-circle"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Solid",
|
||||||
|
"fill-rule": "evenodd",
|
||||||
|
"clip-rule": "evenodd",
|
||||||
|
"d": "M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM8.66667 5.33329C8.66667 4.9651 8.36819 4.66663 8 4.66663C7.63181 4.66663 7.33334 4.9651 7.33334 5.33329V7.99996C7.33334 8.36815 7.63181 8.66663 8 8.66663C8.36819 8.66663 8.66667 8.36815 8.66667 7.99996V5.33329ZM8 9.99996C7.63181 9.99996 7.33334 10.2984 7.33334 10.6666C7.33334 11.0348 7.63181 11.3333 8 11.3333H8.00667C8.37486 11.3333 8.67334 11.0348 8.67334 10.6666C8.67334 10.2984 8.37486 9.99996 8.00667 9.99996H8Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "AlertCircle"
|
||||||
|
}
|
||||||
15
src/components/base/icons/solid/alert-circle/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './AlertCircle.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'AlertCircle';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
38
src/components/base/icons/solid/alert-triangle/data.json
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "12",
|
||||||
|
"height": "12",
|
||||||
|
"viewBox": "0 0 12 12",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "alert-triangle"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Solid",
|
||||||
|
"fill-rule": "evenodd",
|
||||||
|
"clip-rule": "evenodd",
|
||||||
|
"d": "M6.40616 0.834185C6.14751 0.719172 5.85222 0.719172 5.59356 0.834185C5.3938 0.923011 5.26403 1.07947 5.17373 1.20696C5.08495 1.3323 4.9899 1.49651 4.88536 1.67711L0.751783 8.81693C0.646828 8.99818 0.551451 9.16289 0.486781 9.30268C0.421056 9.44475 0.349754 9.63572 0.372478 9.85369C0.401884 10.1357 0.549654 10.392 0.779012 10.5588C0.956259 10.6877 1.15726 10.7217 1.31314 10.736C1.46651 10.75 1.65684 10.75 1.86628 10.75H10.1334C10.3429 10.75 10.5332 10.75 10.6866 10.736C10.8425 10.7217 11.0435 10.6877 11.2207 10.5588C11.4501 10.392 11.5978 10.1357 11.6272 9.85369C11.65 9.63572 11.5787 9.44475 11.5129 9.30268C11.4483 9.1629 11.3529 8.9982 11.248 8.81697L7.11436 1.67709C7.00983 1.49651 6.91477 1.3323 6.82599 1.20696C6.73569 1.07947 6.60593 0.923011 6.40616 0.834185ZM6.49988 4.5C6.49988 4.22386 6.27602 4 5.99988 4C5.72374 4 5.49988 4.22386 5.49988 4.5V6.5C5.49988 6.77614 5.72374 7 5.99988 7C6.27602 7 6.49988 6.77614 6.49988 6.5V4.5ZM5.99988 8C5.72374 8 5.49988 8.22386 5.49988 8.5C5.49988 8.77614 5.72374 9 5.99988 9H6.00488C6.28102 9 6.50488 8.77614 6.50488 8.5C6.50488 8.22386 6.28102 8 6.00488 8H5.99988Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "AlertTriangle"
|
||||||
|
}
|
||||||
12
src/components/base/icons/solid/alert-triangle/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'AlertTriangle';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
39
src/components/base/icons/solid/expand-04/Expand04.json
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "14",
|
||||||
|
"height": "15",
|
||||||
|
"viewBox": "0 0 14 15",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon_2",
|
||||||
|
"d": "M11.6667 8.66667V10.3C11.6667 10.9534 11.6667 11.2801 11.5395 11.5297C11.4277 11.7492 11.2492 11.9277 11.0297 12.0395C10.7801 12.1667 10.4534 12.1667 9.8 12.1667H8.16667M5.83333 2.83333H4.2C3.54661 2.83333 3.21991 2.83333 2.97034 2.96049C2.75082 3.07234 2.57234 3.25082 2.46049 3.47034C2.33333 3.71991 2.33333 4.04661 2.33333 4.7V6.33333M8.75 5.75L12.25 2.25M12.25 2.25H8.75M12.25 2.25V5.75M5.25 9.25L1.75 12.75M1.75 12.75H5.25M1.75 12.75L1.75 9.25",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.25",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Expand04"
|
||||||
|
}
|
||||||
15
src/components/base/icons/solid/expand-04/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './Expand04.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'Expand04';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "check-circle"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Solid",
|
||||||
|
"fill-rule": "evenodd",
|
||||||
|
"clip-rule": "evenodd",
|
||||||
|
"d": "M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM11.4714 6.47136C11.7318 6.21101 11.7318 5.7889 11.4714 5.52855C11.2111 5.26821 10.7889 5.26821 10.5286 5.52855L7 9.05715L5.47141 7.52855C5.21106 7.2682 4.78895 7.2682 4.5286 7.52855C4.26825 7.7889 4.26825 8.21101 4.5286 8.47136L6.5286 10.4714C6.78895 10.7317 7.21106 10.7317 7.47141 10.4714L11.4714 6.47136Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "CheckCircle"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import data from './data.json';
|
||||||
|
import IconBase from '@/components/base/icons/IconBase';
|
||||||
|
import type { IconBaseProps, IconData } from '@/components/base/icons/IconBase';
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((props, ref) => (
|
||||||
|
<IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
));
|
||||||
|
|
||||||
|
Icon.displayName = 'CheckCircle';
|
||||||
|
|
||||||
|
export default Icon;
|
||||||