This commit is contained in:
unknown 2025-08-19 16:42:50 +07:00
parent 5e4525e979
commit e63f0bc7a7
242 changed files with 22660 additions and 5953 deletions

21
next.config.js Normal file
View 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;

View file

@ -1,7 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;

5862
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,25 +3,75 @@
"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",
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@monaco-editor/react": "^4.6.0",
"@remixicon/react": "^4.6.0",
"@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "^2.55.0",
"@tailwindcss/line-clamp": "^0.4.2",
"@types/react-syntax-highlighter": "^15.5.6",
"ahooks": "^3.7.5",
"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": { "devDependencies": {
"typescript": "^5", "@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4", "@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", "tailwindcss": "^4",
"eslint": "^9", "typescript": "^4.9.5"
"eslint-config-next": "15.4.6",
"@eslint/eslintrc": "^3"
} }
} }

View file

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

View file

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

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

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

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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

3
public/icons/youtube.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View file

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

View file

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

View file

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

View 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);
}

View 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);
}

View 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
});
}
}

View 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);
}
}

View 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);
}

View 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)
});
}

View 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([]);
}
}

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

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

@ -0,0 +1,2 @@
declare module 'dify-client';
declare module 'uuid';

View file

@ -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;
}

View file

@ -1,33 +1,31 @@
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
View 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
View 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>
);
}

View 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);
}
}

View file

@ -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
View 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('/');
}

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

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

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

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

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

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

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

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

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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);

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

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

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

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

View 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);
};

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

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

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

View 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": "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"
}

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

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

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

View 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": "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"
}

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

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

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

View file

@ -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"
}

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

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

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

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

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

View file

@ -0,0 +1,2 @@
export { default as ClipboardCheck } from './ClipboardCheck';
export { default as Clipboard } from './Clipboard';

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View file

@ -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"
}

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

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

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

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

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

View file

@ -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"
}

View 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 = 'CheckCircle';
export default Icon;

Some files were not shown because too many files have changed in this diff Show more