New file |
| | |
| | | # EditorConfig is awesome: https://EditorConfig.org |
| | | |
| | | # top-most EditorConfig file |
| | | root = true |
| | | |
| | | # Unix-style newlines with a newline ending every file |
| | | [*] |
| | | end_of_line = lf |
| | | insert_final_newline = true |
| | | |
| | | # Matches multiple files with brace expansion notation |
| | | # Set default charset |
| | | [*.{js,tsx}] |
| | | charset = utf-8 |
| | | indent_style = space |
| | | indent_size = 2 |
| | | |
| | | |
| | | # Matches the exact files either package.json or .travis.yml |
| | | [{package.json,.travis.yml}] |
| | | indent_style = space |
| | | indent_size = 2 |
| | |
| | | # different from api or web app domain. |
| | | # example: http://cloud.dify.ai/console/api |
| | | NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api |
| | | NEXT_PUBLIC_WEB_PREFIX=http://localhost:3000 |
| | | # The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from |
| | | # console or api domain. |
| | | # example: http://udify.app/api |
| | | NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api |
| | | NEXT_PUBLIC_PUBLIC_WEB_PREFIX=http://localhost:3000 |
| | | # The API PREFIX for MARKETPLACE |
| | | NEXT_PUBLIC_MARKETPLACE_API_PREFIX=https://marketplace.dify.ai/api/v1 |
| | | # The URL for MARKETPLACE |
| | | NEXT_PUBLIC_MARKETPLACE_URL_PREFIX=https://marketplace.dify.ai |
| | | |
| | | # SENTRY |
| | | NEXT_PUBLIC_SENTRY_DSN= |
| | |
| | | |
| | | # CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP |
| | | NEXT_PUBLIC_CSP_WHITELIST= |
| | | # Default is not allow to embed into iframe to prevent Clickjacking: https://owasp.org/www-community/attacks/Clickjacking |
| | | NEXT_PUBLIC_ALLOW_EMBED= |
| | | |
| | | # Github Access Token, used for invoking Github API |
| | | NEXT_PUBLIC_GITHUB_ACCESS_TOKEN= |
| | | # The maximum number of top-k value for RAG. |
| | | NEXT_PUBLIC_TOP_K_MAX_VALUE=10 |
| | | |
| | | # The maximum number of tokens for segmentation |
| | | NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000 |
| | | |
| | | # Maximum loop count in the workflow |
| | | NEXT_PUBLIC_LOOP_NODE_MAX_COUNT=100 |
| | | |
| | | # Maximum number of tools in the agent/workflow |
| | | NEXT_PUBLIC_MAX_TOOLS_NUM=10 |
| | | |
| | | # Maximum number of Parallelism branches in the workflow |
| | | NEXT_PUBLIC_MAX_PARALLEL_LIMIT=10 |
| | | |
| | | # The maximum number of iterations for agent setting |
| | | NEXT_PUBLIC_MAX_ITERATIONS_NUM=5 |
| | | |
| | | NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER=true |
| | | NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL=true |
| | | NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL=true |
| | | |
New file |
| | |
| | | /**/node_modules/* |
| | | node_modules/ |
| | | |
| | | dist/ |
| | | build/ |
| | | out/ |
| | | .next/ |
New file |
| | |
| | | { |
| | | "extends": [ |
| | | "next", |
| | | "@antfu", |
| | | "plugin:storybook/recommended" |
| | | ], |
| | | "rules": { |
| | | "@typescript-eslint/consistent-type-definitions": [ |
| | | "error", |
| | | "type" |
| | | ], |
| | | "@typescript-eslint/no-var-requires": "off", |
| | | "no-console": "off", |
| | | "indent": "off", |
| | | "@typescript-eslint/indent": [ |
| | | "error", |
| | | 2, |
| | | { |
| | | "SwitchCase": 1, |
| | | "flatTernaryExpressions": false, |
| | | "ignoredNodes": [ |
| | | "PropertyDefinition[decorators]", |
| | | "TSUnionType", |
| | | "FunctionExpression[params]:has(Identifier[decorators])" |
| | | ] |
| | | } |
| | | ], |
| | | "react-hooks/exhaustive-deps": "warn", |
| | | "react/display-name": "warn" |
| | | } |
| | | } |
| | |
| | | .pnp.cjs |
| | | .pnp.loader.mjs |
| | | .yarn/ |
| | | .yarnrc.yml |
| | | |
| | | # pmpm |
| | | pnpm-lock.yaml |
| | | |
| | | .favorites.json |
| | | |
| | | # storybook |
| | | /storybook-static |
| | | *storybook.log |
| | | |
| | | # mise |
| | |
| | | #!/bin/sh |
| | | #!/usr/bin/env bash |
| | | . "$(dirname -- "$0")/_/husky.sh" |
| | | |
| | | # get the list of modified files |
| | | files=$(git diff --cached --name-only) |
| | | |
| | |
| | | |
| | | for file in $files |
| | | do |
| | | # Use POSIX compliant pattern matching |
| | | case "$file" in |
| | | api/*.py) |
| | | if [[ $file == "api/"* && $file == *.py ]]; then |
| | | # set api_modified flag to true |
| | | api_modified=true |
| | | ;; |
| | | web/*) |
| | | elif [[ $file == "web/"* ]]; then |
| | | # set web_modified flag to true |
| | | web_modified=true |
| | | ;; |
| | | esac |
| | | fi |
| | | done |
| | | |
| | | # run linters based on the modified modules |
| | |
| | | if $api_modified; then |
| | | echo "Running Ruff linter on api module" |
| | | |
| | | # python style checks rely on `ruff` in path |
| | | if ! command -v ruff &> /dev/null; then |
| | | echo "Installing linting tools (Ruff, dotenv-linter ...) ..." |
| | | poetry install -C api --only lint |
| | | fi |
| | | |
| | | # run Ruff linter auto-fixing |
| | | uv run --project api --dev ruff check --fix ./api |
| | | ruff check --fix ./api |
| | | |
| | | # run Ruff linter checks |
| | | uv run --project api --dev ruff check ./api || status=$? |
| | | ruff check --preview ./api || status=$? |
| | | |
| | | status=${status:-0} |
| | | |
| | |
| | | if $web_modified; then |
| | | echo "Running ESLint on web module" |
| | | cd ./web || exit 1 |
| | | lint-staged |
| | | npx lint-staged |
| | | |
| | | echo "Running unit tests check" |
| | | modified_files=$(git diff --cached --name-only -- utils | grep -v '\.spec\.ts$' || true) |
| | |
| | | # check if the test file exists |
| | | if [ -f "../$test_file" ]; then |
| | | echo "Detected changes in $file, running corresponding unit tests..." |
| | | pnpm run test "../$test_file" |
| | | npm run test "../$test_file" |
| | | |
| | | if [ $? -ne 0 ]; then |
| | | echo "Unit tests failed. Please fix the errors before committing." |
New file |
| | |
| | | # Default ignored files |
| | | /shelf/ |
| | | /workspace.xml |
| | | # Editor-based HTTP Client requests |
| | | /httpRequests/ |
| | | # Datasource local storage ignored files |
| | | /dataSources/ |
| | | /dataSources.local.xml |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="ProjectModuleManager"> |
| | | <modules> |
| | | <module fileurl="file://$PROJECT_DIR$/.idea/web.iml" filepath="$PROJECT_DIR$/.idea/web.iml" /> |
| | | </modules> |
| | | </component> |
| | | </project> |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="VcsDirectoryMappings"> |
| | | <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> |
| | | </component> |
| | | </project> |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <module type="WEB_MODULE" version="4"> |
| | | <component name="NewModuleRootManager"> |
| | | <content url="file://$MODULE_DIR$"> |
| | | <excludeFolder url="file://$MODULE_DIR$/.tmp" /> |
| | | <excludeFolder url="file://$MODULE_DIR$/temp" /> |
| | | <excludeFolder url="file://$MODULE_DIR$/tmp" /> |
| | | </content> |
| | | <orderEntry type="inheritedJdk" /> |
| | | <orderEntry type="sourceFolder" forTests="false" /> |
| | | </component> |
| | | </module> |
| | |
| | | import React from 'react' |
| | | import type { Preview } from '@storybook/react' |
| | | import { withThemeByDataAttribute } from '@storybook/addon-themes' |
| | | import { withThemeByDataAttribute } from '@storybook/addon-themes'; |
| | | import I18nServer from '../app/components/i18n-server' |
| | | |
| | | import '../app/styles/globals.css' |
| | |
| | | defaultTheme: 'light', |
| | | attributeName: 'data-theme', |
| | | }), |
| | | (Story) => { |
| | | Story => { |
| | | return <I18nServer> |
| | | <Story /> |
| | | </I18nServer> |
| | | }, |
| | | ] |
| | | } |
| | | ]; |
| | | |
| | | const preview: Preview = { |
| | | parameters: { |
| | |
| | | { |
| | | "recommendations": [ |
| | | "bradlc.vscode-tailwindcss", |
| | | "firsttris.vscode-jest-runner", |
| | | "kisstkondoros.vscode-codemetrics" |
| | | "firsttris.vscode-jest-runner" |
| | | ] |
| | | } |
| | |
| | | "editor.defaultFormatter": "vscode.json-language-features" |
| | | }, |
| | | "typescript.tsdk": "node_modules/typescript/lib", |
| | | "typescript.enablePromptUseWorkspaceTsdk": true, |
| | | "npm.packageManager": "pnpm" |
| | | "typescript.enablePromptUseWorkspaceTsdk": true |
| | | } |
| | |
| | | # base image |
| | | FROM node:22-alpine3.21 AS base |
| | | FROM node:20-alpine3.20 AS base |
| | | LABEL maintainer="takatost@gmail.com" |
| | | |
| | | # if you located in China, you can use aliyun mirror to speed up |
| | | # RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories |
| | | |
| | | RUN apk add --no-cache tzdata |
| | | RUN npm install -g pnpm@10.8.0 |
| | | ENV PNPM_HOME="/pnpm" |
| | | ENV PATH="$PNPM_HOME:$PATH" |
| | | |
| | | |
| | | # install packages |
| | |
| | | WORKDIR /app/web |
| | | |
| | | COPY package.json . |
| | | COPY pnpm-lock.yaml . |
| | | COPY yarn.lock . |
| | | |
| | | # if you located in China, you can use taobao registry to speed up |
| | | # RUN pnpm install --frozen-lockfile --registry https://registry.npmmirror.com/ |
| | | # RUN yarn install --frozen-lockfile --registry https://registry.npmmirror.com/ |
| | | |
| | | RUN pnpm install --frozen-lockfile |
| | | RUN yarn install --frozen-lockfile |
| | | |
| | | # build resources |
| | | FROM base AS builder |
| | |
| | | COPY --from=packages /app/web/ . |
| | | COPY . . |
| | | |
| | | ENV NODE_OPTIONS="--max-old-space-size=4096" |
| | | RUN pnpm build |
| | | RUN yarn build |
| | | |
| | | |
| | | # production stage |
| | |
| | | ENV DEPLOY_ENV=PRODUCTION |
| | | ENV CONSOLE_API_URL=http://127.0.0.1:5001 |
| | | ENV APP_API_URL=http://127.0.0.1:5001 |
| | | ENV MARKETPLACE_API_URL=https://marketplace.dify.ai |
| | | ENV MARKETPLACE_URL=https://marketplace.dify.ai |
| | | ENV PORT=3000 |
| | | ENV NEXT_TELEMETRY_DISABLED=1 |
| | | ENV PM2_INSTANCES=2 |
| | | |
| | | # set timezone |
| | | ENV TZ=UTC |
| | |
| | | COPY --from=builder /app/web/.next/standalone ./ |
| | | COPY --from=builder /app/web/.next/static ./.next/static |
| | | |
| | | COPY docker/pm2.json ./pm2.json |
| | | COPY docker/entrypoint.sh ./entrypoint.sh |
| | | |
| | | |
| | | # global runtime packages |
| | | RUN pnpm add -g pm2 \ |
| | | RUN yarn global add pm2 \ |
| | | && yarn cache clean \ |
| | | && mkdir /.pm2 \ |
| | | && chown -R 1001:0 /.pm2 /app/web \ |
| | | && chmod -R g=u /.pm2 /app/web |
| | |
| | | |
| | | ### Run by source code |
| | | |
| | | Before starting the web frontend service, please make sure the following environment is ready. |
| | | - [Node.js](https://nodejs.org) >= v22.11.x |
| | | - [pnpm](https://pnpm.io) v10.x |
| | | To start the web frontend service, you will need [Node.js v18.x (LTS)](https://nodejs.org/en) and [NPM version 8.x.x](https://www.npmjs.com/) or [Yarn](https://yarnpkg.com/). |
| | | |
| | | First, install the dependencies: |
| | | |
| | | ```bash |
| | | pnpm install |
| | | npm install |
| | | # or |
| | | yarn install --frozen-lockfile |
| | | ``` |
| | | |
| | | Then, configure the environment variables. Create a file named `.env.local` in the current directory and copy the contents from `.env.example`. Modify the values of these environment variables according to your requirements: |
| | |
| | | # different from api or web app domain. |
| | | # example: http://cloud.dify.ai/console/api |
| | | NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api |
| | | NEXT_PUBLIC_WEB_PREFIX=http://localhost:3000 |
| | | # The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from |
| | | # console or api domain. |
| | | # example: http://udify.app/api |
| | | NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api |
| | | NEXT_PUBLIC_PUBLIC_WEB_PREFIX=http://localhost:3000 |
| | | |
| | | # SENTRY |
| | | NEXT_PUBLIC_SENTRY_DSN= |
| | |
| | | Finally, run the development server: |
| | | |
| | | ```bash |
| | | pnpm run dev |
| | | npm run dev |
| | | # or |
| | | yarn dev |
| | | ``` |
| | | |
| | | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. |
| | |
| | | First, build the app for production: |
| | | |
| | | ```bash |
| | | pnpm run build |
| | | npm run build |
| | | ``` |
| | | |
| | | Then, start the server: |
| | | |
| | | ```bash |
| | | pnpm run start |
| | | npm run start |
| | | ``` |
| | | |
| | | If you want to customize the host and port: |
| | | |
| | | ```bash |
| | | pnpm run start --port=3001 --host=0.0.0.0 |
| | | npm run start --port=3001 --host=0.0.0.0 |
| | | ``` |
| | | |
| | | If you want to customize the number of instances launched by PM2, you can configure `PM2_INSTANCES` in `docker-compose.yaml` or `Dockerfile`. |
| | | |
| | | ## Storybook |
| | | |
| | |
| | | To start the storybook server, run: |
| | | |
| | | ```bash |
| | | pnpm storybook |
| | | yarn storybook |
| | | ``` |
| | | |
| | | Open [http://localhost:6006](http://localhost:6006) with your browser to see the result. |
| | |
| | | Run test: |
| | | |
| | | ```bash |
| | | pnpm run test |
| | | npm run test |
| | | ``` |
| | | |
| | | If you are not familiar with writing tests, here is some code to refer to: |
| | |
| | | import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type' |
| | | |
| | | export type IProps = { |
| | | params: Promise<{ appId: string }> |
| | | params: { appId: string } |
| | | } |
| | | |
| | | const Logs = async () => { |
| | |
| | | import React from 'react' |
| | | import type { Locale } from '@/i18n' |
| | | import { type Locale } from '@/i18n' |
| | | import DevelopMain from '@/app/components/develop' |
| | | |
| | | export type IDevelopProps = { |
| | | params: Promise<{ locale: Locale; appId: string }> |
| | | params: { locale: Locale; appId: string } |
| | | } |
| | | |
| | | const Develop = async (props: IDevelopProps) => { |
| | | const params = await props.params |
| | | |
| | | const { |
| | | appId, |
| | | } = params |
| | | |
| | | const Develop = async ({ |
| | | params: { appId }, |
| | | }: IDevelopProps) => { |
| | | return <DevelopMain appId={appId} /> |
| | | } |
| | | |
| | |
| | | import Main from './layout-main' |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import { useUnmount } from 'ahooks' |
| | | import React, { useCallback, useEffect, useState } from 'react' |
| | | import { usePathname, useRouter } from 'next/navigation' |
| | | import { |
| | | RiDashboard2Fill, |
| | | RiDashboard2Line, |
| | | RiFileList3Fill, |
| | | RiFileList3Line, |
| | | RiTerminalBoxFill, |
| | | RiTerminalBoxLine, |
| | | RiTerminalWindowFill, |
| | | RiTerminalWindowLine, |
| | | } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useShallow } from 'zustand/react/shallow' |
| | | import { useContextSelector } from 'use-context-selector' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import { useStore } from '@/app/components/app/store' |
| | | import AppSideBar from '@/app/components/app-sidebar' |
| | | import type { NavIcon } from '@/app/components/app-sidebar/navLink' |
| | | import { fetchAppDetail, fetchAppSSO } from '@/service/apps' |
| | | import AppContext, { useAppContext } from '@/context/app-context' |
| | | import Loading from '@/app/components/base/loading' |
| | | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' |
| | | import type { App } from '@/types/app' |
| | | |
| | | const AppDetailLayout = async (props: { |
| | | export type IAppDetailLayoutProps = { |
| | | children: React.ReactNode |
| | | params: Promise<{ appId: string }> |
| | | }) => { |
| | | params: { appId: string } |
| | | } |
| | | |
| | | const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => { |
| | | const { |
| | | children, |
| | | params, |
| | | params: { appId }, // get appId in path |
| | | } = props |
| | | const { t } = useTranslation() |
| | | const router = useRouter() |
| | | const pathname = usePathname() |
| | | const media = useBreakpoints() |
| | | const isMobile = media === MediaType.mobile |
| | | const { isCurrentWorkspaceEditor, isLoadingCurrentWorkspace } = useAppContext() |
| | | const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({ |
| | | appDetail: state.appDetail, |
| | | setAppDetail: state.setAppDetail, |
| | | setAppSiderbarExpand: state.setAppSiderbarExpand, |
| | | }))) |
| | | const [isLoadingAppDetail, setIsLoadingAppDetail] = useState(false) |
| | | const [appDetailRes, setAppDetailRes] = useState<App | null>(null) |
| | | const [navigation, setNavigation] = useState<Array<{ |
| | | name: string |
| | | href: string |
| | | icon: NavIcon |
| | | selectedIcon: NavIcon |
| | | }>>([]) |
| | | const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) |
| | | |
| | | return <Main appId={(await params).appId}>{children}</Main> |
| | | const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => { |
| | | const navs = [ |
| | | ...(isCurrentWorkspaceEditor |
| | | ? [{ |
| | | name: t('common.appMenus.promptEng'), |
| | | href: `/app/${appId}/${(mode === 'workflow' || mode === 'advanced-chat') ? 'workflow' : 'configuration'}`, |
| | | icon: RiTerminalWindowLine, |
| | | selectedIcon: RiTerminalWindowFill, |
| | | }] |
| | | : [] |
| | | ), |
| | | { |
| | | name: t('common.appMenus.apiAccess'), |
| | | href: `/app/${appId}/develop`, |
| | | icon: RiTerminalBoxLine, |
| | | selectedIcon: RiTerminalBoxFill, |
| | | }, |
| | | ...(isCurrentWorkspaceEditor |
| | | ? [{ |
| | | name: mode !== 'workflow' |
| | | ? t('common.appMenus.logAndAnn') |
| | | : t('common.appMenus.logs'), |
| | | href: `/app/${appId}/logs`, |
| | | icon: RiFileList3Line, |
| | | selectedIcon: RiFileList3Fill, |
| | | }] |
| | | : [] |
| | | ), |
| | | { |
| | | name: t('common.appMenus.overview'), |
| | | href: `/app/${appId}/overview`, |
| | | icon: RiDashboard2Line, |
| | | selectedIcon: RiDashboard2Fill, |
| | | }, |
| | | ] |
| | | return navs |
| | | }, [t]) |
| | | |
| | | useEffect(() => { |
| | | if (appDetail) { |
| | | document.title = `${(appDetail.name || 'App')} - Dify` |
| | | const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' |
| | | const mode = isMobile ? 'collapse' : 'expand' |
| | | setAppSiderbarExpand(isMobile ? mode : localeMode) |
| | | // TODO: consider screen size and mode |
| | | // if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow')) |
| | | // setAppSiderbarExpand('collapse') |
| | | } |
| | | export default AppDetailLayout |
| | | }, [appDetail, isMobile]) |
| | | |
| | | useEffect(() => { |
| | | setAppDetail() |
| | | setIsLoadingAppDetail(true) |
| | | fetchAppDetail({ url: '/apps', id: appId }).then((res) => { |
| | | setAppDetailRes(res) |
| | | }).catch((e: any) => { |
| | | if (e.status === 404) |
| | | router.replace('/apps') |
| | | }).finally(() => { |
| | | setIsLoadingAppDetail(false) |
| | | }) |
| | | }, [appId, router, setAppDetail]) |
| | | |
| | | useEffect(() => { |
| | | if (!appDetailRes || isLoadingCurrentWorkspace || isLoadingAppDetail) |
| | | return |
| | | const res = appDetailRes |
| | | // redirection |
| | | const canIEditApp = isCurrentWorkspaceEditor |
| | | if (!canIEditApp && (pathname.endsWith('configuration') || pathname.endsWith('workflow') || pathname.endsWith('logs'))) { |
| | | router.replace(`/app/${appId}/overview`) |
| | | return |
| | | } |
| | | if ((res.mode === 'workflow' || res.mode === 'advanced-chat') && (pathname).endsWith('configuration')) { |
| | | router.replace(`/app/${appId}/workflow`) |
| | | } |
| | | else if ((res.mode !== 'workflow' && res.mode !== 'advanced-chat') && (pathname).endsWith('workflow')) { |
| | | router.replace(`/app/${appId}/configuration`) |
| | | } |
| | | else { |
| | | setAppDetail({ ...res, enable_sso: false }) |
| | | setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode)) |
| | | if (systemFeatures.enable_web_sso_switch_component && canIEditApp) { |
| | | fetchAppSSO({ appId }).then((ssoRes) => { |
| | | setAppDetail({ ...res, enable_sso: ssoRes.enabled }) |
| | | }) |
| | | } |
| | | } |
| | | }, [appDetailRes, appId, getNavigations, isCurrentWorkspaceEditor, isLoadingAppDetail, isLoadingCurrentWorkspace, pathname, router, setAppDetail, systemFeatures.enable_web_sso_switch_component]) |
| | | |
| | | useUnmount(() => { |
| | | setAppDetail() |
| | | }) |
| | | |
| | | if (!appDetail) { |
| | | return ( |
| | | <div className='flex h-full items-center justify-center bg-background-body'> |
| | | <Loading /> |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | return ( |
| | | <div className={cn(s.app, 'flex', 'overflow-hidden')}> |
| | | {appDetail && ( |
| | | <AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background} desc={appDetail.mode} navigation={navigation} /> |
| | | )} |
| | | <div className="bg-components-panel-bg grow overflow-hidden"> |
| | | {children} |
| | | </div> |
| | | </div> |
| | | ) |
| | | } |
| | | export default React.memo(AppDetailLayout) |
| | |
| | | |
| | | export type ICardViewProps = { |
| | | appId: string |
| | | isInPanel?: boolean |
| | | className?: string |
| | | } |
| | | |
| | | const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => { |
| | | const CardView: FC<ICardViewProps> = ({ appId }) => { |
| | | const { t } = useTranslation() |
| | | const { notify } = useContext(ToastContext) |
| | | const appDetail = useAppStore(state => state.appDetail) |
| | |
| | | return <Loading /> |
| | | |
| | | return ( |
| | | <div className={className || 'mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'}> |
| | | <div className="grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6"> |
| | | <AppCard |
| | | appInfo={appDetail} |
| | | cardType="webapp" |
| | | isInPanel={isInPanel} |
| | | onChangeStatus={onChangeSiteStatus} |
| | | onGenerateCode={onGenerateCode} |
| | | onSaveSiteConfig={onSaveSiteConfig} |
| | |
| | | <AppCard |
| | | cardType="api" |
| | | appInfo={appDetail} |
| | | isInPanel={isInPanel} |
| | | onChangeStatus={onChangeApiStatus} |
| | | /> |
| | | </div> |
| | |
| | | |
| | | return ( |
| | | <div> |
| | | <div className='system-xl-semibold mb-4 mt-8 flex flex-row items-center text-text-primary'> |
| | | <div className='flex flex-row items-center mt-8 mb-4 text-gray-900 text-base'> |
| | | <span className='mr-3'>{t('appOverview.analysis.title')}</span> |
| | | <SimpleSelect |
| | | items={Object.entries(TIME_PERIOD_MAPPING).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} |
| | | className='mt-0 !w-40' |
| | | onSelect={(item) => { |
| | | const id = item.value |
| | | const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1' |
| | | const value = TIME_PERIOD_MAPPING[id]?.value || '-1' |
| | | const name = item.name || t('appLog.filter.period.allTime') |
| | | onSelect({ value, name }) |
| | | }} |
| | |
| | | /> |
| | | </div> |
| | | {!isWorkflow && ( |
| | | <div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'> |
| | | <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'> |
| | | <ConversationsChart period={period} id={appId} /> |
| | | <EndUsersChart period={period} id={appId} /> |
| | | </div> |
| | | )} |
| | | {!isWorkflow && ( |
| | | <div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'> |
| | | <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'> |
| | | {isChatApp |
| | | ? ( |
| | | <AvgSessionInteractions period={period} id={appId} /> |
| | |
| | | </div> |
| | | )} |
| | | {!isWorkflow && ( |
| | | <div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'> |
| | | <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'> |
| | | <UserSatisfactionRate period={period} id={appId} /> |
| | | <CostChart period={period} id={appId} /> |
| | | </div> |
| | | )} |
| | | {!isWorkflow && isChatApp && ( |
| | | <div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'> |
| | | <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'> |
| | | <MessagesChart period={period} id={appId} /> |
| | | </div> |
| | | )} |
| | | {isWorkflow && ( |
| | | <div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'> |
| | | <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'> |
| | | <WorkflowMessagesChart period={period} id={appId} /> |
| | | <WorkflowDailyTerminalsChart period={period} id={appId} /> |
| | | </div> |
| | | )} |
| | | {isWorkflow && ( |
| | | <div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'> |
| | | <div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'> |
| | | <WorkflowCostChart period={period} id={appId} /> |
| | | <AvgUserInteractions period={period} id={appId} /> |
| | | </div> |
| | |
| | | import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel' |
| | | |
| | | export type IDevelopProps = { |
| | | params: Promise<{ appId: string }> |
| | | params: { appId: string } |
| | | } |
| | | |
| | | const Overview = async (props: IDevelopProps) => { |
| | | const params = await props.params |
| | | |
| | | const { |
| | | appId, |
| | | } = params |
| | | |
| | | const Overview = async ({ |
| | | params: { appId }, |
| | | }: IDevelopProps) => { |
| | | return ( |
| | | <div className="h-full overflow-scroll bg-chatbot-bg px-4 py-6 sm:px-12"> |
| | | <div className="h-full px-4 sm:px-16 py-6 overflow-scroll"> |
| | | <ApikeyInfoPanel /> |
| | | <TracingPanel /> |
| | | <CardView appId={appId} /> |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useCallback, useEffect, useRef, useState } from 'react' |
| | | import { |
| | | RiEqualizer2Line, |
| | | } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import type { PopupProps } from './config-popup' |
| | | import ConfigPopup from './config-popup' |
| | | import cn from '@/utils/classnames' |
| | | import Button from '@/app/components/base/button' |
| | | import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import { |
| | | PortalToFollowElem, |
| | | PortalToFollowElemContent, |
| | | PortalToFollowElemTrigger, |
| | | } from '@/app/components/base/portal-to-follow-elem' |
| | | |
| | | const I18N_PREFIX = 'app.tracing' |
| | | |
| | | type Props = { |
| | | readOnly: boolean |
| | |
| | | controlShowPopup, |
| | | ...popupProps |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const [open, doSetOpen] = useState(false) |
| | | const openRef = useRef(open) |
| | | const setOpen = useCallback((v: boolean) => { |
| | |
| | | if (popupProps.readOnly && !hasConfigured) |
| | | return null |
| | | |
| | | const triggerContent = hasConfigured |
| | | ? ( |
| | | <div className={cn(className, 'p-1 rounded-md hover:bg-black/5 cursor-pointer')}> |
| | | <Settings04 className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | ) |
| | | : ( |
| | | <Button variant='primary' |
| | | className={cn(className, '!h-8 !px-3 select-none')} |
| | | > |
| | | <Settings04 className='mr-1 w-4 h-4' /> |
| | | <span className='text-[13px]'>{t(`${I18N_PREFIX}.config`)}</span> |
| | | </Button> |
| | | ) |
| | | |
| | | return ( |
| | | <PortalToFollowElem |
| | | open={open} |
| | |
| | | placement='bottom-end' |
| | | offset={{ |
| | | mainAxis: 12, |
| | | crossAxis: hasConfigured ? 8 : 49, |
| | | crossAxis: hasConfigured ? 8 : 0, |
| | | }} |
| | | > |
| | | <PortalToFollowElemTrigger onClick={handleTrigger}> |
| | | <div className={cn(className, 'rounded-md p-1')}> |
| | | <RiEqualizer2Line className='h-4 w-4 text-text-tertiary' /> |
| | | </div> |
| | | {triggerContent} |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-[11]'> |
| | | <ConfigPopup {...popupProps} /> |
| | |
| | | import { useBoolean } from 'ahooks' |
| | | import TracingIcon from './tracing-icon' |
| | | import ProviderPanel from './provider-panel' |
| | | import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' |
| | | import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type' |
| | | import { TracingProvider } from './type' |
| | | import ProviderConfigModal from './provider-config-modal' |
| | | import Indicator from '@/app/components/header/indicator' |
| | | import Switch from '@/app/components/base/switch' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import Divider from '@/app/components/base/divider' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | const I18N_PREFIX = 'app.tracing' |
| | | |
| | |
| | | langSmithConfig: LangSmithConfig | null |
| | | langFuseConfig: LangFuseConfig | null |
| | | opikConfig: OpikConfig | null |
| | | weaveConfig: WeaveConfig | null |
| | | onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void |
| | | onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig) => void |
| | | onConfigRemoved: (provider: TracingProvider) => void |
| | | } |
| | | |
| | |
| | | langSmithConfig, |
| | | langFuseConfig, |
| | | opikConfig, |
| | | weaveConfig, |
| | | onConfigUpdated, |
| | | onConfigRemoved, |
| | | }) => { |
| | |
| | | } |
| | | }, [onChooseProvider]) |
| | | |
| | | const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => { |
| | | const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig) => { |
| | | onConfigUpdated(currentProvider!, payload) |
| | | hideConfigModal() |
| | | }, [currentProvider, hideConfigModal, onConfigUpdated]) |
| | |
| | | hideConfigModal() |
| | | }, [currentProvider, hideConfigModal, onConfigRemoved]) |
| | | |
| | | const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig && weaveConfig |
| | | const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig |
| | | const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig |
| | | const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig |
| | | |
| | | const switchContent = ( |
| | | <Switch |
| | | className='ml-3' |
| | | defaultValue={enabled} |
| | | onChange={onStatusChange} |
| | | size='l' |
| | | disabled={providerAllNotConfigured} |
| | | /> |
| | | ) |
| | |
| | | /> |
| | | ) |
| | | |
| | | const weavePanel = ( |
| | | <ProviderPanel |
| | | type={TracingProvider.weave} |
| | | readOnly={readOnly} |
| | | config={weaveConfig} |
| | | hasConfigured={!!weaveConfig} |
| | | onConfig={handleOnConfig(TracingProvider.weave)} |
| | | isChosen={chosenProvider === TracingProvider.weave} |
| | | onChoose={handleOnChoose(TracingProvider.weave)} |
| | | key="weave-provider-panel" |
| | | /> |
| | | ) |
| | | const configuredProviderPanel = () => { |
| | | const configuredPanels: JSX.Element[] = [] |
| | | |
| | | if (langFuseConfig) |
| | | configuredPanels.push(langfusePanel) |
| | | const configuredPanels: ProviderPanel[] = [] |
| | | |
| | | if (langSmithConfig) |
| | | configuredPanels.push(langSmithPanel) |
| | | |
| | | if (langFuseConfig) |
| | | configuredPanels.push(langfusePanel) |
| | | |
| | | if (opikConfig) |
| | | configuredPanels.push(opikPanel) |
| | | |
| | | if (weaveConfig) |
| | | configuredPanels.push(weavePanel) |
| | | |
| | | return configuredPanels |
| | | } |
| | | |
| | | const moreProviderPanel = () => { |
| | | const notConfiguredPanels: JSX.Element[] = [] |
| | | |
| | | if (!langFuseConfig) |
| | | notConfiguredPanels.push(langfusePanel) |
| | | const notConfiguredPanels: ProviderPanel[] = [] |
| | | |
| | | if (!langSmithConfig) |
| | | notConfiguredPanels.push(langSmithPanel) |
| | | |
| | | if (!langFuseConfig) |
| | | notConfiguredPanels.push(langfusePanel) |
| | | |
| | | if (!opikConfig) |
| | | notConfiguredPanels.push(opikPanel) |
| | | |
| | | if (!weaveConfig) |
| | | notConfiguredPanels.push(weavePanel) |
| | | |
| | | return notConfiguredPanels |
| | | } |
| | |
| | | return langSmithConfig |
| | | if (currentProvider === TracingProvider.langfuse) |
| | | return langFuseConfig |
| | | if (currentProvider === TracingProvider.opik) |
| | | return opikConfig |
| | | return weaveConfig |
| | | } |
| | | |
| | | return ( |
| | | <div className='w-[420px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-4 shadow-xl'> |
| | | <div className='flex items-center justify-between'> |
| | | <div className='w-[420px] p-4 rounded-2xl bg-white border-[0.5px] border-black/5 shadow-lg'> |
| | | <div className='flex justify-between items-center'> |
| | | <div className='flex items-center'> |
| | | <TracingIcon size='md' className='mr-2' /> |
| | | <div className='title-2xl-semi-bold text-text-primary'>{t(`${I18N_PREFIX}.tracing`)}</div> |
| | | <div className='leading-[120%] text-[18px] font-semibold text-gray-900'>{t(`${I18N_PREFIX}.tracing`)}</div> |
| | | </div> |
| | | <div className='flex items-center'> |
| | | <Indicator color={enabled ? 'green' : 'gray'} /> |
| | | <div className={cn('system-xs-semibold-uppercase ml-1 text-text-tertiary', enabled && 'text-util-colors-green-green-600')}> |
| | | <div className='ml-1.5 text-xs font-semibold text-gray-500 uppercase'> |
| | | {t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)} |
| | | </div> |
| | | {!readOnly && ( |
| | |
| | | : switchContent} |
| | | </> |
| | | )} |
| | | |
| | | </div> |
| | | </div> |
| | | |
| | | <div className='system-xs-regular mt-2 text-text-tertiary'> |
| | | <div className='mt-2 leading-4 text-xs font-normal text-gray-500'> |
| | | {t(`${I18N_PREFIX}.tracingDescription`)} |
| | | </div> |
| | | <Divider className='my-3' /> |
| | | <div className='relative'> |
| | | <div className='mt-3 h-px bg-gray-100'></div> |
| | | <div className='mt-3'> |
| | | {(providerAllConfigured || providerAllNotConfigured) |
| | | ? ( |
| | | <> |
| | | <div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}</div> |
| | | <div className='leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}</div> |
| | | <div className='mt-2 space-y-2'> |
| | | {langfusePanel} |
| | | {langSmithPanel} |
| | | {langfusePanel} |
| | | {opikPanel} |
| | | {weavePanel} |
| | | </div> |
| | | </> |
| | | ) |
| | | : ( |
| | | <> |
| | | <div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.configured`)}</div> |
| | | <div className='leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.configured`)}</div> |
| | | <div className='mt-2 space-y-2'> |
| | | {configuredProviderPanel()} |
| | | </div> |
| | | <div className='system-xs-medium-uppercase mt-3 text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}</div> |
| | | <div className='mt-3 leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}</div> |
| | | <div className='mt-2 space-y-2'> |
| | | {moreProviderPanel()} |
| | | </div> |
| | |
| | | [TracingProvider.langSmith]: 'https://docs.smith.langchain.com/', |
| | | [TracingProvider.langfuse]: 'https://docs.langfuse.com', |
| | | [TracingProvider.opik]: 'https://www.comet.com/docs/opik/tracing/integrations/dify#setup-instructions', |
| | | [TracingProvider.weave]: 'https://weave-docs.wandb.ai/', |
| | | } |
| | |
| | | return ( |
| | | <div className={cn(className)}> |
| | | <div className='flex py-[7px]'> |
| | | <div className={cn(labelClassName, 'flex h-[18px] items-center text-[13px] font-medium text-text-primary')}>{label} </div> |
| | | <div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-gray-900')}>{label} </div> |
| | | {isRequired && <span className='ml-0.5 text-xs font-semibold text-[#D92D20]'>*</span>} |
| | | </div> |
| | | <Input |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useCallback, useEffect, useState } from 'react' |
| | | import { |
| | | RiArrowDownDoubleLine, |
| | | } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { usePathname } from 'next/navigation' |
| | | import { useBoolean } from 'ahooks' |
| | | import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' |
| | | import type { LangFuseConfig, LangSmithConfig } from './type' |
| | | import { TracingProvider } from './type' |
| | | import TracingIcon from './tracing-icon' |
| | | import ConfigButton from './config-button' |
| | | import cn from '@/utils/classnames' |
| | | import { LangfuseIcon, LangsmithIcon, OpikIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing' |
| | | import { LangfuseIcon, LangsmithIcon, OpikIcon } from '@/app/components/base/icons/src/public/tracing' |
| | | import Indicator from '@/app/components/header/indicator' |
| | | import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps' |
| | | import type { TracingStatus } from '@/models/app' |
| | | import Toast from '@/app/components/base/toast' |
| | | import { useAppContext } from '@/context/app-context' |
| | | import Loading from '@/app/components/base/loading' |
| | | import Divider from '@/app/components/base/divider' |
| | | |
| | | const I18N_PREFIX = 'app.tracing' |
| | | |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className={cn('system-xl-semibold flex items-center text-text-primary', className)}> |
| | | <div className={cn(className, 'flex items-center text-lg font-semibold text-gray-900')}> |
| | | {t('common.appMenus.overview')} |
| | | </div> |
| | | ) |
| | |
| | | ? LangfuseIcon |
| | | : inUseTracingProvider === TracingProvider.opik |
| | | ? OpikIcon |
| | | : inUseTracingProvider === TracingProvider.weave |
| | | ? WeaveIcon |
| | | : LangsmithIcon |
| | | : null |
| | | |
| | | const [langSmithConfig, setLangSmithConfig] = useState<LangSmithConfig | null>(null) |
| | | const [langFuseConfig, setLangFuseConfig] = useState<LangFuseConfig | null>(null) |
| | | const [opikConfig, setOpikConfig] = useState<OpikConfig | null>(null) |
| | | const [weaveConfig, setWeaveConfig] = useState<WeaveConfig | null>(null) |
| | | const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig) |
| | | const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig) |
| | | |
| | | const fetchTracingConfig = async () => { |
| | | const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith }) |
| | |
| | | const { tracing_config: opikConfig, has_not_configured: OpikHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.opik }) |
| | | if (!OpikHasNotConfig) |
| | | setOpikConfig(opikConfig as OpikConfig) |
| | | const { tracing_config: weaveConfig, has_not_configured: weaveHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.weave }) |
| | | if (!weaveHasNotConfig) |
| | | setWeaveConfig(weaveConfig as WeaveConfig) |
| | | } |
| | | |
| | | const handleTracingConfigUpdated = async (provider: TracingProvider) => { |
| | |
| | | const { tracing_config } = await doFetchTracingConfig({ appId, provider }) |
| | | if (provider === TracingProvider.langSmith) |
| | | setLangSmithConfig(tracing_config as LangSmithConfig) |
| | | else if (provider === TracingProvider.langfuse) |
| | | else if (provider === TracingProvider.langSmith) |
| | | setLangFuseConfig(tracing_config as LangFuseConfig) |
| | | else if (provider === TracingProvider.opik) |
| | | setOpikConfig(tracing_config as OpikConfig) |
| | | else if (provider === TracingProvider.weave) |
| | | setWeaveConfig(tracing_config as WeaveConfig) |
| | | } |
| | | |
| | | const handleTracingConfigRemoved = (provider: TracingProvider) => { |
| | | if (provider === TracingProvider.langSmith) |
| | | setLangSmithConfig(null) |
| | | else if (provider === TracingProvider.langfuse) |
| | | else if (provider === TracingProvider.langSmith) |
| | | setLangFuseConfig(null) |
| | | else if (provider === TracingProvider.opik) |
| | | setOpikConfig(null) |
| | | else if (provider === TracingProvider.weave) |
| | | setWeaveConfig(null) |
| | | if (provider === inUseTracingProvider) { |
| | | handleTracingStatusChange({ |
| | | enabled: false, |
| | |
| | | }, [setControlShowPopup]) |
| | | if (!isLoaded) { |
| | | return ( |
| | | <div className='mb-3 flex items-center justify-between'> |
| | | <div className='flex items-center justify-between mb-3'> |
| | | <Title className='h-[41px]' /> |
| | | <div className='w-[200px]'> |
| | | <Loading /> |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className={cn('mb-3 flex items-center justify-between')}> |
| | | <div className={cn('mb-3 flex justify-between items-center')}> |
| | | <Title className='h-[41px]' /> |
| | | <div |
| | | className={cn( |
| | | 'flex cursor-pointer items-center rounded-xl border-l-[0.5px] border-t border-effects-highlight bg-background-default-dodge p-2 shadow-xs hover:border-effects-highlight-lightmode-off hover:bg-background-default-lighter', |
| | | controlShowPopup && 'border-effects-highlight-lightmode-off bg-background-default-lighter', |
| | | )} |
| | | onClick={showPopup} |
| | | > |
| | | {!inUseTracingProvider && ( |
| | | <> |
| | | <TracingIcon size='md' /> |
| | | <div className='system-sm-semibold mx-2 text-text-secondary'>{t(`${I18N_PREFIX}.title`)}</div> |
| | | <div className='flex items-center' onClick={e => e.stopPropagation()}> |
| | | <ConfigButton |
| | | appId={appId} |
| | | readOnly={readOnly} |
| | | hasConfigured={false} |
| | | enabled={enabled} |
| | | onStatusChange={handleTracingEnabledChange} |
| | | chosenProvider={inUseTracingProvider} |
| | | onChooseProvider={handleChooseProvider} |
| | | langSmithConfig={langSmithConfig} |
| | | langFuseConfig={langFuseConfig} |
| | | opikConfig={opikConfig} |
| | | weaveConfig={weaveConfig} |
| | | onConfigUpdated={handleTracingConfigUpdated} |
| | | onConfigRemoved={handleTracingConfigRemoved} |
| | | controlShowPopup={controlShowPopup} |
| | | /> |
| | | </div> |
| | | <Divider type='vertical' className='h-3.5' /> |
| | | <div className='rounded-md p-1'> |
| | | <RiArrowDownDoubleLine className='h-4 w-4 text-text-tertiary' /> |
| | | </div> |
| | | <div className='flex items-center p-2 rounded-xl border-[0.5px] border-gray-200 shadow-xs cursor-pointer hover:bg-gray-100' onClick={showPopup}> |
| | | {!inUseTracingProvider |
| | | ? <> |
| | | <TracingIcon size='md' className='mr-2' /> |
| | | <div className='leading-5 text-sm font-semibold text-gray-700'>{t(`${I18N_PREFIX}.title`)}</div> |
| | | </> |
| | | )} |
| | | : <InUseProviderIcon className='ml-1 h-4' />} |
| | | |
| | | {hasConfiguredTracing && ( |
| | | <> |
| | | <div className='ml-4 mr-1 flex items-center'> |
| | | <Indicator color={enabled ? 'green' : 'gray'} /> |
| | | <div className='system-xs-semibold-uppercase ml-1.5 text-text-tertiary'> |
| | | <div className='ml-1.5 text-xs font-semibold text-gray-500 uppercase'> |
| | | {t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)} |
| | | </div> |
| | | </div> |
| | | {InUseProviderIcon && <InUseProviderIcon className='ml-1 h-4' />} |
| | | <Divider type='vertical' className='h-3.5' /> |
| | | )} |
| | | |
| | | {hasConfiguredTracing && ( |
| | | <div className='ml-2 w-px h-3.5 bg-gray-200'></div> |
| | | )} |
| | | <div className='flex items-center' onClick={e => e.stopPropagation()}> |
| | | <ConfigButton |
| | | appId={appId} |
| | |
| | | langSmithConfig={langSmithConfig} |
| | | langFuseConfig={langFuseConfig} |
| | | opikConfig={opikConfig} |
| | | weaveConfig={weaveConfig} |
| | | onConfigUpdated={handleTracingConfigUpdated} |
| | | onConfigRemoved={handleTracingConfigRemoved} |
| | | controlShowPopup={controlShowPopup} |
| | | /> |
| | | </div> |
| | | </> |
| | | )} |
| | | </div > |
| | | </div > |
| | | ) |
| | |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useBoolean } from 'ahooks' |
| | | import Field from './field' |
| | | import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' |
| | | import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type' |
| | | import { TracingProvider } from './type' |
| | | import { docURL } from './config' |
| | | import { |
| | |
| | | import Confirm from '@/app/components/base/confirm' |
| | | import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps' |
| | | import Toast from '@/app/components/base/toast' |
| | | import Divider from '@/app/components/base/divider' |
| | | |
| | | type Props = { |
| | | appId: string |
| | | type: TracingProvider |
| | | payload?: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | null |
| | | payload?: LangSmithConfig | LangFuseConfig | OpikConfig | null |
| | | onRemoved: () => void |
| | | onCancel: () => void |
| | | onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void |
| | | onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig) => void |
| | | onChosen: (provider: TracingProvider) => void |
| | | } |
| | | |
| | |
| | | workspace: '', |
| | | } |
| | | |
| | | const weaveConfigTemplate = { |
| | | api_key: '', |
| | | entity: '', |
| | | project: '', |
| | | endpoint: '', |
| | | } |
| | | |
| | | const ProviderConfigModal: FC<Props> = ({ |
| | | appId, |
| | | type, |
| | |
| | | const isEdit = !!payload |
| | | const isAdd = !isEdit |
| | | const [isSaving, setIsSaving] = useState(false) |
| | | const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig>((() => { |
| | | const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig | OpikConfig>((() => { |
| | | if (isEdit) |
| | | return payload |
| | | |
| | |
| | | else if (type === TracingProvider.langfuse) |
| | | return langFuseConfigTemplate |
| | | |
| | | else if (type === TracingProvider.opik) |
| | | return opikConfigTemplate |
| | | |
| | | return weaveConfigTemplate |
| | | })()) |
| | | const [isShowRemoveConfirm, { |
| | | setTrue: showRemoveConfirm, |
| | |
| | | } |
| | | |
| | | if (type === TracingProvider.opik) { |
| | | // todo: check field validity |
| | | // const postData = config as OpikConfig |
| | | } |
| | | |
| | | if (type === TracingProvider.weave) { |
| | | const postData = config as WeaveConfig |
| | | if (!errorMessage && !postData.api_key) |
| | | errorMessage = t('common.errorMsg.fieldRequired', { field: 'API Key' }) |
| | | if (!errorMessage && !postData.project) |
| | | errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.project`) }) |
| | | const postData = config as OpikConfig |
| | | } |
| | | |
| | | return errorMessage |
| | |
| | | {!isShowRemoveConfirm |
| | | ? ( |
| | | <PortalToFollowElem open> |
| | | <PortalToFollowElemContent className='z-[60] h-full w-full'> |
| | | <div className='fixed inset-0 flex items-center justify-center bg-background-overlay'> |
| | | <div className='mx-2 max-h-[calc(100vh-120px)] w-[640px] overflow-y-auto rounded-2xl bg-components-panel-bg shadow-xl'> |
| | | <PortalToFollowElemContent className='w-full h-full z-[60]'> |
| | | <div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'> |
| | | <div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'> |
| | | <div className='px-8 pt-8'> |
| | | <div className='mb-4 flex items-center justify-between'> |
| | | <div className='title-2xl-semi-bold text-text-primary'>{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}</div> |
| | | <div className='flex justify-between items-center mb-4'> |
| | | <div className='text-xl font-semibold text-gray-900'>{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}</div> |
| | | </div> |
| | | |
| | | <div className='space-y-4'> |
| | | {type === TracingProvider.weave && ( |
| | | <> |
| | | <Field |
| | | label='API Key' |
| | | labelClassName='!text-sm' |
| | | isRequired |
| | | value={(config as WeaveConfig).api_key} |
| | | onChange={handleConfigChange('api_key')} |
| | | placeholder={t(`${I18N_PREFIX}.placeholder`, { key: 'API Key' })!} |
| | | /> |
| | | <Field |
| | | label={t(`${I18N_PREFIX}.project`)!} |
| | | labelClassName='!text-sm' |
| | | isRequired |
| | | value={(config as WeaveConfig).project} |
| | | onChange={handleConfigChange('project')} |
| | | placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.project`) })!} |
| | | /> |
| | | <Field |
| | | label='Entity' |
| | | labelClassName='!text-sm' |
| | | value={(config as WeaveConfig).entity} |
| | | onChange={handleConfigChange('entity')} |
| | | placeholder={t(`${I18N_PREFIX}.placeholder`, { key: 'Entity' })!} |
| | | /> |
| | | <Field |
| | | label='Endpoint' |
| | | labelClassName='!text-sm' |
| | | value={(config as WeaveConfig).endpoint} |
| | | onChange={handleConfigChange('endpoint')} |
| | | placeholder={'https://trace.wandb.ai/'} |
| | | /> |
| | | </> |
| | | )} |
| | | {type === TracingProvider.langSmith && ( |
| | | <> |
| | | <Field |
| | |
| | | /> |
| | | </> |
| | | )} |
| | | |
| | | </div> |
| | | <div className='my-8 flex h-8 items-center justify-between'> |
| | | <div className='my-8 flex justify-between items-center h-8'> |
| | | <a |
| | | className='flex items-center space-x-1 text-xs font-normal leading-[18px] text-[#155EEF]' |
| | | className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-[#155EEF]' |
| | | target='_blank' |
| | | href={docURL[type]} |
| | | > |
| | | <span>{t(`${I18N_PREFIX}.viewDocsLink`, { key: t(`app.tracing.${type}.title`) })}</span> |
| | | <LinkExternal02 className='h-3 w-3' /> |
| | | <LinkExternal02 className='w-3 h-3' /> |
| | | </a> |
| | | <div className='flex items-center'> |
| | | {isEdit && ( |
| | | <> |
| | | <Button |
| | | className='h-9 text-sm font-medium text-text-secondary' |
| | | className='h-9 text-sm font-medium text-gray-700' |
| | | onClick={showRemoveConfirm} |
| | | > |
| | | <span className='text-[#D92D20]'>{t('common.operation.remove')}</span> |
| | | </Button> |
| | | <Divider className='mx-3 h-[18px]' /> |
| | | <div className='mx-3 w-px h-[18px] bg-gray-200'></div> |
| | | </> |
| | | )} |
| | | <Button |
| | | className='mr-2 h-9 text-sm font-medium text-text-secondary' |
| | | className='mr-2 h-9 text-sm font-medium text-gray-700' |
| | | onClick={onCancel} |
| | | > |
| | | {t('common.operation.cancel')} |
| | |
| | | |
| | | </div> |
| | | </div> |
| | | <div className='border-t-[0.5px] border-divider-regular'> |
| | | <div className='flex items-center justify-center bg-background-section-burn py-3 text-xs text-text-tertiary'> |
| | | <Lock01 className='mr-1 h-3 w-3 text-text-tertiary' /> |
| | | <div className='border-t-[0.5px] border-t-black/5'> |
| | | <div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'> |
| | | <Lock01 className='mr-1 w-3 h-3 text-gray-500' /> |
| | | {t('common.modelProvider.encrypted.front')} |
| | | <a |
| | | className='mx-1 text-primary-600' |
| | | className='text-primary-600 mx-1' |
| | | target='_blank' rel='noopener noreferrer' |
| | | href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html' |
| | | > |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useCallback } from 'react' |
| | | import { |
| | | RiEqualizer2Line, |
| | | } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { TracingProvider } from './type' |
| | | import cn from '@/utils/classnames' |
| | | import { LangfuseIconBig, LangsmithIconBig, OpikIconBig, WeaveIconBig } from '@/app/components/base/icons/src/public/tracing' |
| | | import { LangfuseIconBig, LangsmithIconBig, OpikIconBig } from '@/app/components/base/icons/src/public/tracing' |
| | | import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general' |
| | | |
| | | const I18N_PREFIX = 'app.tracing' |
| | |
| | | [TracingProvider.langSmith]: LangsmithIconBig, |
| | | [TracingProvider.langfuse]: LangfuseIconBig, |
| | | [TracingProvider.opik]: OpikIconBig, |
| | | [TracingProvider.weave]: WeaveIconBig, |
| | | })[type] |
| | | } |
| | | |
| | |
| | | }, [hasConfigured, isChosen, onChoose, readOnly]) |
| | | return ( |
| | | <div |
| | | className={cn( |
| | | 'rounded-xl border-[1.5px] bg-background-section-burn px-4 py-3', |
| | | isChosen ? 'border-components-option-card-option-selected-border bg-background-section' : 'border-transparent', |
| | | !isChosen && hasConfigured && !readOnly && 'cursor-pointer', |
| | | )} |
| | | className={cn(isChosen ? 'border-primary-400' : 'border-transparent', !isChosen && hasConfigured && !readOnly && 'cursor-pointer', 'px-4 py-3 rounded-xl border-[1.5px] bg-gray-100')} |
| | | onClick={handleChosen} |
| | | > |
| | | <div className={'flex items-center justify-between space-x-1'}> |
| | | <div className={'flex justify-between items-center space-x-1'}> |
| | | <div className='flex items-center'> |
| | | <Icon className='h-6' /> |
| | | {isChosen && <div className='system-2xs-medium-uppercase ml-1 flex h-4 items-center rounded-[4px] border border-text-accent-secondary px-1 text-text-accent-secondary'>{t(`${I18N_PREFIX}.inUse`)}</div>} |
| | | {isChosen && <div className='ml-1 flex items-center h-4 px-1 rounded-[4px] border border-primary-500 leading-4 text-xs font-medium text-primary-500 uppercase '>{t(`${I18N_PREFIX}.inUse`)}</div>} |
| | | </div> |
| | | {!readOnly && ( |
| | | <div className={'flex items-center justify-between space-x-1'}> |
| | | <div className={'flex justify-between items-center space-x-1'}> |
| | | {hasConfigured && ( |
| | | <div className='flex h-6 cursor-pointer items-center space-x-1 rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-2 text-text-secondary shadow-xs' onClick={viewBtnClick} > |
| | | <View className='h-3 w-3' /> |
| | | <div className='flex px-2 items-center h-6 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer text-gray-700 space-x-1' onClick={viewBtnClick} > |
| | | <View className='w-3 h-3'/> |
| | | <div className='text-xs font-medium'>{t(`${I18N_PREFIX}.view`)}</div> |
| | | </div> |
| | | )} |
| | | <div |
| | | className='flex h-6 cursor-pointer items-center space-x-1 rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-2 text-text-secondary shadow-xs' |
| | | className='flex px-2 items-center h-6 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer text-gray-700 space-x-1' |
| | | onClick={handleConfigBtnClick} |
| | | > |
| | | <RiEqualizer2Line className='h-3 w-3' /> |
| | | <Settings04 className='w-3 h-3' /> |
| | | <div className='text-xs font-medium'>{t(`${I18N_PREFIX}.config`)}</div> |
| | | </div> |
| | | </div> |
| | | )} |
| | | |
| | | </div> |
| | | <div className='system-xs-regular mt-2 text-text-tertiary'> |
| | | <div className='mt-2 leading-4 text-xs font-normal text-gray-500'> |
| | | {t(`${I18N_PREFIX}.${type}.description`)} |
| | | </div> |
| | | </div> |
New file |
| | |
| | | 'use client' |
| | | import { ChevronDoubleDownIcon } from '@heroicons/react/20/solid' |
| | | import type { FC } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import React, { useCallback } from 'react' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | |
| | | const I18N_PREFIX = 'app.tracing' |
| | | |
| | | type Props = { |
| | | isFold: boolean |
| | | onFoldChange: (isFold: boolean) => void |
| | | } |
| | | |
| | | const ToggleFoldBtn: FC<Props> = ({ |
| | | isFold, |
| | | onFoldChange, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | |
| | | const handleFoldChange = useCallback((e: React.MouseEvent<HTMLDivElement>) => { |
| | | e.stopPropagation() |
| | | onFoldChange(!isFold) |
| | | }, [isFold, onFoldChange]) |
| | | return ( |
| | | // text-[0px] to hide spacing between tooltip elements |
| | | <div className='shrink-0 cursor-pointer text-[0px]' onClick={handleFoldChange}> |
| | | <Tooltip |
| | | popupContent={t(`${I18N_PREFIX}.${isFold ? 'expand' : 'collapse'}`)} |
| | | > |
| | | {isFold && ( |
| | | <div className='p-1 rounded-md text-gray-500 hover:text-gray-800 hover:bg-black/5'> |
| | | <ChevronDoubleDownIcon className='w-4 h-4' /> |
| | | </div> |
| | | )} |
| | | {!isFold && ( |
| | | <div className='p-2 rounded-lg text-gray-500 border-[0.5px] border-gray-200 hover:text-gray-800 hover:bg-black/5'> |
| | | <ChevronDoubleDownIcon className='w-4 h-4 transform rotate-180' /> |
| | | </div> |
| | | )} |
| | | </Tooltip> |
| | | </div> |
| | | ) |
| | | } |
| | | export default React.memo(ToggleFoldBtn) |
| | |
| | | const sizeClass = sizeClassMap[size] |
| | | return ( |
| | | <div className={cn(className, sizeClass, 'bg-primary-500 shadow-md')}> |
| | | <Icon className='h-full w-full' /> |
| | | <Icon className='w-full h-full' /> |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | langSmith = 'langsmith', |
| | | langfuse = 'langfuse', |
| | | opik = 'opik', |
| | | weave = 'weave', |
| | | } |
| | | |
| | | export type LangSmithConfig = { |
| | |
| | | project: string |
| | | workspace: string |
| | | url: string |
| | | } |
| | | |
| | | export type WeaveConfig = { |
| | | api_key: string |
| | | entity: string |
| | | project: string |
| | | endpoint: string |
| | | } |
| | |
| | | 'use client' |
| | | |
| | | import WorkflowApp from '@/app/components/workflow-app' |
| | | import Workflow from '@/app/components/workflow' |
| | | |
| | | const Page = () => { |
| | | return ( |
| | | <div className='h-full w-full overflow-x-auto'> |
| | | <WorkflowApp /> |
| | | <div className='w-full h-full overflow-x-auto'> |
| | | <Workflow /> |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | useEffect(() => { |
| | | if (isCurrentWorkspaceDatasetOperator) |
| | | return router.replace('/datasets') |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [isCurrentWorkspaceDatasetOperator]) |
| | | |
| | | return ( |
| | |
| | | import { useCallback, useEffect, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiMoreFill } from '@remixicon/react' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import type { App } from '@/types/app' |
| | | import Confirm from '@/app/components/base/confirm' |
| | | import Toast, { ToastContext } from '@/app/components/base/toast' |
| | |
| | | import type { HtmlContentProps } from '@/app/components/base/popover' |
| | | import CustomPopover from '@/app/components/base/popover' |
| | | import Divider from '@/app/components/base/divider' |
| | | import { WEB_PREFIX } from '@/config' |
| | | import { getRedirection } from '@/utils/app-redirection' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import { NEED_REFRESH_APP_LIST_KEY } from '@/config' |
| | |
| | | import { fetchWorkflowDraft } from '@/service/workflow' |
| | | import { fetchInstalledAppList } from '@/service/explore' |
| | | import { AppTypeIcon } from '@/app/components/app/type-selector' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | export type AppCardProps = { |
| | | app: App |
| | |
| | | }) |
| | | } |
| | | setShowConfirmDelete(false) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [app.id]) |
| | | |
| | | const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({ |
| | |
| | | onRefresh() |
| | | mutateApps() |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.editFailed') }) |
| | | } |
| | | }, [app.id, mutateApps, notify, onRefresh, t]) |
| | |
| | | onPlanInfoChanged() |
| | | getRedirection(isCurrentWorkspaceEditor, newApp, push) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | } |
| | |
| | | a.download = `${app.name}.yml` |
| | | a.click() |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.exportFailed') }) |
| | | } |
| | | } |
| | |
| | | } |
| | | setSecretEnvList(list) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.exportFailed') }) |
| | | } |
| | | } |
| | |
| | | try { |
| | | const { installed_apps }: any = await fetchInstalledAppList(app.id) || {} |
| | | if (installed_apps?.length > 0) |
| | | window.open(`${WEB_PREFIX}/explore/installed/${installed_apps[0].id}`, '_blank') |
| | | window.open(`/explore/installed/${installed_apps[0].id}`, '_blank') |
| | | else |
| | | throw new Error('No app found in Explore') |
| | | } |
| | |
| | | } |
| | | return ( |
| | | <div className="relative w-full py-1" onMouseLeave={onMouseLeave}> |
| | | <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickSettings}> |
| | | <span className='system-sm-regular text-text-secondary'>{t('app.editApp')}</span> |
| | | <button className={s.actionItem} onClick={onClickSettings}> |
| | | <span className={s.actionName}>{t('app.editApp')}</span> |
| | | </button> |
| | | <Divider className="!my-1" /> |
| | | <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickDuplicate}> |
| | | <span className='system-sm-regular text-text-secondary'>{t('app.duplicate')}</span> |
| | | <button className={s.actionItem} onClick={onClickDuplicate}> |
| | | <span className={s.actionName}>{t('app.duplicate')}</span> |
| | | </button> |
| | | <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickExport}> |
| | | <span className='system-sm-regular text-text-secondary'>{t('app.export')}</span> |
| | | <button className={s.actionItem} onClick={onClickExport}> |
| | | <span className={s.actionName}>{t('app.export')}</span> |
| | | </button> |
| | | {(app.mode === 'completion' || app.mode === 'chat') && ( |
| | | <> |
| | | <Divider className="!my-1" /> |
| | | <div |
| | | className='mx-1 flex h-9 cursor-pointer items-center rounded-lg px-3 py-2 hover:bg-state-base-hover' |
| | | className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' |
| | | onClick={onClickSwitch} |
| | | > |
| | | <span className='text-sm leading-5 text-text-secondary'>{t('app.switch')}</span> |
| | | <span className='text-gray-700 text-sm leading-5'>{t('app.switch')}</span> |
| | | </div> |
| | | </> |
| | | )} |
| | | <Divider className="!my-1" /> |
| | | <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickInstalledApp}> |
| | | <span className='system-sm-regular text-text-secondary'>{t('app.openInExplore')}</span> |
| | | <button className={s.actionItem} onClick={onClickInstalledApp}> |
| | | <span className={s.actionName}>{t('app.openInExplore')}</span> |
| | | </button> |
| | | <Divider className="!my-1" /> |
| | | <div |
| | | className='group mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-destructive-hover' |
| | | className={cn(s.actionItem, s.deleteActionItem, 'group')} |
| | | onClick={onClickDelete} |
| | | > |
| | | <span className='system-sm-regular text-text-secondary group-hover:text-text-destructive'> |
| | | <span className={cn(s.actionName, 'group-hover:text-red-500')}> |
| | | {t('common.operation.delete')} |
| | | </span> |
| | | </div> |
| | |
| | | e.preventDefault() |
| | | getRedirection(isCurrentWorkspaceEditor, app, push) |
| | | }} |
| | | className='group relative col-span-1 inline-flex h-[160px] cursor-pointer flex-col rounded-xl border-[1px] border-solid border-components-card-border bg-components-card-bg shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg' |
| | | className='relative h-[160px] group col-span-1 bg-components-card-bg border-[1px] border-solid border-components-card-border rounded-xl shadow-sm inline-flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg' |
| | | > |
| | | <div className='flex h-[66px] shrink-0 grow-0 items-center gap-3 px-[14px] pb-3 pt-[14px]'> |
| | | <div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'> |
| | | <div className='relative shrink-0'> |
| | | <AppIcon |
| | | size="large" |
| | |
| | | background={app.icon_background} |
| | | imageUrl={app.icon_url} |
| | | /> |
| | | <AppTypeIcon type={app.mode} wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm' className='h-3 w-3' /> |
| | | <AppTypeIcon type={app.mode} wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm' className='w-3 h-3' /> |
| | | </div> |
| | | <div className='w-0 grow py-[1px]'> |
| | | <div className='flex items-center text-sm font-semibold leading-5 text-text-secondary'> |
| | | <div className='grow w-0 py-[1px]'> |
| | | <div className='flex items-center text-sm leading-5 font-semibold text-text-secondary'> |
| | | <div className='truncate' title={app.name}>{app.name}</div> |
| | | </div> |
| | | <div className='flex items-center text-[10px] font-medium leading-[18px] text-text-tertiary'> |
| | | <div className='flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium'> |
| | | {app.mode === 'advanced-chat' && <div className='truncate'>{t('app.types.advanced').toUpperCase()}</div>} |
| | | {app.mode === 'chat' && <div className='truncate'>{t('app.types.chatbot').toUpperCase()}</div>} |
| | | {app.mode === 'agent-chat' && <div className='truncate'>{t('app.types.agent').toUpperCase()}</div>} |
| | |
| | | </div> |
| | | </div> |
| | | <div className={cn( |
| | | 'absolute bottom-1 left-0 right-0 h-[42px] shrink-0 items-center pb-[6px] pl-[14px] pr-[6px] pt-1', |
| | | 'absolute bottom-1 left-0 right-0 items-center shrink-0 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]', |
| | | tags.length ? 'flex' : '!hidden group-hover:!flex', |
| | | )}> |
| | | {isCurrentWorkspaceEditor && ( |
| | | <> |
| | | <div className={cn('flex w-0 grow items-center gap-1')} onClick={(e) => { |
| | | <div className={cn('grow flex items-center gap-1 w-0')} onClick={(e) => { |
| | | e.stopPropagation() |
| | | e.preventDefault() |
| | | }}> |
| | | <div className={cn( |
| | | 'mr-[41px] w-full grow group-hover:!mr-0 group-hover:!block', |
| | | 'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full', |
| | | tags.length ? '!block' : '!hidden', |
| | | )}> |
| | | <TagSelector |
| | |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div className='mx-1 !hidden h-[14px] w-[1px] shrink-0 group-hover:!flex' /> |
| | | <div className='!hidden shrink-0 group-hover:!flex'> |
| | | <div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px]' /> |
| | | <div className='!hidden group-hover:!flex shrink-0'> |
| | | <CustomPopover |
| | | htmlContent={<Operations />} |
| | | position="br" |
| | | trigger="click" |
| | | btnElement={ |
| | | <div |
| | | className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-md' |
| | | className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md' |
| | | > |
| | | <RiMoreFill className='h-4 w-4 text-text-tertiary' /> |
| | | <RiMoreFill className='w-4 h-4 text-text-tertiary' /> |
| | | </div> |
| | | } |
| | | btnClassName={open => |
| | | cn( |
| | | open ? '!bg-black/5 !shadow-none' : '!bg-transparent', |
| | | 'h-8 w-8 rounded-md border-none !p-2 hover:!bg-black/5', |
| | | 'h-8 w-8 !p-2 rounded-md border-none hover:!bg-black/5', |
| | | ) |
| | | } |
| | | popupClassName={ |
| | |
| | | ? '!w-[256px] translate-x-[-224px]' |
| | | : '!w-[160px] translate-x-[-128px]' |
| | | } |
| | | className={'!z-20 h-fit'} |
| | | className={'h-fit !z-20'} |
| | | /> |
| | | </div> |
| | | </> |
| | |
| | | 'use client' |
| | | |
| | | import { useCallback, useEffect, useRef, useState } from 'react' |
| | | import { |
| | | useRouter, |
| | | } from 'next/navigation' |
| | | import { useRouter } from 'next/navigation' |
| | | import useSWRInfinite from 'swr/infinite' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useDebounceFn } from 'ahooks' |
| | | import { |
| | | RiApps2Line, |
| | | RiExchange2Line, |
| | | RiFile4Line, |
| | | RiMessage3Line, |
| | | RiRobot3Line, |
| | | } from '@remixicon/react' |
| | |
| | | const [activeTab, setActiveTab] = useTabSearchParams({ |
| | | defaultTab: 'all', |
| | | }) |
| | | const { query: { tagIDs = [], keywords = '', isCreatedByMe: queryIsCreatedByMe = false }, setQuery } = useAppsQueryState() |
| | | const [isCreatedByMe, setIsCreatedByMe] = useState(queryIsCreatedByMe) |
| | | const { query: { tagIDs = [], keywords = '' }, setQuery } = useAppsQueryState() |
| | | const [isCreatedByMe, setIsCreatedByMe] = useState(false) |
| | | const [tagFilterValue, setTagFilterValue] = useState<string[]>(tagIDs) |
| | | const [searchKeywords, setSearchKeywords] = useState(keywords) |
| | | const newAppCardRef = useRef<HTMLDivElement>(null) |
| | | const setKeywords = useCallback((keywords: string) => { |
| | | setQuery(prev => ({ ...prev, keywords })) |
| | | }, [setQuery]) |
| | |
| | | setQuery(prev => ({ ...prev, tagIDs })) |
| | | }, [setQuery]) |
| | | |
| | | const { data, isLoading, error, setSize, mutate } = useSWRInfinite( |
| | | const { data, isLoading, setSize, mutate } = useSWRInfinite( |
| | | (pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, isCreatedByMe, tagIDs, searchKeywords), |
| | | fetchAppList, |
| | | { |
| | | revalidateFirstPage: true, |
| | | shouldRetryOnError: false, |
| | | dedupingInterval: 500, |
| | | errorRetryCount: 3, |
| | | }, |
| | | { revalidateFirstPage: true }, |
| | | ) |
| | | |
| | | const anchorRef = useRef<HTMLDivElement>(null) |
| | | const options = [ |
| | | { value: 'all', text: t('app.types.all'), icon: <RiApps2Line className='mr-1 h-[14px] w-[14px]' /> }, |
| | | { value: 'chat', text: t('app.types.chatbot'), icon: <RiMessage3Line className='mr-1 h-[14px] w-[14px]' /> }, |
| | | { value: 'agent-chat', text: t('app.types.agent'), icon: <RiRobot3Line className='mr-1 h-[14px] w-[14px]' /> }, |
| | | { value: 'completion', text: t('app.types.completion'), icon: <RiFile4Line className='mr-1 h-[14px] w-[14px]' /> }, |
| | | { value: 'advanced-chat', text: t('app.types.advanced'), icon: <RiMessage3Line className='mr-1 h-[14px] w-[14px]' /> }, |
| | | { value: 'workflow', text: t('app.types.workflow'), icon: <RiExchange2Line className='mr-1 h-[14px] w-[14px]' /> }, |
| | | { value: 'all', text: t('app.types.all'), icon: <RiApps2Line className='w-[14px] h-[14px] mr-1' /> }, |
| | | { value: 'chat', text: t('app.types.chatbot'), icon: <RiMessage3Line className='w-[14px] h-[14px] mr-1' /> }, |
| | | { value: 'agent-chat', text: t('app.types.agent'), icon: <RiRobot3Line className='w-[14px] h-[14px] mr-1' /> }, |
| | | { value: 'workflow', text: t('app.types.workflow'), icon: <RiExchange2Line className='w-[14px] h-[14px] mr-1' /> }, |
| | | ] |
| | | |
| | | useEffect(() => { |
| | |
| | | useEffect(() => { |
| | | const hasMore = data?.at(-1)?.has_more ?? true |
| | | let observer: IntersectionObserver | undefined |
| | | |
| | | if (error) { |
| | | if (observer) |
| | | observer.disconnect() |
| | | return |
| | | } |
| | | |
| | | if (anchorRef.current) { |
| | | observer = new IntersectionObserver((entries) => { |
| | | if (entries[0].isIntersecting && !isLoading && !error && hasMore) |
| | | if (entries[0].isIntersecting && !isLoading && hasMore) |
| | | setSize((size: number) => size + 1) |
| | | }, { rootMargin: '100px' }) |
| | | observer.observe(anchorRef.current) |
| | | } |
| | | return () => observer?.disconnect() |
| | | }, [isLoading, setSize, anchorRef, mutate, data, error]) |
| | | }, [isLoading, setSize, anchorRef, mutate, data]) |
| | | |
| | | const { run: handleSearch } = useDebounceFn(() => { |
| | | setSearchKeywords(keywords) |
| | |
| | | handleTagsUpdate() |
| | | } |
| | | |
| | | const handleCreatedByMeChange = useCallback(() => { |
| | | const newValue = !isCreatedByMe |
| | | setIsCreatedByMe(newValue) |
| | | setQuery(prev => ({ ...prev, isCreatedByMe: newValue })) |
| | | }, [isCreatedByMe, setQuery]) |
| | | |
| | | return ( |
| | | <> |
| | | <div className='sticky top-0 z-10 flex flex-wrap items-center justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'> |
| | | <div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'> |
| | | <TabSliderNew |
| | | value={activeTab} |
| | | onChange={setActiveTab} |
| | |
| | | className='mr-2' |
| | | label={t('app.showMyCreatedAppsOnly')} |
| | | isChecked={isCreatedByMe} |
| | | onChange={handleCreatedByMeChange} |
| | | onChange={() => setIsCreatedByMe(!isCreatedByMe)} |
| | | /> |
| | | <TagFilter type='app' value={tagFilterValue} onChange={handleTagsChange} /> |
| | | <Input |
| | |
| | | </div> |
| | | </div> |
| | | {(data && data[0].total > 0) |
| | | ? <div className='relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'> |
| | | ? <div className='grid content-start grid-cols-1 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6 gap-4 px-12 pt-2 grow relative'> |
| | | {isCurrentWorkspaceEditor |
| | | && <NewAppCard ref={newAppCardRef} onSuccess={mutate} />} |
| | | && <NewAppCard onSuccess={mutate} />} |
| | | {data.map(({ data: apps }) => apps.map(app => ( |
| | | <AppCard key={app.id} app={app} onRefresh={mutate} /> |
| | | )))} |
| | | </div> |
| | | : <div className='relative grid grow grid-cols-1 content-start gap-4 overflow-hidden px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'> |
| | | : <div className='grid content-start grid-cols-1 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6 gap-4 px-12 pt-2 grow relative overflow-hidden'> |
| | | {isCurrentWorkspaceEditor |
| | | && <NewAppCard ref={newAppCardRef} className='z-10' onSuccess={mutate} />} |
| | | && <NewAppCard className='z-10' onSuccess={mutate} />} |
| | | <NoAppsFound /> |
| | | </div>} |
| | | <CheckModal /> |
| | |
| | | const { t } = useTranslation() |
| | | function renderDefaultCard() { |
| | | const defaultCards = Array.from({ length: 36 }, (_, index) => ( |
| | | <div key={index} className='inline-flex h-[160px] rounded-xl bg-background-default-lighter'></div> |
| | | <div key={index} className='h-[160px] inline-flex rounded-xl bg-background-default-lighter'></div> |
| | | )) |
| | | return defaultCards |
| | | } |
| | | return ( |
| | | <> |
| | | {renderDefaultCard()} |
| | | <div className='absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent'> |
| | | <div className='absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent'> |
| | | <span className='system-md-medium text-text-tertiary'>{t('app.newApp.noAppsFound')}</span> |
| | | </div> |
| | | </> |
| | |
| | | 'use client' |
| | | |
| | | import { useMemo, useState } from 'react' |
| | | import { forwardRef, useMemo, useState } from 'react' |
| | | import { |
| | | useRouter, |
| | | useSearchParams, |
| | |
| | | onSuccess?: () => void |
| | | } |
| | | |
| | | const CreateAppCard = ( |
| | | { |
| | | ref, |
| | | className, |
| | | onSuccess, |
| | | }: CreateAppCardProps & { |
| | | ref: React.RefObject<HTMLDivElement>; |
| | | }, |
| | | ) => { |
| | | const CreateAppCard = forwardRef<HTMLDivElement, CreateAppCardProps>(({ className, onSuccess }, ref) => { |
| | | const { t } = useTranslation() |
| | | const { onPlanInfoChanged } = useProviderContext() |
| | | const searchParams = useSearchParams() |
| | |
| | | return ( |
| | | <div |
| | | ref={ref} |
| | | className={cn('relative col-span-1 inline-flex h-[160px] flex-col justify-between rounded-xl border-[0.5px] border-components-card-border bg-components-card-bg', className)} |
| | | className={cn('relative col-span-1 inline-flex flex-col justify-between h-[160px] bg-components-card-bg rounded-xl border-[0.5px] border-components-card-border', className)} |
| | | > |
| | | <div className='grow rounded-t-xl p-2'> |
| | | <div className='px-6 pb-1 pt-2 text-xs font-medium leading-[18px] text-text-tertiary'>{t('app.createApp')}</div> |
| | | <button className='mb-1 flex w-full cursor-pointer items-center rounded-lg px-6 py-[7px] text-[13px] font-medium leading-[18px] text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary' onClick={() => setShowNewAppModal(true)}> |
| | | <FilePlus01 className='mr-2 h-4 w-4 shrink-0' /> |
| | | <div className='grow p-2 rounded-t-xl'> |
| | | <div className='px-6 pt-2 pb-1 text-xs font-medium leading-[18px] text-text-tertiary'>{t('app.createApp')}</div> |
| | | <button className='w-full flex items-center mb-1 px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover' onClick={() => setShowNewAppModal(true)}> |
| | | <FilePlus01 className='shrink-0 mr-2 w-4 h-4' /> |
| | | {t('app.newApp.startFromBlank')} |
| | | </button> |
| | | <button className='flex w-full cursor-pointer items-center rounded-lg px-6 py-[7px] text-[13px] font-medium leading-[18px] text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary' onClick={() => setShowNewAppTemplateDialog(true)}> |
| | | <FilePlus02 className='mr-2 h-4 w-4 shrink-0' /> |
| | | <button className='w-full flex items-center px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover' onClick={() => setShowNewAppTemplateDialog(true)}> |
| | | <FilePlus02 className='shrink-0 mr-2 w-4 h-4' /> |
| | | {t('app.newApp.startFromTemplate')} |
| | | </button> |
| | | <button |
| | | onClick={() => setShowCreateFromDSLModal(true)} |
| | | className='flex w-full cursor-pointer items-center rounded-lg px-6 py-[7px] text-[13px] font-medium leading-[18px] text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary'> |
| | | <FileArrow01 className='mr-2 h-4 w-4 shrink-0' /> |
| | | className='w-full flex items-center px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover'> |
| | | <FileArrow01 className='shrink-0 mr-2 w-4 h-4' /> |
| | | {t('app.importDSL')} |
| | | </button> |
| | | </div> |
| | |
| | | /> |
| | | </div> |
| | | ) |
| | | } |
| | | }) |
| | | |
| | | CreateAppCard.displayName = 'CreateAppCard' |
| | | export default CreateAppCard |
| | |
| | | type AppsQuery = { |
| | | tagIDs?: string[] |
| | | keywords?: string |
| | | isCreatedByMe?: boolean |
| | | } |
| | | |
| | | // Parse the query parameters from the URL search string. |
| | | function parseParams(params: ReadonlyURLSearchParams): AppsQuery { |
| | | const tagIDs = params.get('tagIDs')?.split(';') |
| | | const keywords = params.get('keywords') || undefined |
| | | const isCreatedByMe = params.get('isCreatedByMe') === 'true' |
| | | return { tagIDs, keywords, isCreatedByMe } |
| | | return { tagIDs, keywords } |
| | | } |
| | | |
| | | // Update the URL search string with the given query parameters. |
| | | function updateSearchParams(query: AppsQuery, current: URLSearchParams) { |
| | | const { tagIDs, keywords, isCreatedByMe } = query || {} |
| | | const { tagIDs, keywords } = query || {} |
| | | |
| | | if (tagIDs && tagIDs.length > 0) |
| | | current.set('tagIDs', tagIDs.join(';')) |
| | |
| | | current.set('keywords', keywords) |
| | | else |
| | | current.delete('keywords') |
| | | |
| | | if (isCreatedByMe) |
| | | current.set('isCreatedByMe', 'true') |
| | | else |
| | | current.delete('isCreatedByMe') |
| | | } |
| | | |
| | | function useAppsQueryState() { |
| | |
| | | import Apps from './Apps' |
| | | import AppContext from '@/context/app-context' |
| | | import { LicenseStatus } from '@/types/feature' |
| | | import { useEducationInit } from '@/app/education-apply/hooks' |
| | | |
| | | const AppList = () => { |
| | | const { t } = useTranslation() |
| | | useEducationInit() |
| | | |
| | | const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures) |
| | | |
| | | return ( |
| | | <div className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body'> |
| | | <div className='relative flex flex-col overflow-y-auto bg-background-body shrink-0 h-0 grow'> |
| | | <Apps /> |
| | | {systemFeatures.license.status === LicenseStatus.NONE && <footer className='shrink-0 grow-0 px-12 py-6'> |
| | | <h3 className='text-gradient text-xl font-semibold leading-tight'>{t('app.join')}</h3> |
| | | <p className='system-sm-regular mt-1 text-text-tertiary'>{t('app.communityIntro')}</p> |
| | | <div className='mt-3 flex items-center gap-2'> |
| | | {systemFeatures.license.status === LicenseStatus.NONE && <footer className='px-12 py-6 grow-0 shrink-0'> |
| | | <h3 className='text-xl font-semibold leading-tight text-gradient'>{t('app.join')}</h3> |
| | | <p className='mt-1 system-sm-regular text-text-tertiary'>{t('app.communityIntro')}</p> |
| | | <div className='flex items-center gap-2 mt-3'> |
| | | <Link className={style.socialMediaLink} target='_blank' rel='noopener noreferrer' href='https://github.com/langgenius/dify'> |
| | | <RiGithubFill className='h-5 w-5 text-text-tertiary' /> |
| | | <RiGithubFill className='w-5 h-5 text-text-tertiary' /> |
| | | </Link> |
| | | <Link className={style.socialMediaLink} target='_blank' rel='noopener noreferrer' href='https://discord.gg/FngNHpbcY7'> |
| | | <RiDiscordFill className='h-5 w-5 text-text-tertiary' /> |
| | | <RiDiscordFill className='w-5 h-5 text-text-tertiary' /> |
| | | </Link> |
| | | </div> |
| | | </footer>} |
New file |
| | |
| | | |
| | | .commonIcon { |
| | | @apply w-4 h-4 inline-block align-middle; |
| | | background-repeat: no-repeat; |
| | | background-position: center center; |
| | | background-size: contain; |
| | | } |
| | | .actionIcon { |
| | | @apply bg-gray-500; |
| | | mask-image: url(~@/assets/action.svg); |
| | | } |
| | | .actionItem { |
| | | @apply h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer; |
| | | width: calc(100% - 0.5rem); |
| | | } |
| | | .deleteActionItem { |
| | | @apply hover:bg-red-50 !important; |
| | | } |
| | | .actionName { |
| | | @apply text-gray-700 text-sm; |
| | | } |
| | | |
| | | /* .completionPic { |
| | | background-image: url(~@/app/components/app-sidebar/completion.png) |
| | | } |
| | | |
| | | .expertPic { |
| | | background-image: url(~@/app/components/app-sidebar/expert.png) |
| | | } */ |
| | |
| | | import React from 'react' |
| | | |
| | | const page = () => { |
| | | type Props = {} |
| | | |
| | | const page = (props: Props) => { |
| | | return ( |
| | | <div>dataset detail api</div> |
| | | ) |
| | |
| | | import MainDetail from '@/app/components/datasets/documents/detail' |
| | | |
| | | export type IDocumentDetailProps = { |
| | | params: Promise<{ datasetId: string; documentId: string }> |
| | | params: { datasetId: string; documentId: string } |
| | | } |
| | | |
| | | const DocumentDetail = async (props: IDocumentDetailProps) => { |
| | | const params = await props.params |
| | | |
| | | const { |
| | | datasetId, |
| | | documentId, |
| | | } = params |
| | | |
| | | const DocumentDetail = async ({ |
| | | params: { datasetId, documentId }, |
| | | }: IDocumentDetailProps) => { |
| | | return ( |
| | | <MainDetail datasetId={datasetId} documentId={documentId} /> |
| | | ) |
| | |
| | | import Settings from '@/app/components/datasets/documents/detail/settings' |
| | | |
| | | export type IProps = { |
| | | params: Promise<{ datasetId: string; documentId: string }> |
| | | params: { datasetId: string; documentId: string } |
| | | } |
| | | |
| | | const DocumentSettings = async (props: IProps) => { |
| | | const params = await props.params |
| | | |
| | | const { |
| | | datasetId, |
| | | documentId, |
| | | } = params |
| | | |
| | | const DocumentSettings = async ({ |
| | | params: { datasetId, documentId }, |
| | | }: IProps) => { |
| | | return ( |
| | | <Settings datasetId={datasetId} documentId={documentId} /> |
| | | ) |
| | |
| | | import DatasetUpdateForm from '@/app/components/datasets/create' |
| | | |
| | | export type IProps = { |
| | | params: Promise<{ datasetId: string }> |
| | | params: { datasetId: string } |
| | | } |
| | | |
| | | const Create = async (props: IProps) => { |
| | | const params = await props.params |
| | | |
| | | const { |
| | | datasetId, |
| | | } = params |
| | | |
| | | const Create = async ({ |
| | | params: { datasetId }, |
| | | }: IProps) => { |
| | | return ( |
| | | <DatasetUpdateForm datasetId={datasetId} /> |
| | | ) |
| | |
| | | import Main from '@/app/components/datasets/documents' |
| | | |
| | | export type IProps = { |
| | | params: Promise<{ datasetId: string }> |
| | | params: { datasetId: string } |
| | | } |
| | | |
| | | const Documents = async (props: IProps) => { |
| | | const params = await props.params |
| | | |
| | | const { |
| | | datasetId, |
| | | } = params |
| | | |
| | | const Documents = async ({ |
| | | params: { datasetId }, |
| | | }: IProps) => { |
| | | return ( |
| | | <Main datasetId={datasetId} /> |
| | | ) |
| | |
| | | import Main from '@/app/components/datasets/hit-testing' |
| | | |
| | | type Props = { |
| | | params: Promise<{ datasetId: string }> |
| | | params: { datasetId: string } |
| | | } |
| | | |
| | | const HitTesting = async (props: Props) => { |
| | | const params = await props.params |
| | | |
| | | const { |
| | | datasetId, |
| | | } = params |
| | | |
| | | const HitTesting = ({ |
| | | params: { datasetId }, |
| | | }: Props) => { |
| | | return ( |
| | | <Main datasetId={datasetId} /> |
| | | ) |
| | |
| | | import Main from './layout-main' |
| | | 'use client' |
| | | import type { FC, SVGProps } from 'react' |
| | | import React, { useEffect, useMemo } from 'react' |
| | | import { usePathname } from 'next/navigation' |
| | | import useSWR from 'swr' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useBoolean } from 'ahooks' |
| | | import { |
| | | Cog8ToothIcon, |
| | | DocumentTextIcon, |
| | | PaperClipIcon, |
| | | } from '@heroicons/react/24/outline' |
| | | import { |
| | | Cog8ToothIcon as Cog8ToothSolidIcon, |
| | | // CommandLineIcon as CommandLineSolidIcon, |
| | | DocumentTextIcon as DocumentTextSolidIcon, |
| | | } from '@heroicons/react/24/solid' |
| | | import { RiApps2AddLine, RiInformation2Line } from '@remixicon/react' |
| | | import s from './style.module.css' |
| | | import classNames from '@/utils/classnames' |
| | | import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets' |
| | | import type { RelatedAppResponse } from '@/models/datasets' |
| | | import AppSideBar from '@/app/components/app-sidebar' |
| | | import Loading from '@/app/components/base/loading' |
| | | import DatasetDetailContext from '@/context/dataset-detail' |
| | | import { DataSourceType } from '@/models/datasets' |
| | | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' |
| | | import { LanguagesSupported } from '@/i18n/language' |
| | | import { useStore } from '@/app/components/app/store' |
| | | import { getLocaleOnClient } from '@/i18n' |
| | | import { useAppContext } from '@/context/app-context' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import LinkedAppsPanel from '@/app/components/base/linked-apps-panel' |
| | | |
| | | const DatasetDetailLayout = async ( |
| | | props: { |
| | | export type IAppDetailLayoutProps = { |
| | | children: React.ReactNode |
| | | params: Promise<{ datasetId: string }> |
| | | }, |
| | | ) => { |
| | | const params = await props.params |
| | | params: { datasetId: string } |
| | | } |
| | | |
| | | const TargetIcon = ({ className }: SVGProps<SVGElement>) => { |
| | | return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}> |
| | | <g clipPath="url(#clip0_4610_6951)"> |
| | | <path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" /> |
| | | </g> |
| | | <defs> |
| | | <clipPath id="clip0_4610_6951"> |
| | | <rect width="16" height="16" fill="white" /> |
| | | </clipPath> |
| | | </defs> |
| | | </svg> |
| | | } |
| | | |
| | | const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => { |
| | | return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}> |
| | | <path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" /> |
| | | <path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" /> |
| | | <path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" /> |
| | | </svg> |
| | | } |
| | | |
| | | const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => { |
| | | return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}> |
| | | <path opacity="0.12" d="M1 3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7V10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1Z" fill="#155EEF" /> |
| | | <path d="M6 10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7M6 10.5V4.7M6 10.5L6.05003 10.425C6.39735 9.90398 6.57101 9.64349 6.80045 9.45491C7.00357 9.28796 7.23762 9.1627 7.4892 9.0863C7.77337 9 8.08645 9 8.71259 9H9.4C9.96005 9 10.2401 9 10.454 8.89101C10.6422 8.79513 10.7951 8.64215 10.891 8.45399C11 8.24008 11 7.96005 11 7.4V3.1C11 2.53995 11 2.25992 10.891 2.04601C10.7951 1.85785 10.6422 1.70487 10.454 1.60899C10.2401 1.5 9.96005 1.5 9.4 1.5H9.2C8.07989 1.5 7.51984 1.5 7.09202 1.71799C6.71569 1.90973 6.40973 2.21569 6.21799 2.59202C6 3.01984 6 3.5799 6 4.7" stroke="#155EEF" strokeLinecap="round" strokeLinejoin="round" /> |
| | | </svg> |
| | | } |
| | | |
| | | type IExtraInfoProps = { |
| | | isMobile: boolean |
| | | relatedApps?: RelatedAppResponse |
| | | expand: boolean |
| | | } |
| | | |
| | | const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { |
| | | const locale = getLocaleOnClient() |
| | | const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile) |
| | | const { t } = useTranslation() |
| | | |
| | | const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0 |
| | | const relatedAppsTotal = relatedApps?.data?.length || 0 |
| | | |
| | | useEffect(() => { |
| | | setShowTips(!isMobile) |
| | | }, [isMobile, setShowTips]) |
| | | |
| | | return <div> |
| | | {hasRelatedApps && ( |
| | | <> |
| | | {!isMobile && ( |
| | | <Tooltip |
| | | position='right' |
| | | noDecoration |
| | | needsDelay |
| | | popupContent={ |
| | | <LinkedAppsPanel |
| | | relatedApps={relatedApps.data} |
| | | isMobile={isMobile} |
| | | /> |
| | | } |
| | | > |
| | | <div className='inline-flex items-center system-xs-medium-uppercase text-text-secondary space-x-1 cursor-pointer'> |
| | | <span>{relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')}</span> |
| | | <RiInformation2Line className='w-4 h-4' /> |
| | | </div> |
| | | </Tooltip> |
| | | )} |
| | | |
| | | {isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}> |
| | | {relatedAppsTotal || '--'} |
| | | <PaperClipIcon className='h-4 w-4 text-gray-700' /> |
| | | </div>} |
| | | </> |
| | | )} |
| | | {!hasRelatedApps && !expand && ( |
| | | <Tooltip |
| | | position='right' |
| | | noDecoration |
| | | needsDelay |
| | | popupContent={ |
| | | <div className='p-4 w-[240px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border rounded-xl'> |
| | | <div className='inline-flex p-2 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle'> |
| | | <RiApps2AddLine className='h-4 w-4 text-text-tertiary' /> |
| | | </div> |
| | | <div className='text-xs text-text-tertiary my-2'>{t('common.datasetMenus.emptyTip')}</div> |
| | | <a |
| | | className='inline-flex items-center text-xs text-text-accent mt-2 cursor-pointer' |
| | | href={ |
| | | locale === LanguagesSupported[1] |
| | | ? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' |
| | | : 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application' |
| | | } |
| | | target='_blank' rel='noopener noreferrer' |
| | | > |
| | | <BookOpenIcon className='mr-1' /> |
| | | {t('common.datasetMenus.viewDoc')} |
| | | </a> |
| | | </div> |
| | | } |
| | | > |
| | | <div className='inline-flex items-center system-xs-medium-uppercase text-text-secondary space-x-1 cursor-pointer'> |
| | | <span>{t('common.datasetMenus.noRelatedApp')}</span> |
| | | <RiInformation2Line className='w-4 h-4' /> |
| | | </div> |
| | | </Tooltip> |
| | | )} |
| | | </div> |
| | | } |
| | | |
| | | const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => { |
| | | const { |
| | | children, |
| | | params: { datasetId }, |
| | | } = props |
| | | const pathname = usePathname() |
| | | const hideSideBar = /documents\/create$/.test(pathname) |
| | | const { t } = useTranslation() |
| | | const { isCurrentWorkspaceDatasetOperator } = useAppContext() |
| | | |
| | | return <Main params={(await params)}>{children}</Main> |
| | | const media = useBreakpoints() |
| | | const isMobile = media === MediaType.mobile |
| | | |
| | | const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({ |
| | | url: 'fetchDatasetDetail', |
| | | datasetId, |
| | | }, apiParams => fetchDatasetDetail(apiParams.datasetId)) |
| | | |
| | | const { data: relatedApps } = useSWR({ |
| | | action: 'fetchDatasetRelatedApps', |
| | | datasetId, |
| | | }, apiParams => fetchDatasetRelatedApps(apiParams.datasetId)) |
| | | |
| | | const navigation = useMemo(() => { |
| | | const baseNavigation = [ |
| | | { name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon }, |
| | | // { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon }, |
| | | { name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon }, |
| | | ] |
| | | |
| | | if (datasetRes?.provider !== 'external') { |
| | | baseNavigation.unshift({ |
| | | name: t('common.datasetMenus.documents'), |
| | | href: `/datasets/${datasetId}/documents`, |
| | | icon: DocumentTextIcon, |
| | | selectedIcon: DocumentTextSolidIcon, |
| | | }) |
| | | } |
| | | export default DatasetDetailLayout |
| | | return baseNavigation |
| | | }, [datasetRes?.provider, datasetId, t]) |
| | | |
| | | useEffect(() => { |
| | | if (datasetRes) |
| | | document.title = `${datasetRes.name || 'Dataset'} - Dify` |
| | | }, [datasetRes]) |
| | | |
| | | const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand) |
| | | |
| | | useEffect(() => { |
| | | const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' |
| | | const mode = isMobile ? 'collapse' : 'expand' |
| | | setAppSiderbarExpand(isMobile ? mode : localeMode) |
| | | }, [isMobile, setAppSiderbarExpand]) |
| | | |
| | | if (!datasetRes && !error) |
| | | return <Loading type='app' /> |
| | | |
| | | return ( |
| | | <div className='grow flex overflow-hidden'> |
| | | {!hideSideBar && <AppSideBar |
| | | title={datasetRes?.name || '--'} |
| | | icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'} |
| | | icon_background={datasetRes?.icon_background || '#F5F5F5'} |
| | | desc={datasetRes?.description || '--'} |
| | | isExternal={datasetRes?.provider === 'external'} |
| | | navigation={navigation} |
| | | extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} expand={mode === 'collapse'} /> : undefined} |
| | | iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} |
| | | />} |
| | | <DatasetDetailContext.Provider value={{ |
| | | indexingTechnique: datasetRes?.indexing_technique, |
| | | dataset: datasetRes, |
| | | mutateDatasetRes: () => mutateDatasetRes(), |
| | | }}> |
| | | <div className="bg-background-default-subtle grow overflow-hidden">{children}</div> |
| | | </DatasetDetailContext.Provider> |
| | | </div> |
| | | ) |
| | | } |
| | | export default React.memo(DatasetDetailLayout) |
| | |
| | | import Form from '@/app/components/datasets/settings/form' |
| | | |
| | | const Settings = async () => { |
| | | const locale = await getLocaleOnServer() |
| | | const locale = getLocaleOnServer() |
| | | const { t } = await translate(locale, 'dataset-settings') |
| | | |
| | | return ( |
| | | <div className='h-full overflow-y-auto'> |
| | | <div className='px-6 py-3'> |
| | | <div className='system-xl-semibold mb-1 text-text-primary'>{t('title')}</div> |
| | | <div className='mb-1 system-xl-semibold text-text-primary'>{t('title')}</div> |
| | | <div className='system-sm-regular text-text-tertiary'>{t('desc')}</div> |
| | | </div> |
| | | <Form /> |
New file |
| | |
| | | .statusPoint { |
| | | @apply flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded; |
| | | } |
| | | .subTitle { |
| | | @apply uppercase text-xs text-gray-500 font-medium px-3 pb-2 pt-4; |
| | | } |
| | | .emptyIconDiv { |
| | | @apply h-7 w-7 bg-gray-50 border border-[#EAECF5] inline-flex justify-center items-center rounded-lg; |
| | | } |
New file |
| | |
| | | 'use client' |
| | | |
| | | import type { FC } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import CopyFeedback from '@/app/components/base/copy-feedback' |
| | | import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button' |
| | | import { randomString } from '@/utils' |
| | | |
| | | type ApiServerProps = { |
| | | apiBaseUrl: string |
| | | } |
| | | const ApiServer: FC<ApiServerProps> = ({ |
| | | apiBaseUrl, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className='flex items-center flex-wrap gap-y-2'> |
| | | <div className='flex items-center mr-2 pl-1.5 pr-1 h-8 bg-white/80 border-[0.5px] border-white rounded-lg leading-5'> |
| | | <div className='mr-0.5 px-1.5 h-5 border border-gray-200 text-[11px] text-gray-500 rounded-md shrink-0'>{t('appApi.apiServer')}</div> |
| | | <div className='px-1 truncate w-fit sm:w-[248px] text-[13px] font-medium text-gray-800'>{apiBaseUrl}</div> |
| | | <div className='mx-1 w-[1px] h-[14px] bg-gray-200'></div> |
| | | <CopyFeedback |
| | | content={apiBaseUrl} |
| | | selectorId={randomString(8)} |
| | | className={'!w-6 !h-6 hover:bg-gray-200'} |
| | | /> |
| | | </div> |
| | | <div className='flex items-center mr-2 px-3 h-8 bg-[#ECFDF3] text-xs font-semibold text-[#039855] rounded-lg border-[0.5px] border-[#D1FADF]'> |
| | | {t('appApi.ok')} |
| | | </div> |
| | | <SecretKeyButton |
| | | className='flex-shrink-0 !h-8 bg-white' |
| | | textCls='!text-gray-700 font-medium' |
| | | iconCls='stroke-[1.2px]' |
| | | /> |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | export default ApiServer |
| | |
| | | import ExternalAPIPanel from '../../components/datasets/external-api/external-api-panel' |
| | | import Datasets from './Datasets' |
| | | import DatasetFooter from './DatasetFooter' |
| | | import ApiServer from '../../components/develop/ApiServer' |
| | | import ApiServer from './ApiServer' |
| | | import Doc from './Doc' |
| | | import TabSliderNew from '@/app/components/base/tab-slider-new' |
| | | import TagManagementModal from '@/app/components/base/tag-management' |
| | |
| | | const showTagManagementModal = useTagStore(s => s.showTagManagementModal) |
| | | const { showExternalApiPanel, setShowExternalApiPanel } = useExternalApiPanel() |
| | | const [includeAll, { toggle: toggleIncludeAll }] = useBoolean(false) |
| | | |
| | | document.title = `${t('dataset.knowledge')} - Dify` |
| | | |
| | | const options = useMemo(() => { |
| | | return [ |
| | |
| | | }, [currentWorkspace, router]) |
| | | |
| | | return ( |
| | | <div ref={containerRef} className='scroll-container relative flex grow flex-col overflow-y-auto bg-background-body'> |
| | | <div className='sticky top-0 z-10 flex flex-wrap items-center justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'> |
| | | <div ref={containerRef} className='grow relative flex flex-col bg-background-body overflow-y-auto scroll-container'> |
| | | <div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'> |
| | | <TabSliderNew |
| | | value={activeTab} |
| | | onChange={newActiveTab => setActiveTab(newActiveTab)} |
| | |
| | | onChange={e => handleKeywordsChange(e.target.value)} |
| | | onClear={() => handleKeywordsChange('')} |
| | | /> |
| | | <div className="h-4 w-[1px] bg-divider-regular" /> |
| | | <div className="w-[1px] h-4 bg-divider-regular" /> |
| | | <Button |
| | | className='shadows-shadow-xs gap-0.5' |
| | | className='gap-0.5 shadows-shadow-xs' |
| | | onClick={() => setShowExternalApiPanel(true)} |
| | | > |
| | | <ApiConnectionMod className='h-4 w-4 text-components-button-secondary-text' /> |
| | | <div className='system-sm-medium flex items-center justify-center gap-1 px-0.5 text-components-button-secondary-text'>{t('dataset.externalAPIPanelTitle')}</div> |
| | | <ApiConnectionMod className='w-4 h-4 text-components-button-secondary-text' /> |
| | | <div className='flex px-0.5 justify-center items-center gap-1 text-components-button-secondary-text system-sm-medium'>{t('dataset.externalAPIPanelTitle')}</div> |
| | | </Button> |
| | | </div> |
| | | )} |
| | |
| | | if (onSuccess) |
| | | onSuccess() |
| | | } |
| | | catch { |
| | | catch (e: any) { |
| | | } |
| | | setShowConfirmDelete(false) |
| | | }, [dataset.id, notify, onSuccess, t]) |
| | |
| | | } |
| | | return ( |
| | | <div className="relative w-full py-1" onMouseLeave={onMouseLeave}> |
| | | <div className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickRename}> |
| | | <span className='text-sm text-text-secondary'>{t('common.operation.settings')}</span> |
| | | <div className='h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer' onClick={onClickRename}> |
| | | <span className='text-gray-700 text-sm'>{t('common.operation.settings')}</span> |
| | | </div> |
| | | {props.showDelete && ( |
| | | <> |
| | | <Divider className="!my-1" /> |
| | | <div |
| | | className='group mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-destructive-hover' |
| | | className='group h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-red-50 rounded-lg cursor-pointer' |
| | | onClick={onClickDelete} |
| | | > |
| | | <span className={cn('text-sm text-text-secondary', 'group-hover:text-text-destructive')}> |
| | | <span className={cn('text-gray-700 text-sm', 'group-hover:text-red-500')}> |
| | | {t('common.operation.delete')} |
| | | </span> |
| | | </div> |
| | |
| | | return ( |
| | | <> |
| | | <div |
| | | className='group relative col-span-1 flex min-h-[160px] cursor-pointer flex-col rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg' |
| | | className='group relative col-span-1 bg-components-card-bg border-[0.5px] border-solid border-components-card-border rounded-xl shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg' |
| | | data-disable-nprogress={true} |
| | | onClick={(e) => { |
| | | e.preventDefault() |
| | |
| | | }} |
| | | > |
| | | {isExternalProvider(dataset.provider) && <CornerLabel label='External' className='absolute right-0' labelClassName='rounded-tr-xl' />} |
| | | <div className='flex h-[66px] shrink-0 grow-0 items-center gap-3 px-[14px] pb-3 pt-[14px]'> |
| | | <div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'> |
| | | <div className={cn( |
| | | 'flex shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#E0EAFF] bg-[#F5F8FF] p-2.5', |
| | | 'shrink-0 flex items-center justify-center p-2.5 bg-[#F5F8FF] rounded-md border-[0.5px] border-[#E0EAFF]', |
| | | !dataset.embedding_available && 'opacity-50 hover:opacity-100', |
| | | )}> |
| | | <Folder className='h-5 w-5 text-[#444CE7]' /> |
| | | <Folder className='w-5 h-5 text-[#444CE7]' /> |
| | | </div> |
| | | <div className='w-0 grow py-[1px]'> |
| | | <div className='flex items-center text-sm font-semibold leading-5 text-text-secondary'> |
| | | <div className={cn('truncate', !dataset.embedding_available && 'text-text-tertiary opacity-50 hover:opacity-100')} title={dataset.name}>{dataset.name}</div> |
| | | <div className='grow w-0 py-[1px]'> |
| | | <div className='flex items-center text-sm leading-5 font-semibold text-text-secondary'> |
| | | <div className={cn('truncate', !dataset.embedding_available && 'opacity-50 hover:opacity-100 text-text-tertiary')} title={dataset.name}>{dataset.name}</div> |
| | | {!dataset.embedding_available && ( |
| | | <Tooltip |
| | | popupContent={t('dataset.unavailableTip')} |
| | | > |
| | | <span className='ml-1 inline-flex w-max shrink-0 rounded-md border border-divider-regular px-1 text-xs font-normal leading-[18px] text-text-tertiary'>{t('dataset.unavailable')}</span> |
| | | <span className='shrink-0 inline-flex w-max ml-1 px-1 border border-gray-200 rounded-md text-gray-500 text-xs font-normal leading-[18px]'>{t('dataset.unavailable')}</span> |
| | | </Tooltip> |
| | | )} |
| | | </div> |
| | | <div className='mt-[1px] flex items-center text-xs leading-[18px] text-text-tertiary'> |
| | | <div className='flex items-center mt-[1px] text-xs leading-[18px] text-text-tertiary'> |
| | | <div |
| | | className={cn('truncate', (!dataset.embedding_available || !dataset.document_count) && 'opacity-50')} |
| | | title={dataset.provider === 'external' ? `${dataset.app_count}${t('dataset.appCount')}` : `${dataset.document_count}${t('dataset.documentCount')} · ${Math.round(dataset.word_count / 1000)}${t('dataset.wordCount')} · ${dataset.app_count}${t('dataset.appCount')}`} |
| | |
| | | </> |
| | | : <> |
| | | <span>{dataset.document_count}{t('dataset.documentCount')}</span> |
| | | <span className='mx-0.5 w-1 shrink-0 text-text-tertiary'>·</span> |
| | | <span className='shrink-0 mx-0.5 w-1 text-gray-400'>·</span> |
| | | <span>{Math.round(dataset.word_count / 1000)}{t('dataset.wordCount')}</span> |
| | | <span className='mx-0.5 w-1 shrink-0 text-text-tertiary'>·</span> |
| | | <span className='shrink-0 mx-0.5 w-1 text-gray-400'>·</span> |
| | | <span>{dataset.app_count}{t('dataset.appCount')}</span> |
| | | </> |
| | | } |
| | |
| | | </div> |
| | | <div |
| | | className={cn( |
| | | 'mb-2 max-h-[72px] grow px-[14px] text-xs leading-normal text-text-tertiary group-hover:line-clamp-2 group-hover:max-h-[36px]', |
| | | 'grow mb-2 px-[14px] max-h-[72px] text-xs leading-normal text-text-tertiary group-hover:line-clamp-2 group-hover:max-h-[36px]', |
| | | tags.length ? 'line-clamp-2' : 'line-clamp-4', |
| | | !dataset.embedding_available && 'opacity-50 hover:opacity-100', |
| | | )} |
| | |
| | | {dataset.description} |
| | | </div> |
| | | <div className={cn( |
| | | 'mt-4 h-[42px] shrink-0 items-center pb-[6px] pl-[14px] pr-[6px] pt-1', |
| | | 'items-center shrink-0 mt-1 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]', |
| | | tags.length ? 'flex' : '!hidden group-hover:!flex', |
| | | )}> |
| | | <div className={cn('flex w-0 grow items-center gap-1', !dataset.embedding_available && 'opacity-50 hover:opacity-100')} onClick={(e) => { |
| | | <div className={cn('grow flex items-center gap-1 w-0', !dataset.embedding_available && 'opacity-50 hover:opacity-100')} onClick={(e) => { |
| | | e.stopPropagation() |
| | | e.preventDefault() |
| | | }}> |
| | | <div className={cn( |
| | | 'mr-[41px] w-full grow group-hover:!mr-0 group-hover:!block', |
| | | 'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full', |
| | | tags.length ? '!block' : '!hidden', |
| | | )}> |
| | | <TagSelector |
| | |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div className='mx-1 !hidden h-[14px] w-[1px] shrink-0 bg-divider-regular group-hover:!flex' /> |
| | | <div className='!hidden shrink-0 group-hover:!flex'> |
| | | <div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px] bg-gray-200' /> |
| | | <div className='!hidden group-hover:!flex shrink-0'> |
| | | <CustomPopover |
| | | htmlContent={<Operations showDelete={!isCurrentWorkspaceDatasetOperator} />} |
| | | position="br" |
| | | trigger="click" |
| | | btnElement={ |
| | | <div |
| | | className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-md' |
| | | className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md' |
| | | > |
| | | <RiMoreFill className='h-4 w-4 text-text-secondary' /> |
| | | <RiMoreFill className='w-4 h-4 text-gray-700' /> |
| | | </div> |
| | | } |
| | | btnClassName={open => |
| | | cn( |
| | | open ? '!bg-black/5 !shadow-none' : '!bg-transparent', |
| | | 'h-8 w-8 rounded-md border-none !p-2 hover:!bg-black/5', |
| | | 'h-8 w-8 !p-2 rounded-md border-none hover:!bg-black/5', |
| | | ) |
| | | } |
| | | className={'!z-20 h-fit !w-[128px]'} |
| | | className={'!w-[128px] h-fit !z-20'} |
| | | /> |
| | | </div> |
| | | </div> |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <footer className='shrink-0 grow-0 px-12 py-6'> |
| | | <h3 className='text-gradient text-xl font-semibold leading-tight'>{t('dataset.didYouKnow')}</h3> |
| | | <p className='mt-1 text-sm font-normal leading-tight text-text-secondary'> |
| | | {t('dataset.intro1')}<span className='inline-flex items-center gap-1 text-text-accent'>{t('dataset.intro2')}</span>{t('dataset.intro3')}<br /> |
| | | {t('dataset.intro4')}<span className='inline-flex items-center gap-1 text-text-accent'>{t('dataset.intro5')}</span>{t('dataset.intro6')} |
| | | <footer className='px-12 py-6 grow-0 shrink-0'> |
| | | <h3 className='text-xl font-semibold leading-tight text-gradient'>{t('dataset.didYouKnow')}</h3> |
| | | <p className='mt-1 text-sm font-normal leading-tight text-gray-700'> |
| | | {t('dataset.intro1')}<span className='inline-flex items-center gap-1 text-blue-600'>{t('dataset.intro2')}</span>{t('dataset.intro3')}<br /> |
| | | {t('dataset.intro4')}<span className='inline-flex items-center gap-1 text-blue-600'>{t('dataset.intro5')}</span>{t('dataset.intro6')} |
| | | </p> |
| | | </footer> |
| | | ) |
| | |
| | | 'use client' |
| | | |
| | | import { useCallback, useEffect, useRef } from 'react' |
| | | import { useEffect, useRef } from 'react' |
| | | import useSWRInfinite from 'swr/infinite' |
| | | import { debounce } from 'lodash-es' |
| | | import { useTranslation } from 'react-i18next' |
| | |
| | | useEffect(() => { |
| | | loadingStateRef.current = isLoading |
| | | document.title = `${t('dataset.knowledge')} - Dify` |
| | | }, [isLoading, t]) |
| | | }, [isLoading]) |
| | | |
| | | const onScroll = useCallback( |
| | | debounce(() => { |
| | | if (!loadingStateRef.current && containerRef.current && anchorRef.current) { |
| | | const { scrollTop, clientHeight } = containerRef.current |
| | | const anchorOffset = anchorRef.current.offsetTop |
| | | useEffect(() => { |
| | | const onScroll = debounce(() => { |
| | | if (!loadingStateRef.current) { |
| | | const { scrollTop, clientHeight } = containerRef.current! |
| | | const anchorOffset = anchorRef.current!.offsetTop |
| | | if (anchorOffset - scrollTop - clientHeight < 100) |
| | | setSize(size => size + 1) |
| | | } |
| | | }, 50), |
| | | [setSize], |
| | | ) |
| | | }, 50) |
| | | |
| | | useEffect(() => { |
| | | const currentContainer = containerRef.current |
| | | currentContainer?.addEventListener('scroll', onScroll) |
| | | return () => { |
| | | currentContainer?.removeEventListener('scroll', onScroll) |
| | | onScroll.cancel() |
| | | } |
| | | }, [onScroll]) |
| | | containerRef.current?.addEventListener('scroll', onScroll) |
| | | return () => containerRef.current?.removeEventListener('scroll', onScroll) |
| | | }, []) |
| | | |
| | | return ( |
| | | <nav className='grid shrink-0 grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'> |
| | | <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'> |
| | | { isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} /> } |
| | | {data?.map(({ data: datasets }) => datasets.map(dataset => ( |
| | | <DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />), |
| | |
| | | 'use client' |
| | | |
| | | import { useEffect, useMemo, useState } from 'react' |
| | | import { useEffect, useState } from 'react' |
| | | import { useContext } from 'use-context-selector' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiListUnordered } from '@remixicon/react' |
| | | import TemplateEn from './template/template.en.mdx' |
| | | import TemplateZh from './template/template.zh.mdx' |
| | | import TemplateJa from './template/template.ja.mdx' |
| | | import I18n from '@/context/i18n' |
| | | import { LanguagesSupported } from '@/i18n/language' |
| | | import useTheme from '@/hooks/use-theme' |
| | | import { Theme } from '@/types/app' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type DocProps = { |
| | | apiBaseUrl: string |
| | |
| | | const { t } = useTranslation() |
| | | const [toc, setToc] = useState<Array<{ href: string; text: string }>>([]) |
| | | const [isTocExpanded, setIsTocExpanded] = useState(false) |
| | | const { theme } = useTheme() |
| | | |
| | | // Set initial TOC expanded state based on screen width |
| | | useEffect(() => { |
| | |
| | | } |
| | | } |
| | | |
| | | const Template = useMemo(() => { |
| | | switch (locale) { |
| | | case LanguagesSupported[1]: |
| | | return <TemplateZh apiBaseUrl={apiBaseUrl} /> |
| | | case LanguagesSupported[7]: |
| | | return <TemplateJa apiBaseUrl={apiBaseUrl} /> |
| | | default: |
| | | return <TemplateEn apiBaseUrl={apiBaseUrl} /> |
| | | } |
| | | }, [apiBaseUrl, locale]) |
| | | |
| | | return ( |
| | | <div className="flex"> |
| | | <div className={`fixed right-20 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}> |
| | | <div className={`fixed right-16 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}> |
| | | {isTocExpanded |
| | | ? ( |
| | | <nav className="toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg bg-components-panel-bg p-4 shadow-md"> |
| | | <div className="mb-4 flex items-center justify-between"> |
| | | <h3 className="text-lg font-semibold text-text-primary">{t('appApi.develop.toc')}</h3> |
| | | <nav className="toc w-full bg-gray-50 p-4 rounded-lg shadow-md max-h-[calc(100vh-150px)] overflow-y-auto"> |
| | | <div className="flex justify-between items-center mb-4"> |
| | | <h3 className="text-lg font-semibold">{t('appApi.develop.toc')}</h3> |
| | | <button |
| | | onClick={() => setIsTocExpanded(false)} |
| | | className="text-text-tertiary hover:text-text-secondary" |
| | | className="text-gray-500 hover:text-gray-700" |
| | | > |
| | | ✕ |
| | | </button> |
| | |
| | | <li key={index}> |
| | | <a |
| | | href={item.href} |
| | | className="text-text-secondary transition-colors duration-200 hover:text-text-primary hover:underline" |
| | | className="text-gray-600 hover:text-gray-900 hover:underline transition-colors duration-200" |
| | | onClick={e => handleTocClick(e, item)} |
| | | > |
| | | {item.text} |
| | |
| | | : ( |
| | | <button |
| | | onClick={() => setIsTocExpanded(true)} |
| | | className="flex h-10 w-10 items-center justify-center rounded-full bg-components-button-secondary-bg shadow-md transition-colors duration-200 hover:bg-components-button-secondary-bg-hover" |
| | | className="w-10 h-10 bg-gray-50 rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200" |
| | | > |
| | | <RiListUnordered className="h-6 w-6 text-components-button-secondary-text" /> |
| | | <RiListUnordered className="w-6 h-6" /> |
| | | </button> |
| | | )} |
| | | </div> |
| | | <article className={cn('prose-xl prose mx-1 rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'prose-invert')}> |
| | | {Template} |
| | | <article className='mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl'> |
| | | {locale !== LanguagesSupported[1] |
| | | ? <TemplateEn apiBaseUrl={apiBaseUrl} /> |
| | | : <TemplateZh apiBaseUrl={apiBaseUrl} /> |
| | | } |
| | | </article> |
| | | </div> |
| | | ) |
| | |
| | | 'use client' |
| | | |
| | | import { forwardRef } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import Link from 'next/link' |
| | | import { |
| | | RiAddLine, |
| | | RiArrowRightLine, |
| | | } from '@remixicon/react' |
| | | |
| | | const CreateAppCard = ( |
| | | { |
| | | ref, |
| | | ..._ |
| | | }, |
| | | ) => { |
| | | const CreateAppCard = forwardRef<HTMLAnchorElement>((_, ref) => { |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className='bg-background-default-dimm flex min-h-[160px] flex-col rounded-xl border-[0.5px] |
| | | border-components-panel-border transition-all duration-200 ease-in-out' |
| | | <div className='flex flex-col bg-background-default-dimm border-[0.5px] border-components-panel-border rounded-xl |
| | | min-h-[160px] transition-all duration-200 ease-in-out' |
| | | > |
| | | <Link ref={ref} className='group flex grow cursor-pointer items-start p-4' href={'/datasets/create'}> |
| | | <a ref={ref} className='group flex flex-grow items-start p-4 cursor-pointer' href='/datasets/create'> |
| | | <div className='flex items-center gap-3'> |
| | | <div className='flex h-10 w-10 items-center justify-center rounded-lg border border-dashed border-divider-regular bg-background-default-lighter |
| | | p-2 group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge' |
| | | <div className='w-10 h-10 p-2 flex items-center justify-center border border-dashed border-divider-regular rounded-lg |
| | | bg-background-default-lighter group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge' |
| | | > |
| | | <RiAddLine className='h-4 w-4 text-text-tertiary group-hover:text-text-accent'/> |
| | | <RiAddLine className='w-4 h-4 text-text-tertiary group-hover:text-text-accent'/> |
| | | </div> |
| | | <div className='system-md-semibold text-text-secondary group-hover:text-text-accent'>{t('dataset.createDataset')}</div> |
| | | </div> |
| | | </Link> |
| | | <div className='system-xs-regular p-4 pt-0 text-text-tertiary'>{t('dataset.createDatasetIntro')}</div> |
| | | <Link className='group flex cursor-pointer items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle p-4' href={'datasets/connect'}> |
| | | </a> |
| | | <div className='p-4 pt-0 text-text-tertiary system-xs-regular'>{t('dataset.createDatasetIntro')}</div> |
| | | <a className='group flex p-4 items-center gap-1 border-t-[0.5px] border-divider-subtle rounded-b-xl cursor-pointer' href='/datasets/connect'> |
| | | <div className='system-xs-medium text-text-tertiary group-hover:text-text-accent'>{t('dataset.connectDataset')}</div> |
| | | <RiArrowRightLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-accent' /> |
| | | </Link> |
| | | <RiArrowRightLine className='w-3.5 h-3.5 text-text-tertiary group-hover:text-text-accent' /> |
| | | </a> |
| | | </div> |
| | | ) |
| | | } |
| | | }) |
| | | |
| | | CreateAppCard.displayName = 'CreateAppCard' |
| | | |
| | |
| | | return <Container /> |
| | | } |
| | | |
| | | export const metadata = { |
| | | title: 'Datasets - Dify', |
| | | } |
| | | |
| | | export default AppList |
| | |
| | | {/** |
| | | * @typedef Props |
| | | * @property {string} apiBaseUrl |
| | | */} |
| | | |
| | | import { CodeGroup } from '@/app/components/develop/code.tsx' |
| | | import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstruction, Paragraph } from '@/app/components/develop/md.tsx' |
| | | |
| | |
| | | <Col> |
| | | This API is based on an existing knowledge and creates a new document through text based on this knowledge. |
| | | |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | </Property> |
| | | <Property name='text' type='string' key='text'> |
| | | Document content |
| | | </Property> |
| | | <Property name='doc_type' type='string' key='doc_type'> |
| | | Type of document (optional): |
| | | - <code>book</code> Book |
| | | - <code>web_page</code> Web page |
| | | - <code>paper</code> Academic paper/article |
| | | - <code>social_media_post</code> Social media post |
| | | - <code>wikipedia_entry</code> Wikipedia entry |
| | | - <code>personal_document</code> Personal document |
| | | - <code>business_document</code> Business document |
| | | - <code>im_chat_log</code> Chat log |
| | | - <code>synced_from_notion</code> Notion document |
| | | - <code>synced_from_github</code> GitHub document |
| | | - <code>others</code> Other document types |
| | | </Property> |
| | | <Property name='doc_metadata' type='object' key='doc_metadata'> |
| | | Document metadata (required if doc_type is provided). Fields vary by doc_type: |
| | | For <code>book</code>: |
| | | - <code>title</code> Book title |
| | | - <code>language</code> Book language |
| | | - <code>author</code> Book author |
| | | - <code>publisher</code> Publisher name |
| | | - <code>publication_date</code> Publication date |
| | | - <code>isbn</code> ISBN number |
| | | - <code>category</code> Book category |
| | | |
| | | For <code>web_page</code>: |
| | | - <code>title</code> Page title |
| | | - <code>url</code> Page URL |
| | | - <code>language</code> Page language |
| | | - <code>publish_date</code> Publish date |
| | | - <code>author/publisher</code> Author or publisher |
| | | - <code>topic/keywords</code> Topic or keywords |
| | | - <code>description</code> Page description |
| | | |
| | | Please check [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) for more details on the fields required for each doc_type. |
| | | |
| | | For doc_type "others", any valid JSON object is accepted |
| | | </Property> |
| | | <Property name='indexing_technique' type='string' key='indexing_technique'> |
| | | Index mode |
| | |
| | | <Col> |
| | | This API is based on an existing knowledge and creates a new document through a file based on this knowledge. |
| | | |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | - <code>text_model</code> Text documents are directly embedded; `economy` mode defaults to using this form |
| | | - <code>hierarchical_model</code> Parent-child mode |
| | | - <code>qa_model</code> Q&A Mode: Generates Q&A pairs for segmented documents and then embeds the questions |
| | | |
| | | - <code>doc_type</code> Type of document (optional) |
| | | - <code>book</code> Book |
| | | Document records a book or publication |
| | | - <code>web_page</code> Web page |
| | | Document records web page content |
| | | - <code>paper</code> Academic paper/article |
| | | Document records academic paper or research article |
| | | - <code>social_media_post</code> Social media post |
| | | Content from social media posts |
| | | - <code>wikipedia_entry</code> Wikipedia entry |
| | | Content from Wikipedia entries |
| | | - <code>personal_document</code> Personal document |
| | | Documents related to personal content |
| | | - <code>business_document</code> Business document |
| | | Documents related to business content |
| | | - <code>im_chat_log</code> Chat log |
| | | Records of instant messaging chats |
| | | - <code>synced_from_notion</code> Notion document |
| | | Documents synchronized from Notion |
| | | - <code>synced_from_github</code> GitHub document |
| | | Documents synchronized from GitHub |
| | | - <code>others</code> Other document types |
| | | Other document types not listed above |
| | | |
| | | - <code>doc_metadata</code> Document metadata (required if doc_type is provided) |
| | | Fields vary by doc_type: |
| | | |
| | | For <code>book</code>: |
| | | - <code>title</code> Book title |
| | | Title of the book |
| | | - <code>language</code> Book language |
| | | Language of the book |
| | | - <code>author</code> Book author |
| | | Author of the book |
| | | - <code>publisher</code> Publisher name |
| | | Name of the publishing house |
| | | - <code>publication_date</code> Publication date |
| | | Date when the book was published |
| | | - <code>isbn</code> ISBN number |
| | | International Standard Book Number |
| | | - <code>category</code> Book category |
| | | Category or genre of the book |
| | | |
| | | For <code>web_page</code>: |
| | | - <code>title</code> Page title |
| | | Title of the web page |
| | | - <code>url</code> Page URL |
| | | URL address of the web page |
| | | - <code>language</code> Page language |
| | | Language of the web page |
| | | - <code>publish_date</code> Publish date |
| | | Date when the web page was published |
| | | - <code>author/publisher</code> Author or publisher |
| | | Author or publisher of the web page |
| | | - <code>topic/keywords</code> Topic or keywords |
| | | Topics or keywords of the web page |
| | | - <code>description</code> Page description |
| | | Description of the web page content |
| | | |
| | | Please check [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) for more details on the fields required for each doc_type. |
| | | For doc_type "others", any valid JSON object is accepted |
| | | |
| | | - <code>doc_language</code> In Q&A mode, specify the language of the document, for example: <code>English</code>, <code>Chinese</code> |
| | | |
| | |
| | | <Property name='description' type='string' key='description'> |
| | | Knowledge description (optional) |
| | | </Property> |
| | | <Property name='doc_type' type='string' key='doc_type'> |
| | | Type of document (optional): |
| | | - <code>book</code> Book |
| | | - <code>web_page</code> Web page |
| | | - <code>paper</code> Academic paper/article |
| | | - <code>social_media_post</code> Social media post |
| | | - <code>wikipedia_entry</code> Wikipedia entry |
| | | - <code>personal_document</code> Personal document |
| | | - <code>business_document</code> Business document |
| | | - <code>im_chat_log</code> Chat log |
| | | - <code>synced_from_notion</code> Notion document |
| | | - <code>synced_from_github</code> GitHub document |
| | | - <code>others</code> Other document types |
| | | </Property> |
| | | <Property name='doc_metadata' type='object' key='doc_metadata'> |
| | | Document metadata (required if doc_type is provided). Fields vary by doc_type: |
| | | For <code>book</code>: |
| | | - <code>title</code> Book title |
| | | - <code>language</code> Book language |
| | | - <code>author</code> Book author |
| | | - <code>publisher</code> Publisher name |
| | | - <code>publication_date</code> Publication date |
| | | - <code>isbn</code> ISBN number |
| | | - <code>category</code> Book category |
| | | |
| | | For <code>web_page</code>: |
| | | - <code>title</code> Page title |
| | | - <code>url</code> Page URL |
| | | - <code>language</code> Page language |
| | | - <code>publish_date</code> Publish date |
| | | - <code>author/publisher</code> Author or publisher |
| | | - <code>topic/keywords</code> Topic or keywords |
| | | - <code>description</code> Page description |
| | | |
| | | Please check [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) for more details on the fields required for each doc_type. |
| | | |
| | | For doc_type "others", any valid JSON object is accepted |
| | | </Property> |
| | | <Property name='indexing_technique' type='string' key='indexing_technique'> |
| | | Index technique (optional) |
| | | If this is not set, embedding_model, embedding_model_provider and retrieval_model will be set to null |
| | | - <code>high_quality</code> High quality |
| | | - <code>economy</code> Economy |
| | | </Property> |
| | |
| | | </Property> |
| | | <Property name='external_knowledge_id' type='str' key='external_knowledge_id'> |
| | | External knowledge ID (optional) |
| | | </Property> |
| | | <Property name='embedding_model' type='str' key='embedding_model'> |
| | | Embedding model name (optional) |
| | | </Property> |
| | | <Property name='embedding_model_provider' type='str' key='embedding_model_provider'> |
| | | Embedding model provider name (optional) |
| | | </Property> |
| | | <Property name='retrieval_model' type='object' key='retrieval_model'> |
| | | Retrieval model (optional) |
| | | - <code>search_method</code> (string) Search method |
| | | - <code>hybrid_search</code> Hybrid search |
| | | - <code>semantic_search</code> Semantic search |
| | | - <code>full_text_search</code> Full-text search |
| | | - <code>reranking_enable</code> (bool) Whether to enable reranking |
| | | - <code>reranking_model</code> (object) Rerank model configuration |
| | | - <code>reranking_provider_name</code> (string) Rerank model provider |
| | | - <code>reranking_model_name</code> (string) Rerank model name |
| | | - <code>top_k</code> (int) Number of results to return |
| | | - <code>score_threshold_enabled</code> (bool) Whether to enable score threshold |
| | | - <code>score_threshold</code> (float) Score threshold |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | |
| | | <Col> |
| | | ### Query |
| | | <Properties> |
| | | <Property name='keyword' type='string' key='keyword'> |
| | | Search keyword, optional |
| | | </Property> |
| | | <Property name='tag_ids' type='array[string]' key='tag_ids'> |
| | | Tag ID list, optional |
| | | </Property> |
| | | <Property name='page' type='string' key='page'> |
| | | Page number, optional, default 1 |
| | | Page number |
| | | </Property> |
| | | <Property name='limit' type='string' key='limit'> |
| | | Number of items returned, optional, default 20, range 1-100 |
| | | </Property> |
| | | <Property name='include_all' type='boolean' key='include_all'> |
| | | Whether to include all datasets (only effective for owners), optional, defaults to false |
| | | Number of items returned, default 20, range 1-100 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}' |
| | | method='GET' |
| | | title='Get knowledge base details by knowledge base ID' |
| | | name='#view_dataset' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge Base ID |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="GET" |
| | | label="/datasets/{dataset_id}" |
| | | targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "id": "eaedb485-95ac-4ffd-ab1e-18da6d676a2f", |
| | | "name": "Test Knowledge Base", |
| | | "description": "", |
| | | "provider": "vendor", |
| | | "permission": "only_me", |
| | | "data_source_type": null, |
| | | "indexing_technique": null, |
| | | "app_count": 0, |
| | | "document_count": 0, |
| | | "word_count": 0, |
| | | "created_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", |
| | | "created_at": 1735620612, |
| | | "updated_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", |
| | | "updated_at": 1735620612, |
| | | "embedding_model": null, |
| | | "embedding_model_provider": null, |
| | | "embedding_available": true, |
| | | "retrieval_model_dict": { |
| | | "search_method": "semantic_search", |
| | | "reranking_enable": false, |
| | | "reranking_mode": null, |
| | | "reranking_model": { |
| | | "reranking_provider_name": "", |
| | | "reranking_model_name": "" |
| | | }, |
| | | "weights": null, |
| | | "top_k": 2, |
| | | "score_threshold_enabled": false, |
| | | "score_threshold": null |
| | | }, |
| | | "tags": [], |
| | | "doc_form": null, |
| | | "external_knowledge_info": { |
| | | "external_knowledge_id": null, |
| | | "external_knowledge_api_id": null, |
| | | "external_knowledge_api_name": null, |
| | | "external_knowledge_api_endpoint": null |
| | | }, |
| | | "external_retrieval_model": { |
| | | "top_k": 2, |
| | | "score_threshold": 0.0, |
| | | "score_threshold_enabled": null |
| | | } |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}' |
| | | method='PATCH' |
| | | title='Update knowledge base' |
| | | name='#update_dataset' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge Base ID |
| | | </Property> |
| | | <Property name='indexing_technique' type='string' key='indexing_technique'> |
| | | Index technique (optional) |
| | | - <code>high_quality</code> High quality |
| | | - <code>economy</code> Economy |
| | | </Property> |
| | | <Property name='permission' type='string' key='permission'> |
| | | Permission |
| | | - <code>only_me</code> Only me |
| | | - <code>all_team_members</code> All team members |
| | | - <code>partial_members</code> Partial members |
| | | </Property> |
| | | <Property name='embedding_model_provider' type='string' key='embedding_model_provider'> |
| | | Specified embedding model provider, must be set up in the system first, corresponding to the provider field(Optional) |
| | | </Property> |
| | | <Property name='embedding_model' type='string' key='embedding_model'> |
| | | Specified embedding model, corresponding to the model field(Optional) |
| | | </Property> |
| | | <Property name='retrieval_model' type='object' key='retrieval_model'> |
| | | Retrieval model (optional, if not filled, it will be recalled according to the default method) |
| | | - <code>search_method</code> (text) Search method: One of the following four keywords is required |
| | | - <code>keyword_search</code> Keyword search |
| | | - <code>semantic_search</code> Semantic search |
| | | - <code>full_text_search</code> Full-text search |
| | | - <code>hybrid_search</code> Hybrid search |
| | | - <code>reranking_enable</code> (bool) Whether to enable reranking, required if the search mode is semantic_search or hybrid_search (optional) |
| | | - <code>reranking_mode</code> (object) Rerank model configuration, required if reranking is enabled |
| | | - <code>reranking_provider_name</code> (string) Rerank model provider |
| | | - <code>reranking_model_name</code> (string) Rerank model name |
| | | - <code>weights</code> (float) Semantic search weight setting in hybrid search mode |
| | | - <code>top_k</code> (integer) Number of results to return (optional) |
| | | - <code>score_threshold_enabled</code> (bool) Whether to enable score threshold |
| | | - <code>score_threshold</code> (float) Score threshold |
| | | </Property> |
| | | <Property name='partial_member_list' type='array' key='partial_member_list'> |
| | | Partial member list(Optional) |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="PATCH" |
| | | label="/datasets/{dataset_id}" |
| | | targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{ |
| | | "name": "Test Knowledge Base", |
| | | "indexing_technique": "high_quality", |
| | | "permission": "only_me", |
| | | "embedding_model_provider": "zhipuai", |
| | | "embedding_model": "embedding-3", |
| | | "retrieval_model": { |
| | | "search_method": "keyword_search", |
| | | "reranking_enable": false, |
| | | "reranking_mode": null, |
| | | "reranking_model": { |
| | | "reranking_provider_name": "", |
| | | "reranking_model_name": "" |
| | | }, |
| | | "weights": null, |
| | | "top_k": 1, |
| | | "score_threshold_enabled": false, |
| | | "score_threshold": null |
| | | }, |
| | | "partial_member_list": [] |
| | | }' |
| | | `} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' \ |
| | | --data-raw '{ |
| | | "name": "Test Knowledge Base", |
| | | "indexing_technique": "high_quality", |
| | | "permission": "only_me", |
| | | "embedding_model_provider": "zhipuai", |
| | | "embedding_model": "embedding-3", |
| | | "retrieval_model": { |
| | | "search_method": "keyword_search", |
| | | "reranking_enable": false, |
| | | "reranking_mode": null, |
| | | "reranking_model": { |
| | | "reranking_provider_name": "", |
| | | "reranking_model_name": "" |
| | | }, |
| | | "weights": null, |
| | | "top_k": 1, |
| | | "score_threshold_enabled": false, |
| | | "score_threshold": null |
| | | }, |
| | | "partial_member_list": [] |
| | | }' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "id": "eaedb485-95ac-4ffd-ab1e-18da6d676a2f", |
| | | "name": "Test Knowledge Base", |
| | | "description": "", |
| | | "provider": "vendor", |
| | | "permission": "only_me", |
| | | "data_source_type": null, |
| | | "indexing_technique": "high_quality", |
| | | "app_count": 0, |
| | | "document_count": 0, |
| | | "word_count": 0, |
| | | "created_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", |
| | | "created_at": 1735620612, |
| | | "updated_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", |
| | | "updated_at": 1735622679, |
| | | "embedding_model": "embedding-3", |
| | | "embedding_model_provider": "zhipuai", |
| | | "embedding_available": null, |
| | | "retrieval_model_dict": { |
| | | "search_method": "semantic_search", |
| | | "reranking_enable": false, |
| | | "reranking_mode": null, |
| | | "reranking_model": { |
| | | "reranking_provider_name": "", |
| | | "reranking_model_name": "" |
| | | }, |
| | | "weights": null, |
| | | "top_k": 2, |
| | | "score_threshold_enabled": false, |
| | | "score_threshold": null |
| | | }, |
| | | "tags": [], |
| | | "doc_form": null, |
| | | "external_knowledge_info": { |
| | | "external_knowledge_id": null, |
| | | "external_knowledge_api_id": null, |
| | | "external_knowledge_api_name": null, |
| | | "external_knowledge_api_endpoint": null |
| | | }, |
| | | "external_retrieval_model": { |
| | | "top_k": 2, |
| | | "score_threshold": 0.0, |
| | | "score_threshold_enabled": null |
| | | }, |
| | | "partial_member_list": [] |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}' |
| | | method='DELETE' |
| | | title='Delete a Knowledge Base' |
| | | name='#delete_dataset' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | <Col> |
| | | This API is based on an existing knowledge and updates the document through text based on this knowledge. |
| | | |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | <Col> |
| | | This API is based on an existing knowledge, and updates documents through files based on this knowledge |
| | | |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | - <code>separator</code> Segmentation identifier. Currently, only one delimiter is allowed. The default is <code>***</code> |
| | | - <code>max_tokens</code> The maximum length (tokens) must be validated to be shorter than the length of the parent chunk |
| | | - <code>chunk_overlap</code> Define the overlap between adjacent chunks (optional) |
| | | - <code>doc_type</code> Type of document (optional) |
| | | - <code>book</code> Book |
| | | Document records a book or publication |
| | | - <code>web_page</code> Web page |
| | | Document records web page content |
| | | - <code>paper</code> Academic paper/article |
| | | Document records academic paper or research article |
| | | - <code>social_media_post</code> Social media post |
| | | Content from social media posts |
| | | - <code>wikipedia_entry</code> Wikipedia entry |
| | | Content from Wikipedia entries |
| | | - <code>personal_document</code> Personal document |
| | | Documents related to personal content |
| | | - <code>business_document</code> Business document |
| | | Documents related to business content |
| | | - <code>im_chat_log</code> Chat log |
| | | Records of instant messaging chats |
| | | - <code>synced_from_notion</code> Notion document |
| | | Documents synchronized from Notion |
| | | - <code>synced_from_github</code> GitHub document |
| | | Documents synchronized from GitHub |
| | | - <code>others</code> Other document types |
| | | Other document types not listed above |
| | | |
| | | - <code>doc_metadata</code> Document metadata (required if doc_type is provided) |
| | | Fields vary by doc_type: |
| | | |
| | | For <code>book</code>: |
| | | - <code>title</code> Book title |
| | | Title of the book |
| | | - <code>language</code> Book language |
| | | Language of the book |
| | | - <code>author</code> Book author |
| | | Author of the book |
| | | - <code>publisher</code> Publisher name |
| | | Name of the publishing house |
| | | - <code>publication_date</code> Publication date |
| | | Date when the book was published |
| | | - <code>isbn</code> ISBN number |
| | | International Standard Book Number |
| | | - <code>category</code> Book category |
| | | Category or genre of the book |
| | | |
| | | For <code>web_page</code>: |
| | | - <code>title</code> Page title |
| | | Title of the web page |
| | | - <code>url</code> Page URL |
| | | URL address of the web page |
| | | - <code>language</code> Page language |
| | | Language of the web page |
| | | - <code>publish_date</code> Publish date |
| | | Date when the web page was published |
| | | - <code>author/publisher</code> Author or publisher |
| | | Author or publisher of the web page |
| | | - <code>topic/keywords</code> Topic or keywords |
| | | Topics or keywords of the web page |
| | | - <code>description</code> Page description |
| | | Description of the web page content |
| | | |
| | | Please check [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) for more details on the fields required for each doc_type. |
| | | For doc_type "others", any valid JSON object is accepted |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```text {{ title: 'Response' }} |
| | | 204 No Content |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "result": "success" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | |
| | | <Property name='status' type='string' key='status'> |
| | | Search status, completed |
| | | </Property> |
| | | <Property name='page' type='string' key='page'> |
| | | Page number (optional) |
| | | </Property> |
| | | <Property name='limit' type='string' key='limit'> |
| | | Number of items returned, default 20, range 1-100 (optional) |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | |
| | | "error": null, |
| | | "stopped_at": null |
| | | }], |
| | | "doc_form": "text_model", |
| | | "has_more": false, |
| | | "limit": 20, |
| | | "total": 9, |
| | | "page": 1 |
| | | "doc_form": "text_model" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | |
| | | title="Request" |
| | | tag="DELETE" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}" |
| | | targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`} |
| | | targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/segments/{segment_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \ |
| | | curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/segments/{segment_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```text {{ title: 'Response' }} |
| | | 204 No Content |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "result": "success" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": { |
| | | "data": [{ |
| | | "id": "", |
| | | "position": 1, |
| | | "document_id": "", |
| | |
| | | "completed_at": 1695312007, |
| | | "error": null, |
| | | "stopped_at": null |
| | | }, |
| | | "doc_form": "text_model" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' |
| | | method='POST' |
| | | title='Create Child Chunk' |
| | | name='#create_child_chunk' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | <Property name='document_id' type='string' key='document_id'> |
| | | Document ID |
| | | </Property> |
| | | <Property name='segment_id' type='string' key='segment_id'> |
| | | Segment ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='content' type='string' key='content'> |
| | | Child chunk content |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="POST" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks" |
| | | targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"content": "Child chunk content"}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' \ |
| | | --data-raw '{ |
| | | "content": "Child chunk content" |
| | | }' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": { |
| | | "id": "", |
| | | "segment_id": "", |
| | | "content": "Child chunk content", |
| | | "word_count": 25, |
| | | "tokens": 0, |
| | | "index_node_id": "", |
| | | "index_node_hash": "", |
| | | "status": "completed", |
| | | "created_by": "", |
| | | "created_at": 1695312007, |
| | | "indexing_at": 1695312007, |
| | | "completed_at": 1695312007, |
| | | "error": null, |
| | | "stopped_at": null |
| | | } |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' |
| | | method='GET' |
| | | title='Get Child Chunks' |
| | | name='#get_child_chunks' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | <Property name='document_id' type='string' key='document_id'> |
| | | Document ID |
| | | </Property> |
| | | <Property name='segment_id' type='string' key='segment_id'> |
| | | Segment ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Query |
| | | <Properties> |
| | | <Property name='keyword' type='string' key='keyword'> |
| | | Search keyword (optional) |
| | | </Property> |
| | | <Property name='page' type='integer' key='page'> |
| | | Page number (optional, default: 1) |
| | | </Property> |
| | | <Property name='limit' type='integer' key='limit'> |
| | | Items per page (optional, default: 20, max: 100) |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="GET" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks" |
| | | targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks?page=1&limit=20' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks?page=1&limit=20' \ |
| | | --header 'Authorization: Bearer {api_key}' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": [{ |
| | | "id": "", |
| | | "segment_id": "", |
| | | "content": "Child chunk content", |
| | | "word_count": 25, |
| | | "tokens": 0, |
| | | "index_node_id": "", |
| | | "index_node_hash": "", |
| | | "status": "completed", |
| | | "created_by": "", |
| | | "created_at": 1695312007, |
| | | "indexing_at": 1695312007, |
| | | "completed_at": 1695312007, |
| | | "error": null, |
| | | "stopped_at": null |
| | | }], |
| | | "total": 1, |
| | | "total_pages": 1, |
| | | "page": 1, |
| | | "limit": 20 |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' |
| | | method='DELETE' |
| | | title='Delete Child Chunk' |
| | | name='#delete_child_chunk' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | <Property name='document_id' type='string' key='document_id'> |
| | | Document ID |
| | | </Property> |
| | | <Property name='segment_id' type='string' key='segment_id'> |
| | | Segment ID |
| | | </Property> |
| | | <Property name='child_chunk_id' type='string' key='child_chunk_id'> |
| | | Child Chunk ID |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="DELETE" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}" |
| | | targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```text {{ title: 'Response' }} |
| | | 204 No Content |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' |
| | | method='PATCH' |
| | | title='Update Child Chunk' |
| | | name='#update_child_chunk' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | <Property name='document_id' type='string' key='document_id'> |
| | | Document ID |
| | | </Property> |
| | | <Property name='segment_id' type='string' key='segment_id'> |
| | | Segment ID |
| | | </Property> |
| | | <Property name='child_chunk_id' type='string' key='child_chunk_id'> |
| | | Child Chunk ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='content' type='string' key='content'> |
| | | Child chunk content |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="PATCH" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}" |
| | | targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"content": "Updated child chunk content"}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' \ |
| | | --data-raw '{ |
| | | "content": "Updated child chunk content" |
| | | }' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": { |
| | | "id": "", |
| | | "segment_id": "", |
| | | "content": "Updated child chunk content", |
| | | "word_count": 25, |
| | | "tokens": 0, |
| | | "index_node_id": "", |
| | | "index_node_hash": "", |
| | | "status": "completed", |
| | | "created_by": "", |
| | | "created_at": 1695312007, |
| | | "indexing_at": 1695312007, |
| | | "completed_at": 1695312007, |
| | | "error": null, |
| | | "stopped_at": null |
| | | } |
| | | "doc_form": "text_model" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | |
| | | "id": "a8c6c36f-9f5d-4d7a-8472-f5d7b75d71d2", |
| | | "data_source_type": "upload_file", |
| | | "name": "readme.txt", |
| | | "doc_type": null |
| | | } |
| | | }, |
| | | "score": 3.730463140527718e-05, |
| | | "tsne_position": null |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata' |
| | | method='POST' |
| | | title='Create a Knowledge Metadata' |
| | | name='#create_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='segment' type='object' key='segment'> |
| | | - <code>type</code> (string) Metadata type, required |
| | | - <code>name</code> (string) Metadata name, required |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="POST" |
| | | label="/datasets/{dataset_id}/metadata" |
| | | targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/metadata' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'\\\n--data-raw '{"type": "string", "name": "test"}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "id": "abc", |
| | | "type": "string", |
| | | "name": "test", |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata/{metadata_id}' |
| | | method='PATCH' |
| | | title='Update a Knowledge Metadata' |
| | | name='#update_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | <Property name='metadata_id' type='string' key='metadata_id'> |
| | | Metadata ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='segment' type='object' key='segment'> |
| | | - <code>name</code> (string) Metadata name, required |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="PATCH" |
| | | label="/datasets/{dataset_id}/metadata/{metadata_id}" |
| | | targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/metadata/{metadata_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'\\\n--data-raw '{"name": "test"}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "id": "abc", |
| | | "type": "string", |
| | | "name": "test", |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata/{metadata_id}' |
| | | method='DELETE' |
| | | title='Delete a Knowledge Metadata' |
| | | name='#delete_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | <Property name='metadata_id' type='string' key='metadata_id'> |
| | | Metadata ID |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="DELETE" |
| | | label="/datasets/{dataset_id}/metadata/{metadata_id}" |
| | | targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/metadata/{metadata_id}' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata/built-in/{action}' |
| | | method='POST' |
| | | title='Disable Or Enable Built-in Metadata' |
| | | name='#toggle_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | <Property name='action' type='string' key='action'> |
| | | disable/enable |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="POST" |
| | | label="/datasets/{dataset_id}/metadata/built-in/{action}" |
| | | targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/metadata/built-in/{action}' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/metadata' |
| | | method='POST' |
| | | title='Update Documents Metadata' |
| | | name='#update_documents_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='operation_data' type='object list' key='segments'> |
| | | - <code>document_id</code> (string) Document ID |
| | | - <code>metadata_list</code> (list) Metadata list |
| | | - <code>id</code> (string) Metadata ID |
| | | - <code>value</code> (string) Metadata value |
| | | - <code>name</code> (string) Metadata name |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="POST" |
| | | label="/datasets/{dataset_id}/documents/metadata" |
| | | targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/metadata' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'\\\n--data-raw '{"operation_data": [{"document_id": "document_id", "metadata_list": [{"id": "id", "value": "value", "name": "name"}]}]}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata' |
| | | method='GET' |
| | | title='Get Knowledge Metadata List' |
| | | name='#dataset_metadata_list' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | Knowledge ID |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="GET" |
| | | label="/datasets/{dataset_id}/metadata" |
| | | targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/metadata' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "doc_metadata": [ |
| | | { |
| | | "id": "", |
| | | "name": "name", |
| | | "type": "string", |
| | | "use_count": 0, |
| | | }, |
| | | ... |
| | | ], |
| | | "built_in_field_enabled": true |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/workspaces/current/models/model-types/text-embedding' |
| | | method='GET' |
| | | title='Get available embedding models' |
| | | name='#model_type_list' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Query |
| | | <Properties> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="GET" |
| | | label="/datasets/{dataset_id}" |
| | | targetCode={`curl --location --location --request GET '${props.apiBaseUrl}/workspaces/current/models/model-types/text-embedding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' `} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request GET '${props.apiBaseUrl}/workspaces/current/models/model-types/text-embedding' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' \ |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": [ |
| | | { |
| | | "provider": "zhipuai", |
| | | "label": { |
| | | "zh_Hans": "智谱 AI", |
| | | "en_US": "ZHIPU AI" |
| | | }, |
| | | "icon_small": { |
| | | "zh_Hans": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_small/zh_Hans", |
| | | "en_US": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_small/en_US" |
| | | }, |
| | | "icon_large": { |
| | | "zh_Hans": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_large/zh_Hans", |
| | | "en_US": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_large/en_US" |
| | | }, |
| | | "status": "active", |
| | | "models": [ |
| | | { |
| | | "model": "embedding-3", |
| | | "label": { |
| | | "zh_Hans": "embedding-3", |
| | | "en_US": "embedding-3" |
| | | }, |
| | | "model_type": "text-embedding", |
| | | "features": null, |
| | | "fetch_from": "predefined-model", |
| | | "model_properties": { |
| | | "context_size": 8192 |
| | | }, |
| | | "deprecated": false, |
| | | "status": "active", |
| | | "load_balancing_enabled": false |
| | | }, |
| | | { |
| | | "model": "embedding-2", |
| | | "label": { |
| | | "zh_Hans": "embedding-2", |
| | | "en_US": "embedding-2" |
| | | }, |
| | | "model_type": "text-embedding", |
| | | "features": null, |
| | | "fetch_from": "predefined-model", |
| | | "model_properties": { |
| | | "context_size": 8192 |
| | | }, |
| | | "deprecated": false, |
| | | "status": "active", |
| | | "load_balancing_enabled": false |
| | | }, |
| | | { |
| | | "model": "text_embedding", |
| | | "label": { |
| | | "zh_Hans": "text_embedding", |
| | | "en_US": "text_embedding" |
| | | }, |
| | | "model_type": "text-embedding", |
| | | "features": null, |
| | | "fetch_from": "predefined-model", |
| | | "model_properties": { |
| | | "context_size": 512 |
| | | }, |
| | | "deprecated": false, |
| | | "status": "active", |
| | | "load_balancing_enabled": false |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| | |
| | | {/** |
| | | * @typedef Props |
| | | * @property {string} apiBaseUrl |
| | | */} |
| | | |
| | | import { CodeGroup } from '@/app/components/develop/code.tsx' |
| | | import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstruction, Paragraph } from '@/app/components/develop/md.tsx' |
| | | |
| | |
| | | <Property name='text' type='string' key='text'> |
| | | 文档内容 |
| | | </Property> |
| | | <Property name='doc_type' type='string' key='doc_type'> |
| | | 文档类型(选填) |
| | | - <code>book</code> 图书 Book |
| | | - <code>web_page</code> 网页 Web page |
| | | - <code>paper</code> 学术论文/文章 Academic paper/article |
| | | - <code>social_media_post</code> 社交媒体帖子 Social media post |
| | | - <code>wikipedia_entry</code> 维基百科条目 Wikipedia entry |
| | | - <code>personal_document</code> 个人文档 Personal document |
| | | - <code>business_document</code> 商业文档 Business document |
| | | - <code>im_chat_log</code> 即时通讯记录 Chat log |
| | | - <code>synced_from_notion</code> Notion同步文档 Notion document |
| | | - <code>synced_from_github</code> GitHub同步文档 GitHub document |
| | | - <code>others</code> 其他文档类型 Other document types |
| | | </Property> |
| | | <Property name='doc_metadata' type='object' key='doc_metadata'> |
| | | |
| | | 文档元数据(如提供文档类型则必填)。字段因文档类型而异: |
| | | |
| | | 针对图书 For <code>book</code>: |
| | | - <code>title</code> 书名 Book title |
| | | - <code>language</code> 图书语言 Book language |
| | | - <code>author</code> 作者 Book author |
| | | - <code>publisher</code> 出版社 Publisher name |
| | | - <code>publication_date</code> 出版日期 Publication date |
| | | - <code>isbn</code> ISBN号码 ISBN number |
| | | - <code>category</code> 图书分类 Book category |
| | | |
| | | 针对网页 For <code>web_page</code>: |
| | | - <code>title</code> 页面标题 Page title |
| | | - <code>url</code> 页面网址 Page URL |
| | | - <code>language</code> 页面语言 Page language |
| | | - <code>publish_date</code> 发布日期 Publish date |
| | | - <code>author/publisher</code> 作者/发布者 Author or publisher |
| | | - <code>topic/keywords</code> 主题/关键词 Topic or keywords |
| | | - <code>description</code> 页面描述 Page description |
| | | |
| | | 请查看 [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) 了解各文档类型所需字段的详细信息。 |
| | | |
| | | 针对"其他"类型文档,接受任何有效的JSON对象 |
| | | </Property> |
| | | <Property name='indexing_technique' type='string' key='indexing_technique'> |
| | | 索引方式 |
| | | - <code>high_quality</code> 高质量:使用 |
| | | ding 模型进行嵌入,构建为向量数据库索引 |
| | | - <code>high_quality</code> 高质量:使用 embedding 模型进行嵌入,构建为向量数据库索引 |
| | | - <code>economy</code> 经济:使用 keyword table index 的倒排索引进行构建 |
| | | </Property> |
| | | <Property name='doc_form' type='string' key='doc_form'> |
| | |
| | | - <code>semantic_search</code> 语义检索 |
| | | - <code>full_text_search</code> 全文检索 |
| | | - <code>reranking_enable</code> (bool) 是否开启rerank |
| | | - <code>reranking_mode</code> (String) 混合检索 |
| | | - <code>weighted_score</code> 权重设置 |
| | | - <code>reranking_model</code> Rerank 模型 |
| | | - <code>reranking_model</code> (object) Rerank 模型配置 |
| | | - <code>reranking_provider_name</code> (string) Rerank 模型的提供商 |
| | | - <code>reranking_model_name</code> (string) Rerank 模型的名称 |
| | |
| | | - <code>text_model</code> text 文档直接 embedding,经济模式默认为该模式 |
| | | - <code>hierarchical_model</code> parent-child 模式 |
| | | - <code>qa_model</code> Q&A 模式:为分片文档生成 Q&A 对,然后对问题进行 embedding |
| | | - <code>doc_type</code> 文档类型(选填)Type of document (optional) |
| | | - <code>book</code> 图书 |
| | | 文档记录一本书籍或出版物 |
| | | - <code>web_page</code> 网页 |
| | | 网页内容的文档记录 |
| | | - <code>paper</code> 学术论文/文章 |
| | | 学术论文或研究文章的记录 |
| | | - <code>social_media_post</code> 社交媒体帖子 |
| | | 社交媒体上的帖子内容 |
| | | - <code>wikipedia_entry</code> 维基百科条目 |
| | | 维基百科的词条内容 |
| | | - <code>personal_document</code> 个人文档 |
| | | 个人相关的文档记录 |
| | | - <code>business_document</code> 商业文档 |
| | | 商业相关的文档记录 |
| | | - <code>im_chat_log</code> 即时通讯记录 |
| | | 即时通讯的聊天记录 |
| | | - <code>synced_from_notion</code> Notion同步文档 |
| | | 从Notion同步的文档内容 |
| | | - <code>synced_from_github</code> GitHub同步文档 |
| | | 从GitHub同步的文档内容 |
| | | - <code>others</code> 其他文档类型 |
| | | 其他未列出的文档类型 |
| | | |
| | | - <code>doc_metadata</code> 文档元数据(如提供文档类型则必填 |
| | | 字段因文档类型而异 |
| | | |
| | | 针对图书类型 For <code>book</code>: |
| | | - <code>title</code> 书名 |
| | | 书籍的标题 |
| | | - <code>language</code> 图书语言 |
| | | 书籍的语言 |
| | | - <code>author</code> 作者 |
| | | 书籍的作者 |
| | | - <code>publisher</code> 出版社 |
| | | 出版社的名称 |
| | | - <code>publication_date</code> 出版日期 |
| | | 书籍的出版日期 |
| | | - <code>isbn</code> ISBN号码 |
| | | 书籍的ISBN编号 |
| | | - <code>category</code> 图书分类 |
| | | 书籍的分类类别 |
| | | |
| | | 针对网页类型 For <code>web_page</code>: |
| | | - <code>title</code> 页面标题 |
| | | 网页的标题 |
| | | - <code>url</code> 页面网址 |
| | | 网页的URL地址 |
| | | - <code>language</code> 页面语言 |
| | | 网页的语言 |
| | | - <code>publish_date</code> 发布日期 |
| | | 网页的发布日期 |
| | | - <code>author/publisher</code> 作者/发布者 |
| | | 网页的作者或发布者 |
| | | - <code>topic/keywords</code> 主题/关键词 |
| | | 网页的主题或关键词 |
| | | - <code>description</code> 页面描述 |
| | | 网页的描述信息 |
| | | |
| | | 请查看 [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) 了解各文档类型所需字段的详细信息。 |
| | | |
| | | 针对"其他"类型文档,接受任何有效的JSON对象 |
| | | |
| | | - <code>doc_language</code> 在 Q&A 模式下,指定文档的语言,例如:<code>English</code>、<code>Chinese</code> |
| | | |
| | |
| | | <Property name='external_knowledge_id' type='str' key='external_knowledge_id'> |
| | | 外部知识库 ID(选填) |
| | | </Property> |
| | | <Property name='embedding_model' type='str' key='embedding_model'> |
| | | Embedding 模型名称 |
| | | </Property> |
| | | <Property name='embedding_model_provider' type='str' key='embedding_model_provider'> |
| | | Embedding 模型供应商 |
| | | </Property> |
| | | <Property name='retrieval_model' type='object' key='retrieval_model'> |
| | | 检索模式 |
| | | - <code>search_method</code> (string) 检索方法 |
| | | - <code>hybrid_search</code> 混合检索 |
| | | - <code>semantic_search</code> 语义检索 |
| | | - <code>full_text_search</code> 全文检索 |
| | | - <code>reranking_enable</code> (bool) 是否开启rerank |
| | | - <code>reranking_model</code> (object) Rerank 模型配置 |
| | | - <code>reranking_provider_name</code> (string) Rerank 模型的提供商 |
| | | - <code>reranking_model_name</code> (string) Rerank 模型的名称 |
| | | - <code>top_k</code> (int) 召回条数 |
| | | - <code>score_threshold_enabled</code> (bool)是否开启召回分数限制 |
| | | - <code>score_threshold</code> (float) 召回分数限制 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | |
| | | <Col> |
| | | ### Query |
| | | <Properties> |
| | | <Property name='keyword' type='string' key='keyword'> |
| | | 搜索关键词,可选 |
| | | </Property> |
| | | <Property name='tag_ids' type='array[string]' key='tag_ids'> |
| | | 标签 ID 列表,可选 |
| | | </Property> |
| | | <Property name='page' type='integer' key='page'> |
| | | 页码,可选,默认为 1 |
| | | <Property name='page' type='string' key='page'> |
| | | 页码 |
| | | </Property> |
| | | <Property name='limit' type='string' key='limit'> |
| | | 返回条数,可选,默认 20,范围 1-100 |
| | | </Property> |
| | | <Property name='include_all' type='boolean' key='include_all'> |
| | | 是否包含所有数据集(仅对所有者生效),可选,默认为 false |
| | | 返回条数,默认 20,范围 1-100 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | |
| | | "limit": 20, |
| | | "total": 50, |
| | | "page": 1 |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}' |
| | | method='GET' |
| | | title='查看知识库详情' |
| | | name='#view_dataset' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="GET" |
| | | label="/datasets/{dataset_id}" |
| | | targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "id": "eaedb485-95ac-4ffd-ab1e-18da6d676a2f", |
| | | "name": "Test Knowledge Base", |
| | | "description": "", |
| | | "provider": "vendor", |
| | | "permission": "only_me", |
| | | "data_source_type": null, |
| | | "indexing_technique": null, |
| | | "app_count": 0, |
| | | "document_count": 0, |
| | | "word_count": 0, |
| | | "created_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", |
| | | "created_at": 1735620612, |
| | | "updated_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", |
| | | "updated_at": 1735620612, |
| | | "embedding_model": null, |
| | | "embedding_model_provider": null, |
| | | "embedding_available": true, |
| | | "retrieval_model_dict": { |
| | | "search_method": "semantic_search", |
| | | "reranking_enable": false, |
| | | "reranking_mode": null, |
| | | "reranking_model": { |
| | | "reranking_provider_name": "", |
| | | "reranking_model_name": "" |
| | | }, |
| | | "weights": null, |
| | | "top_k": 2, |
| | | "score_threshold_enabled": false, |
| | | "score_threshold": null |
| | | }, |
| | | "tags": [], |
| | | "doc_form": null, |
| | | "external_knowledge_info": { |
| | | "external_knowledge_id": null, |
| | | "external_knowledge_api_id": null, |
| | | "external_knowledge_api_name": null, |
| | | "external_knowledge_api_endpoint": null |
| | | }, |
| | | "external_retrieval_model": { |
| | | "top_k": 2, |
| | | "score_threshold": 0.0, |
| | | "score_threshold_enabled": null |
| | | } |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}' |
| | | method='PATCH' |
| | | title='修改知识库详情' |
| | | name='#update_dataset' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='indexing_technique' type='string' key='indexing_technique'> |
| | | 索引模式(选填,建议填写) |
| | | - <code>high_quality</code> 高质量 |
| | | - <code>economy</code> 经济 |
| | | </Property> |
| | | <Property name='permission' type='string' key='permission'> |
| | | 权限(选填,默认 only_me) |
| | | - <code>only_me</code> 仅自己 |
| | | - <code>all_team_members</code> 所有团队成员 |
| | | - <code>partial_members</code> 部分团队成员 |
| | | </Property> |
| | | <Property name='embedding_model_provider' type='string' key='embedding_model_provider'> |
| | | 嵌入模型提供商(选填), 必须先在系统内设定好接入的模型,对应的是provider字段 |
| | | </Property> |
| | | <Property name='embedding_model' type='string' key='embedding_model'> |
| | | 嵌入模型(选填) |
| | | </Property> |
| | | <Property name='retrieval_model' type='object' key='retrieval_model'> |
| | | 检索参数(选填,如不填,按照默认方式召回) |
| | | - <code>search_method</code> (text) 检索方法:以下四个关键字之一,必填 |
| | | - <code>keyword_search</code> 关键字检索 |
| | | - <code>semantic_search</code> 语义检索 |
| | | - <code>full_text_search</code> 全文检索 |
| | | - <code>hybrid_search</code> 混合检索 |
| | | - <code>reranking_enable</code> (bool) 是否启用 Reranking,非必填,如果检索模式为 semantic_search 模式或者 hybrid_search 则传值 |
| | | - <code>reranking_mode</code> (object) Rerank 模型配置,非必填,如果启用了 reranking 则传值 |
| | | - <code>reranking_provider_name</code> (string) Rerank 模型提供商 |
| | | - <code>reranking_model_name</code> (string) Rerank 模型名称 |
| | | - <code>weights</code> (float) 混合检索模式下语意检索的权重设置 |
| | | - <code>top_k</code> (integer) 返回结果数量,非必填 |
| | | - <code>score_threshold_enabled</code> (bool) 是否开启 score 阈值 |
| | | - <code>score_threshold</code> (float) Score 阈值 |
| | | </Property> |
| | | <Property name='partial_member_list' type='array' key='partial_member_list'> |
| | | 部分团队成员 ID 列表(选填) |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="PATCH" |
| | | label="/datasets/{dataset_id}" |
| | | targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{ |
| | | "name": "Test Knowledge Base", |
| | | "indexing_technique": "high_quality", |
| | | "permission": "only_me", |
| | | "embedding_model_provider": "zhipuai", |
| | | "embedding_model": "embedding-3", |
| | | "retrieval_model": { |
| | | "search_method": "keyword_search", |
| | | "reranking_enable": false, |
| | | "reranking_mode": null, |
| | | "reranking_model": { |
| | | "reranking_provider_name": "", |
| | | "reranking_model_name": "" |
| | | }, |
| | | "weights": null, |
| | | "top_k": 1, |
| | | "score_threshold_enabled": false, |
| | | "score_threshold": null |
| | | }, |
| | | "partial_member_list": [] |
| | | }' |
| | | `} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' \ |
| | | --data-raw '{ |
| | | "name": "Test Knowledge Base", |
| | | "indexing_technique": "high_quality", |
| | | "permission": "only_me", |
| | | "embedding_model_provider": "zhipuai", |
| | | "embedding_model": "embedding-3", |
| | | "retrieval_model": { |
| | | "search_method": "keyword_search", |
| | | "reranking_enable": false, |
| | | "reranking_mode": null, |
| | | "reranking_model": { |
| | | "reranking_provider_name": "", |
| | | "reranking_model_name": "" |
| | | }, |
| | | "weights": null, |
| | | "top_k": 1, |
| | | "score_threshold_enabled": false, |
| | | "score_threshold": null |
| | | }, |
| | | "partial_member_list": [] |
| | | }' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "id": "eaedb485-95ac-4ffd-ab1e-18da6d676a2f", |
| | | "name": "Test Knowledge Base", |
| | | "description": "", |
| | | "provider": "vendor", |
| | | "permission": "only_me", |
| | | "data_source_type": null, |
| | | "indexing_technique": "high_quality", |
| | | "app_count": 0, |
| | | "document_count": 0, |
| | | "word_count": 0, |
| | | "created_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", |
| | | "created_at": 1735620612, |
| | | "updated_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", |
| | | "updated_at": 1735622679, |
| | | "embedding_model": "embedding-3", |
| | | "embedding_model_provider": "zhipuai", |
| | | "embedding_available": null, |
| | | "retrieval_model_dict": { |
| | | "search_method": "semantic_search", |
| | | "reranking_enable": false, |
| | | "reranking_mode": null, |
| | | "reranking_model": { |
| | | "reranking_provider_name": "", |
| | | "reranking_model_name": "" |
| | | }, |
| | | "weights": null, |
| | | "top_k": 2, |
| | | "score_threshold_enabled": false, |
| | | "score_threshold": null |
| | | }, |
| | | "tags": [], |
| | | "doc_form": null, |
| | | "external_knowledge_info": { |
| | | "external_knowledge_id": null, |
| | | "external_knowledge_api_id": null, |
| | | "external_knowledge_api_name": null, |
| | | "external_knowledge_api_endpoint": null |
| | | }, |
| | | "external_retrieval_model": { |
| | | "top_k": 2, |
| | | "score_threshold": 0.0, |
| | | "score_threshold_enabled": null |
| | | }, |
| | | "partial_member_list": [] |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | |
| | | </Property> |
| | | <Property name='text' type='string' key='text'> |
| | | 文档内容(选填) |
| | | </Property> |
| | | <Property name='doc_type' type='string' key='doc_type'> |
| | | 文档类型(选填) |
| | | - <code>book</code> 图书 Book |
| | | - <code>web_page</code> 网页 Web page |
| | | - <code>paper</code> 学术论文/文章 Academic paper/article |
| | | - <code>social_media_post</code> 社交媒体帖子 Social media post |
| | | - <code>wikipedia_entry</code> 维基百科条目 Wikipedia entry |
| | | - <code>personal_document</code> 个人文档 Personal document |
| | | - <code>business_document</code> 商业文档 Business document |
| | | - <code>im_chat_log</code> 即时通讯记录 Chat log |
| | | - <code>synced_from_notion</code> Notion同步文档 Notion document |
| | | - <code>synced_from_github</code> GitHub同步文档 GitHub document |
| | | - <code>others</code> 其他文档类型 Other document types |
| | | </Property> |
| | | <Property name='doc_metadata' type='object' key='doc_metadata'> |
| | | |
| | | 文档元数据(如提供文档类型则必填)。字段因文档类型而异: |
| | | |
| | | 针对图书 For <code>book</code>: |
| | | - <code>title</code> 书名 Book title |
| | | - <code>language</code> 图书语言 Book language |
| | | - <code>author</code> 作者 Book author |
| | | - <code>publisher</code> 出版社 Publisher name |
| | | - <code>publication_date</code> 出版日期 Publication date |
| | | - <code>isbn</code> ISBN号码 ISBN number |
| | | - <code>category</code> 图书分类 Book category |
| | | |
| | | 针对网页 For <code>web_page</code>: |
| | | - <code>title</code> 页面标题 Page title |
| | | - <code>url</code> 页面网址 Page URL |
| | | - <code>language</code> 页面语言 Page language |
| | | - <code>publish_date</code> 发布日期 Publish date |
| | | - <code>author/publisher</code> 作者/发布者 Author or publisher |
| | | - <code>topic/keywords</code> 主题/关键词 Topic or keywords |
| | | - <code>description</code> 页面描述 Page description |
| | | |
| | | 请查看 [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) 了解各文档类型所需字段的详细信息。 |
| | | |
| | | 针对"其他"类型文档,接受任何有效的JSON对象 |
| | | </Property> |
| | | <Property name='process_rule' type='object' key='process_rule'> |
| | | 处理规则(选填) |
| | |
| | | - <code>separator</code> 分段标识符,目前仅允许设置一个分隔符。默认为 <code>***</code> |
| | | - <code>max_tokens</code> 最大长度 (token) 需要校验小于父级的长度 |
| | | - <code>chunk_overlap</code> 分段重叠指的是在对数据进行分段时,段与段之间存在一定的重叠部分(选填) |
| | | - <code>doc_type</code> 文档类型(选填)Type of document (optional) |
| | | - <code>book</code> 图书 |
| | | 文档记录一本书籍或出版物 |
| | | - <code>web_page</code> 网页 |
| | | 网页内容的文档记录 |
| | | - <code>paper</code> 学术论文/文章 |
| | | 学术论文或研究文章的记录 |
| | | - <code>social_media_post</code> 社交媒体帖子 |
| | | 社交媒体上的帖子内容 |
| | | - <code>wikipedia_entry</code> 维基百科条目 |
| | | 维基百科的词条内容 |
| | | - <code>personal_document</code> 个人文档 |
| | | 个人相关的文档记录 |
| | | - <code>business_document</code> 商业文档 |
| | | 商业相关的文档记录 |
| | | - <code>im_chat_log</code> 即时通讯记录 |
| | | 即时通讯的聊天记录 |
| | | - <code>synced_from_notion</code> Notion同步文档 |
| | | 从Notion同步的文档内容 |
| | | - <code>synced_from_github</code> GitHub同步文档 |
| | | 从GitHub同步的文档内容 |
| | | - <code>others</code> 其他文档类型 |
| | | 其他未列出的文档类型 |
| | | |
| | | - <code>doc_metadata</code> 文档元数据(如提供文档类型则必填 |
| | | 字段因文档类型而异 |
| | | |
| | | 针对图书类型 For <code>book</code>: |
| | | - <code>title</code> 书名 |
| | | 书籍的标题 |
| | | - <code>language</code> 图书语言 |
| | | 书籍的语言 |
| | | - <code>author</code> 作者 |
| | | 书籍的作者 |
| | | - <code>publisher</code> 出版社 |
| | | 出版社的名称 |
| | | - <code>publication_date</code> 出版日期 |
| | | 书籍的出版日期 |
| | | - <code>isbn</code> ISBN号码 |
| | | 书籍的ISBN编号 |
| | | - <code>category</code> 图书分类 |
| | | 书籍的分类类别 |
| | | |
| | | 针对网页类型 For <code>web_page</code>: |
| | | - <code>title</code> 页面标题 |
| | | 网页的标题 |
| | | - <code>url</code> 页面网址 |
| | | 网页的URL地址 |
| | | - <code>language</code> 页面语言 |
| | | 网页的语言 |
| | | - <code>publish_date</code> 发布日期 |
| | | 网页的发布日期 |
| | | - <code>author/publisher</code> 作者/发布者 |
| | | 网页的作者或发布者 |
| | | - <code>topic/keywords</code> 主题/关键词 |
| | | 网页的主题或关键词 |
| | | - <code>description</code> 页面描述 |
| | | 网页的描述信息 |
| | | |
| | | 请查看 [api/services/dataset_service.py](https://github.com/langgenius/dify/blob/main/api/services/dataset_service.py#L475) 了解各文档类型所需字段的详细信息。 |
| | | |
| | | 针对"其他"类型文档,接受任何有效的JSON对象 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```text {{ title: 'Response' }} |
| | | 204 No Content |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "result": "success" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | |
| | | <Property name='status' type='string' key='status'> |
| | | 搜索状态,completed |
| | | </Property> |
| | | <Property name='page' type='string' key='page'> |
| | | 页码,可选 |
| | | </Property> |
| | | <Property name='limit' type='string' key='limit'> |
| | | 返回条数,可选,默认 20,范围 1-100 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | |
| | | "error": null, |
| | | "stopped_at": null |
| | | }], |
| | | "doc_form": "text_model", |
| | | "has_more": false, |
| | | "limit": 20, |
| | | "total": 9, |
| | | "page": 1 |
| | | "doc_form": "text_model" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```text {{ title: 'Response' }} |
| | | 204 No Content |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "result": "success" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": { |
| | | "data": [{ |
| | | "id": "", |
| | | "position": 1, |
| | | "document_id": "", |
| | |
| | | "completed_at": 1695312007, |
| | | "error": null, |
| | | "stopped_at": null |
| | | }, |
| | | "doc_form": "text_model" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' |
| | | method='POST' |
| | | title='新增文档子分段' |
| | | name='#create_child_chunk' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | <Property name='document_id' type='string' key='document_id'> |
| | | 文档 ID |
| | | </Property> |
| | | <Property name='segment_id' type='string' key='segment_id'> |
| | | 分段 ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='content' type='string' key='content'> |
| | | 子分段内容 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="POST" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks" |
| | | targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"content": "子分段内容"}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' \ |
| | | --data-raw '{ |
| | | "content": "子分段内容" |
| | | }' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": { |
| | | "id": "", |
| | | "segment_id": "", |
| | | "content": "子分段内容", |
| | | "word_count": 25, |
| | | "tokens": 0, |
| | | "index_node_id": "", |
| | | "index_node_hash": "", |
| | | "status": "completed", |
| | | "created_by": "", |
| | | "created_at": 1695312007, |
| | | "indexing_at": 1695312007, |
| | | "completed_at": 1695312007, |
| | | "error": null, |
| | | "stopped_at": null |
| | | } |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' |
| | | method='GET' |
| | | title='查询文档子分段' |
| | | name='#get_child_chunks' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | <Property name='document_id' type='string' key='document_id'> |
| | | 文档 ID |
| | | </Property> |
| | | <Property name='segment_id' type='string' key='segment_id'> |
| | | 分段 ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Query |
| | | <Properties> |
| | | <Property name='keyword' type='string' key='keyword'> |
| | | 搜索关键词(选填) |
| | | </Property> |
| | | <Property name='page' type='integer' key='page'> |
| | | 页码(选填,默认1) |
| | | </Property> |
| | | <Property name='limit' type='integer' key='limit'> |
| | | 每页数量(选填,默认20,最大100) |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="GET" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks" |
| | | targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks?page=1&limit=20' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks?page=1&limit=20' \ |
| | | --header 'Authorization: Bearer {api_key}' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": [{ |
| | | "id": "", |
| | | "segment_id": "", |
| | | "content": "子分段内容", |
| | | "word_count": 25, |
| | | "tokens": 0, |
| | | "index_node_id": "", |
| | | "index_node_hash": "", |
| | | "status": "completed", |
| | | "created_by": "", |
| | | "created_at": 1695312007, |
| | | "indexing_at": 1695312007, |
| | | "completed_at": 1695312007, |
| | | "error": null, |
| | | "stopped_at": null |
| | | }], |
| | | "total": 1, |
| | | "total_pages": 1, |
| | | "page": 1, |
| | | "limit": 20 |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' |
| | | method='DELETE' |
| | | title='删除文档子分段' |
| | | name='#delete_child_chunk' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | <Property name='document_id' type='string' key='document_id'> |
| | | 文档 ID |
| | | </Property> |
| | | <Property name='segment_id' type='string' key='segment_id'> |
| | | 分段 ID |
| | | </Property> |
| | | <Property name='child_chunk_id' type='string' key='child_chunk_id'> |
| | | 子分段 ID |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="DELETE" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}" |
| | | targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```text {{ title: 'Response' }} |
| | | 204 No Content |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Row> |
| | | <Col> |
| | | ### 错误信息 |
| | | <Properties> |
| | | <Property name='code' type='string' key='code'> |
| | | 返回的错误代码 |
| | | </Property> |
| | | </Properties> |
| | | <Properties> |
| | | <Property name='status' type='number' key='status'> |
| | | 返回的错误状态 |
| | | </Property> |
| | | </Properties> |
| | | <Properties> |
| | | <Property name='message' type='string' key='message'> |
| | | 返回的错误信息 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col> |
| | | <CodeGroup title="Example"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "code": "no_file_uploaded", |
| | | "message": "Please upload your file.", |
| | | "status": 400 |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' |
| | | method='PATCH' |
| | | title='更新文档子分段' |
| | | name='#update_child_chunk' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | <Property name='document_id' type='string' key='document_id'> |
| | | 文档 ID |
| | | </Property> |
| | | <Property name='segment_id' type='string' key='segment_id'> |
| | | 分段 ID |
| | | </Property> |
| | | <Property name='child_chunk_id' type='string' key='child_chunk_id'> |
| | | 子分段 ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='content' type='string' key='content'> |
| | | 子分段内容 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="PATCH" |
| | | label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}" |
| | | targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"content": "更新的子分段内容"}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' \ |
| | | --data-raw '{ |
| | | "content": "更新的子分段内容" |
| | | }' |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": { |
| | | "id": "", |
| | | "segment_id": "", |
| | | "content": "更新的子分段内容", |
| | | "word_count": 25, |
| | | "tokens": 0, |
| | | "index_node_id": "", |
| | | "index_node_hash": "", |
| | | "status": "completed", |
| | | "created_by": "", |
| | | "created_at": 1695312007, |
| | | "indexing_at": 1695312007, |
| | | "completed_at": 1695312007, |
| | | "error": null, |
| | | "stopped_at": null |
| | | } |
| | | "doc_form": "text_model" |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | |
| | | </Property> |
| | | <Property name='retrieval_model' type='object' key='retrieval_model'> |
| | | 检索参数(选填,如不填,按照默认方式召回) |
| | | - <code>search_method</code> (text) 检索方法:以下四个关键字之一,必填 |
| | | - <code>search_method</code> (text) 检索方法:以下三个关键字之一,必填 |
| | | - <code>keyword_search</code> 关键字检索 |
| | | - <code>semantic_search</code> 语义检索 |
| | | - <code>full_text_search</code> 全文检索 |
| | |
| | | "id": "a8c6c36f-9f5d-4d7a-8472-f5d7b75d71d2", |
| | | "data_source_type": "upload_file", |
| | | "name": "readme.txt", |
| | | "doc_type": null |
| | | } |
| | | }, |
| | | "score": 3.730463140527718e-05, |
| | |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata' |
| | | method='POST' |
| | | title='新增元数据' |
| | | name='#create_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Params |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='segment' type='object' key='segment'> |
| | | - <code>type</code> (string) 元数据类型,必填 |
| | | - <code>name</code> (string) 元数据名称,必填 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="POST" |
| | | label="/datasets/{dataset_id}/metadata" |
| | | targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/metadata' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'\\\n--data-raw '{"type": "string", "name": "test"}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "id": "abc", |
| | | "type": "string", |
| | | "name": "test", |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata/{metadata_id}' |
| | | method='PATCH' |
| | | title='更新元数据' |
| | | name='#update_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | <Property name='metadata_id' type='string' key='metadata_id'> |
| | | 元数据 ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='segment' type='object' key='segment'> |
| | | - <code>name</code> (string) 元数据名称,必填 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="PATCH" |
| | | label="/datasets/{dataset_id}/metadata/{metadata_id}" |
| | | targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/metadata/{metadata_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'\\\n--data-raw '{"name": "test"}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "id": "abc", |
| | | "type": "string", |
| | | "name": "test", |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata/{metadata_id}' |
| | | method='DELETE' |
| | | title='删除元数据' |
| | | name='#delete_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | <Property name='metadata_id' type='string' key='metadata_id'> |
| | | 元数据 ID |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="DELETE" |
| | | label="/datasets/{dataset_id}/metadata/{metadata_id}" |
| | | targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/metadata/{metadata_id}' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata/built-in/{action}' |
| | | method='POST' |
| | | title='启用/禁用内置元数据' |
| | | name='#toggle_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | <Property name='action' type='string' key='action'> |
| | | disable/enable |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="POST" |
| | | label="/datasets/{dataset_id}/metadata/built-in/{action}" |
| | | targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/metadata/built-in/{action}' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/documents/metadata' |
| | | method='POST' |
| | | title='更新文档元数据' |
| | | name='#update_documents_metadata' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | </Properties> |
| | | |
| | | ### Request Body |
| | | <Properties> |
| | | <Property name='operation_data' type='object list' key='segments'> |
| | | - <code>document_id</code> (string) 文档 ID |
| | | - <code>metadata_list</code> (list) 元数据列表 |
| | | - <code>id</code> (string) 元数据 ID |
| | | - <code>type</code> (string) 元数据类型 |
| | | - <code>name</code> (string) 元数据名称 |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="POST" |
| | | label="/datasets/{dataset_id}/documents/metadata" |
| | | targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/metadata' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'\\\n--data-raw '{"operation_data": [{"document_id": "document_id", "metadata_list": [{"id": "id", "value": "value", "name": "name"}]}]}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/datasets/{dataset_id}/metadata' |
| | | method='GET' |
| | | title='查询知识库元数据列表' |
| | | name='#dataset_metadata_list' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Path |
| | | <Properties> |
| | | <Property name='dataset_id' type='string' key='dataset_id'> |
| | | 知识库 ID |
| | | </Property> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="GET" |
| | | label="/datasets/{dataset_id}/metadata" |
| | | targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/metadata' \\\n--header 'Authorization: Bearer {api_key}'`} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "doc_metadata": [ |
| | | { |
| | | "id": "", |
| | | "name": "name", |
| | | "type": "string", |
| | | "use_count": 0, |
| | | }, |
| | | ... |
| | | ], |
| | | "built_in_field_enabled": true |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | | <Heading |
| | | url='/workspaces/current/models/model-types/text-embedding' |
| | | method='GET' |
| | | title='获取嵌入模型列表' |
| | | name='#model_type_list' |
| | | /> |
| | | <Row> |
| | | <Col> |
| | | ### Query |
| | | <Properties> |
| | | </Properties> |
| | | </Col> |
| | | <Col sticky> |
| | | <CodeGroup |
| | | title="Request" |
| | | tag="GET" |
| | | label="/datasets/{dataset_id}" |
| | | targetCode={`curl --location --location --request GET '${props.apiBaseUrl}/workspaces/current/models/model-types/text-embedding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' `} |
| | | > |
| | | ```bash {{ title: 'cURL' }} |
| | | curl --location --request GET '${props.apiBaseUrl}/workspaces/current/models/model-types/text-embedding' \ |
| | | --header 'Authorization: Bearer {api_key}' \ |
| | | --header 'Content-Type: application/json' \ |
| | | ``` |
| | | </CodeGroup> |
| | | <CodeGroup title="Response"> |
| | | ```json {{ title: 'Response' }} |
| | | { |
| | | "data": [ |
| | | { |
| | | "provider": "zhipuai", |
| | | "label": { |
| | | "zh_Hans": "智谱 AI", |
| | | "en_US": "ZHIPU AI" |
| | | }, |
| | | "icon_small": { |
| | | "zh_Hans": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_small/zh_Hans", |
| | | "en_US": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_small/en_US" |
| | | }, |
| | | "icon_large": { |
| | | "zh_Hans": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_large/zh_Hans", |
| | | "en_US": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_large/en_US" |
| | | }, |
| | | "status": "active", |
| | | "models": [ |
| | | { |
| | | "model": "embedding-3", |
| | | "label": { |
| | | "zh_Hans": "embedding-3", |
| | | "en_US": "embedding-3" |
| | | }, |
| | | "model_type": "text-embedding", |
| | | "features": null, |
| | | "fetch_from": "predefined-model", |
| | | "model_properties": { |
| | | "context_size": 8192 |
| | | }, |
| | | "deprecated": false, |
| | | "status": "active", |
| | | "load_balancing_enabled": false |
| | | }, |
| | | { |
| | | "model": "embedding-2", |
| | | "label": { |
| | | "zh_Hans": "embedding-2", |
| | | "en_US": "embedding-2" |
| | | }, |
| | | "model_type": "text-embedding", |
| | | "features": null, |
| | | "fetch_from": "predefined-model", |
| | | "model_properties": { |
| | | "context_size": 8192 |
| | | }, |
| | | "deprecated": false, |
| | | "status": "active", |
| | | "load_balancing_enabled": false |
| | | }, |
| | | { |
| | | "model": "text_embedding", |
| | | "label": { |
| | | "zh_Hans": "text_embedding", |
| | | "en_US": "text_embedding" |
| | | }, |
| | | "model_type": "text-embedding", |
| | | "features": null, |
| | | "fetch_from": "predefined-model", |
| | | "model_properties": { |
| | | "context_size": 512 |
| | | }, |
| | | "deprecated": false, |
| | | "status": "active", |
| | | "load_balancing_enabled": false |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | </CodeGroup> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <hr className='ml-0 mr-0' /> |
| | | |
| | |
| | | import Main from '@/app/components/explore/installed-app' |
| | | |
| | | export type IInstalledAppProps = { |
| | | params: Promise<{ |
| | | params: { |
| | | appId: string |
| | | }> |
| | | } |
| | | } |
| | | |
| | | const InstalledApp: FC<IInstalledAppProps> = async ({ params }) => { |
| | | const InstalledApp: FC<IInstalledAppProps> = ({ params: { appId } }) => { |
| | | return ( |
| | | <Main id={(await params).appId} /> |
| | | <Main id={appId} /> |
| | | ) |
| | | } |
| | | export default React.memo(InstalledApp) |
| | |
| | | children: React.ReactNode |
| | | }> = ({ children }) => { |
| | | return ( |
| | | <div className="h-full min-w-[300px] pb-[env(safe-area-inset-bottom)]"> |
| | | <div className="min-w-[300px] h-full pb-[env(safe-area-inset-bottom)]"> |
| | | {children} |
| | | </div> |
| | | ) |
| | |
| | | }, [message, tokenFromUrl]) // Added dependencies to useEffect |
| | | |
| | | return ( |
| | | <div className="flex h-full items-center justify-center"> |
| | | <div className={cn('flex w-full grow flex-col items-center justify-center', 'px-6', 'md:px-[108px]')}> |
| | | <div className="flex items-center justify-center h-full"> |
| | | <div className={cn('flex flex-col items-center w-full grow justify-center', 'px-6', 'md:px-[108px]')}> |
| | | <Loading type='area' /> |
| | | </div> |
| | | </div> |
| | |
| | | return ( |
| | | <> |
| | | <div> |
| | | <div className="group relative"> |
| | | <div className="relative group"> |
| | | <Avatar {...props} /> |
| | | <div |
| | | onClick={() => { setIsShowAvatarPicker(true) }} |
| | | className="absolute inset-0 flex cursor-pointer items-center justify-center rounded-full bg-black bg-opacity-50 opacity-0 transition-opacity group-hover:opacity-100" |
| | | className="absolute inset-0 bg-black bg-opacity-50 rounded-full opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer flex items-center justify-center" |
| | | > |
| | | <span className="text-xs text-white"> |
| | | <span className="text-white text-xs"> |
| | | <RiPencilLine /> |
| | | </span> |
| | | </div> |
| | |
| | | <ImageInput onImageInput={handleImageInput} cropShape='round' /> |
| | | <Divider className='m-0' /> |
| | | |
| | | <div className='flex w-full items-center justify-center gap-2 p-3'> |
| | | <div className='w-full flex items-center justify-center p-3 gap-2'> |
| | | <Button className='w-full' onClick={() => setIsShowAvatarPicker(false)}> |
| | | {t('app.iconPicker.cancel')} |
| | | </Button> |
| | |
| | | 'use client' |
| | | import { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiGraduationCapFill, |
| | | } from '@remixicon/react' |
| | | |
| | | import { useContext } from 'use-context-selector' |
| | | import DeleteAccount from '../delete-account' |
| | | import s from './index.module.css' |
| | |
| | | import Button from '@/app/components/base/button' |
| | | import { updateUserProfile } from '@/service/common' |
| | | import { useAppContext } from '@/context/app-context' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import { ToastContext } from '@/app/components/base/toast' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | import { IS_CE_EDITION } from '@/config' |
| | | import Input from '@/app/components/base/input' |
| | | import PremiumBadge from '@/app/components/base/premium-badge' |
| | | |
| | | const titleClassName = ` |
| | | system-sm-semibold text-text-secondary |
| | |
| | | const { t } = useTranslation() |
| | | const { systemFeatures } = useAppContext() |
| | | const { mutateUserProfile, userProfile, apps } = useAppContext() |
| | | const { isEducationAccount } = useProviderContext() |
| | | const { notify } = useContext(ToastContext) |
| | | const [editNameModalVisible, setEditNameModalVisible] = useState(false) |
| | | const [editName, setEditName] = useState('') |
| | |
| | | <div className='mr-3'> |
| | | <AppIcon size='tiny' /> |
| | | </div> |
| | | <div className='system-sm-medium mt-[3px] text-text-secondary'>{item.name}</div> |
| | | <div className='mt-[3px] system-sm-medium text-text-secondary'>{item.name}</div> |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | return ( |
| | | <> |
| | | <div className='pb-3 pt-2'> |
| | | <div className='pt-2 pb-3'> |
| | | <h4 className='title-2xl-semi-bold text-text-primary'>{t('common.account.myAccount')}</h4> |
| | | </div> |
| | | <div className='mb-8 flex items-center rounded-xl bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 p-6'> |
| | | <div className='mb-8 p-6 rounded-xl flex items-center bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1'> |
| | | <AvatarWithEdit avatar={userProfile.avatar_url} name={userProfile.name} onSave={ mutateUserProfile } size={64} /> |
| | | <div className='ml-4'> |
| | | <p className='system-xl-semibold text-text-primary'> |
| | | {userProfile.name} |
| | | {isEducationAccount && ( |
| | | <PremiumBadge size='s' color='blue' className='ml-1 !px-2'> |
| | | <RiGraduationCapFill className='mr-1 h-3 w-3' /> |
| | | <span className='system-2xs-medium'>EDU</span> |
| | | </PremiumBadge> |
| | | )} |
| | | </p> |
| | | <p className='system-xl-semibold text-text-primary'>{userProfile.name}</p> |
| | | <p className='system-xs-regular text-text-tertiary'>{userProfile.email}</p> |
| | | </div> |
| | | </div> |
| | | <div className='mb-8'> |
| | | <div className={titleClassName}>{t('common.account.name')}</div> |
| | | <div className='mt-2 flex w-full items-center justify-between gap-2'> |
| | | <div className='system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled '> |
| | | <div className='flex items-center justify-between gap-2 w-full mt-2'> |
| | | <div className='flex-1 bg-components-input-bg-normal rounded-lg p-2 system-sm-regular text-components-input-text-filled '> |
| | | <span className='pl-1'>{userProfile.name}</span> |
| | | </div> |
| | | <div className='system-sm-medium cursor-pointer rounded-lg bg-components-button-tertiary-bg px-3 py-2 text-components-button-tertiary-text' onClick={handleEditName}> |
| | | <div className='bg-components-button-tertiary-bg rounded-lg py-2 px-3 cursor-pointer system-sm-medium text-components-button-tertiary-text' onClick={handleEditName}> |
| | | {t('common.operation.edit')} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div className='mb-8'> |
| | | <div className={titleClassName}>{t('common.account.email')}</div> |
| | | <div className='mt-2 flex w-full items-center justify-between gap-2'> |
| | | <div className='system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled '> |
| | | <div className='flex items-center justify-between gap-2 w-full mt-2'> |
| | | <div className='flex-1 bg-components-input-bg-normal rounded-lg p-2 system-sm-regular text-components-input-text-filled '> |
| | | <span className='pl-1'>{userProfile.email}</span> |
| | | </div> |
| | | </div> |
| | |
| | | systemFeatures.enable_email_password_login && ( |
| | | <div className='mb-8 flex justify-between gap-2'> |
| | | <div> |
| | | <div className='system-sm-semibold mb-1 text-text-secondary'>{t('common.account.password')}</div> |
| | | <div className='body-xs-regular mb-2 text-text-tertiary'>{t('common.account.passwordTip')}</div> |
| | | <div className='mb-1 system-sm-semibold text-text-secondary'>{t('common.account.password')}</div> |
| | | <div className='mb-2 body-xs-regular text-text-tertiary'>{t('common.account.passwordTip')}</div> |
| | | </div> |
| | | <Button onClick={() => setEditPasswordModalVisible(true)}>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</Button> |
| | | </div> |
| | |
| | | onClose={() => setEditNameModalVisible(false)} |
| | | className={s.modal} |
| | | > |
| | | <div className='title-2xl-semi-bold mb-6 text-text-primary'>{t('common.account.editName')}</div> |
| | | <div className='mb-6 title-2xl-semi-bold text-text-primary'>{t('common.account.editName')}</div> |
| | | <div className={titleClassName}>{t('common.account.name')}</div> |
| | | <Input className='mt-2' |
| | | value={editName} |
| | | onChange={e => setEditName(e.target.value)} |
| | | /> |
| | | <div className='mt-10 flex justify-end'> |
| | | <div className='flex justify-end mt-10'> |
| | | <Button className='mr-2' onClick={() => setEditNameModalVisible(false)}>{t('common.operation.cancel')}</Button> |
| | | <Button |
| | | disabled={editing || !editName} |
| | |
| | | }} |
| | | className={s.modal} |
| | | > |
| | | <div className='title-2xl-semi-bold mb-6 text-text-primary'>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</div> |
| | | <div className='mb-6 title-2xl-semi-bold text-text-primary'>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</div> |
| | | {userProfile.is_password_set && ( |
| | | <> |
| | | <div className={titleClassName}>{t('common.account.currentPassword')}</div> |
| | |
| | | </div> |
| | | </> |
| | | )} |
| | | <div className='system-sm-semibold mt-8 text-text-secondary'> |
| | | <div className='mt-8 system-sm-semibold text-text-secondary'> |
| | | {userProfile.is_password_set ? t('common.account.newPassword') : t('common.account.password')} |
| | | </div> |
| | | <div className='relative mt-2'> |
| | |
| | | </Button> |
| | | </div> |
| | | </div> |
| | | <div className='system-sm-semibold mt-8 text-text-secondary'>{t('common.account.confirmPassword')}</div> |
| | | <div className='mt-8 system-sm-semibold text-text-secondary'>{t('common.account.confirmPassword')}</div> |
| | | <div className='relative mt-2'> |
| | | <Input |
| | | type={showConfirmPassword ? 'text' : 'password'} |
| | |
| | | </Button> |
| | | </div> |
| | | </div> |
| | | <div className='mt-10 flex justify-end'> |
| | | <div className='flex justify-end mt-10'> |
| | | <Button className='mr-2' onClick={() => { |
| | | setEditPasswordModalVisible(false) |
| | | resetPasswordForm() |
| | |
| | | import { useTranslation } from 'react-i18next' |
| | | import { Fragment } from 'react' |
| | | import { useRouter } from 'next/navigation' |
| | | import { |
| | | RiGraduationCapFill, |
| | | } from '@remixicon/react' |
| | | import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' |
| | | import { Menu, Transition } from '@headlessui/react' |
| | | import Avatar from '@/app/components/base/avatar' |
| | | import { logout } from '@/service/common' |
| | | import { useAppContext } from '@/context/app-context' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import PremiumBadge from '@/app/components/base/premium-badge' |
| | | |
| | | export type IAppSelector = { |
| | | isMobile: boolean |
| | |
| | | const router = useRouter() |
| | | const { t } = useTranslation() |
| | | const { userProfile } = useAppContext() |
| | | const { isEducationAccount } = useProviderContext() |
| | | |
| | | const handleLogout = async () => { |
| | | await logout({ |
| | |
| | | ({ open }) => ( |
| | | <> |
| | | <div> |
| | | <MenuButton |
| | | <Menu.Button |
| | | className={` |
| | | p-1x inline-flex |
| | | items-center rounded-[20px] text-sm |
| | | inline-flex items-center |
| | | rounded-[20px] p-1x text-sm |
| | | text-text-primary |
| | | mobile:px-1 |
| | | ${open && 'bg-components-panel-bg-blur'} |
| | | `} |
| | | > |
| | | <Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={32} /> |
| | | </MenuButton> |
| | | </Menu.Button> |
| | | </div> |
| | | <Transition |
| | | as={Fragment} |
| | |
| | | leaveFrom="transform opacity-100 scale-100" |
| | | leaveTo="transform opacity-0 scale-95" |
| | | > |
| | | <MenuItems |
| | | <Menu.Items |
| | | className=" |
| | | absolute -right-2 -top-1 w-60 max-w-80 |
| | | origin-top-right divide-y divide-divider-subtle rounded-lg bg-components-panel-bg-blur |
| | | divide-y divide-divider-subtle origin-top-right rounded-lg bg-components-panel-bg-blur |
| | | shadow-lg |
| | | " |
| | | > |
| | | <MenuItem> |
| | | <Menu.Item> |
| | | <div className='p-1'> |
| | | <div className='flex flex-nowrap items-center px-3 py-2'> |
| | | <div className='grow'> |
| | | <div className='system-md-medium break-all text-text-primary'> |
| | | {userProfile.name} |
| | | {isEducationAccount && ( |
| | | <PremiumBadge size='s' color='blue' className='ml-1 !px-2'> |
| | | <RiGraduationCapFill className='mr-1 h-3 w-3' /> |
| | | <span className='system-2xs-medium'>EDU</span> |
| | | </PremiumBadge> |
| | | )} |
| | | </div> |
| | | <div className='system-xs-regular break-all text-text-tertiary'>{userProfile.email}</div> |
| | | <div className='system-md-medium text-text-primary break-all'>{userProfile.name}</div> |
| | | <div className='system-xs-regular text-text-tertiary break-all'>{userProfile.email}</div> |
| | | </div> |
| | | <Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={32} /> |
| | | </div> |
| | | </div> |
| | | </MenuItem> |
| | | <MenuItem> |
| | | </Menu.Item> |
| | | <Menu.Item> |
| | | <div className='p-1' onClick={() => handleLogout()}> |
| | | <div |
| | | className='group flex h-9 cursor-pointer items-center justify-start rounded-lg px-3 hover:bg-state-base-hover' |
| | | className='flex items-center justify-start h-9 px-3 rounded-lg cursor-pointer group hover:bg-state-base-hover' |
| | | > |
| | | <LogOut01 className='mr-1 flex h-4 w-4 text-text-tertiary' /> |
| | | <div className='text-[14px] font-normal text-text-secondary'>{t('common.userProfile.logout')}</div> |
| | | <LogOut01 className='w-4 h-4 text-text-tertiary flex mr-1' /> |
| | | <div className='font-normal text-[14px] text-text-secondary'>{t('common.userProfile.logout')}</div> |
| | | </div> |
| | | </div> |
| | | </MenuItem> |
| | | </MenuItems> |
| | | </Menu.Item> |
| | | </Menu.Items> |
| | | </Transition> |
| | | </> |
| | | ) |
| | |
| | | }, [getDeleteEmailVerifyCode, props]) |
| | | |
| | | return <> |
| | | <div className='body-md-medium py-1 text-text-destructive'> |
| | | <div className='py-1 text-text-destructive body-md-medium'> |
| | | {t('common.account.deleteTip')} |
| | | </div> |
| | | <div className='body-md-regular pb-2 pt-1 text-text-secondary'> |
| | | <div className='pt-1 pb-2 text-text-secondary body-md-regular'> |
| | | {t('common.account.deletePrivacyLinkTip')} |
| | | <Link href='https://dify.ai/privacy' className='text-text-accent'>{t('common.account.deletePrivacyLink')}</Link> |
| | | </div> |
| | | <label className='system-sm-semibold mb-1 mt-3 flex h-6 items-center text-text-secondary'>{t('common.account.deleteLabel')}</label> |
| | | <label className='mt-3 mb-1 h-6 flex items-center system-sm-semibold text-text-secondary'>{t('common.account.deleteLabel')}</label> |
| | | <Input placeholder={t('common.account.deletePlaceholder') as string} onChange={(e) => { |
| | | setUserInputEmail(e.target.value) |
| | | }} /> |
| | | <div className='mt-3 flex w-full flex-col gap-2'> |
| | | <div className='w-full flex flex-col mt-3 gap-2'> |
| | | <Button className='w-full' disabled={userInputEmail !== userProfile.email || isSendingEmail} loading={isSendingEmail} variant='primary' onClick={handleConfirm}>{t('common.account.sendVerificationButton')}</Button> |
| | | <Button className='w-full' onClick={props.onCancel}>{t('common.operation.cancel')}</Button> |
| | | </div> |
| | |
| | | className="max-w-[480px]" |
| | | footer={false} |
| | | > |
| | | <label className='system-sm-semibold mb-1 mt-3 flex items-center text-text-secondary'>{t('common.account.feedbackLabel')}</label> |
| | | <label className='mt-3 mb-1 flex items-center system-sm-semibold text-text-secondary'>{t('common.account.feedbackLabel')}</label> |
| | | <Textarea rows={6} value={userFeedback} placeholder={t('common.account.feedbackPlaceholder') as string} onChange={(e) => { |
| | | setUserFeedback(e.target.value) |
| | | }} /> |
| | | <div className='mt-3 flex w-full flex-col gap-2'> |
| | | <div className='w-full flex flex-col mt-3 gap-2'> |
| | | <Button className='w-full' loading={isPending} variant='primary' onClick={handleSubmit}>{t('common.operation.submit')}</Button> |
| | | <Button className='w-full' onClick={handleSkip}>{t('common.operation.skip')}</Button> |
| | | </div> |
| | |
| | | catch (error) { console.error(error) } |
| | | }, [emailToken, verificationCode, confirmDeleteAccount, props]) |
| | | return <> |
| | | <div className='body-md-medium pt-1 text-text-destructive'> |
| | | <div className='pt-1 text-text-destructive body-md-medium'> |
| | | {t('common.account.deleteTip')} |
| | | </div> |
| | | <div className='body-md-regular pb-2 pt-1 text-text-secondary'> |
| | | <div className='pt-1 pb-2 text-text-secondary body-md-regular'> |
| | | {t('common.account.deletePrivacyLinkTip')} |
| | | <Link href='https://dify.ai/privacy' className='text-text-accent'>{t('common.account.deletePrivacyLink')}</Link> |
| | | </div> |
| | | <label className='system-sm-semibold mb-1 mt-3 flex h-6 items-center text-text-secondary'>{t('common.account.verificationLabel')}</label> |
| | | <label className='mt-3 mb-1 h-6 flex items-center system-sm-semibold text-text-secondary'>{t('common.account.verificationLabel')}</label> |
| | | <Input minLength={6} maxLength={6} placeholder={t('common.account.verificationPlaceholder') as string} onChange={(e) => { |
| | | setVerificationCode(e.target.value) |
| | | }} /> |
| | | <div className='mt-3 flex w-full flex-col gap-2'> |
| | | <div className='w-full flex flex-col mt-3 gap-2'> |
| | | <Button className='w-full' disabled={shouldButtonDisabled} loading={isDeleting} variant='warning' onClick={handleConfirm}>{t('common.account.permanentlyDeleteButton')}</Button> |
| | | <Button className='w-full' onClick={props.onCancel}>{t('common.operation.cancel')}</Button> |
| | | <Countdown onResend={sendEmail} /> |
| | |
| | | import { useRouter } from 'next/navigation' |
| | | import Button from '../components/base/button' |
| | | import Avatar from './avatar' |
| | | import DifyLogo from '@/app/components/base/logo/dify-logo' |
| | | import { useCallback } from 'react' |
| | | import LogoSite from '@/app/components/base/logo/logo-site' |
| | | |
| | | const Header = () => { |
| | | const { t } = useTranslation() |
| | | const router = useRouter() |
| | | |
| | | const back = useCallback(() => { |
| | | const back = () => { |
| | | router.back() |
| | | }, [router]) |
| | | |
| | | } |
| | | return ( |
| | | <div className='flex flex-1 items-center justify-between px-4'> |
| | | <div className='flex items-center gap-3'> |
| | | <div className='flex cursor-pointer items-center' onClick={back}> |
| | | <DifyLogo /> |
| | | <div className='flex items-center cursor-pointer' onClick={back}> |
| | | <LogoSite className='object-contain' /> |
| | | </div> |
| | | <div className='h-4 w-[1px] origin-center rotate-[11.31deg] bg-divider-regular' /> |
| | | <p className='title-3xl-semi-bold relative mt-[-2px] text-text-primary'>{t('common.account.account')}</p> |
| | | <div className='w-[1px] h-4 bg-divider-regular' /> |
| | | <p className='text-text-primary title-3xl-semi-bold'>{t('common.account.account')}</p> |
| | | </div> |
| | | <div className='flex shrink-0 items-center gap-3'> |
| | | <Button className='system-sm-medium gap-2 px-3 py-2' onClick={back}> |
| | | <RiRobot2Line className='h-4 w-4' /> |
| | | <div className='flex items-center flex-shrink-0 gap-3'> |
| | | <Button className='gap-2 py-2 px-3 system-sm-medium' onClick={back}> |
| | | <RiRobot2Line className='w-4 h-4' /> |
| | | <p>{t('common.account.studio')}</p> |
| | | <RiArrowRightUpLine className='h-4 w-4' /> |
| | | <RiArrowRightUpLine className='w-4 h-4' /> |
| | | </Button> |
| | | <div className='h-4 w-[1px] bg-divider-regular' /> |
| | | <div className='w-[1px] h-4 bg-divider-regular' /> |
| | | <Avatar /> |
| | | </div> |
| | | </div> |
| | |
| | | <HeaderWrapper> |
| | | <Header /> |
| | | </HeaderWrapper> |
| | | <div className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-components-panel-bg'> |
| | | <div className='relative flex flex-col overflow-y-auto bg-components-panel-bg shrink-0 h-0 grow'> |
| | | {children} |
| | | </div> |
| | | </ModalContextProvider> |
| | |
| | | import AccountPage from './account-page' |
| | | |
| | | export default function Account() { |
| | | return <div className='mx-auto w-full max-w-[640px] px-6 pt-12'> |
| | | return <div className='max-w-[640px] w-full mx-auto pt-12 px-6'> |
| | | <AccountPage /> |
| | | </div> |
| | | } |
| | |
| | | return ( |
| | | <div className={ |
| | | cn( |
| | | 'flex w-full grow flex-col items-center justify-center', |
| | | 'flex flex-col items-center w-full grow justify-center', |
| | | 'px-6', |
| | | 'md:px-[108px]', |
| | | ) |
| | |
| | | {!checkRes && <Loading />} |
| | | {checkRes && !checkRes.is_valid && ( |
| | | <div className="flex flex-col md:w-[400px]"> |
| | | <div className="mx-auto w-full"> |
| | | <div className="mb-3 flex h-20 w-20 items-center justify-center rounded-[20px] border border-divider-regular bg-components-option-card-option-bg p-5 text-[40px] font-bold shadow-lg">🤷♂️</div> |
| | | <h2 className="text-[32px] font-bold text-text-primary">{t('login.invalid')}</h2> |
| | | <div className="w-full mx-auto"> |
| | | <div className="mb-3 flex justify-center items-center w-20 h-20 p-5 rounded-[20px] border border-gray-100 shadow-lg text-[40px] font-bold">🤷♂️</div> |
| | | <h2 className="text-[32px] font-bold text-gray-900">{t('login.invalid')}</h2> |
| | | </div> |
| | | <div className="mx-auto mt-6 w-full"> |
| | | <div className="w-full mx-auto mt-6"> |
| | | <Button variant='primary' className='w-full !text-sm'> |
| | | <a href="https://dify.ai">{t('login.explore')}</a> |
| | | </Button> |
| | |
| | | import React from 'react' |
| | | import Header from '../signin/_header' |
| | | import style from '../signin/page.module.css' |
| | | import ActivateForm from './activateForm' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | const Activate = () => { |
| | | return ( |
| | | <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> |
| | | <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> |
| | | <div className={cn( |
| | | style.background, |
| | | 'flex w-full min-h-screen', |
| | | 'sm:p-4 lg:p-8', |
| | | 'gap-x-20', |
| | | 'justify-center lg:justify-start', |
| | | )}> |
| | | <div className={ |
| | | cn( |
| | | 'flex w-full flex-col bg-white shadow rounded-2xl shrink-0', |
| | | 'space-between', |
| | | ) |
| | | }> |
| | | <Header /> |
| | | <ActivateForm /> |
| | | <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> |
| | | <div className='px-8 py-6 text-sm font-normal text-gray-500'> |
| | | © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. |
| | | </div> |
| | | </div> |
New file |
| | |
| | | .logo { |
| | | background: #fff center no-repeat url(./team-28x28.png); |
| | | background-size: 56px; |
| | | } |
| | |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useRouter } from 'next/navigation' |
| | | import { useContext, useContextSelector } from 'use-context-selector' |
| | | import { RiArrowDownSLine } from '@remixicon/react' |
| | | import React, { useCallback, useState } from 'react' |
| | | import { |
| | | RiDeleteBinLine, |
| | | RiEditLine, |
| | | RiEqualizer2Line, |
| | | RiExchange2Line, |
| | | RiFileCopy2Line, |
| | | RiFileDownloadLine, |
| | | RiFileUploadLine, |
| | | RiMoreLine, |
| | | } from '@remixicon/react' |
| | | import AppIcon from '../base/app-icon' |
| | | import SwitchAppModal from '../app/switch-app-modal' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import { |
| | | PortalToFollowElem, |
| | | PortalToFollowElemContent, |
| | | PortalToFollowElemTrigger, |
| | | } from '@/app/components/base/portal-to-follow-elem' |
| | | import Divider from '@/app/components/base/divider' |
| | | import Confirm from '@/app/components/base/confirm' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | import { ToastContext } from '@/app/components/base/toast' |
| | |
| | | import DuplicateAppModal from '@/app/components/app/duplicate-modal' |
| | | import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' |
| | | import CreateAppModal from '@/app/components/explore/create-app-modal' |
| | | import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' |
| | | import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' |
| | | import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' |
| | | import { NEED_REFRESH_APP_LIST_KEY } from '@/config' |
| | | import { getRedirection } from '@/utils/app-redirection' |
| | |
| | | import type { EnvironmentVariable } from '@/app/components/workflow/types' |
| | | import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal' |
| | | import { fetchWorkflowDraft } from '@/service/workflow' |
| | | import ContentDialog from '@/app/components/base/content-dialog' |
| | | import Button from '@/app/components/base/button' |
| | | import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView' |
| | | import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../base/portal-to-follow-elem' |
| | | |
| | | export type IAppInfoProps = { |
| | | expand: boolean |
| | |
| | | const [showEditModal, setShowEditModal] = useState(false) |
| | | const [showDuplicateModal, setShowDuplicateModal] = useState(false) |
| | | const [showConfirmDelete, setShowConfirmDelete] = useState(false) |
| | | const [showSwitchTip, setShowSwitchTip] = useState<string>('') |
| | | const [showSwitchModal, setShowSwitchModal] = useState<boolean>(false) |
| | | const [showImportDSLModal, setShowImportDSLModal] = useState<boolean>(false) |
| | | const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([]) |
| | |
| | | setAppDetail(app) |
| | | mutateApps() |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.editFailed') }) |
| | | } |
| | | }, [appDetail, mutateApps, notify, setAppDetail, t]) |
| | |
| | | onPlanInfoChanged() |
| | | getRedirection(true, newApp, replace) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | } |
| | |
| | | a.download = `${appDetail.name}.yml` |
| | | a.click() |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.exportFailed') }) |
| | | } |
| | | } |
| | |
| | | } |
| | | setSecretEnvList(list) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.exportFailed') }) |
| | | } |
| | | } |
| | |
| | | }) |
| | | } |
| | | setShowConfirmDelete(false) |
| | | }, [appDetail, mutateApps, notify, onPlanInfoChanged, replace, setAppDetail, t]) |
| | | }, [appDetail, mutateApps, notify, onPlanInfoChanged, replace, t]) |
| | | |
| | | const { isCurrentWorkspaceEditor } = useAppContext() |
| | | |
| | | const [showMore, setShowMore] = useState(false) |
| | | const handleTriggerMore = useCallback(() => { |
| | | setShowMore(true) |
| | | }, [setShowMore]) |
| | | |
| | | if (!appDetail) |
| | | return null |
| | | |
| | | return ( |
| | | <div> |
| | | <button |
| | | <PortalToFollowElem |
| | | open={open} |
| | | onOpenChange={setOpen} |
| | | placement='bottom-start' |
| | | offset={4} |
| | | > |
| | | <div className='relative'> |
| | | <PortalToFollowElemTrigger |
| | | onClick={() => { |
| | | if (isCurrentWorkspaceEditor) |
| | | setOpen(v => !v) |
| | | }} |
| | | className='block w-full' |
| | | className='block' |
| | | > |
| | | <div className={cn('flex rounded-lg', expand ? 'flex-col gap-2 p-2 pb-2.5' : 'items-start justify-center gap-1 p-1', open && 'bg-state-base-hover', isCurrentWorkspaceEditor && 'cursor-pointer hover:bg-state-base-hover')}> |
| | | <div className={`flex items-center self-stretch ${expand ? 'justify-between' : 'flex-col gap-1'}`}> |
| | | <div className={cn('flex p-1 rounded-lg', open && 'bg-gray-100', isCurrentWorkspaceEditor && 'hover:bg-gray-100 cursor-pointer')}> |
| | | <div className='relative shrink-0 mr-2'> |
| | | <AppIcon |
| | | size={expand ? 'large' : 'small'} |
| | | iconType={appDetail.icon_type} |
| | |
| | | background={appDetail.icon_background} |
| | | imageUrl={appDetail.icon_url} |
| | | /> |
| | | <div className='flex items-center justify-center rounded-md p-0.5'> |
| | | <div className='flex h-5 w-5 items-center justify-center'> |
| | | <RiEqualizer2Line className='h-4 w-4 text-text-tertiary' /> |
| | | <span className={cn( |
| | | 'absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm', |
| | | !expand && '!w-3.5 !h-3.5 !bottom-[-2px] !right-[-2px]', |
| | | )}> |
| | | {appDetail.mode === 'advanced-chat' && ( |
| | | <ChatBot className={cn('w-3 h-3 text-[#1570EF]', !expand && '!w-2.5 !h-2.5')} /> |
| | | )} |
| | | {appDetail.mode === 'agent-chat' && ( |
| | | <CuteRobot className={cn('w-3 h-3 text-indigo-600', !expand && '!w-2.5 !h-2.5')} /> |
| | | )} |
| | | {appDetail.mode === 'chat' && ( |
| | | <ChatBot className={cn('w-3 h-3 text-[#1570EF]', !expand && '!w-2.5 !h-2.5')} /> |
| | | )} |
| | | {appDetail.mode === 'completion' && ( |
| | | <AiText className={cn('w-3 h-3 text-[#0E9384]', !expand && '!w-2.5 !h-2.5')} /> |
| | | )} |
| | | {appDetail.mode === 'workflow' && ( |
| | | <Route className={cn('w-3 h-3 text-[#f79009]', !expand && '!w-2.5 !h-2.5')} /> |
| | | )} |
| | | </span> |
| | | </div> |
| | | {expand && ( |
| | | <div className="grow w-0"> |
| | | <div className='flex justify-between items-center text-sm leading-5 font-medium text-text-secondary'> |
| | | <div className='truncate' title={appDetail.name}>{appDetail.name}</div> |
| | | {isCurrentWorkspaceEditor && <RiArrowDownSLine className='shrink-0 ml-[2px] w-3 h-3 text-gray-500' />} |
| | | </div> |
| | | <div className='flex items-center text-[10px] leading-[18px] font-medium text-gray-500 gap-1'> |
| | | {appDetail.mode === 'advanced-chat' && ( |
| | | <> |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div> |
| | | <div title={t('app.types.advanced') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.advanced').toUpperCase()}</div> |
| | | </> |
| | | )} |
| | | {appDetail.mode === 'agent-chat' && ( |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.agent').toUpperCase()}</div> |
| | | )} |
| | | {appDetail.mode === 'chat' && ( |
| | | <> |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div> |
| | | <div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div> |
| | | </> |
| | | )} |
| | | {appDetail.mode === 'completion' && ( |
| | | <> |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.completion').toUpperCase()}</div> |
| | | <div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div> |
| | | </> |
| | | )} |
| | | {appDetail.mode === 'workflow' && ( |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.workflow').toUpperCase()}</div> |
| | | )} |
| | | </div> |
| | | </div> |
| | | )} |
| | | </div> |
| | | { |
| | | expand && ( |
| | | <div className='flex flex-col items-start gap-1'> |
| | | <div className='flex w-full'> |
| | | <div className='system-md-semibold truncate text-text-secondary'>{appDetail.name}</div> |
| | | </div> |
| | | <div className='system-2xs-medium-uppercase text-text-tertiary'>{appDetail.mode === 'advanced-chat' ? t('app.types.advanced') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div> |
| | | </div> |
| | | ) |
| | | } |
| | | </div> |
| | | </button> |
| | | <ContentDialog |
| | | show={open} |
| | | onClose={() => setOpen(false)} |
| | | className='absolute bottom-2 left-2 top-2 flex w-[420px] flex-col rounded-2xl !p-0' |
| | | > |
| | | <div className='flex shrink-0 flex-col items-start justify-center gap-3 self-stretch p-4'> |
| | | <div className='flex items-center gap-3 self-stretch'> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-[1002]'> |
| | | <div className='relative w-[320px] bg-white rounded-2xl shadow-xl'> |
| | | {/* header */} |
| | | <div className={cn('flex pl-4 pt-3 pr-3', !appDetail.description && 'pb-2')}> |
| | | <div className='relative shrink-0 mr-2'> |
| | | <AppIcon |
| | | size="large" |
| | | iconType={appDetail.icon_type} |
| | |
| | | background={appDetail.icon_background} |
| | | imageUrl={appDetail.icon_url} |
| | | /> |
| | | <div className='flex w-full grow flex-col items-start justify-center'> |
| | | <div className='system-md-semibold w-full truncate text-text-secondary'>{appDetail.name}</div> |
| | | <div className='system-2xs-medium-uppercase text-text-tertiary'>{appDetail.mode === 'advanced-chat' ? t('app.types.advanced') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div> |
| | | <span className='absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'> |
| | | {appDetail.mode === 'advanced-chat' && ( |
| | | <ChatBot className='w-3 h-3 text-[#1570EF]' /> |
| | | )} |
| | | {appDetail.mode === 'agent-chat' && ( |
| | | <CuteRobot className='w-3 h-3 text-indigo-600' /> |
| | | )} |
| | | {appDetail.mode === 'chat' && ( |
| | | <ChatBot className='w-3 h-3 text-[#1570EF]' /> |
| | | )} |
| | | {appDetail.mode === 'completion' && ( |
| | | <AiText className='w-3 h-3 text-[#0E9384]' /> |
| | | )} |
| | | {appDetail.mode === 'workflow' && ( |
| | | <Route className='w-3 h-3 text-[#f79009]' /> |
| | | )} |
| | | </span> |
| | | </div> |
| | | <div className='grow w-0'> |
| | | <div title={appDetail.name} className='flex justify-between items-center text-sm leading-5 font-medium text-gray-900 truncate'>{appDetail.name}</div> |
| | | <div className='flex items-center text-[10px] leading-[18px] font-medium text-gray-500 gap-1'> |
| | | {appDetail.mode === 'advanced-chat' && ( |
| | | <> |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div> |
| | | <div title={t('app.types.advanced') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.advanced').toUpperCase()}</div> |
| | | </> |
| | | )} |
| | | {appDetail.mode === 'agent-chat' && ( |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.agent').toUpperCase()}</div> |
| | | )} |
| | | {appDetail.mode === 'chat' && ( |
| | | <> |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div> |
| | | <div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div> |
| | | </> |
| | | )} |
| | | {appDetail.mode === 'completion' && ( |
| | | <> |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.completion').toUpperCase()}</div> |
| | | <div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div> |
| | | </> |
| | | )} |
| | | {appDetail.mode === 'workflow' && ( |
| | | <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.workflow').toUpperCase()}</div> |
| | | )} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | {/* description */} |
| | | {appDetail.description && ( |
| | | <div className='system-xs-regular text-text-tertiary'>{appDetail.description}</div> |
| | | <div className='px-4 py-2 text-gray-500 text-xs leading-[18px]'>{appDetail.description}</div> |
| | | )} |
| | | {/* operations */} |
| | | <div className='flex flex-wrap items-center gap-1 self-stretch'> |
| | | <Button |
| | | size={'small'} |
| | | variant={'secondary'} |
| | | className='gap-[1px]' |
| | | onClick={() => { |
| | | <Divider className="!my-1" /> |
| | | <div className="w-full py-1"> |
| | | <div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={() => { |
| | | setOpen(false) |
| | | setShowEditModal(true) |
| | | }} |
| | | > |
| | | <RiEditLine className='h-3.5 w-3.5 text-components-button-secondary-text' /> |
| | | <span className='system-xs-medium text-components-button-secondary-text'>{t('app.editApp')}</span> |
| | | </Button> |
| | | <Button |
| | | size={'small'} |
| | | variant={'secondary'} |
| | | className='gap-[1px]' |
| | | onClick={() => { |
| | | }}> |
| | | <span className='text-gray-700 text-sm leading-5'>{t('app.editApp')}</span> |
| | | </div> |
| | | <div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={() => { |
| | | setOpen(false) |
| | | setShowDuplicateModal(true) |
| | | }}> |
| | | <span className='text-gray-700 text-sm leading-5'>{t('app.duplicate')}</span> |
| | | </div> |
| | | {(appDetail.mode === 'completion' || appDetail.mode === 'chat') && ( |
| | | <> |
| | | <Divider className="!my-1" /> |
| | | <div |
| | | className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' |
| | | onMouseEnter={() => setShowSwitchTip(appDetail.mode)} |
| | | onMouseLeave={() => setShowSwitchTip('')} |
| | | onClick={() => { |
| | | setOpen(false) |
| | | setShowSwitchModal(true) |
| | | }} |
| | | > |
| | | <RiFileCopy2Line className='h-3.5 w-3.5 text-components-button-secondary-text' /> |
| | | <span className='system-xs-medium text-components-button-secondary-text'>{t('app.duplicate')}</span> |
| | | </Button> |
| | | <Button |
| | | size={'small'} |
| | | variant={'secondary'} |
| | | className='gap-[1px]' |
| | | onClick={exportCheck} |
| | | > |
| | | <RiFileDownloadLine className='h-3.5 w-3.5 text-components-button-secondary-text' /> |
| | | <span className='system-xs-medium text-components-button-secondary-text'>{t('app.export')}</span> |
| | | </Button> |
| | | {appDetail.mode !== 'agent-chat' && <PortalToFollowElem |
| | | open={showMore} |
| | | onOpenChange={setShowMore} |
| | | placement='bottom-end' |
| | | offset={{ |
| | | mainAxis: 4, |
| | | }}> |
| | | <PortalToFollowElemTrigger onClick={handleTriggerMore}> |
| | | <Button |
| | | size={'small'} |
| | | variant={'secondary'} |
| | | className='gap-[1px]' |
| | | > |
| | | <RiMoreLine className='h-3.5 w-3.5 text-components-button-secondary-text' /> |
| | | <span className='system-xs-medium text-components-button-secondary-text'>{t('common.operation.more')}</span> |
| | | </Button> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-[21]'> |
| | | <div className='flex w-[264px] flex-col rounded-[12px] border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[5px]'> |
| | | <span className='text-gray-700 text-sm leading-5'>{t('app.switch')}</span> |
| | | </div> |
| | | </> |
| | | )} |
| | | <Divider className="!my-1" /> |
| | | <div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={exportCheck}> |
| | | <span className='text-gray-700 text-sm leading-5'>{t('app.export')}</span> |
| | | </div> |
| | | { |
| | | (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') |
| | | && <div className='flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover' |
| | | (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && ( |
| | | <div |
| | | className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' |
| | | onClick={() => { |
| | | setOpen(false) |
| | | setShowImportDSLModal(true) |
| | | }}> |
| | | <RiFileUploadLine className='h-4 w-4 text-text-tertiary' /> |
| | | <span className='system-md-regular text-text-secondary'>{t('workflow.common.importDSL')}</span> |
| | | <span className='text-gray-700 text-sm leading-5'>{t('workflow.common.importDSL')}</span> |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | (appDetail.mode === 'completion' || appDetail.mode === 'chat') |
| | | && <div className='flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover' |
| | | onClick={() => { |
| | | setOpen(false) |
| | | setShowSwitchModal(true) |
| | | }}> |
| | | <RiExchange2Line className='h-4 w-4 text-text-tertiary' /> |
| | | <span className='system-md-regular text-text-secondary'>{t('app.switch')}</span> |
| | | </div> |
| | | } |
| | | </div> |
| | | </PortalToFollowElemContent> |
| | | </PortalToFollowElem>} |
| | | </div> |
| | | </div> |
| | | <div className='flex flex-1'> |
| | | <CardView |
| | | appId={appDetail.id} |
| | | isInPanel={true} |
| | | className='flex grow flex-col gap-2 overflow-auto px-2 py-1' |
| | | /> |
| | | </div> |
| | | <div className='flex min-h-fit shrink-0 flex-col items-start justify-center gap-3 self-stretch border-t-[0.5px] border-divider-subtle p-2'> |
| | | <Button |
| | | size={'medium'} |
| | | variant={'ghost'} |
| | | className='gap-0.5' |
| | | onClick={() => { |
| | | <Divider className="!my-1" /> |
| | | <div className='group h-9 py-2 px-3 mx-1 flex items-center hover:bg-red-50 rounded-lg cursor-pointer' onClick={() => { |
| | | setOpen(false) |
| | | setShowConfirmDelete(true) |
| | | }} |
| | | > |
| | | <RiDeleteBinLine className='h-4 w-4 text-text-tertiary' /> |
| | | <span className='system-sm-medium text-text-tertiary'>{t('common.operation.deleteApp')}</span> |
| | | </Button> |
| | | }}> |
| | | <span className='text-gray-700 text-sm leading-5 group-hover:text-red-500'> |
| | | {t('common.operation.delete')} |
| | | </span> |
| | | </div> |
| | | </ContentDialog> |
| | | </div> |
| | | {/* switch tip */} |
| | | <div |
| | | className={cn( |
| | | 'hidden absolute left-[324px] top-0 w-[376px] rounded-xl bg-white border-[0.5px] border-[rgba(0,0,0,0.05)] shadow-lg', |
| | | showSwitchTip && '!block', |
| | | )} |
| | | > |
| | | <div className={cn( |
| | | 'w-full h-[256px] bg-center bg-no-repeat bg-contain rounded-xl', |
| | | showSwitchTip === 'chat' && s.expertPic, |
| | | showSwitchTip === 'completion' && s.completionPic, |
| | | )} /> |
| | | <div className='px-4 pb-2'> |
| | | <div className='flex items-center gap-1 text-gray-700 text-md leading-6 font-semibold'> |
| | | {showSwitchTip === 'chat' ? t('app.types.advanced') : t('app.types.workflow')} |
| | | <span className='px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span> |
| | | </div> |
| | | <div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div> |
| | | <div className='mt-1 text-gray-500 text-sm leading-5'>{t('app.newApp.advancedDescription')}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </PortalToFollowElemContent> |
| | | {showSwitchModal && ( |
| | | <SwitchAppModal |
| | | inAppDetail |
| | |
| | | /> |
| | | )} |
| | | </div> |
| | | </PortalToFollowElem> |
| | | ) |
| | | } |
| | | |
| | |
| | | |
| | | const ICON_MAP = { |
| | | app: <AppIcon className='border !border-[rgba(0,0,0,0.05)]' />, |
| | | api: <AppIcon innerIcon={ApiSvg} className='border !border-purple-200 !bg-purple-50' />, |
| | | api: <AppIcon innerIcon={ApiSvg} className='border !bg-purple-50 !border-purple-200' />, |
| | | dataset: <AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' />, |
| | | webapp: <AppIcon innerIcon={WebappSvg} className='border !border-primary-200 !bg-primary-100' />, |
| | | webapp: <AppIcon innerIcon={WebappSvg} className='border !bg-primary-100 !border-primary-200' />, |
| | | notion: <AppIcon innerIcon={NotionSvg} className='!border-[0.5px] !border-indigo-100 !bg-white' />, |
| | | } |
| | | |
| | | export default function AppBasic({ icon, icon_background, name, isExternal, type, hoverTip, textStyle, isExtraInLine, mode = 'expand', iconType = 'app' }: IAppBasicProps) { |
| | | export default function AppBasic({ icon, icon_background, name, isExternal, type, hoverTip, textStyle, mode = 'expand', iconType = 'app' }: IAppBasicProps) { |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className="flex grow items-center"> |
| | | <div className="flex items-start p-1"> |
| | | {icon && icon_background && iconType === 'app' && ( |
| | | <div className='mr-3 shrink-0'> |
| | | <div className='flex-shrink-0 mr-3'> |
| | | <AppIcon icon={icon} background={icon_background} /> |
| | | </div> |
| | | )} |
| | | {iconType !== 'app' |
| | | && <div className='mr-3 shrink-0'> |
| | | && <div className='flex-shrink-0 mr-3'> |
| | | {ICON_MAP[iconType]} |
| | | </div> |
| | | |
| | | } |
| | | {mode === 'expand' && <div className="group w-full"> |
| | | <div className={`system-md-semibold flex flex-row items-center text-text-secondary group-hover:text-text-primary ${textStyle?.main ?? ''}`}> |
| | | <div className="min-w-0 overflow-hidden text-ellipsis break-normal"> |
| | | {mode === 'expand' && <div className="group"> |
| | | <div className={`flex flex-row items-center text-sm font-semibold text-gray-700 group-hover:text-gray-900 break-all ${textStyle?.main ?? ''}`}> |
| | | {name} |
| | | </div> |
| | | {hoverTip |
| | | && <Tooltip |
| | | popupContent={ |
| | |
| | | /> |
| | | } |
| | | </div> |
| | | {isExtraInLine ? ( |
| | | <div className="system-2xs-medium-uppercase flex text-text-tertiary">{type}</div> |
| | | ) : ( |
| | | <div className='system-2xs-medium-uppercase text-text-tertiary'>{isExternal ? t('dataset.externalTag') : type}</div> |
| | | )} |
| | | <div className={`text-xs font-normal text-gray-500 group-hover:text-gray-700 break-all ${textStyle?.extra ?? ''}`}>{type}</div> |
| | | <div className='text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? t('dataset.externalTag') : ''}</div> |
| | | </div>} |
| | | </div> |
| | | ) |
| | |
| | | const { t } = useTranslation() |
| | | return ( |
| | | <div className='pl-1 pt-1'> |
| | | <div className='mr-3 shrink-0'> |
| | | <div className='flex-shrink-0 mr-3'> |
| | | <AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' /> |
| | | </div> |
| | | {expand && ( |
| | |
| | | <div className='system-md-semibold text-text-secondary'> |
| | | {name} |
| | | </div> |
| | | <div className='system-2xs-medium-uppercase mt-1 text-text-tertiary'>{isExternal ? t('dataset.externalTag') : t('dataset.localDocs')}</div> |
| | | <div className='system-xs-regular my-3 text-text-tertiary first-letter:capitalize'>{description}</div> |
| | | <div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? t('dataset.externalTag') : t('dataset.localDocs')}</div> |
| | | <div className='my-3 system-xs-regular text-text-tertiary first-letter:capitalize'>{description}</div> |
| | | </div> |
| | | )} |
| | | {extraInfo} |
| | |
| | | import React, { useEffect } from 'react' |
| | | import { useShallow } from 'zustand/react/shallow' |
| | | import { RiLayoutLeft2Line, RiLayoutRight2Line } from '@remixicon/react' |
| | | import { RiLayoutRight2Line } from '@remixicon/react' |
| | | import { LayoutRight2LineMod } from '../base/icons/src/public/knowledge' |
| | | import NavLink from './navLink' |
| | | import type { NavIcon } from './navLink' |
| | | import AppBasic from './basic' |
| | |
| | | return ( |
| | | <div |
| | | className={` |
| | | flex shrink-0 flex-col border-r border-divider-burn bg-background-default-subtle transition-all |
| | | shrink-0 flex flex-col bg-background-default-subtle border-r border-divider-burn transition-all |
| | | ${expand ? 'w-[216px]' : 'w-14'} |
| | | `} |
| | | > |
| | | <div |
| | | className={` |
| | | shrink-0 |
| | | ${expand ? 'p-2' : 'p-1'} |
| | | ${expand ? 'p-3' : 'p-2'} |
| | | `} |
| | | > |
| | | {iconType === 'app' && ( |
| | |
| | | )} |
| | | </div> |
| | | <div className='px-4'> |
| | | <div className={cn('mx-auto mt-1 h-[1px] bg-divider-subtle', !expand && 'w-6')} /> |
| | | <div className={cn('mt-1 mx-auto h-[1px] bg-divider-subtle', !expand && 'w-6')} /> |
| | | </div> |
| | | <nav |
| | | className={` |
| | |
| | | `} |
| | | > |
| | | <div |
| | | className='flex h-6 w-6 cursor-pointer items-center justify-center' |
| | | className='flex items-center justify-center w-6 h-6 text-gray-500 cursor-pointer' |
| | | onClick={() => handleToggle(appSidebarExpand)} |
| | | > |
| | | { |
| | | expand |
| | | ? <RiLayoutRight2Line className='h-5 w-5 text-components-menu-item-text' /> |
| | | : <RiLayoutLeft2Line className='h-5 w-5 text-components-menu-item-text' /> |
| | | ? <RiLayoutRight2Line className='w-5 h-5 text-components-menu-item-text' /> |
| | | : <LayoutRight2LineMod className='w-5 h-5 text-components-menu-item-text' /> |
| | | } |
| | | </div> |
| | | </div> |
| | |
| | | import { useSelectedLayoutSegment } from 'next/navigation' |
| | | import Link from 'next/link' |
| | | import classNames from '@/utils/classnames' |
| | | import type { RemixiconComponentType } from '@remixicon/react' |
| | | |
| | | export type NavIcon = React.ComponentType< |
| | | React.PropsWithoutRef<React.ComponentProps<'svg'>> & { |
| | | title?: string | undefined |
| | | titleId?: string | undefined |
| | | }> | RemixiconComponentType |
| | | } |
| | | > |
| | | |
| | | export type NavLinkProps = { |
| | | name: string |
| | |
| | | key={name} |
| | | href={href} |
| | | className={classNames( |
| | | isActive ? 'bg-state-accent-active text-text-accent font-semibold' : 'text-components-menu-item-text hover:bg-state-base-hover hover:text-components-menu-item-text-hover', |
| | | isActive ? 'bg-state-accent-active text-text-accent font-semibold' : 'text-components-menu-item-text hover:bg-gray-100 hover:text-components-menu-item-text-hover', |
| | | 'group flex items-center h-9 rounded-md py-2 text-sm font-normal', |
| | | mode === 'expand' ? 'px-3' : 'px-2.5', |
| | | )} |
| | |
| | | onChange, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const avatar = type === EditItemType.Query ? <User className='h-6 w-6' /> : <Robot className='h-6 w-6' /> |
| | | const avatar = type === EditItemType.Query ? <User className='w-6 h-6' /> : <Robot className='w-6 h-6' /> |
| | | const name = type === EditItemType.Query ? t('appAnnotation.addModal.queryName') : t('appAnnotation.addModal.answerName') |
| | | const placeholder = type === EditItemType.Query ? t('appAnnotation.addModal.queryPlaceholder') : t('appAnnotation.addModal.answerPlaceholder') |
| | | |
| | | return ( |
| | | <div className='flex' onClick={e => e.stopPropagation()}> |
| | | <div className='mr-3 shrink-0'> |
| | | <div className='shrink-0 mr-3'> |
| | | {avatar} |
| | | </div> |
| | | <div className='grow'> |
| | | <div className='system-xs-semibold mb-1 text-text-primary'>{name}</div> |
| | | <div className='mb-1 system-xs-semibold text-text-primary'>{name}</div> |
| | | <Textarea |
| | | value={content} |
| | | onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)} |
| | |
| | | try { |
| | | await onAdd(payload) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | } |
| | | setIsSaving(false) |
| | | |
| | |
| | | maxWidthClassName='!max-w-[480px]' |
| | | title={t('appAnnotation.addModal.title') as string} |
| | | body={( |
| | | <div className='space-y-6 p-6 pb-4'> |
| | | <div className='p-6 pb-4 space-y-6'> |
| | | <EditItem |
| | | type={EditItemType.Query} |
| | | content={question} |
| | |
| | | ( |
| | | <div> |
| | | {isAnnotationFull && ( |
| | | <div className='mb-4 mt-6 px-6'> |
| | | <div className='mt-6 mb-4 px-6'> |
| | | <AnnotationFull /> |
| | | </div> |
| | | )} |
| | | <div className='system-sm-medium flex h-16 items-center justify-between rounded-bl-xl rounded-br-xl border-t border-divider-subtle bg-background-section-burn px-4 text-text-tertiary'> |
| | | <div className='px-4 flex h-16 items-center justify-between border-t border-divider-subtle bg-background-section-burn rounded-bl-xl rounded-br-xl system-sm-medium text-text-tertiary'> |
| | | <div |
| | | className='flex items-center space-x-2' |
| | | > |
| | |
| | | <div className='mt-6'> |
| | | <div className='system-sm-medium text-text-primary'>{t('share.generation.csvStructureTitle')}</div> |
| | | <div className='mt-2 max-h-[500px] overflow-auto'> |
| | | <table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-divider-regular text-xs'> |
| | | <table className='table-fixed w-full border-separate border-spacing-0 border border-divider-regular rounded-lg text-xs'> |
| | | <thead className='text-text-tertiary'> |
| | | <tr> |
| | | <td className='h-9 border-b border-divider-regular pl-3 pr-2'>{t('appAnnotation.batchModal.question')}</td> |
| | | <td className='h-9 border-b border-divider-regular pl-3 pr-2'>{t('appAnnotation.batchModal.answer')}</td> |
| | | <td className='h-9 pl-3 pr-2 border-b border-divider-regular'>{t('appAnnotation.batchModal.question')}</td> |
| | | <td className='h-9 pl-3 pr-2 border-b border-divider-regular'>{t('appAnnotation.batchModal.answer')}</td> |
| | | </tr> |
| | | </thead> |
| | | <tbody className='text-text-secondary'> |
| | | <tbody className='text-gray-700'> |
| | | <tr> |
| | | <td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('appAnnotation.batchModal.question')} 1</td> |
| | | <td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('appAnnotation.batchModal.answer')} 1</td> |
| | | <td className='h-9 pl-3 pr-2 border-b border-divider-subtle text-[13px]'>{t('appAnnotation.batchModal.question')} 1</td> |
| | | <td className='h-9 pl-3 pr-2 border-b border-divider-subtle text-[13px]'>{t('appAnnotation.batchModal.answer')} 1</td> |
| | | </tr> |
| | | <tr> |
| | | <td className='h-9 pl-3 pr-2 text-[13px]'>{t('appAnnotation.batchModal.question')} 2</td> |
| | |
| | | </table> |
| | | </div> |
| | | <CSVDownloader |
| | | className="mt-2 block cursor-pointer" |
| | | className="block mt-2 cursor-pointer" |
| | | type={Type.Link} |
| | | filename={`template-${locale}`} |
| | | bom={true} |
| | | data={getTemplate()} |
| | | > |
| | | <div className='system-xs-medium flex h-[18px] items-center space-x-1 text-text-accent'> |
| | | <DownloadIcon className='mr-1 h-3 w-3' /> |
| | | <div className='flex items-center h-[18px] space-x-1 text-text-accent system-xs-medium'> |
| | | <DownloadIcon className='w-3 h-3 mr-1' /> |
| | | {t('appAnnotation.batchModal.template')} |
| | | </div> |
| | | </CSVDownloader> |
| | |
| | | /> |
| | | <div ref={dropRef}> |
| | | {!file && ( |
| | | <div className={cn('system-sm-regular flex h-20 items-center rounded-xl border border-dashed border-components-dropzone-border bg-components-dropzone-bg', dragging && 'border border-components-dropzone-border-accent bg-components-dropzone-bg-accent')}> |
| | | <div className='flex w-full items-center justify-center space-x-2'> |
| | | <div className={cn('flex items-center h-20 rounded-xl bg-components-dropzone-bg border border-dashed border-components-dropzone-border system-sm-regular', dragging && 'bg-components-dropzone-bg-accent border border-components-dropzone-border-accent')}> |
| | | <div className='w-full flex items-center justify-center space-x-2'> |
| | | <CSVIcon className="shrink-0" /> |
| | | <div className='text-text-tertiary'> |
| | | {t('appAnnotation.batchModal.csvUploadTitle')} |
| | | <span className='cursor-pointer text-text-accent' onClick={selectHandle}>{t('appAnnotation.batchModal.browse')}</span> |
| | | <span className='text-text-accent cursor-pointer' onClick={selectHandle}>{t('appAnnotation.batchModal.browse')}</span> |
| | | </div> |
| | | </div> |
| | | {dragging && <div ref={dragRef} className='absolute left-0 top-0 h-full w-full' />} |
| | | {dragging && <div ref={dragRef} className='absolute w-full h-full top-0 left-0' />} |
| | | </div> |
| | | )} |
| | | {file && ( |
| | | <div className={cn('group flex h-20 items-center rounded-xl border border-components-panel-border bg-components-panel-bg px-6 text-sm font-normal', 'hover:border-components-panel-bg-blur hover:bg-components-panel-bg-blur')}> |
| | | <div className={cn('flex items-center h-20 px-6 rounded-xl bg-components-panel-bg border border-components-panel-border text-sm font-normal group', 'hover:bg-components-panel-bg-blur hover:border-components-panel-bg-blur')}> |
| | | <CSVIcon className="shrink-0" /> |
| | | <div className='ml-2 flex w-0 grow'> |
| | | <span className='max-w-[calc(100%_-_30px)] overflow-hidden text-ellipsis whitespace-nowrap text-text-primary'>{file.name.replace(/.csv$/, '')}</span> |
| | | <div className='flex ml-2 w-0 grow'> |
| | | <span className='max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-text-primary'>{file.name.replace(/.csv$/, '')}</span> |
| | | <span className='shrink-0 text-text-tertiary'>.csv</span> |
| | | </div> |
| | | <div className='hidden items-center group-hover:flex'> |
| | | <div className='hidden group-hover:flex items-center'> |
| | | <Button variant='secondary' onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button> |
| | | <div className='mx-2 h-4 w-px bg-divider-regular' /> |
| | | <div className='cursor-pointer p-2' onClick={removeFile}> |
| | | <RiDeleteBinLine className='h-4 w-4 text-text-tertiary' /> |
| | | <div className='mx-2 w-px h-4 bg-divider-regular' /> |
| | | <div className='p-2 cursor-pointer' onClick={removeFile}> |
| | | <RiDeleteBinLine className='w-4 h-4 text-text-tertiary' /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | import { annotationBatchImport, checkAnnotationBatchImportProgress } from '@/service/annotation' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import AnnotationFull from '@/app/components/billing/annotation-full' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | export enum ProcessStatus { |
| | | WAITING = 'waiting', |
| | |
| | | } |
| | | |
| | | return ( |
| | | <Modal isShow={isShow} onClose={noop} className='!max-w-[520px] !rounded-xl px-8 py-6'> |
| | | <div className='system-xl-medium relative pb-1 text-text-primary'>{t('appAnnotation.batchModal.title')}</div> |
| | | <div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onCancel}> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <Modal isShow={isShow} onClose={() => { }} className='px-8 py-6 !max-w-[520px] !rounded-xl'> |
| | | <div className='relative pb-1 system-xl-medium text-text-primary'>{t('appAnnotation.batchModal.title')}</div> |
| | | <div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onCancel}> |
| | | <RiCloseLine className='w-4 h-4 text-text-tertiary' /> |
| | | </div> |
| | | <CSVUploader |
| | | file={currentCSV} |
| | |
| | | </div> |
| | | )} |
| | | |
| | | <div className='mt-[28px] flex justify-end pt-6'> |
| | | <Button className='system-sm-medium mr-2 text-text-tertiary' onClick={onCancel}> |
| | | <div className='mt-[28px] pt-6 flex justify-end'> |
| | | <Button className='mr-2 text-text-tertiary system-sm-medium' onClick={onCancel}> |
| | | {t('appAnnotation.batchModal.cancel')} |
| | | </Button> |
| | | <Button |
| | |
| | | } |
| | | |
| | | export const EditTitle: FC<{ className?: string; title: string }> = ({ className, title }) => ( |
| | | <div className={cn(className, 'system-xs-medium flex h-[18px] items-center text-text-tertiary')}> |
| | | <RiEditFill className='mr-1 h-3.5 w-3.5' /> |
| | | <div className={cn(className, 'flex items-center h-[18px] system-xs-medium text-text-tertiary')}> |
| | | <RiEditFill className='mr-1 w-3.5 h-3.5' /> |
| | | <div>{title}</div> |
| | | <div |
| | | className='ml-2 h-[1px] grow' |
| | | className='ml-2 grow h-[1px]' |
| | | style={{ |
| | | background: 'linear-gradient(90deg, rgba(0, 0, 0, 0.05) -1.65%, rgba(0, 0, 0, 0.00) 100%)', |
| | | }} |
| | |
| | | const { t } = useTranslation() |
| | | const [newContent, setNewContent] = useState('') |
| | | const showNewContent = newContent && newContent !== content |
| | | const avatar = type === EditItemType.Query ? <User className='h-6 w-6' /> : <Robot className='h-6 w-6' /> |
| | | const avatar = type === EditItemType.Query ? <User className='w-6 h-6' /> : <Robot className='w-6 h-6' /> |
| | | const name = type === EditItemType.Query ? t('appAnnotation.editModal.queryName') : t('appAnnotation.editModal.answerName') |
| | | const editTitle = type === EditItemType.Query ? t('appAnnotation.editModal.yourQuery') : t('appAnnotation.editModal.yourAnswer') |
| | | const placeholder = type === EditItemType.Query ? t('appAnnotation.editModal.queryPlaceholder') : t('appAnnotation.editModal.answerPlaceholder') |
| | |
| | | |
| | | return ( |
| | | <div className='flex' onClick={e => e.stopPropagation()}> |
| | | <div className='mr-3 shrink-0'> |
| | | <div className='shrink-0 mr-3'> |
| | | {avatar} |
| | | </div> |
| | | <div className='grow'> |
| | | <div className='system-xs-semibold mb-1 text-text-primary'>{name}</div> |
| | | <div className='mb-1 system-xs-semibold text-text-primary'>{name}</div> |
| | | <div className='system-sm-regular text-text-primary'>{content}</div> |
| | | {!isEdit |
| | | ? ( |
| | |
| | | {showNewContent && ( |
| | | <div className='mt-3'> |
| | | <EditTitle title={editTitle} /> |
| | | <div className='system-sm-regular mt-1 text-text-primary'>{newContent}</div> |
| | | <div className='mt-1 system-sm-regular text-text-primary'>{newContent}</div> |
| | | </div> |
| | | )} |
| | | <div className='mt-2 flex items-center'> |
| | | {!readonly && ( |
| | | <div |
| | | className='system-xs-medium flex cursor-pointer items-center space-x-1 text-text-accent' |
| | | className='flex items-center space-x-1 system-xs-medium text-text-accent cursor-pointer' |
| | | onClick={() => { |
| | | setIsEdit(true) |
| | | }} |
| | | > |
| | | <RiEditLine className='mr-1 h-3.5 w-3.5' /> |
| | | <RiEditLine className='mr-1 w-3.5 h-3.5' /> |
| | | <div>{t('common.operation.edit')}</div> |
| | | </div> |
| | | )} |
| | | |
| | | {showNewContent && ( |
| | | <div className='system-xs-medium ml-2 flex items-center text-text-tertiary'> |
| | | <div className='ml-2 flex items-center system-xs-medium text-text-tertiary'> |
| | | <div className='mr-2'>·</div> |
| | | <div |
| | | className='flex cursor-pointer items-center space-x-1' |
| | | className='flex items-center space-x-1 cursor-pointer' |
| | | onClick={() => { |
| | | setNewContent(content) |
| | | onSave(content) |
| | | }} |
| | | > |
| | | <div className='h-3.5 w-3.5'> |
| | | <RiDeleteBinLine className='h-3.5 w-3.5' /> |
| | | <div className='w-3.5 h-3.5'> |
| | | <RiDeleteBinLine className='w-3.5 h-3.5' /> |
| | | </div> |
| | | <div>{t('common.operation.delete')}</div> |
| | | </div> |
| | |
| | | title={t('appAnnotation.editModal.title') as string} |
| | | body={( |
| | | <div> |
| | | <div className='space-y-6 p-6 pb-4'> |
| | | <div className='p-6 pb-4 space-y-6'> |
| | | <EditItem |
| | | type={EditItemType.Query} |
| | | content={query} |
| | |
| | | foot={ |
| | | <div> |
| | | {isAnnotationFull && ( |
| | | <div className='mb-4 mt-6 px-6'> |
| | | <div className='mt-6 mb-4 px-6'> |
| | | <AnnotationFull /> |
| | | </div> |
| | | )} |
| | |
| | | { |
| | | annotationId |
| | | ? ( |
| | | <div className='system-sm-medium flex h-16 items-center justify-between rounded-bl-xl rounded-br-xl border-t border-divider-subtle bg-background-section-burn px-4 text-text-tertiary'> |
| | | <div className='px-4 flex h-16 items-center justify-between border-t border-divider-subtle bg-background-section-burn rounded-bl-xl rounded-br-xl system-sm-medium text-text-tertiary'> |
| | | <div |
| | | className='flex cursor-pointer items-center space-x-2 pl-3' |
| | | className='flex items-center pl-3 space-x-2 cursor-pointer' |
| | | onClick={() => setShowModal(true)} |
| | | > |
| | | <MessageCheckRemove /> |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className='flex h-full items-center justify-center'> |
| | | <div className='box-border h-fit w-[560px] rounded-2xl bg-background-section-burn px-5 py-4'> |
| | | <span className='system-md-semibold text-text-secondary'>{t('appAnnotation.noData.title')}<ThreeDotsIcon className='relative -left-1.5 -top-3 inline' /></span> |
| | | <div className='system-sm-regular mt-2 text-text-tertiary'> |
| | | <div className='flex items-center justify-center h-full'> |
| | | <div className='bg-background-section-burn w-[560px] h-fit box-border px-5 py-4 rounded-2xl'> |
| | | <span className='text-text-secondary system-md-semibold'>{t('appAnnotation.noData.title')}<ThreeDotsIcon className='inline relative -top-3 -left-1.5' /></span> |
| | | <div className='mt-2 text-text-tertiary system-sm-regular'> |
| | | {t('appAnnotation.noData.description')} |
| | | </div> |
| | | </div> |
| | |
| | | appId: string |
| | | queryParams: QueryParam |
| | | setQueryParams: (v: QueryParam) => void |
| | | children: React.JSX.Element |
| | | children: JSX.Element |
| | | } |
| | | |
| | | const Filter: FC<IFilterProps> = ({ |
| | |
| | | if (!data) |
| | | return null |
| | | return ( |
| | | <div className='mb-2 flex flex-row flex-wrap items-center justify-between gap-2'> |
| | | <div className='flex justify-between flex-row flex-wrap gap-2 items-center mb-2'> |
| | | <Input |
| | | wrapperClassName='w-[200px]' |
| | | showLeftIcon |
| | |
| | | import { |
| | | useCSVDownloader, |
| | | } from 'react-papaparse' |
| | | import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react' |
| | | import { Menu, Transition } from '@headlessui/react' |
| | | import Button from '../../../base/button' |
| | | import AddAnnotationModal from '../add-annotation-modal' |
| | | import type { AnnotationItemBasic } from '../type' |
| | |
| | | const Operations = () => { |
| | | return ( |
| | | <div className="w-full py-1"> |
| | | <button className='mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 hover:bg-components-panel-on-panel-item-bg-hover disabled:opacity-50' onClick={() => { |
| | | <button className='h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-components-panel-on-panel-item-bg-hover rounded-lg cursor-pointer disabled:opacity-50 w-[calc(100%_-_8px)]' onClick={() => { |
| | | setShowBulkImportModal(true) |
| | | }}> |
| | | <FilePlus02 className='h-4 w-4 text-text-tertiary' /> |
| | | <span className='system-sm-regular grow text-left text-text-secondary'>{t('appAnnotation.table.header.bulkImport')}</span> |
| | | <FilePlus02 className='w-4 h-4 text-text-tertiary' /> |
| | | <span className='grow text-text-secondary system-sm-regular text-left'>{t('appAnnotation.table.header.bulkImport')}</span> |
| | | </button> |
| | | <Menu as="div" className="relative h-full w-full"> |
| | | <MenuButton className='mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 hover:bg-components-panel-on-panel-item-bg-hover disabled:opacity-50'> |
| | | <FileDownload02 className='h-4 w-4 text-text-tertiary' /> |
| | | <span className='system-sm-regular grow text-left text-text-secondary'>{t('appAnnotation.table.header.bulkExport')}</span> |
| | | <ChevronRight className='h-[14px] w-[14px] shrink-0 text-text-tertiary' /> |
| | | </MenuButton> |
| | | <Menu as="div" className="relative w-full h-full"> |
| | | <Menu.Button className='h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-components-panel-on-panel-item-bg-hover rounded-lg cursor-pointer disabled:opacity-50 w-[calc(100%_-_8px)]'> |
| | | <FileDownload02 className='w-4 h-4 text-text-tertiary' /> |
| | | <span className='grow text-text-secondary system-sm-regular text-left'>{t('appAnnotation.table.header.bulkExport')}</span> |
| | | <ChevronRight className='shrink-0 w-[14px] h-[14px] text-text-tertiary' /> |
| | | </Menu.Button> |
| | | <Transition |
| | | as={Fragment} |
| | | enter="transition ease-out duration-100" |
| | |
| | | leaveFrom="transform opacity-100 scale-100" |
| | | leaveTo="transform opacity-0 scale-95" |
| | | > |
| | | <MenuItems |
| | | <Menu.Items |
| | | className={cn( |
| | | 'absolute left-1 top-[1px] z-10 min-w-[100px] origin-top-right -translate-x-full rounded-xl border-[0.5px] border-components-panel-on-panel-item-bg bg-components-panel-bg py-1 shadow-xs', |
| | | 'absolute top-[1px] left-1 -translate-x-full py-1 min-w-[100px] z-10 bg-components-panel-bg border-[0.5px] border-components-panel-on-panel-item-bg origin-top-right rounded-xl shadow-xs', |
| | | )} |
| | | > |
| | | <CSVDownloader |
| | |
| | | ...list.map(item => [item.question, item.answer]), |
| | | ]} |
| | | > |
| | | <button disabled={annotationUnavailable} className='mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 hover:bg-components-panel-on-panel-item-bg-hover disabled:opacity-50'> |
| | | <span className='system-sm-regular grow text-left text-text-secondary'>CSV</span> |
| | | <button disabled={annotationUnavailable} className='h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-components-panel-on-panel-item-bg-hover rounded-lg cursor-pointer disabled:opacity-50 w-[calc(100%_-_8px)]'> |
| | | <span className='grow text-text-secondary system-sm-regular text-left'>CSV</span> |
| | | </button> |
| | | </CSVDownloader> |
| | | <button disabled={annotationUnavailable} className={cn('mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 hover:bg-components-panel-on-panel-item-bg-hover disabled:opacity-50', '!border-0')} onClick={JSONLOutput}> |
| | | <span className='system-sm-regular grow text-left text-text-secondary'>JSONL</span> |
| | | <button disabled={annotationUnavailable} className={cn('h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-components-panel-on-panel-item-bg-hover rounded-lg cursor-pointer disabled:opacity-50 w-[calc(100%_-_8px)]', '!border-0')} onClick={JSONLOutput}> |
| | | <span className='grow text-text-secondary system-sm-regular text-left'>JSONL</span> |
| | | </button> |
| | | </MenuItems> |
| | | </Menu.Items> |
| | | </Transition> |
| | | </Menu> |
| | | </div> |
| | |
| | | return ( |
| | | <div className='flex space-x-2'> |
| | | <Button variant='primary' onClick={() => setShowAddModal(true)}> |
| | | <RiAddLine className='mr-0.5 h-4 w-4' /> |
| | | <RiAddLine className='w-4 h-4 mr-0.5' /> |
| | | <div>{t('appAnnotation.table.header.addAnnotation')}</div> |
| | | </Button> |
| | | <CustomPopover |
| | |
| | | trigger="click" |
| | | btnElement={ |
| | | <Button variant='secondary' className='w-8 p-0'> |
| | | <RiMoreFill className='h-4 w-4' /> |
| | | <RiMoreFill className='w-4 h-4' /> |
| | | </Button> |
| | | } |
| | | btnClassName='p-0 border-0' |
| | | className={'!z-20 h-fit !w-[155px]'} |
| | | className={'!w-[155px] h-fit !z-20'} |
| | | popupClassName='!w-full !overflow-visible' |
| | | manualClose |
| | | /> |
| | |
| | | setList(data as AnnotationItem[]) |
| | | setTotal(total) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | |
| | | } |
| | | setIsLoading(false) |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className='flex h-full flex-col'> |
| | | <p className='system-sm-regular text-text-tertiary'>{t('appLog.description')}</p> |
| | | <div className='flex flex-1 flex-col py-4'> |
| | | <div className='flex flex-col h-full'> |
| | | <p className='text-text-tertiary system-sm-regular'>{t('appLog.description')}</p> |
| | | <div className='flex flex-col py-4 flex-1'> |
| | | <Filter appId={appDetail.id} queryParams={queryParams} setQueryParams={setQueryParams}> |
| | | <div className='flex items-center space-x-2'> |
| | | {isChatApp && ( |
| | | <> |
| | | <div className={cn(!annotationConfig?.enabled && 'pr-2', 'flex h-7 items-center space-x-1 rounded-lg border border-components-panel-border bg-components-panel-bg-blur pl-2')}> |
| | | <MessageFast className='h-4 w-4 text-util-colors-indigo-indigo-600' /> |
| | | <div className={cn(!annotationConfig?.enabled && 'pr-2', 'flex items-center h-7 rounded-lg bg-components-panel-bg-blur border border-components-panel-border pl-2 space-x-1')}> |
| | | <MessageFast className='w-4 h-4 text-util-colors-indigo-indigo-600' /> |
| | | <div className='system-sm-medium text-text-primary'>{t('appAnnotation.name')}</div> |
| | | <Switch |
| | | key={controlRefreshSwitch} |
| | |
| | | ></Switch> |
| | | {annotationConfig?.enabled && ( |
| | | <div className='flex items-center pl-1.5'> |
| | | <div className='mr-1 h-3.5 w-[1px] shrink-0 bg-divider-subtle'></div> |
| | | <div className='shrink-0 mr-1 w-[1px] h-3.5 bg-divider-subtle'></div> |
| | | <ActionButton onClick={() => setIsShowEdit(true)}> |
| | | <RiEqualizer2Line className='h-4 w-4 text-text-tertiary' /> |
| | | <RiEqualizer2Line className='w-4 h-4 text-text-tertiary' /> |
| | | </ActionButton> |
| | | </div> |
| | | )} |
| | | </div> |
| | | <div className='mx-3 h-3.5 w-[1px] shrink-0 bg-divider-regular'></div> |
| | | <div className='shrink-0 mx-3 w-[1px] h-3.5 bg-divider-regular'></div> |
| | | </> |
| | | )} |
| | | |
| | |
| | | onRemove={handleRemove} |
| | | onView={handleView} |
| | | /> |
| | | : <div className='flex h-full grow items-center justify-center'><EmptyElement /></div> |
| | | : <div className='grow flex h-full items-center justify-center'><EmptyElement /></div> |
| | | } |
| | | {/* Show Pagination only if the total is more than the limit */} |
| | | {(total && total > APP_PAGE_LIMIT) |
| | |
| | | <table className={cn('mt-2 w-full min-w-[440px] border-collapse border-0')}> |
| | | <thead className='system-xs-medium-uppercase text-text-tertiary'> |
| | | <tr> |
| | | <td className='w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1'>{t('appAnnotation.table.header.question')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.table.header.answer')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.table.header.createdAt')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.table.header.hits')}</td> |
| | | <td className='w-[96px] whitespace-nowrap rounded-r-lg bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.table.header.actions')}</td> |
| | | <td className='pl-2 pr-1 w-5 rounded-l-lg bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.table.header.question')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.table.header.answer')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.table.header.createdAt')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.table.header.hits')}</td> |
| | | <td className='pl-3 py-1.5 rounded-r-lg bg-background-section-burn whitespace-nowrap w-[96px]'>{t('appAnnotation.table.header.actions')}</td> |
| | | </tr> |
| | | </thead> |
| | | <tbody className="system-sm-regular text-text-secondary"> |
| | | <tbody className="text-text-secondary system-sm-regular"> |
| | | {list.map(item => ( |
| | | <tr |
| | | key={item.id} |
| | | className='cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover' |
| | | className='border-b border-divider-subtle hover:bg-background-default-hover cursor-pointer' |
| | | onClick={ |
| | | () => { |
| | | onView(item) |
| | |
| | | } |
| | | > |
| | | <td |
| | | className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2' |
| | | className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]' |
| | | title={item.question} |
| | | >{item.question}</td> |
| | | <td |
| | | className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2' |
| | | className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]' |
| | | title={item.answer} |
| | | >{item.answer}</td> |
| | | <td className='p-3 pr-2'>{formatTime(item.created_at, t('appLog.dateTimeFormat') as string)}</td> |
| | |
| | | {/* Actions */} |
| | | <div className='flex space-x-1 text-text-tertiary'> |
| | | <ActionButton onClick={() => onView(item)}> |
| | | <RiEditLine className='h-4 w-4' /> |
| | | <RiEditLine className='w-4 h-4' /> |
| | | </ActionButton> |
| | | <ActionButton |
| | | onClick={() => { |
| | |
| | | setShowConfirmDelete(true) |
| | | }} |
| | | > |
| | | <RiDeleteBinLine className='h-4 w-4' /> |
| | | <RiDeleteBinLine className='w-4 h-4' /> |
| | | </ActionButton> |
| | | </div> |
| | | </td> |
| | |
| | | const HitHistoryNoData: FC = () => { |
| | | const { t } = useTranslation() |
| | | return ( |
| | | <div className='mx-auto mt-20 w-[480px] space-y-2 rounded-2xl bg-background-section-burn p-5'> |
| | | <div className='inline-block rounded-lg border border-divider-subtle p-3'> |
| | | <ClockFastForward className='h-5 w-5 text-text-tertiary' /> |
| | | <div className='mx-auto mt-20 w-[480px] p-5 rounded-2xl bg-background-section-burn space-y-2'> |
| | | <div className='inline-block p-3 rounded-lg border border-divider-subtle'> |
| | | <ClockFastForward className='w-5 h-5 text-text-tertiary' /> |
| | | </div> |
| | | <div className='system-sm-regular text-text-tertiary'>{t('appAnnotation.viewModal.noHitHistory')}</div> |
| | | </div> |
| | |
| | | setHitHistoryList(data as HitHistoryItem[]) |
| | | setTotal(total) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | } |
| | | } |
| | | |
| | |
| | | <table className={cn('w-full min-w-[440px] border-collapse border-0')} > |
| | | <thead className="system-xs-medium-uppercase text-text-tertiary"> |
| | | <tr> |
| | | <td className='w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1'>{t('appAnnotation.hitHistoryTable.query')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.match')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.response')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.source')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.score')}</td> |
| | | <td className='w-[160px] whitespace-nowrap rounded-r-lg bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.time')}</td> |
| | | <td className='pl-2 pr-1 w-5 rounded-l-lg bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.query')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.match')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.response')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.source')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.score')}</td> |
| | | <td className='pl-3 py-1.5 rounded-r-lg bg-background-section-burn whitespace-nowrap w-[160px]'>{t('appAnnotation.hitHistoryTable.time')}</td> |
| | | </tr> |
| | | </thead> |
| | | <tbody className="system-sm-regular text-text-secondary"> |
| | | <tbody className="text-text-secondary system-sm-regular"> |
| | | {hitHistoryList.map(item => ( |
| | | <tr |
| | | key={item.id} |
| | | className={'cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover'} |
| | | className={'border-b border-divider-subtle hover:bg-background-default-hover cursor-pointer'} |
| | | > |
| | | <td |
| | | className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2' |
| | | className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]' |
| | | title={item.question} |
| | | >{item.question}</td> |
| | | <td |
| | | className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2' |
| | | className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]' |
| | | title={item.match} |
| | | >{item.match}</td> |
| | | <td |
| | | className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2' |
| | | className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]' |
| | | title={item.response} |
| | | >{item.response}</td> |
| | | <td className='p-3 pr-2'>{item.source}</td> |
| | |
| | | maxWidthClassName='!max-w-[800px]' |
| | | title={ |
| | | <TabSlider |
| | | className='relative top-[9px] shrink-0' |
| | | className='shrink-0 relative top-[9px]' |
| | | value={activeTab} |
| | | onChange={v => setActiveTab(v as TabType)} |
| | | options={tabs} |
| | |
| | | } |
| | | body={( |
| | | <div> |
| | | <div className='space-y-6 p-6 pb-4'> |
| | | <div className='p-6 pb-4 space-y-6'> |
| | | {activeTab === TabType.annotation ? annotationTab : hitHistoryTab} |
| | | </div> |
| | | <Confirm |
| | |
| | | )} |
| | | foot={id |
| | | ? ( |
| | | <div className='system-sm-medium flex h-16 items-center justify-between rounded-bl-xl rounded-br-xl border-t border-divider-subtle bg-background-section-burn px-4 text-text-tertiary'> |
| | | <div className='px-4 flex h-16 items-center justify-between border-t border-divider-subtle bg-background-section-burn rounded-bl-xl rounded-br-xl system-sm-medium text-text-tertiary'> |
| | | <div |
| | | className='flex cursor-pointer items-center space-x-2 pl-3' |
| | | className='flex items-center pl-3 space-x-2 cursor-pointer' |
| | | onClick={() => setShowModal(true)} |
| | | > |
| | | <MessageCheckRemove /> |
| | |
| | | } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import dayjs from 'dayjs' |
| | | import { |
| | | RiArrowDownSLine, |
| | | RiPlanetLine, |
| | | RiPlayCircleLine, |
| | | RiPlayList2Line, |
| | | RiTerminalBoxLine, |
| | | } from '@remixicon/react' |
| | | import { useKeyPress } from 'ahooks' |
| | | import { RiArrowDownSLine, RiPlanetLine } from '@remixicon/react' |
| | | import Toast from '../../base/toast' |
| | | import type { ModelAndParameter } from '../configuration/debug/types' |
| | | import { getKeyboardKeyCodeBySystem } from '../../workflow/utils' |
| | | import SuggestedAction from './suggested-action' |
| | | import PublishWithMultipleModel from './publish-with-multiple-model' |
| | | import Button from '@/app/components/base/button' |
| | |
| | | PortalToFollowElemContent, |
| | | PortalToFollowElemTrigger, |
| | | } from '@/app/components/base/portal-to-follow-elem' |
| | | import { WEB_PREFIX } from '@/config' |
| | | import { fetchInstalledAppList } from '@/service/explore' |
| | | import EmbeddedModal from '@/app/components/app/overview/embedded' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | import { useGetLanguage } from '@/context/i18n' |
| | | import { PlayCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' |
| | | import { CodeBrowser } from '@/app/components/base/icons/src/vender/line/development' |
| | | import { LeftIndent02 } from '@/app/components/base/icons/src/vender/line/editor' |
| | | import { FileText } from '@/app/components/base/icons/src/vender/line/files' |
| | | import WorkflowToolConfigureButton from '@/app/components/tools/workflow-tool/configure-button' |
| | | import type { InputVar } from '@/app/components/workflow/types' |
| | | import { appDefaultIconBackground } from '@/config' |
| | | import type { PublishWorkflowParams } from '@/types/workflow' |
| | | |
| | | export type AppPublisherProps = { |
| | | disabled?: boolean |
| | |
| | | debugWithMultipleModel?: boolean |
| | | multipleModelConfigs?: ModelAndParameter[] |
| | | /** modelAndParameter is passed when debugWithMultipleModel is true */ |
| | | onPublish?: (params?: any) => Promise<any> | any |
| | | onPublish?: (modelAndParameter?: ModelAndParameter) => Promise<any> | any |
| | | onRestore?: () => Promise<any> | any |
| | | onToggle?: (state: boolean) => void |
| | | crossAxisOffset?: number |
| | |
| | | inputs?: InputVar[] |
| | | onRefreshData?: () => void |
| | | } |
| | | |
| | | const PUBLISH_SHORTCUT = ['⌘', '⇧', 'P'] |
| | | |
| | | const AppPublisher = ({ |
| | | disabled = false, |
| | |
| | | const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {} |
| | | const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode |
| | | const appURL = `${appBaseURL}/${appMode}/${accessToken}` |
| | | const isChatApp = ['chat', 'agent-chat', 'completion'].includes(appDetail?.mode || '') |
| | | |
| | | const language = useGetLanguage() |
| | | const formatTimeFromNow = useCallback((time: number) => { |
| | | return dayjs(time).locale(language === 'zh_Hans' ? 'zh-cn' : language.replace('_', '-')).fromNow() |
| | | }, [language]) |
| | | |
| | | const handlePublish = useCallback(async (params?: ModelAndParameter | PublishWorkflowParams) => { |
| | | const handlePublish = async (modelAndParameter?: ModelAndParameter) => { |
| | | try { |
| | | await onPublish?.(params) |
| | | await onPublish?.(modelAndParameter) |
| | | setPublished(true) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | setPublished(false) |
| | | } |
| | | }, [onPublish]) |
| | | } |
| | | |
| | | const handleRestore = useCallback(async () => { |
| | | try { |
| | | await onRestore?.() |
| | | setOpen(false) |
| | | } |
| | | catch {} |
| | | catch (e) { } |
| | | }, [onRestore]) |
| | | |
| | | const handleTrigger = useCallback(() => { |
| | |
| | | try { |
| | | const { installed_apps }: any = await fetchInstalledAppList(appDetail?.id) || {} |
| | | if (installed_apps?.length > 0) |
| | | window.open(`${WEB_PREFIX}/explore/installed/${installed_apps[0].id}`, '_blank') |
| | | window.open(`/explore/installed/${installed_apps[0].id}`, '_blank') |
| | | else |
| | | throw new Error('No app found in Explore') |
| | | } |
| | |
| | | |
| | | const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false) |
| | | |
| | | useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => { |
| | | e.preventDefault() |
| | | if (publishDisabled || published) |
| | | return |
| | | handlePublish() |
| | | }, |
| | | { exactMatch: true, useCapture: true }) |
| | | |
| | | return ( |
| | | <> |
| | | <PortalToFollowElem |
| | | open={open} |
| | | onOpenChange={setOpen} |
| | |
| | | <PortalToFollowElemTrigger onClick={handleTrigger}> |
| | | <Button |
| | | variant='primary' |
| | | className='p-2' |
| | | className='pl-3 pr-2' |
| | | disabled={disabled} |
| | | > |
| | | {t('workflow.common.publish')} |
| | | <RiArrowDownSLine className='h-4 w-4 text-components-button-primary-text' /> |
| | | <RiArrowDownSLine className='w-4 h-4 ml-0.5' /> |
| | | </Button> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-[11]'> |
| | | <div className='w-[320px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5'> |
| | | <div className='w-[336px] bg-white rounded-2xl border-[0.5px] border-gray-200 shadow-xl'> |
| | | <div className='p-4 pt-3'> |
| | | <div className='system-xs-medium-uppercase flex h-6 items-center text-text-tertiary'> |
| | | <div className='flex items-center h-6 text-xs font-medium text-gray-500 uppercase'> |
| | | {publishedAt ? t('workflow.common.latestPublished') : t('workflow.common.currentDraftUnpublished')} |
| | | </div> |
| | | {publishedAt |
| | | ? ( |
| | | <div className='flex items-center justify-between'> |
| | | <div className='system-sm-medium flex items-center text-text-secondary'> |
| | | <div className='flex justify-between items-center h-[18px]'> |
| | | <div className='flex items-center mt-[3px] mb-[3px] leading-[18px] text-[13px] font-medium text-gray-700'> |
| | | {t('workflow.common.publishedAt')} {formatTimeFromNow(publishedAt)} |
| | | </div> |
| | | {isChatApp && <Button |
| | | variant='secondary-accent' |
| | | <Button |
| | | className={` |
| | | ml-2 px-2 text-primary-600 |
| | | ${published && 'text-primary-300 border-gray-100'} |
| | | `} |
| | | size='small' |
| | | onClick={handleRestore} |
| | | disabled={published} |
| | | > |
| | | {t('workflow.common.restore')} |
| | | </Button>} |
| | | </Button> |
| | | </div> |
| | | ) |
| | | : ( |
| | | <div className='system-sm-medium flex items-center text-text-secondary'> |
| | | <div className='flex items-center h-[18px] leading-[18px] text-[13px] font-medium text-gray-700'> |
| | | {t('workflow.common.autoSaved')} · {Boolean(draftUpdatedAt) && formatTimeFromNow(draftUpdatedAt!)} |
| | | </div> |
| | | )} |
| | |
| | | : ( |
| | | <Button |
| | | variant='primary' |
| | | className='mt-3 w-full' |
| | | className='w-full mt-3' |
| | | onClick={() => handlePublish()} |
| | | disabled={publishDisabled || published} |
| | | > |
| | | { |
| | | published |
| | | ? t('workflow.common.published') |
| | | : ( |
| | | <div className='flex gap-1'> |
| | | <span>{t('workflow.common.publishUpdate')}</span> |
| | | <div className='flex gap-0.5'> |
| | | {PUBLISH_SHORTCUT.map(key => ( |
| | | <span key={key} className='system-kbd h-4 w-4 rounded-[4px] bg-components-kbd-bg-white text-text-primary-on-surface'> |
| | | {key} |
| | | </span> |
| | | ))} |
| | | </div> |
| | | </div> |
| | | ) |
| | | : publishedAt ? t('workflow.common.update') : t('workflow.common.publish') |
| | | } |
| | | </Button> |
| | | ) |
| | | } |
| | | </div> |
| | | <div className='border-t-[0.5px] border-t-divider-regular p-4 pt-3'> |
| | | <SuggestedAction |
| | | disabled={!publishedAt} |
| | | link={appURL} |
| | | icon={<RiPlayCircleLine className='h-4 w-4' />} |
| | | > |
| | | {t('workflow.common.runApp')} |
| | | </SuggestedAction> |
| | | {appDetail?.mode === 'workflow' || appDetail?.mode === 'completion' |
| | | <div className='p-4 pt-3 border-t-[0.5px] border-t-black/5'> |
| | | <SuggestedAction disabled={!publishedAt} link={appURL} icon={<PlayCircle />}>{t('workflow.common.runApp')}</SuggestedAction> |
| | | {appDetail?.mode === 'workflow' |
| | | ? ( |
| | | <SuggestedAction |
| | | disabled={!publishedAt} |
| | | link={`${appURL}${appURL.includes('?') ? '&' : '?'}mode=batch`} |
| | | icon={<RiPlayList2Line className='h-4 w-4' />} |
| | | icon={<LeftIndent02 className='w-4 h-4' />} |
| | | > |
| | | {t('workflow.common.batchRunApp')} |
| | | </SuggestedAction> |
| | |
| | | handleTrigger() |
| | | }} |
| | | disabled={!publishedAt} |
| | | icon={<CodeBrowser className='h-4 w-4' />} |
| | | icon={<CodeBrowser className='w-4 h-4' />} |
| | | > |
| | | {t('workflow.common.embedIntoSite')} |
| | | </SuggestedAction> |
| | | )} |
| | | <SuggestedAction |
| | | onClick={() => { |
| | | publishedAt && handleOpenInExplore() |
| | | handleOpenInExplore() |
| | | }} |
| | | disabled={!publishedAt} |
| | | icon={<RiPlanetLine className='h-4 w-4' />} |
| | | icon={<RiPlanetLine className='w-4 h-4' />} |
| | | > |
| | | {t('workflow.common.openInExplore')} |
| | | </SuggestedAction> |
| | | <SuggestedAction |
| | | disabled={!publishedAt} |
| | | link='./develop' |
| | | icon={<RiTerminalBoxLine className='h-4 w-4' />} |
| | | > |
| | | {t('workflow.common.accessAPIReference')} |
| | | </SuggestedAction> |
| | | <SuggestedAction disabled={!publishedAt} link='./develop' icon={<FileText className='w-4 h-4' />}>{t('workflow.common.accessAPIReference')}</SuggestedAction> |
| | | {appDetail?.mode === 'workflow' && ( |
| | | <WorkflowToolConfigureButton |
| | | disabled={!publishedAt} |
| | |
| | | accessToken={accessToken} |
| | | /> |
| | | </PortalToFollowElem > |
| | | </> |
| | | ) |
| | | } |
| | | |
| | |
| | | className='mt-3 w-full' |
| | | > |
| | | {t('appDebug.operation.applyConfig')} |
| | | <RiArrowDownSLine className='ml-0.5 h-3 w-3' /> |
| | | <RiArrowDownSLine className='ml-0.5 w-3 h-3' /> |
| | | </Button> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-50 mt-1 w-[288px]'> |
| | | <div className='rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg'> |
| | | <div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'> |
| | | <PortalToFollowElemContent className='mt-1 w-[288px] z-50'> |
| | | <div className='p-1 rounded-lg border-[0.5px] border-gray-200 shadow-lg bg-white'> |
| | | <div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'> |
| | | {t('appDebug.publishAs')} |
| | | </div> |
| | | { |
| | | validModelConfigs.map((item, index) => ( |
| | | <div |
| | | key={item.id} |
| | | className='flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-tertiary hover:bg-state-base-hover' |
| | | className='flex items-center h-8 px-3 text-sm text-gray-500 rounded-lg cursor-pointer hover:bg-gray-100' |
| | | onClick={() => handleSelect(item)} |
| | | > |
| | | <span className='min-w-[18px] italic'>#{index + 1}</span> |
| | | <span className='italic min-w-[18px]'>#{index + 1}</span> |
| | | <ModelIcon modelName={item.model} provider={item.providerItem} className='ml-2' /> |
| | | <div |
| | | className='ml-1 truncate text-text-secondary' |
| | | className='ml-1 text-gray-700 truncate' |
| | | title={item.modelItem.label[language]} |
| | | > |
| | | {item.modelItem.label[language]} |
| | |
| | | import type { HTMLProps, PropsWithChildren } from 'react' |
| | | import { RiArrowRightUpLine } from '@remixicon/react' |
| | | import classNames from '@/utils/classnames' |
| | | import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' |
| | | |
| | | export type SuggestedActionProps = PropsWithChildren<HTMLProps<HTMLAnchorElement> & { |
| | | icon?: React.ReactNode |
| | |
| | | target='_blank' |
| | | rel='noreferrer' |
| | | className={classNames( |
| | | 'flex justify-start items-center gap-2 py-2 px-2.5 bg-background-section-burn rounded-lg transition-colors [&:not(:first-child)]:mt-1', |
| | | disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'text-text-secondary hover:bg-state-accent-hover hover:text-text-accent cursor-pointer', |
| | | 'flex justify-start items-center gap-2 h-[34px] px-2.5 bg-gray-100 rounded-lg transition-colors [&:not(:first-child)]:mt-1', |
| | | disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'hover:bg-primary-50 hover:text-primary-600 cursor-pointer', |
| | | className, |
| | | )} |
| | | {...props} |
| | | > |
| | | <div className='relative h-4 w-4'>{icon}</div> |
| | | <div className='system-sm-medium shrink grow basis-0'>{children}</div> |
| | | <RiArrowRightUpLine className='h-3.5 w-3.5' /> |
| | | <div className='relative w-4 h-4'>{icon}</div> |
| | | <div className='grow shrink basis-0 text-[13px] font-medium leading-[18px]'>{children}</div> |
| | | <ArrowUpRight /> |
| | | </a> |
| | | ) |
| | | |
| | |
| | | children, |
| | | }) => { |
| | | return ( |
| | | <div className={cn('rounded-xl border-l-[0.5px] border-t-[0.5px] border-effects-highlight bg-background-section-burn pb-3', noBodySpacing && 'pb-0', className)}> |
| | | <div className={cn('rounded-xl border-t-[0.5px] border-l-[0.5px] bg-background-section-burn pb-3', noBodySpacing && '!pb-0', className)}> |
| | | {/* Header */} |
| | | <div className={cn('px-3 pt-2', hasHeaderBottomBorder && 'border-b border-divider-subtle')}> |
| | | <div className='flex h-8 items-center justify-between'> |
| | | <div className='flex shrink-0 items-center space-x-1'> |
| | | {headerIcon && <div className='flex h-6 w-6 items-center justify-center'>{headerIcon}</div>} |
| | | <div className='system-sm-semibold text-text-secondary'>{title}</div> |
| | | <div className='flex justify-between items-center h-8'> |
| | | <div className='flex items-center space-x-1 shrink-0'> |
| | | {headerIcon && <div className='flex items-center justify-center w-6 h-6'>{headerIcon}</div>} |
| | | <div className='text-text-secondary system-sm-semibold'>{title}</div> |
| | | </div> |
| | | <div className='flex items-center gap-2'> |
| | | <div className='flex gap-2 items-center'> |
| | | {headerRight && <div>{headerRight}</div>} |
| | | </div> |
| | | </div> |
| | |
| | | name, |
| | | }) => { |
| | | return ( |
| | | <div className='mb-1 flex items-center'> |
| | | <div className='mr-3 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{name}</div> |
| | | <div className='h-[1px] grow' |
| | | <div className='flex items-center mb-1'> |
| | | <div className='mr-3 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>{name}</div> |
| | | <div className='grow h-[1px]' |
| | | style={{ |
| | | background: 'linear-gradient(270deg, rgba(243, 244, 246, 0) 0%, #F3F4F6 100%)', |
| | | |
| | |
| | | const computedIsHovered = isHoverStatus || isHovered |
| | | return ( |
| | | <div |
| | | className={cn(className, computedIsHovered && 'bg-[#FEE4E2]', 'flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-[#FEE4E2]')} |
| | | className={cn(className, computedIsHovered && 'bg-[#FEE4E2]', 'flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-[#FEE4E2]')} |
| | | onMouseEnter={() => setIsHovered(true)} |
| | | onMouseLeave={() => setIsHovered(false)} |
| | | onClick={onClick} |
| | |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiAddLine, |
| | | RiEditLine, |
| | | } from '@remixicon/react' |
| | | import { PlusIcon } from '@heroicons/react/20/solid' |
| | | import cn from '@/utils/classnames' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | export type IOperationBtnProps = { |
| | | className?: string |
| | |
| | | } |
| | | |
| | | const iconMap = { |
| | | add: <RiAddLine className='h-3.5 w-3.5' />, |
| | | edit: <RiEditLine className='h-3.5 w-3.5' />, |
| | | add: <PlusIcon className='w-3.5 h-3.5' />, |
| | | edit: (<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | | <path d="M6.99998 11.6666H12.25M1.75 11.6666H2.72682C3.01217 11.6666 3.15485 11.6666 3.28912 11.6344C3.40816 11.6058 3.52196 11.5587 3.62635 11.4947C3.74408 11.4226 3.84497 11.3217 4.04675 11.1199L11.375 3.79164C11.8583 3.30839 11.8583 2.52488 11.375 2.04164C10.8918 1.55839 10.1083 1.55839 9.62501 2.04164L2.29674 9.3699C2.09496 9.57168 1.99407 9.67257 1.92192 9.7903C1.85795 9.89469 1.81081 10.0085 1.78224 10.1275C1.75 10.2618 1.75 10.4045 1.75 10.6898V11.6666Z" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" /> |
| | | </svg> |
| | | ), |
| | | } |
| | | |
| | | const OperationBtn: FC<IOperationBtnProps> = ({ |
| | | className, |
| | | type, |
| | | actionName, |
| | | onClick = noop, |
| | | onClick = () => { }, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | return ( |
| | | <div |
| | | className={cn('flex h-7 cursor-pointer select-none items-center space-x-1 rounded-md px-3 text-text-secondary hover:bg-state-base-hover', className)} |
| | | className={cn(className, 'flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200 select-none')} |
| | | onClick={onClick}> |
| | | <div> |
| | | {iconMap[type]} |
| | |
| | | return ( |
| | | <div |
| | | key={name} |
| | | className={`${s.item} ${className} mb-2 flex h-5 items-center justify-center rounded-md px-1 text-xs font-medium text-primary-600`} |
| | | className={`${s.item} ${className} flex mb-2 items-center justify-center rounded-md px-1 h-5 text-xs font-medium text-primary-600`} |
| | | > |
| | | <span className='opacity-60'>{'{{'}</span> |
| | | <span>{name}</span> |
| | |
| | | description={t('appDebug.feature.dataSet.queryVariable.unableToQueryDataSetTip')} |
| | | footer={ |
| | | <div className='flex space-x-2'> |
| | | <Button variant='primary' className='flex !w-[96px] justify-start' onClick={onConfirm}> |
| | | <Button variant='primary' className='flex justify-start !w-[96px]' onClick={onConfirm}> |
| | | <span className='text-[13px] font-medium'>{t('appDebug.feature.dataSet.queryVariable.ok')}</span> |
| | | </Button> |
| | | </div> |
| | |
| | | footer, |
| | | }) => { |
| | | return ( |
| | | <div className={`${s.mask} absolute inset-0 z-10 pt-16`} |
| | | <div className={`${s.mask} absolute z-10 inset-0 pt-16`} |
| | | > |
| | | <div className='mx-auto px-10'> |
| | | <div className={`${s.icon} flex h-11 w-11 items-center justify-center rounded-xl bg-white`}>{warningIcon}</div> |
| | | <div className='mt-4 text-[24px] font-semibold leading-normal text-gray-800'> |
| | | <div className={`${s.icon} flex items-center justify-center w-11 h-11 rounded-xl bg-white`}>{warningIcon}</div> |
| | | <div className='mt-4 text-[24px] leading-normal font-semibold text-gray-800'> |
| | | {title} |
| | | </div> |
| | | <div className='mt-3 text-base text-gray-500'> |
| | |
| | | Clipboard, |
| | | ClipboardCheck, |
| | | } from '@/app/components/base/icons/src/vender/line/files' |
| | | import Button from '@/app/components/base/button' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import PromptEditor from '@/app/components/base/prompt-editor' |
| | | import ConfigContext from '@/context/debug-configuration' |
| | |
| | | const [editorHeight, setEditorHeight] = React.useState(isChatMode ? 200 : 508) |
| | | const contextMissing = ( |
| | | <div |
| | | className='flex h-11 items-center justify-between rounded-tl-xl rounded-tr-xl pb-1 pl-4 pr-3 pt-2' |
| | | className='flex justify-between items-center h-11 pt-2 pr-3 pb-1 pl-4 rounded-tl-xl rounded-tr-xl' |
| | | style={{ |
| | | background: 'linear-gradient(180deg, #FEF0C7 0%, rgba(254, 240, 199, 0) 100%)', |
| | | }} |
| | | > |
| | | <div className='flex items-center pr-2' > |
| | | <RiErrorWarningFill className='mr-1 h-4 w-4 text-[#F79009]' /> |
| | | <div className='text-[13px] font-medium leading-[18px] text-[#DC6803]'>{t('appDebug.promptMode.contextMissing')}</div> |
| | | <RiErrorWarningFill className='mr-1 w-4 h-4 text-[#F79009]' /> |
| | | <div className='leading-[18px] text-[13px] font-medium text-[#DC6803]'>{t('appDebug.promptMode.contextMissing')}</div> |
| | | </div> |
| | | <Button |
| | | size='small' |
| | | variant='secondary-accent' |
| | | <div |
| | | className='flex items-center h-6 px-2 rounded-md bg-[#fff] border border-gray-200 shadow-xs text-xs font-medium text-primary-600 cursor-pointer' |
| | | onClick={onHideContextMissingTip} |
| | | >{t('common.operation.ok')}</Button> |
| | | >{t('common.operation.ok')}</div> |
| | | </div> |
| | | ) |
| | | return ( |
| | | <div className={`rounded-xl bg-gradient-to-r from-components-input-border-active-prompt-1 to-components-input-border-active-prompt-2 p-0.5 shadow-xs ${!isContextMissing ? '' : s.warningBorder}`}> |
| | | <div className='rounded-xl bg-background-default'> |
| | | <div className={`relative ${!isContextMissing ? s.gradientBorder : s.warningBorder}`}> |
| | | <div className='rounded-xl bg-white'> |
| | | {isContextMissing |
| | | ? contextMissing |
| | | : ( |
| | | <div className={cn(s.boxHeader, 'flex h-11 items-center justify-between rounded-tl-xl rounded-tr-xl bg-background-default pb-1 pl-4 pr-3 pt-2 hover:shadow-xs')}> |
| | | <div className={cn(s.boxHeader, 'flex justify-between items-center h-11 pt-2 pr-3 pb-1 pl-4 rounded-tl-xl rounded-tr-xl bg-white hover:shadow-xs')}> |
| | | {isChatMode |
| | | ? ( |
| | | <MessageTypeSelector value={type} onChange={onTypeChange} /> |
| | |
| | | </div>)} |
| | | <div className={cn(s.optionWrap, 'items-center space-x-1')}> |
| | | {canDelete && ( |
| | | <RiDeleteBinLine onClick={onDelete} className='h-6 w-6 cursor-pointer p-1 text-text-tertiary' /> |
| | | <RiDeleteBinLine onClick={onDelete} className='h-6 w-6 p-1 text-gray-500 cursor-pointer' /> |
| | | )} |
| | | {!isCopied |
| | | ? ( |
| | | <Clipboard className='h-6 w-6 cursor-pointer p-1 text-text-tertiary' onClick={() => { |
| | | <Clipboard className='h-6 w-6 p-1 text-gray-500 cursor-pointer' onClick={() => { |
| | | copy(value) |
| | | setIsCopied(true) |
| | | }} /> |
| | | ) |
| | | : ( |
| | | <ClipboardCheck className='h-6 w-6 p-1 text-text-tertiary' /> |
| | | <ClipboardCheck className='h-6 w-6 p-1 text-gray-500' /> |
| | | )} |
| | | </div> |
| | | </div> |
| | | )} |
| | | |
| | | <PromptEditorHeightResizeWrap |
| | | className='min-h-[102px] overflow-y-auto px-4 text-sm text-text-secondary' |
| | | className='px-4 min-h-[102px] overflow-y-auto text-sm text-gray-700' |
| | | height={editorHeight} |
| | | minHeight={minHeight} |
| | | onHeightChange={setEditorHeight} |
| | | footer={( |
| | | <div className='flex pb-2 pl-4'> |
| | | <div className="h-[18px] rounded-md bg-divider-regular px-1 text-xs leading-[18px] text-text-tertiary">{value.length}</div> |
| | | <div className='pl-4 pb-2 flex'> |
| | | <div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{value.length}</div> |
| | | </div> |
| | | )} |
| | | hideResize={noResize} |
| | |
| | | }}> |
| | | <div |
| | | ref={mainContentRef} |
| | | className='w-[420px] rounded-xl bg-components-panel-bg p-6' |
| | | className='w-[420px] rounded-xl bg-gray-50 p-6' |
| | | style={{ |
| | | boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)', |
| | | }} |
| | | > |
| | | <div className='flex items-start space-x-3'> |
| | | <div |
| | | className='flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-components-card-border bg-components-card-bg-alt shadow-lg' |
| | | className='shrink-0 flex items-center justify-center h-10 w-10 rounded-xl border border-gray-100' |
| | | style={{ |
| | | backgroundColor: 'rgba(255, 255, 255, 0.9)', |
| | | boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)', |
| | | }} |
| | | >{VarIcon}</div> |
| | | <div className='grow-1'> |
| | | <div className='text-sm font-medium text-text-primary'>{t('appDebug.autoAddVar')}</div> |
| | | <div className='mt-[15px] flex max-h-[66px] flex-wrap space-x-1 overflow-y-auto px-1'> |
| | | <div className='text-sm font-medium text-gray-900'>{t('appDebug.autoAddVar')}</div> |
| | | <div className='flex flex-wrap mt-[15px] max-h-[66px] overflow-y-auto px-1 space-x-1'> |
| | | {varNameArr.map(name => ( |
| | | <VarHighlight key={name} name={name} /> |
| | | ))} |
| | |
| | | isShow={isShow} |
| | | onClose={onClose} |
| | | > |
| | | <div className={'mt-6 text-sm font-medium leading-[21px] text-text-primary'}>{t('appDebug.feature.conversationHistory.editModal.userPrefix')}</div> |
| | | <input className={'mt-2 box-border h-10 w-full rounded-lg bg-components-input-bg-normal px-3 text-sm leading-10'} |
| | | <div className={'mt-6 font-medium text-sm leading-[21px] text-gray-900'}>{t('appDebug.feature.conversationHistory.editModal.userPrefix')}</div> |
| | | <input className={'mt-2 w-full rounded-lg h-10 box-border px-3 text-sm leading-10 bg-gray-100'} |
| | | value={tempData.user_prefix} |
| | | onChange={e => setTempData({ |
| | | ...tempData, |
| | |
| | | })} |
| | | /> |
| | | |
| | | <div className={'mt-6 text-sm font-medium leading-[21px] text-text-primary'}>{t('appDebug.feature.conversationHistory.editModal.assistantPrefix')}</div> |
| | | <input className={'mt-2 box-border h-10 w-full rounded-lg bg-components-input-bg-normal px-3 text-sm leading-10'} |
| | | <div className={'mt-6 font-medium text-sm leading-[21px] text-gray-900'}>{t('appDebug.feature.conversationHistory.editModal.assistantPrefix')}</div> |
| | | <input className={'mt-2 w-full rounded-lg h-10 box-border px-3 text-sm leading-10 bg-gray-100'} |
| | | value={tempData.assistant_prefix} |
| | | onChange={e => setTempData({ |
| | | ...tempData, |
| | |
| | | /> |
| | | |
| | | <div className='mt-10 flex justify-end'> |
| | | <Button className='mr-2 shrink-0' onClick={onClose}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' className='shrink-0' onClick={() => onSave(tempData)} loading={saveLoading}>{t('common.operation.save')}</Button> |
| | | <Button className='mr-2 flex-shrink-0' onClick={onClose}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' className='flex-shrink-0' onClick={() => onSave(tempData)} loading={saveLoading}>{t('common.operation.save')}</Button> |
| | | </div> |
| | | </Modal> |
| | | ) |
| | |
| | | </div> |
| | | } |
| | | headerIcon={ |
| | | <div className='rounded-md p-1 shadow-xs'> |
| | | <MessageClockCircle className='h-4 w-4 text-[#DD2590]' /> |
| | | <div className='p-1 rounded-md bg-white shadow-xs'> |
| | | <MessageClockCircle className='w-4 h-4 text-[#DD2590]' /> |
| | | </div>} |
| | | headerRight={ |
| | | <div className='flex items-center'> |
| | | <div className='text-xs text-text-tertiary'>{t('appDebug.feature.conversationHistory.description')}</div> |
| | | <div className='ml-3 h-[14px] w-[1px] bg-divider-regular'></div> |
| | | <div className='text-xs text-gray-500'>{t('appDebug.feature.conversationHistory.description')}</div> |
| | | <div className='ml-3 w-[1px] h-[14px] bg-gray-200'></div> |
| | | <OperationBtn type="edit" onClick={onShowEditModal} /> |
| | | </div> |
| | | } |
| | | noBodySpacing |
| | | > |
| | | {showWarning && ( |
| | | <div className='flex justify-between rounded-b-xl bg-background-section-burn px-3 py-2 text-xs text-text-secondary'> |
| | | <div className='flex justify-between py-2 px-3 rounded-b-xl bg-[#FFFAEB] text-xs text-gray-700'> |
| | | <div>{t('appDebug.feature.conversationHistory.tip')} |
| | | <a href={`${locale === LanguagesSupported[1] |
| | | ? 'https://docs.dify.ai/zh-hans/learn-more/extended-reading/prompt-engineering/README' |
| | | : 'https://docs.dify.ai/en/features/prompt-engineering'}`} |
| | | ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering' |
| | | : 'https://docs.dify.ai/features/prompt-engineering'}`} |
| | | target='_blank' rel='noopener noreferrer' |
| | | className='text-[#155EEF]'>{t('appDebug.feature.conversationHistory.learnMore')} |
| | | </a> |
| | |
| | | } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import SimplePromptInput from './simple-prompt-input' |
| | | import Button from '@/app/components/base/button' |
| | | import AdvancedMessageInput from '@/app/components/app/configuration/config-prompt/advanced-prompt-input' |
| | | import { PromptRole } from '@/models/debug' |
| | | import type { PromptItem, PromptVariable } from '@/models/debug' |
| | |
| | | } |
| | | </div> |
| | | {(modelModeType === ModelModeType.chat && (currentAdvancedPrompt as PromptItem[]).length < MAX_PROMPT_MESSAGE_LENGTH) && ( |
| | | <Button |
| | | <div |
| | | onClick={handleAddMessage} |
| | | className='mt-3 w-full'> |
| | | <RiAddLine className='mr-2 h-4 w-4' /> |
| | | className='mt-3 flex items-center h-8 justify-center bg-gray-50 rounded-lg cursor-pointer text-[13px] font-medium text-gray-700 space-x-2'> |
| | | <RiAddLine className='w-4 h-4' /> |
| | | <div>{t('appDebug.promptMode.operation.addMessage')}</div> |
| | | </Button> |
| | | </div> |
| | | )} |
| | | </div> |
| | | ) |
| | |
| | | <div className='relative left-[-8px]' ref={ref}> |
| | | <div |
| | | onClick={toggleShow} |
| | | className={cn(showOption && 'bg-indigo-100', 'flex h-7 cursor-pointer items-center space-x-0.5 rounded-lg pl-1.5 pr-1 text-indigo-800')}> |
| | | className={cn(showOption && 'bg-indigo-100', 'flex items-center h-7 pl-1.5 pr-1 space-x-0.5 rounded-lg cursor-pointer text-indigo-800')}> |
| | | <div className='text-sm font-semibold uppercase'>{value}</div> |
| | | <ChevronSelectorVertical className='h-3 w-3 ' /> |
| | | <ChevronSelectorVertical className='w-3 h-3 ' /> |
| | | </div> |
| | | {showOption && ( |
| | | <div className='absolute top-[30px] z-10 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg'> |
| | | <div className='absolute z-10 top-[30px] p-1 border border-gray-200 shadow-lg rounded-lg bg-white'> |
| | | {allTypes.map(type => ( |
| | | <div |
| | | key={type} |
| | |
| | | setHide() |
| | | onChange(type) |
| | | }} |
| | | className='flex h-9 min-w-[44px] cursor-pointer items-center rounded-lg px-3 text-sm font-medium uppercase text-text-secondary hover:bg-state-base-hover' |
| | | className='flex items-center h-9 min-w-[44px] px-3 rounded-lg cursor-pointer text-sm font-medium text-gray-700 uppercase hover:bg-gray-50' |
| | | >{type}</div> |
| | | )) |
| | | } |
| | |
| | | height: number |
| | | minHeight: number |
| | | onHeightChange: (height: number) => void |
| | | children: React.JSX.Element |
| | | footer?: React.JSX.Element |
| | | children: JSX.Element |
| | | footer?: JSX.Element |
| | | hideResize?: boolean |
| | | } |
| | | |
| | |
| | | {footer} |
| | | {!hideResize && ( |
| | | <div |
| | | className='absolute bottom-0 left-0 flex h-2 w-full cursor-row-resize justify-center' |
| | | className='absolute bottom-0 left-0 w-full flex justify-center h-2 cursor-row-resize' |
| | | onMouseDown={handleStartResize}> |
| | | <div className='h-[3px] w-5 rounded-sm bg-gray-300'></div> |
| | | <div className='w-5 h-[3px] rounded-sm bg-gray-300'></div> |
| | | </div> |
| | | )} |
| | | </div> |
| | |
| | | import produce from 'immer' |
| | | import { useContext } from 'use-context-selector' |
| | | import ConfirmAddVar from './confirm-add-var' |
| | | import s from './style.module.css' |
| | | import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap' |
| | | import cn from '@/utils/classnames' |
| | | import type { PromptVariable } from '@/models/debug' |
| | | import { type PromptVariable } from '@/models/debug' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import type { CompletionParams } from '@/types/app' |
| | | import { AppType } from '@/types/app' |
| | |
| | | import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '@/app/components/base/prompt-editor/plugins/update-block' |
| | | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' |
| | | import { useFeaturesStore } from '@/app/components/base/features/hooks' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | export type ISimplePromptInput = { |
| | | mode: AppType |
| | |
| | | readonly = false, |
| | | onChange, |
| | | noTitle, |
| | | gradientBorder, |
| | | editorHeight: initEditorHeight, |
| | | noResize, |
| | | }) => { |
| | |
| | | const [editorHeight, setEditorHeight] = useState(minHeight) |
| | | |
| | | return ( |
| | | <div className={cn('relative rounded-xl bg-gradient-to-r from-components-input-border-active-prompt-1 to-components-input-border-active-prompt-2 p-0.5 shadow-xs')}> |
| | | <div className='rounded-xl bg-background-section-burn'> |
| | | <div className={cn((!readonly || gradientBorder) ? `${s.gradientBorder}` : 'bg-gray-50', ' relative shadow-md')}> |
| | | <div className='rounded-xl bg-[#EEF4FF]'> |
| | | {!noTitle && ( |
| | | <div className="flex h-11 items-center justify-between pl-3 pr-2.5"> |
| | | <div className="flex justify-between items-center h-11 pl-3 pr-6"> |
| | | <div className="flex items-center space-x-1"> |
| | | <div className='h2 system-sm-semibold-uppercase text-text-secondary'>{mode !== AppType.completion ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}</div> |
| | | <div className='h2'>{mode !== AppType.completion ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}</div> |
| | | {!readonly && ( |
| | | <Tooltip |
| | | popupContent={ |
| | |
| | | )} |
| | | |
| | | <PromptEditorHeightResizeWrap |
| | | className='min-h-[228px] rounded-t-xl bg-background-default px-4 pt-2 text-sm text-text-secondary' |
| | | className='px-4 pt-2 min-h-[228px] bg-white rounded-t-xl text-sm text-gray-700' |
| | | height={editorHeight} |
| | | minHeight={minHeight} |
| | | onHeightChange={setEditorHeight} |
| | | hideResize={noResize} |
| | | footer={( |
| | | <div className='flex rounded-b-xl bg-background-default pb-2 pl-4'> |
| | | <div className="h-[18px] rounded-md bg-components-badge-bg-gray-soft px-1 text-xs leading-[18px] text-text-tertiary">{promptTemplate.length}</div> |
| | | <div className='pl-4 pb-2 flex bg-white rounded-b-xl'> |
| | | <div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{promptTemplate.length}</div> |
| | | </div> |
| | | )} |
| | | > |
| | |
| | | user: '', |
| | | assistant: '', |
| | | }, |
| | | onEditRole: noop, |
| | | onEditRole: () => { }, |
| | | }} |
| | | queryBlock={{ |
| | | show: false, |
| | | selectable: !hasSetBlockStatus.query, |
| | | }} |
| | | onChange={(value) => { |
| | | if (handleChange) |
| | | handleChange(value, []) |
| | | handleChange?.(value, []) |
| | | }} |
| | | onBlur={() => { |
| | | handleChange(promptTemplate, getVars(promptTemplate)) |
| | |
| | | type Props = { |
| | | className?: string |
| | | title: string |
| | | children: React.JSX.Element |
| | | children: JSX.Element |
| | | } |
| | | |
| | | const Field: FC<Props> = ({ |
| | |
| | | }) => { |
| | | return ( |
| | | <div className={cn(className)}> |
| | | <div className='system-sm-semibold leading-8 text-text-secondary'>{title}</div> |
| | | <div className='text-text-secondary system-sm-semibold leading-8'>{title}</div> |
| | | <div>{children}</div> |
| | | </div> |
| | | ) |
| | |
| | | /> |
| | | )} |
| | | |
| | | <div className='!mt-5 flex h-6 items-center space-x-2'> |
| | | <div className='!mt-5 flex items-center h-6 space-x-2'> |
| | | <Checkbox checked={tempPayload.required} onCheck={() => handlePayloadChange('required')(!tempPayload.required)} /> |
| | | <span className='system-sm-semibold text-text-secondary'>{t('appDebug.variableConfig.required')}</span> |
| | | <span className='text-text-secondary system-sm-semibold'>{t('appDebug.variableConfig.required')}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useState } from 'react' |
| | | import { RiAddLine, RiDeleteBinLine, RiDraggable } from '@remixicon/react' |
| | | import React from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { PlusIcon } from '@heroicons/react/24/outline' |
| | | import { ReactSortable } from 'react-sortablejs' |
| | | import cn from '@/utils/classnames' |
| | | import RemoveIcon from '../../base/icons/remove-icon' |
| | | |
| | | import s from './style.module.css' |
| | | |
| | | export type Options = string[] |
| | | export type IConfigSelectProps = { |
| | |
| | | onChange, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const [focusID, setFocusID] = useState<number | null>(null) |
| | | const [deletingID, setDeletingID] = useState<number | null>(null) |
| | | |
| | | const optionList = options.map((content, index) => { |
| | | return ({ |
| | |
| | | animation={150} |
| | | > |
| | | {options.map((o, index) => ( |
| | | <div |
| | | className={cn( |
| | | 'group relative flex items-center rounded-lg border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg pl-2.5 hover:bg-components-panel-on-panel-item-bg-hover', |
| | | focusID === index && 'border-components-input-border-active bg-components-input-bg-active hover:border-components-input-border-active hover:bg-components-input-bg-active', |
| | | deletingID === index && 'border-components-input-border-destructive bg-state-destructive-hover hover:border-components-input-border-destructive hover:bg-state-destructive-hover', |
| | | )} |
| | | key={index} |
| | | > |
| | | <RiDraggable className='handle h-4 w-4 cursor-grab text-text-quaternary' /> |
| | | <div className={`${s.inputWrap} relative`} key={index}> |
| | | <div className='handle flex items-center justify-center w-4 h-4 cursor-grab'> |
| | | <svg width="6" height="10" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | | <path fillRule="evenodd" clipRule="evenodd" d="M1 2C1.55228 2 2 1.55228 2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2ZM1 6C1.55228 6 2 5.55228 2 5C2 4.44772 1.55228 4 1 4C0.447715 4 0 4.44772 0 5C0 5.55228 0.447715 6 1 6ZM6 1C6 1.55228 5.55228 2 5 2C4.44772 2 4 1.55228 4 1C4 0.447715 4.44772 0 5 0C5.55228 0 6 0.447715 6 1ZM5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6ZM2 9C2 9.55229 1.55228 10 1 10C0.447715 10 0 9.55229 0 9C0 8.44771 0.447715 8 1 8C1.55228 8 2 8.44771 2 9ZM5 10C5.55228 10 6 9.55229 6 9C6 8.44771 5.55228 8 5 8C4.44772 8 4 8.44771 4 9C4 9.55229 4.44772 10 5 10Z" fill="#98A2B3" /> |
| | | </svg> |
| | | </div> |
| | | <input |
| | | key={index} |
| | | type='input' |
| | | type="input" |
| | | value={o || ''} |
| | | onChange={(e) => { |
| | | const value = e.target.value |
| | |
| | | return item |
| | | })) |
| | | }} |
| | | className={'h-9 w-full grow cursor-pointer overflow-x-auto rounded-lg border-0 bg-transparent pl-1.5 pr-8 text-sm leading-9 text-text-secondary focus:outline-none'} |
| | | onFocus={() => setFocusID(index)} |
| | | onBlur={() => setFocusID(null)} |
| | | className={'w-full pl-1.5 pr-8 text-sm leading-9 text-gray-900 border-0 grow h-9 bg-transparent focus:outline-none cursor-pointer'} |
| | | /> |
| | | <div |
| | | role='button' |
| | | className='absolute right-1.5 top-1/2 block translate-y-[-50%] cursor-pointer rounded-md p-1 text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive' |
| | | <RemoveIcon |
| | | className={`${s.deleteBtn} absolute top-1/2 translate-y-[-50%] right-1.5 items-center justify-center w-6 h-6 rounded-md cursor-pointer hover:bg-[#FEE4E2]`} |
| | | onClick={() => { |
| | | onChange(options.filter((_, i) => index !== i)) |
| | | }} |
| | | onMouseEnter={() => setDeletingID(index)} |
| | | onMouseLeave={() => setDeletingID(null)} |
| | | > |
| | | <RiDeleteBinLine className='h-3.5 w-3.5' /> |
| | | </div> |
| | | /> |
| | | </div> |
| | | ))} |
| | | </ReactSortable> |
| | |
| | | |
| | | <div |
| | | onClick={() => { onChange([...options, '']) }} |
| | | className='mt-1 flex h-9 cursor-pointer items-center gap-2 rounded-lg bg-components-button-tertiary-bg px-3 text-components-button-tertiary-text hover:bg-components-button-tertiary-bg-hover'> |
| | | <RiAddLine className='h-4 w-4' /> |
| | | <div className='system-sm-medium text-[13px]'>{t('appDebug.variableConfig.addOption')}</div> |
| | | className='flex items-center h-9 px-3 gap-2 rounded-lg cursor-pointer text-gray-400 bg-gray-100'> |
| | | <PlusIcon width={16} height={16}></PlusIcon> |
| | | <div className='text-gray-500 text-[13px]'>{t('appDebug.variableConfig.addOption')}</div> |
| | | </div> |
| | | </div> |
| | | ) |
New file |
| | |
| | | .inputWrap { |
| | | display: flex; |
| | | align-items: center; |
| | | border-radius: 8px; |
| | | border: 1px solid #EAECF0; |
| | | padding-left: 10px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .deleteBtn { |
| | | display: none; |
| | | display: flex; |
| | | } |
| | | |
| | | .inputWrap:hover { |
| | | box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); |
| | | } |
| | | |
| | | .inputWrap:hover .deleteBtn { |
| | | display: flex; |
| | | } |
| | |
| | | min={1} |
| | | value={value || ''} |
| | | onChange={(e) => { |
| | | let value = Number.parseInt(e.target.value, 10) |
| | | let value = parseInt(e.target.value, 10) |
| | | if (value > maxLength) |
| | | value = maxLength |
| | | |
| | |
| | | import React, { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useBoolean } from 'ahooks' |
| | | import type { Timeout } from 'ahooks/lib/useRequest/src/types' |
| | | import { useContext } from 'use-context-selector' |
| | | import produce from 'immer' |
| | | import { |
| | | RiDeleteBinLine, |
| | | } from '@remixicon/react' |
| | | import Panel from '../base/feature-panel' |
| | | import EditModal from './config-modal' |
| | | import VarItem from './var-item' |
| | | import IconTypeIcon from './input-type-icon' |
| | | import type { IInputTypeIconProps } from './input-type-icon' |
| | | import s from './style.module.css' |
| | | import SelectVarType from './select-var-type' |
| | | import { BracketsX as VarIcon } from '@/app/components/base/icons/src/vender/line/development' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import type { PromptVariable } from '@/models/debug' |
| | | import { DEFAULT_VALUE_MAX_LEN } from '@/config' |
| | | import { getNewVar } from '@/utils/var' |
| | | import { DEFAULT_VALUE_MAX_LEN, getMaxVarNameLength } from '@/config' |
| | | import { checkKeys, getNewVar } from '@/utils/var' |
| | | import Switch from '@/app/components/base/switch' |
| | | import Toast from '@/app/components/base/toast' |
| | | import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import Confirm from '@/app/components/base/confirm' |
| | | import ConfigContext from '@/context/debug-configuration' |
| | | import { AppType } from '@/types/app' |
| | |
| | | onPromptVariablesChange?: (promptVariables: PromptVariable[]) => void |
| | | } |
| | | |
| | | let conflictTimer: Timeout |
| | | |
| | | const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVariablesChange }) => { |
| | | const { t } = useTranslation() |
| | | const { |
| | |
| | | const { eventEmitter } = useEventEmitterContextContext() |
| | | |
| | | const hasVar = promptVariables.length > 0 |
| | | const updatePromptVariable = (key: string, updateKey: string, newValue: string | boolean) => { |
| | | const newPromptVariables = promptVariables.map((item) => { |
| | | if (item.key === key) { |
| | | return { |
| | | ...item, |
| | | [updateKey]: newValue, |
| | | } |
| | | } |
| | | |
| | | return item |
| | | }) |
| | | onPromptVariablesChange?.(newPromptVariables) |
| | | } |
| | | const [currIndex, setCurrIndex] = useState<number>(-1) |
| | | const currItem = currIndex !== -1 ? promptVariables[currIndex] : null |
| | | const currItemToEdit: InputVar | null = (() => { |
| | |
| | | |
| | | if (payload.type !== InputVarType.select) |
| | | delete draft[currIndex].options |
| | | }) |
| | | |
| | | onPromptVariablesChange?.(newPromptVariables) |
| | | } |
| | | const updatePromptKey = (index: number, newKey: string) => { |
| | | clearTimeout(conflictTimer) |
| | | const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) |
| | | if (!isValid) { |
| | | Toast.notify({ |
| | | type: 'error', |
| | | message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), |
| | | }) |
| | | return |
| | | } |
| | | |
| | | const newPromptVariables = promptVariables.map((item, i) => { |
| | | if (i === index) { |
| | | return { |
| | | ...item, |
| | | key: newKey, |
| | | } |
| | | } |
| | | return item |
| | | }) |
| | | |
| | | conflictTimer = setTimeout(() => { |
| | | const isKeyExists = promptVariables.some(item => item.key?.trim() === newKey.trim()) |
| | | if (isKeyExists) { |
| | | Toast.notify({ |
| | | type: 'error', |
| | | message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }), |
| | | }) |
| | | } |
| | | }, 1000) |
| | | |
| | | onPromptVariablesChange?.(newPromptVariables) |
| | | } |
| | | |
| | | const updatePromptNameIfNameEmpty = (index: number, newKey: string) => { |
| | | if (!newKey) |
| | | return |
| | | const newPromptVariables = promptVariables.map((item, i) => { |
| | | if (i === index && !item.name) { |
| | | return { |
| | | ...item, |
| | | name: newKey, |
| | | } |
| | | } |
| | | return item |
| | | }) |
| | | |
| | | onPromptVariablesChange?.(newPromptVariables) |
| | |
| | | return ( |
| | | <Panel |
| | | className="mt-2" |
| | | headerIcon={ |
| | | <VarIcon className='w-4 h-4 text-primary-500' /> |
| | | } |
| | | title={ |
| | | <div className='flex items-center'> |
| | | <div className='mr-1'>{t('appDebug.variableTitle')}</div> |
| | |
| | | </div> |
| | | } |
| | | headerRight={!readonly ? <SelectVarType onChange={handleAddVar} /> : null} |
| | | noBodySpacing |
| | | > |
| | | {!hasVar && ( |
| | | <div className='mt-1 px-3 pb-3'> |
| | | <div className='pb-1 pt-2 text-xs text-text-tertiary'>{t('appDebug.notSetVar')}</div> |
| | | </div> |
| | | <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.notSetVar')}</div> |
| | | )} |
| | | {hasVar && ( |
| | | <div className='mt-1 px-3 pb-3'> |
| | | <div className='rounded-lg border border-gray-200 bg-white overflow-x-auto'> |
| | | <table className={`${s.table} min-w-[440px] w-full max-w-full border-collapse border-0 rounded-lg text-sm`}> |
| | | <thead className="border-b border-gray-200 text-gray-500 text-xs font-medium"> |
| | | <tr className='uppercase'> |
| | | <td>{t('appDebug.variableTable.key')}</td> |
| | | <td>{t('appDebug.variableTable.name')}</td> |
| | | {!readonly && ( |
| | | <> |
| | | <td>{t('appDebug.variableTable.optional')}</td> |
| | | <td>{t('appDebug.variableTable.action')}</td> |
| | | </> |
| | | )} |
| | | |
| | | </tr> |
| | | </thead> |
| | | <tbody className="text-gray-700"> |
| | | {promptVariables.map(({ key, name, type, required, config, icon, icon_background }, index) => ( |
| | | <VarItem |
| | | key={index} |
| | | readonly={readonly} |
| | | name={key} |
| | | label={name} |
| | | required={!!required} |
| | | type={type} |
| | | onEdit={() => handleConfig({ type, key, index, name, config, icon, icon_background })} |
| | | onRemove={() => handleRemoveVar(index)} |
| | | <tr key={index} className="h-9 leading-9"> |
| | | <td className="w-[160px] border-b border-gray-100 pl-3"> |
| | | <div className='flex items-center space-x-1'> |
| | | <IconTypeIcon type={type as IInputTypeIconProps['type']} className='text-gray-400' /> |
| | | {!readonly |
| | | ? ( |
| | | <input |
| | | type="text" |
| | | placeholder="key" |
| | | value={key} |
| | | onChange={e => updatePromptKey(index, e.target.value)} |
| | | onBlur={e => updatePromptNameIfNameEmpty(index, e.target.value)} |
| | | maxLength={getMaxVarNameLength(name)} |
| | | className="h-6 leading-6 block w-full rounded-md border-0 py-1.5 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" |
| | | /> |
| | | ) |
| | | : ( |
| | | <div className='h-6 leading-6 text-[13px] text-gray-700'>{key}</div> |
| | | )} |
| | | </div> |
| | | </td> |
| | | <td className="py-1 border-b border-gray-100"> |
| | | {!readonly |
| | | ? ( |
| | | <input |
| | | type="text" |
| | | placeholder={key} |
| | | value={name} |
| | | onChange={e => updatePromptVariable(key, 'name', e.target.value)} |
| | | maxLength={getMaxVarNameLength(name)} |
| | | className="h-6 leading-6 block w-full rounded-md border-0 py-1.5 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" |
| | | />) |
| | | : ( |
| | | <div className='h-6 leading-6 text-[13px] text-gray-700'>{name}</div> |
| | | )} |
| | | </td> |
| | | {!readonly && ( |
| | | <> |
| | | <td className='w-[84px] border-b border-gray-100'> |
| | | <div className='flex items-center h-full'> |
| | | <Switch defaultValue={!required} size='md' onChange={value => updatePromptVariable(key, 'required', !value)} /> |
| | | </div> |
| | | </td> |
| | | <td className='w-20 border-b border-gray-100'> |
| | | <div className='flex h-full items-center space-x-1'> |
| | | <div className=' p-1 rounded-md hover:bg-black/5 w-6 h-6 cursor-pointer' onClick={() => handleConfig({ type, key, index, name, config, icon, icon_background })}> |
| | | <Settings01 className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | <div className=' p-1 rounded-md hover:bg-black/5 w-6 h-6 cursor-pointer' onClick={() => handleRemoveVar(index)} > |
| | | <RiDeleteBinLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | </div> |
| | | </td> |
| | | </> |
| | | )} |
| | | </tr> |
| | | ))} |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | )} |
| | | |
| | |
| | | return ( |
| | | <div |
| | | className={cn( |
| | | 'flex h-[58px] flex-col items-center justify-center space-y-1 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg text-text-secondary', |
| | | selected ? 'system-xs-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs' : ' system-xs-regular cursor-pointer hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs')} |
| | | 'flex flex-col justify-center items-center h-[58px] rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg space-y-1', |
| | | selected ? 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs system-xs-medium' : ' hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs cursor-pointer system-xs-regular')} |
| | | onClick={onClick} |
| | | > |
| | | <div className='shrink-0'> |
| | | <InputVarTypeIcon type={type} className='h-5 w-5' /> |
| | | <InputVarTypeIcon type={type} className='w-5 h-5' /> |
| | | </div> |
| | | <span>{typeName}</span> |
| | | </div> |
| | |
| | | const SelectItem: FC<ItemProps> = ({ text, type, value, Icon, onClick }) => { |
| | | return ( |
| | | <div |
| | | className='flex h-8 cursor-pointer items-center rounded-lg px-3 hover:bg-gray-50' |
| | | className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer' |
| | | onClick={() => onClick(value)} |
| | | > |
| | | {Icon ? <Icon className='h-4 w-4 text-gray-500' /> : <InputVarTypeIcon type={type!} className='h-4 w-4 text-gray-500' />} |
| | | <div className='ml-2 truncate text-xs text-gray-600'>{text}</div> |
| | | {Icon ? <Icon className='w-4 h-4 text-gray-500' /> : <InputVarTypeIcon type={type!} className='w-4 h-4 text-gray-500' />} |
| | | <div className='ml-2 text-xs text-gray-600 truncate'>{text}</div> |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | <OperationBtn type='add' className={cn(open && 'bg-gray-200')} /> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent style={{ zIndex: 1000 }}> |
| | | <div className='min-w-[192px] rounded-lg border border-gray-200 bg-white shadow-lg'> |
| | | <div className='bg-white border border-gray-200 shadow-lg rounded-lg min-w-[192px]'> |
| | | <div className='p-1'> |
| | | <SelectItem type={InputVarType.textInput} value='string' text={t('appDebug.variableConfig.string')} onClick={handleChange}></SelectItem> |
| | | <SelectItem type={InputVarType.paragraph} value='paragraph' text={t('appDebug.variableConfig.paragraph')} onClick={handleChange}></SelectItem> |
New file |
| | |
| | | .table td { |
| | | padding-left: 12px; |
| | | } |
| | | |
| | | .table thead td { |
| | | height: 33px; |
| | | line-height: 33px; |
| | | } |
| | | |
| | | .table tbody tr:last-child td { |
| | | border-bottom: none; |
| | | } |
| | |
| | | return null |
| | | |
| | | return ( |
| | | <div className='mt-2 flex items-center gap-2 rounded-xl border-l-[0.5px] border-t-[0.5px] border-effects-highlight bg-background-section-burn p-2'> |
| | | <div className='mt-2 flex items-center gap-2 p-2 rounded-xl border-t-[0.5px] border-l-[0.5px] bg-background-section-burn'> |
| | | <div className='shrink-0 p-1'> |
| | | <div className='rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-indigo-indigo-600 p-1 shadow-xs'> |
| | | <Vision className='h-4 w-4 text-text-primary-on-surface' /> |
| | | <div className='p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-indigo-indigo-600'> |
| | | <Vision className='w-4 h-4 text-text-primary-on-surface' /> |
| | | </div> |
| | | </div> |
| | | <div className='flex grow items-center'> |
| | | <div className='system-sm-semibold mr-1 text-text-secondary'>{t('appDebug.vision.name')}</div> |
| | | <div className='grow flex items-center'> |
| | | <div className='mr-1 text-text-secondary system-sm-semibold'>{t('appDebug.vision.name')}</div> |
| | | <Tooltip |
| | | popupContent={ |
| | | <div className='w-[180px]' > |
| | |
| | | } |
| | | /> |
| | | </div> |
| | | <div className='flex shrink-0 items-center'> |
| | | <div className='shrink-0 flex items-center'> |
| | | {/* <div className='mr-2 flex items-center gap-0.5'> |
| | | <div className='text-text-tertiary system-xs-medium-uppercase'>{t('appDebug.vision.visionSettings.resolution')}</div> |
| | | <Tooltip |
| | |
| | | /> |
| | | </div> */} |
| | | <ParamConfig /> |
| | | <div className='ml-1 mr-3 h-3.5 w-[1px] bg-divider-regular'></div> |
| | | <div className='ml-1 mr-3 w-[1px] h-3.5 bg-divider-subtle'></div> |
| | | <Switch |
| | | defaultValue={isImageEnabled} |
| | | onChange={handleChange} |
| | |
| | | |
| | | return ( |
| | | <div> |
| | | <div className='text-base font-semibold leading-6 text-text-primary'>{t('appDebug.vision.visionSettings.title')}</div> |
| | | <div className='space-y-6 pt-3'> |
| | | <div className='leading-6 text-base font-semibold text-gray-800'>{t('appDebug.vision.visionSettings.title')}</div> |
| | | <div className='pt-3 space-y-6'> |
| | | <div> |
| | | <div className='mb-2 flex items-center space-x-1'> |
| | | <div className='text-[13px] font-semibold leading-[18px] text-text-secondary'>{t('appDebug.vision.visionSettings.resolution')}</div> |
| | | <div className='leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.vision.visionSettings.resolution')}</div> |
| | | <Tooltip |
| | | popupContent={ |
| | | <div className='w-[180px]' > |
| | |
| | | </div> |
| | | </div> |
| | | <div> |
| | | <div className='mb-2 text-[13px] font-semibold leading-[18px] text-text-secondary'>{t('appDebug.vision.visionSettings.uploadMethod')}</div> |
| | | <div className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.vision.visionSettings.uploadMethod')}</div> |
| | | <div className='flex items-center gap-1'> |
| | | <OptionCard |
| | | className='grow' |
| | |
| | | import type { FC } from 'react' |
| | | import { memo, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiSettings2Line } from '@remixicon/react' |
| | | import ParamConfigContent from './param-config-content' |
| | | import Button from '@/app/components/base/button' |
| | | import cn from '@/utils/classnames' |
| | | import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import { |
| | | PortalToFollowElem, |
| | | PortalToFollowElemContent, |
| | | PortalToFollowElemTrigger, |
| | | } from '@/app/components/base/portal-to-follow-elem' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | const ParamsConfig: FC = () => { |
| | | const { t } = useTranslation() |
| | |
| | | }} |
| | | > |
| | | <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> |
| | | <Button variant='ghost' size='small' className={cn('')}> |
| | | <RiSettings2Line className='h-3.5 w-3.5' /> |
| | | <div className='ml-1'>{t('appDebug.voice.settings')}</div> |
| | | </Button> |
| | | <div className={cn('flex items-center rounded-md h-7 px-3 space-x-1 text-text-tertiary cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}> |
| | | <Settings01 className='w-3.5 h-3.5 ' /> |
| | | <div className='ml-1 leading-[18px] text-xs font-medium '>{t('appDebug.voice.settings')}</div> |
| | | </div> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent style={{ zIndex: 50 }}> |
| | | <div className='w-80 space-y-3 rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg p-4 shadow-lg sm:w-[412px]'> |
| | | <div className='w-80 sm:w-[412px] p-4 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg space-y-3'> |
| | | <ParamConfigContent /> |
| | | </div> |
| | | </PortalToFollowElemContent> |
| | |
| | | import type { FC } from 'react' |
| | | import React, { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiSettings2Line } from '@remixicon/react' |
| | | import AgentSetting from './agent/agent-setting' |
| | | import Button from '@/app/components/base/button' |
| | | import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import type { AgentConfig } from '@/models/debug' |
| | | |
| | | type Props = { |
| | |
| | | |
| | | return ( |
| | | <> |
| | | <Button onClick={() => setIsShowAgentSetting(true)} className='mr-2 shrink-0'> |
| | | <RiSettings2Line className='mr-1 h-4 w-4 text-text-tertiary' /> |
| | | <Button onClick={() => setIsShowAgentSetting(true)} className='shrink-0 mr-2'> |
| | | <Settings01 className='mr-1 w-4 h-4 text-gray-500' /> |
| | | {t('appDebug.agent.setting.name')} |
| | | </Button> |
| | | {isShowAgentSetting && ( |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useEffect, useRef, useState } from 'react' |
| | | import React, { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiCloseLine } from '@remixicon/react' |
| | | import { useClickAway } from 'ahooks' |
| | | import ItemPanel from './item-panel' |
| | | import Button from '@/app/components/base/button' |
| | | import { CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' |
| | | import { Unblur } from '@/app/components/base/icons/src/vender/solid/education' |
| | | import Slider from '@/app/components/base/slider' |
| | | import type { AgentConfig } from '@/models/debug' |
| | | import { DEFAULT_AGENT_PROMPT, MAX_ITERATIONS_NUM } from '@/config' |
| | | import { DEFAULT_AGENT_PROMPT } from '@/config' |
| | | |
| | | type Props = { |
| | | isChatModel: boolean |
| | |
| | | } |
| | | |
| | | const maxIterationsMin = 1 |
| | | const maxIterationsMax = 5 |
| | | |
| | | const AgentSetting: FC<Props> = ({ |
| | | isChatModel, |
| | |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const [tempPayload, setTempPayload] = useState(payload) |
| | | const ref = useRef(null) |
| | | const [mounted, setMounted] = useState(false) |
| | | |
| | | useClickAway(() => { |
| | | if (mounted) |
| | | onCancel() |
| | | }, ref) |
| | | |
| | | useEffect(() => { |
| | | setMounted(true) |
| | | }, []) |
| | | |
| | | const handleSave = () => { |
| | | onSave(tempPayload) |
| | | } |
| | | |
| | | return ( |
| | | <div className='fixed inset-0 z-[100] flex justify-end overflow-hidden p-2' |
| | | <div className='fixed z-[100] inset-0 overflow-hidden flex justify-end p-2' |
| | | style={{ |
| | | backgroundColor: 'rgba(16, 24, 40, 0.20)', |
| | | }} |
| | | > |
| | | <div |
| | | ref={ref} |
| | | className='flex h-full w-[640px] flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl' |
| | | className='w-[640px] flex flex-col h-full overflow-hidden bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl' |
| | | > |
| | | <div className='flex h-14 shrink-0 items-center justify-between border-b border-divider-regular pl-6 pr-5'> |
| | | <div className='flex flex-col text-base font-semibold text-text-primary'> |
| | | <div className='shrink-0 flex justify-between items-center pl-6 pr-5 h-14 border-b border-b-gray-100'> |
| | | <div className='flex flex-col text-base font-semibold text-gray-900'> |
| | | <div className='leading-6'>{t('appDebug.agent.setting.name')}</div> |
| | | </div> |
| | | <div className='flex items-center'> |
| | | <div |
| | | onClick={onCancel} |
| | | className='flex h-6 w-6 cursor-pointer items-center justify-center' |
| | | className='flex justify-center items-center w-6 h-6 cursor-pointer' |
| | | > |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <RiCloseLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | {/* Body */} |
| | | <div className='grow overflow-y-auto border-b p-6 pb-[68px] pt-5' style={{ |
| | | <div className='grow p-6 pt-5 border-b overflow-y-auto pb-[68px]' style={{ |
| | | borderBottom: 'rgba(0, 0, 0, 0.05)', |
| | | }}> |
| | | {/* Agent Mode */} |
| | | <ItemPanel |
| | | className='mb-4' |
| | | icon={ |
| | | <CuteRobot className='h-4 w-4 text-indigo-600' /> |
| | | <CuteRobot className='w-4 h-4 text-indigo-600' /> |
| | | } |
| | | name={t('appDebug.agent.agentMode')} |
| | | description={t('appDebug.agent.agentModeDes')} |
| | | > |
| | | <div className='text-[13px] font-medium leading-[18px] text-text-primary'>{isFunctionCall ? t('appDebug.agent.agentModeType.functionCall') : t('appDebug.agent.agentModeType.ReACT')}</div> |
| | | <div className='leading-[18px] text-[13px] font-medium text-gray-900'>{isFunctionCall ? t('appDebug.agent.agentModeType.functionCall') : t('appDebug.agent.agentModeType.ReACT')}</div> |
| | | </ItemPanel> |
| | | |
| | | <ItemPanel |
| | | className='mb-4' |
| | | icon={ |
| | | <Unblur className='h-4 w-4 text-[#FB6514]' /> |
| | | <Unblur className='w-4 h-4 text-[#FB6514]' /> |
| | | } |
| | | name={t('appDebug.agent.setting.maximumIterations.name')} |
| | | description={t('appDebug.agent.setting.maximumIterations.description')} |
| | |
| | | <Slider |
| | | className='mr-3 w-[156px]' |
| | | min={maxIterationsMin} |
| | | max={MAX_ITERATIONS_NUM} |
| | | max={maxIterationsMax} |
| | | value={tempPayload.max_iteration} |
| | | onChange={(value) => { |
| | | setTempPayload({ |
| | |
| | | <input |
| | | type="number" |
| | | min={maxIterationsMin} |
| | | max={MAX_ITERATIONS_NUM} step={1} |
| | | className="block h-7 w-11 rounded-lg border-0 bg-components-input-bg-normal px-1.5 pl-1 leading-7 text-text-primary placeholder:text-text-tertiary focus:ring-1 focus:ring-inset focus:ring-primary-600" |
| | | max={maxIterationsMax} step={1} |
| | | className="block w-11 h-7 leading-7 rounded-lg border-0 pl-1 px-1.5 bg-gray-100 text-gray-900 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-primary-600" |
| | | value={tempPayload.max_iteration} |
| | | onChange={(e) => { |
| | | let value = Number.parseInt(e.target.value, 10) |
| | | let value = parseInt(e.target.value, 10) |
| | | if (value < maxIterationsMin) |
| | | value = maxIterationsMin |
| | | |
| | | if (value > MAX_ITERATIONS_NUM) |
| | | value = MAX_ITERATIONS_NUM |
| | | if (value > maxIterationsMax) |
| | | value = maxIterationsMax |
| | | setTempPayload({ |
| | | ...tempPayload, |
| | | max_iteration: value, |
| | |
| | | </ItemPanel> |
| | | |
| | | {!isFunctionCall && ( |
| | | <div className='rounded-xl bg-background-section-burn py-2 shadow-xs'> |
| | | <div className='flex h-8 items-center px-4 text-sm font-semibold leading-6 text-text-secondary'>{t('tools.builtInPromptTitle')}</div> |
| | | <div className='h-[396px] overflow-y-auto whitespace-pre-line px-4 text-sm font-normal leading-5 text-text-secondary'> |
| | | <div className='py-2 bg-gray-50 rounded-xl shadow-xs'> |
| | | <div className='flex items-center h-8 px-4 leading-6 text-sm font-semibold text-gray-700'>{t('tools.builtInPromptTitle')}</div> |
| | | <div className='h-[396px] px-4 overflow-y-auto leading-5 text-sm font-normal text-gray-700 whitespace-pre-line'> |
| | | {isChatModel ? DEFAULT_AGENT_PROMPT.chat : DEFAULT_AGENT_PROMPT.completion} |
| | | </div> |
| | | <div className='px-4'> |
| | | <div className='inline-flex h-5 items-center rounded-md bg-components-input-bg-normal px-1 text-xs font-medium leading-[18px] text-text-tertiary'>{(isChatModel ? DEFAULT_AGENT_PROMPT.chat : DEFAULT_AGENT_PROMPT.completion).length}</div> |
| | | <div className='inline-flex items-center h-5 px-1 rounded-md bg-gray-100 leading-[18px] text-xs font-medium text-gray-500'>{(isChatModel ? DEFAULT_AGENT_PROMPT.chat : DEFAULT_AGENT_PROMPT.completion).length}</div> |
| | | </div> |
| | | </div> |
| | | )} |
| | | |
| | | </div> |
| | | <div |
| | | className='sticky bottom-0 z-[5] flex w-full justify-end border-t border-divider-regular bg-background-section-burn px-6 py-4' |
| | | className='sticky z-[5] bottom-0 w-full flex justify-end py-4 px-6 border-t bg-white ' |
| | | style={{ |
| | | borderColor: 'rgba(0, 0, 0, 0.05)', |
| | | }} |
| | | > |
| | | <Button |
| | | onClick={onCancel} |
| | |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | type Props = { |
| | | className?: string |
| | | icon: React.JSX.Element |
| | | icon: JSX.Element |
| | | name: string |
| | | description: string |
| | | children: React.JSX.Element |
| | | children: JSX.Element |
| | | } |
| | | |
| | | const ItemPanel: FC<Props> = ({ |
| | |
| | | children, |
| | | }) => { |
| | | return ( |
| | | <div className={cn(className, 'flex h-12 items-center justify-between rounded-lg bg-background-section-burn px-3')}> |
| | | <div className={cn(className, 'flex justify-between items-center h-12 px-3 rounded-lg bg-gray-50')}> |
| | | <div className='flex items-center'> |
| | | {icon} |
| | | <div className='ml-3 mr-1 text-sm font-semibold leading-6 text-text-secondary'>{name}</div> |
| | | <div className='ml-3 mr-1 leading-6 text-sm font-semibold text-gray-800'>{name}</div> |
| | | <Tooltip |
| | | popupContent={ |
| | | <div className='w-[180px]'> |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useMemo, useState } from 'react' |
| | | import React, { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useContext } from 'use-context-selector' |
| | | import copy from 'copy-to-clipboard' |
| | | import produce from 'immer' |
| | | import { |
| | | RiDeleteBinLine, |
| | | RiEqualizer2Line, |
| | | RiInformation2Line, |
| | | RiHammerFill, |
| | | } from '@remixicon/react' |
| | | import { useFormattingChangedDispatcher } from '../../../debug/hooks' |
| | | import SettingBuiltInTool from './setting-built-in-tool' |
| | | import cn from '@/utils/classnames' |
| | | import Panel from '@/app/components/app/configuration/base/feature-panel' |
| | | import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general' |
| | | import OperationBtn from '@/app/components/app/configuration/base/operation-btn' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | import Button from '@/app/components/base/button' |
| | | import Indicator from '@/app/components/header/indicator' |
| | | import Switch from '@/app/components/base/switch' |
| | | import Toast from '@/app/components/base/toast' |
| | | import ConfigContext from '@/context/debug-configuration' |
| | | import type { AgentTool } from '@/types/app' |
| | | import { type Collection, CollectionType } from '@/app/components/tools/types' |
| | |
| | | import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other' |
| | | import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' |
| | | import { updateBuiltInToolCredential } from '@/service/tools' |
| | | import cn from '@/utils/classnames' |
| | | import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' |
| | | import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' |
| | | import { canFindTool } from '@/utils' |
| | | import AddToolModal from '@/app/components/tools/add-tool-modal' |
| | | |
| | | type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null |
| | | const AgentTools: FC = () => { |
| | |
| | | const formattingChangedDispatcher = useFormattingChangedDispatcher() |
| | | |
| | | const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null) |
| | | const currentCollection = useMemo(() => { |
| | | if (!currentTool) return null |
| | | const collection = collectionList.find(collection => canFindTool(collection.id, currentTool?.provider_id) && collection.type === currentTool?.provider_type) |
| | | return collection |
| | | }, [currentTool, collectionList]) |
| | | const [isShowSettingTool, setIsShowSettingTool] = useState(false) |
| | | const [isShowSettingAuth, setShowSettingAuth] = useState(false) |
| | | const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => { |
| | | const collection = collectionList.find( |
| | | collection => |
| | | canFindTool(collection.id, item.provider_id) |
| | | && collection.type === item.provider_type, |
| | | ) |
| | | const collection = collectionList.find(collection => collection.id === item.provider_id && collection.type === item.provider_type) |
| | | const icon = collection?.icon |
| | | return { |
| | | ...item, |
| | |
| | | formattingChangedDispatcher() |
| | | } |
| | | |
| | | const handleToolAuthSetting = (value: AgentToolWithMoreInfo) => { |
| | | const newModelConfig = produce(modelConfig, (draft) => { |
| | | const tool = (draft.agentConfig.tools).find((item: any) => item.provider_id === value?.collection?.id && item.tool_name === value?.tool_name) |
| | | if (tool) |
| | | (tool as AgentTool).notAuthor = false |
| | | }) |
| | | setModelConfig(newModelConfig) |
| | | setIsShowSettingTool(false) |
| | | formattingChangedDispatcher() |
| | | } |
| | | |
| | | const [isDeleting, setIsDeleting] = useState<number>(-1) |
| | | |
| | | const handleSelectTool = (tool: ToolDefaultValue) => { |
| | | const newModelConfig = produce(modelConfig, (draft) => { |
| | | draft.agentConfig.tools.push({ |
| | | provider_id: tool.provider_id, |
| | | provider_type: tool.provider_type as CollectionType, |
| | | provider_name: tool.provider_name, |
| | | tool_name: tool.tool_name, |
| | | tool_label: tool.tool_label, |
| | | tool_parameters: tool.params, |
| | | notAuthor: !tool.is_team_authorization, |
| | | enabled: true, |
| | | }) |
| | | }) |
| | | setModelConfig(newModelConfig) |
| | | } |
| | | |
| | | return ( |
| | | <> |
| | | <Panel |
| | | className={cn('mt-2', tools.length === 0 && 'pb-2')} |
| | | className="mt-2" |
| | | noBodySpacing={tools.length === 0} |
| | | headerIcon={ |
| | | <RiHammerFill className='w-4 h-4 text-primary-500' /> |
| | | } |
| | | title={ |
| | | <div className='flex items-center'> |
| | | <div className='mr-1'>{t('appDebug.agent.tools.name')}</div> |
| | |
| | | } |
| | | headerRight={ |
| | | <div className='flex items-center'> |
| | | <div className='text-xs font-normal leading-[18px] text-text-tertiary'>{tools.filter(item => !!item.enabled).length}/{tools.length} {t('appDebug.agent.tools.enabled')}</div> |
| | | <div className='leading-[18px] text-xs font-normal text-gray-500'>{tools.filter((item: any) => !!item.enabled).length}/{tools.length} {t('appDebug.agent.tools.enabled')}</div> |
| | | {tools.length < MAX_TOOLS_NUM && ( |
| | | <> |
| | | <div className='ml-3 mr-1 h-3.5 w-px bg-divider-regular'></div> |
| | | <ToolPicker |
| | | trigger={<OperationBtn type="add" />} |
| | | isShow={isShowChooseTool} |
| | | onShowChange={setIsShowChooseTool} |
| | | disabled={false} |
| | | supportAddCustomTool |
| | | onSelect={handleSelectTool} |
| | | selectedTools={tools} |
| | | /> |
| | | <div className='ml-3 mr-1 h-3.5 w-px bg-gray-200'></div> |
| | | <OperationBtn type="add" onClick={() => setIsShowChooseTool(true)} /> |
| | | </> |
| | | )} |
| | | </div> |
| | | } |
| | | > |
| | | <div className='grid grid-cols-1 flex-wrap items-center justify-between gap-1 2xl:grid-cols-2'> |
| | | <div className='grid gap-1 grid-cols-1 2xl:grid-cols-2 items-center flex-wrap justify-between'> |
| | | {tools.map((item: AgentTool & { icon: any; collection?: Collection }, index) => ( |
| | | <div key={index} |
| | | className={cn( |
| | | 'cursor group relative flex w-full items-center justify-between rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg p-1.5 pr-2 shadow-xs last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', |
| | | isDeleting === index && 'border-state-destructive-border hover:bg-state-destructive-hover', |
| | | )} |
| | | className={cn((item.isDeleted || item.notAuthor) ? 'bg-white/50' : 'bg-white', (item.enabled && !item.isDeleted && !item.notAuthor) && 'shadow-xs', index > 1 && 'mt-1', 'group relative flex justify-between items-center last-of-type:mb-0 pl-2.5 py-2 pr-3 w-full rounded-lg border-[0.5px] border-gray-200 ')} |
| | | > |
| | | <div className='flex w-0 grow items-center'> |
| | | {item.isDeleted && <DefaultToolIcon className='h-5 w-5' />} |
| | | {!item.isDeleted && ( |
| | | <div className={cn((item.notAuthor || !item.enabled) && 'opacity-50')}> |
| | | {typeof item.icon === 'string' && <div className='h-5 w-5 rounded-md bg-cover bg-center' style={{ backgroundImage: `url(${item.icon})` }} />} |
| | | {typeof item.icon !== 'string' && <AppIcon className='rounded-md' size='xs' icon={item.icon?.content} background={item.icon?.background} />} |
| | | </div> |
| | | )} |
| | | <div className='grow w-0 flex items-center'> |
| | | {(item.isDeleted || item.notAuthor) |
| | | ? ( |
| | | <DefaultToolIcon className='w-6 h-6' /> |
| | | ) |
| | | : ( |
| | | typeof item.icon === 'string' |
| | | ? ( |
| | | <div |
| | | className={cn( |
| | | 'system-xs-regular ml-1.5 flex w-0 grow items-center truncate', |
| | | (item.isDeleted || item.notAuthor || !item.enabled) ? 'opacity-50' : '', |
| | | )} |
| | | className='w-6 h-6 bg-cover bg-center rounded-md' |
| | | style={{ |
| | | backgroundImage: `url(${item.icon})`, |
| | | }} |
| | | ></div> |
| | | ) |
| | | : ( |
| | | <AppIcon |
| | | className='rounded-md' |
| | | size='tiny' |
| | | icon={item.icon?.content} |
| | | background={item.icon?.background} |
| | | /> |
| | | ))} |
| | | <div |
| | | className={cn((item.isDeleted || item.notAuthor) ? 'line-through opacity-50' : '', 'grow w-0 ml-2 leading-[18px] text-[13px] font-medium text-gray-800 truncate')} |
| | | > |
| | | <span className='system-xs-medium pr-1.5 text-text-secondary'>{item.provider_type === CollectionType.builtIn ? item.provider_name.split('/').pop() : item.tool_label}</span> |
| | | <span className='text-text-tertiary'>{item.tool_label}</span> |
| | | {!item.isDeleted && ( |
| | | <span className='text-gray-800 pr-2'>{item.provider_type === CollectionType.builtIn ? item.provider_name : item.tool_label}</span> |
| | | <Tooltip |
| | | needsDelay |
| | | popupContent={ |
| | | <div className='w-[180px]'> |
| | | <div className='mb-1.5 text-text-secondary'>{item.tool_name}</div> |
| | | <div className='mb-1.5 text-text-tertiary'>{t('tools.toolNameUsageTip')}</div> |
| | | <div className='cursor-pointer text-text-accent' onClick={() => copy(item.tool_name)}>{t('tools.copyToolName')}</div> |
| | | </div> |
| | | } |
| | | popupContent={t('tools.toolNameUsageTip')} |
| | | > |
| | | <div className='h-4 w-4'> |
| | | <div className='ml-0.5 hidden group-hover:inline-block'> |
| | | <RiInformation2Line className='h-4 w-4 text-text-tertiary' /> |
| | | </div> |
| | | </div> |
| | | <span className='text-gray-500'>{item.tool_name}</span> |
| | | </Tooltip> |
| | | )} |
| | | </div> |
| | | </div> |
| | | <div className='ml-1 flex shrink-0 items-center'> |
| | | {item.isDeleted && ( |
| | | <div className='mr-2 flex items-center'> |
| | | <div className='shrink-0 ml-1 flex items-center'> |
| | | {(item.isDeleted || item.notAuthor) |
| | | ? ( |
| | | <div className='flex items-center'> |
| | | <Tooltip |
| | | popupContent={t('tools.toolRemoved')} |
| | | popupContent={t(`tools.${item.isDeleted ? 'toolRemoved' : 'notAuthorized'}`)} |
| | | needsDelay |
| | | > |
| | | <div className='mr-1 cursor-pointer rounded-md p-1 hover:bg-black/5'> |
| | | <AlertTriangle className='h-4 w-4 text-[#F79009]' /> |
| | | <div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => { |
| | | if (item.notAuthor) |
| | | setIsShowChooseTool(true) |
| | | }}> |
| | | <AlertTriangle className='w-4 h-4 text-[#F79009]' /> |
| | | </div> |
| | | </Tooltip> |
| | | <div |
| | | className='cursor-pointer rounded-md p-1 text-text-tertiary hover:text-text-destructive' |
| | | onClick={() => { |
| | | |
| | | <div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => { |
| | | const newModelConfig = produce(modelConfig, (draft) => { |
| | | draft.agentConfig.tools.splice(index, 1) |
| | | }) |
| | | setModelConfig(newModelConfig) |
| | | formattingChangedDispatcher() |
| | | }} |
| | | onMouseOver={() => setIsDeleting(index)} |
| | | onMouseLeave={() => setIsDeleting(-1)} |
| | | > |
| | | <RiDeleteBinLine className='h-4 w-4' /> |
| | | }}> |
| | | <RiDeleteBinLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | <div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div> |
| | | </div> |
| | | )} |
| | | {!item.isDeleted && ( |
| | | <div className='mr-2 hidden items-center gap-1 group-hover:flex'> |
| | | {!item.notAuthor && ( |
| | | ) |
| | | : ( |
| | | <div className='hidden group-hover:flex items-center'> |
| | | <Tooltip |
| | | popupContent={t('tools.setBuiltInTools.infoAndSetting')} |
| | | needsDelay |
| | | > |
| | | <div className='cursor-pointer rounded-md p-1 hover:bg-black/5' onClick={() => { |
| | | <div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => { |
| | | setCurrentTool(item) |
| | | setIsShowSettingTool(true) |
| | | }}> |
| | | <RiEqualizer2Line className='h-4 w-4 text-text-tertiary' /> |
| | | <InfoCircle className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | </Tooltip> |
| | | )} |
| | | <div |
| | | className='cursor-pointer rounded-md p-1 text-text-tertiary hover:text-text-destructive' |
| | | onClick={() => { |
| | | |
| | | <div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => { |
| | | const newModelConfig = produce(modelConfig, (draft) => { |
| | | draft.agentConfig.tools.splice(index, 1) |
| | | }) |
| | | setModelConfig(newModelConfig) |
| | | formattingChangedDispatcher() |
| | | }} |
| | | onMouseOver={() => setIsDeleting(index)} |
| | | onMouseLeave={() => setIsDeleting(-1)} |
| | | > |
| | | <RiDeleteBinLine className='h-4 w-4' /> |
| | | }}> |
| | | <RiDeleteBinLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | <div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div> |
| | | </div> |
| | | )} |
| | | <div className={cn(item.isDeleted && 'opacity-50')}> |
| | | {!item.notAuthor && ( |
| | | <div className={cn((item.isDeleted || item.notAuthor) && 'opacity-50')}> |
| | | <Switch |
| | | defaultValue={item.isDeleted ? false : item.enabled} |
| | | disabled={item.isDeleted} |
| | | defaultValue={(item.isDeleted || item.notAuthor) ? false : item.enabled} |
| | | disabled={(item.isDeleted || item.notAuthor)} |
| | | size='md' |
| | | onChange={(enabled) => { |
| | | const newModelConfig = produce(modelConfig, (draft) => { |
| | |
| | | setModelConfig(newModelConfig) |
| | | formattingChangedDispatcher() |
| | | }} /> |
| | | )} |
| | | {item.notAuthor && ( |
| | | <Button variant='secondary' size='small' onClick={() => { |
| | | setCurrentTool(item) |
| | | setShowSettingAuth(true) |
| | | }}> |
| | | {t('tools.notAuthorized')} |
| | | <Indicator className='ml-2' color='orange' /> |
| | | </Button> |
| | | )} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | ))} |
| | | </div > |
| | | </Panel > |
| | | {isShowSettingTool && ( |
| | | {isShowChooseTool && ( |
| | | <AddToolModal onHide={() => setIsShowChooseTool(false)} /> |
| | | )} |
| | | { |
| | | isShowSettingTool && ( |
| | | <SettingBuiltInTool |
| | | toolName={currentTool?.tool_name as string} |
| | | setting={currentTool?.tool_parameters} |
| | | setting={currentTool?.tool_parameters as any} |
| | | collection={currentTool?.collection as Collection} |
| | | isBuiltIn={currentTool?.collection?.type === CollectionType.builtIn} |
| | | isModel={currentTool?.collection?.type === CollectionType.model} |
| | | onSave={handleToolSettingChange} |
| | | onHide={() => setIsShowSettingTool(false)} |
| | | /> |
| | | )} |
| | | {isShowSettingAuth && ( |
| | | <ConfigCredential |
| | | collection={currentCollection as any} |
| | | onCancel={() => setShowSettingAuth(false)} |
| | | onSaved={async (value) => { |
| | | await updateBuiltInToolCredential((currentCollection as any).name, value) |
| | | Toast.notify({ |
| | | type: 'success', |
| | | message: t('common.api.actionSuccess'), |
| | | }) |
| | | handleToolAuthSetting(currentTool) |
| | | setShowSettingAuth(false) |
| | | }} |
| | | /> |
| | | )} |
| | | />) |
| | | } |
| | | </> |
| | | ) |
| | | } |
| | |
| | | import React, { useEffect, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useContext } from 'use-context-selector' |
| | | import { |
| | | RiArrowLeftLine, |
| | | RiCloseLine, |
| | | } from '@remixicon/react' |
| | | import Drawer from '@/app/components/base/drawer' |
| | | import Loading from '@/app/components/base/loading' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | import Icon from '@/app/components/plugins/card/base/card-icon' |
| | | import OrgInfo from '@/app/components/plugins/card/base/org-info' |
| | | import Description from '@/app/components/plugins/card/base/description' |
| | | import TabSlider from '@/app/components/base/tab-slider-plain' |
| | | |
| | | import Button from '@/app/components/base/button' |
| | | import cn from '@/utils/classnames' |
| | | import Drawer from '@/app/components/base/drawer-plus' |
| | | import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' |
| | | import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' |
| | | import type { Collection, Tool } from '@/app/components/tools/types' |
| | | import { CollectionType } from '@/app/components/tools/types' |
| | | import { fetchBuiltInToolList, fetchCustomToolList, fetchModelToolList, fetchWorkflowToolList } from '@/service/tools' |
| | | import I18n from '@/context/i18n' |
| | | import Button from '@/app/components/base/button' |
| | | import Loading from '@/app/components/base/loading' |
| | | import { DiagonalDividingLine } from '@/app/components/base/icons/src/public/common' |
| | | import { getLanguage } from '@/i18n/language' |
| | | import cn from '@/utils/classnames' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | |
| | | type Props = { |
| | | showBackButton?: boolean |
| | | collection: Collection |
| | | isBuiltIn?: boolean |
| | | isModel?: boolean |
| | |
| | | } |
| | | |
| | | const SettingBuiltInTool: FC<Props> = ({ |
| | | showBackButton = false, |
| | | collection, |
| | | isBuiltIn = true, |
| | | isModel = true, |
| | |
| | | const [tools, setTools] = useState<Tool[]>([]) |
| | | const currTool = tools.find(tool => tool.name === toolName) |
| | | const formSchemas = currTool ? toolParametersToFormSchemas(currTool.parameters) : [] |
| | | const infoSchemas = formSchemas.filter(item => item.form === 'llm') |
| | | const settingSchemas = formSchemas.filter(item => item.form !== 'llm') |
| | | const infoSchemas = formSchemas.filter((item: any) => item.form === 'llm') |
| | | const settingSchemas = formSchemas.filter((item: any) => item.form !== 'llm') |
| | | const hasSetting = settingSchemas.length > 0 |
| | | const [tempSetting, setTempSetting] = useState(setting) |
| | | const [currType, setCurrType] = useState('info') |
| | |
| | | setTempSetting(addDefaultValue(setting, formSchemas)) |
| | | } |
| | | } |
| | | catch { } |
| | | catch (e) { } |
| | | setIsLoading(false) |
| | | })() |
| | | }, [collection?.name, collection?.id, collection?.type]) |
| | |
| | | |
| | | const isValid = (() => { |
| | | let valid = true |
| | | settingSchemas.forEach((item) => { |
| | | settingSchemas.forEach((item: any) => { |
| | | if (item.required && !tempSetting[item.name]) |
| | | valid = false |
| | | }) |
| | | return valid |
| | | })() |
| | | |
| | | const getType = (type: string) => { |
| | | if (type === 'number-input') |
| | | return t('tools.setBuiltInTools.number') |
| | | if (type === 'text-input') |
| | | return t('tools.setBuiltInTools.string') |
| | | if (type === 'file') |
| | | return t('tools.setBuiltInTools.file') |
| | | return type |
| | | } |
| | | |
| | | const infoUI = ( |
| | | <div className=''> |
| | | {infoSchemas.length > 0 && ( |
| | | <div className='space-y-1 py-2'> |
| | | {infoSchemas.map((item, index) => ( |
| | | <div key={index} className='py-1'> |
| | | <div className='flex items-center gap-2'> |
| | | <div className='code-sm-semibold text-text-secondary'>{item.label[language]}</div> |
| | | <div className='system-xs-regular text-text-tertiary'> |
| | | {getType(item.type)} |
| | | <div className='pt-2'> |
| | | <div className='leading-5 text-sm font-medium text-gray-900'> |
| | | {t('tools.setBuiltInTools.toolDescription')} |
| | | </div> |
| | | <div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'> |
| | | {currTool?.description[language]} |
| | | </div> |
| | | |
| | | {infoSchemas.length > 0 && ( |
| | | <div className='mt-6'> |
| | | <div className='flex items-center mb-4 leading-[18px] text-xs font-semibold text-gray-500 uppercase'> |
| | | <div className='mr-3'>{t('tools.setBuiltInTools.parameters')}</div> |
| | | <div className='grow w-0 h-px bg-[#f3f4f6]'></div> |
| | | </div> |
| | | <div className='space-y-4'> |
| | | {infoSchemas.map((item: any, index) => ( |
| | | <div key={index}> |
| | | <div className='flex items-center space-x-2 leading-[18px]'> |
| | | <div className='text-[13px] font-semibold text-gray-900'>{item.label[language]}</div> |
| | | <div className='text-xs font-medium text-gray-500'>{item.type === 'number-input' ? t('tools.setBuiltInTools.number') : t('tools.setBuiltInTools.string')}</div> |
| | | {item.required && ( |
| | | <div className='system-xs-medium text-text-warning-secondary'>{t('tools.setBuiltInTools.required')}</div> |
| | | <div className='text-xs font-medium text-[#EC4A0A]'>{t('tools.setBuiltInTools.required')}</div> |
| | | )} |
| | | </div> |
| | | {item.human_description && ( |
| | | <div className='system-xs-regular mt-0.5 text-text-tertiary'> |
| | | <div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'> |
| | | {item.human_description?.[language]} |
| | | </div> |
| | | )} |
| | | </div> |
| | | ))} |
| | | </div> |
| | | </div> |
| | | )} |
| | | </div> |
| | |
| | | <Form |
| | | value={tempSetting} |
| | | onChange={setTempSetting} |
| | | formSchemas={settingSchemas} |
| | | formSchemas={settingSchemas as any} |
| | | isEditMode={false} |
| | | showOnVariableMap={{}} |
| | | validating={false} |
| | | inputClassName='!bg-gray-50' |
| | | readonly={readonly} |
| | | /> |
| | | ) |
| | | |
| | | return ( |
| | | <Drawer |
| | | isOpen |
| | | clickOutsideNotOpen={false} |
| | | onClose={onHide} |
| | | footer={null} |
| | | mask={false} |
| | | positionCenter={false} |
| | | panelClassName={cn('mb-2 mr-2 mt-[64px] !w-[420px] !max-w-[420px] justify-start rounded-2xl border-[0.5px] border-components-panel-border !bg-components-panel-bg !p-0 shadow-xl')} |
| | | > |
| | | <> |
| | | {isLoading && <Loading type='app' />} |
| | | {!isLoading && ( |
| | | <> |
| | | {/* header */} |
| | | <div className='relative border-b border-divider-subtle p-4 pb-3'> |
| | | <div className='absolute right-3 top-3'> |
| | | <ActionButton onClick={onHide}> |
| | | <RiCloseLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | </div> |
| | | {showBackButton && ( |
| | | isShow |
| | | onHide={onHide} |
| | | title={( |
| | | <div className='flex items-center'> |
| | | {typeof collection.icon === 'string' |
| | | ? ( |
| | | <div |
| | | className='system-xs-semibold-uppercase mb-2 flex cursor-pointer items-center gap-1 text-text-accent-secondary' |
| | | onClick={onHide} |
| | | > |
| | | <RiArrowLeftLine className='h-4 w-4' /> |
| | | BACK |
| | | </div> |
| | | )} |
| | | <div className='flex items-center gap-1'> |
| | | <Icon size='tiny' className='h-6 w-6' src={collection.icon} /> |
| | | <OrgInfo |
| | | packageNameClassName='w-auto' |
| | | orgName={collection.author} |
| | | packageName={collection.name.split('/').pop() || ''} |
| | | /> |
| | | </div> |
| | | <div className='system-md-semibold mt-1 text-text-primary'>{currTool?.label[language]}</div> |
| | | {!!currTool?.description[language] && ( |
| | | <Description className='mt-3' text={currTool.description[language]} descriptionLineRows={2}></Description> |
| | | )} |
| | | </div> |
| | | {/* form */} |
| | | <div className='h-full'> |
| | | <div className='flex h-full flex-col'> |
| | | {(hasSetting && !readonly) ? ( |
| | | <TabSlider |
| | | className='mt-1 shrink-0 px-4' |
| | | itemClassName='py-3' |
| | | noBorderBottom |
| | | value={currType} |
| | | onChange={(value) => { |
| | | setCurrType(value) |
| | | className='w-6 h-6 bg-cover bg-center rounded-md flex-shrink-0' |
| | | style={{ |
| | | backgroundImage: `url(${collection.icon})`, |
| | | }} |
| | | options={[ |
| | | { value: 'info', text: t('tools.setBuiltInTools.parameters')! }, |
| | | { value: 'setting', text: t('tools.setBuiltInTools.setting')! }, |
| | | ]} |
| | | ></div> |
| | | ) |
| | | : ( |
| | | <AppIcon |
| | | className='rounded-md' |
| | | size='tiny' |
| | | icon={(collection.icon as any)?.content} |
| | | background={(collection.icon as any)?.background} |
| | | /> |
| | | ) : ( |
| | | <div className='system-sm-semibold-uppercase p-4 pb-1 text-text-primary'>{t('tools.setBuiltInTools.parameters')}</div> |
| | | )} |
| | | <div className='h-0 grow overflow-y-auto px-4'> |
| | | |
| | | <div className='ml-2 leading-6 text-base font-semibold text-gray-900'>{currTool?.label[language]}</div> |
| | | {(hasSetting && !readonly) && (<> |
| | | <DiagonalDividingLine className='mx-4' /> |
| | | <div className='flex space-x-6'> |
| | | <div |
| | | className={cn(isInfoActive ? 'text-gray-900 font-semibold' : 'font-normal text-gray-600 cursor-pointer', 'relative text-base')} |
| | | onClick={() => setCurrType('info')} |
| | | > |
| | | {t('tools.setBuiltInTools.info')} |
| | | {isInfoActive && <div className='absolute left-0 bottom-[-16px] w-full h-0.5 bg-primary-600'></div>} |
| | | </div> |
| | | <div className={cn(!isInfoActive ? 'text-gray-900 font-semibold' : 'font-normal text-gray-600 cursor-pointer', 'relative text-base ')} |
| | | onClick={() => setCurrType('setting')} |
| | | > |
| | | {t('tools.setBuiltInTools.setting')} |
| | | {!isInfoActive && <div className='absolute left-0 bottom-[-16px] w-full h-0.5 bg-primary-600'></div>} |
| | | </div> |
| | | </div> |
| | | </>)} |
| | | </div> |
| | | )} |
| | | panelClassName='mt-[65px] !w-[405px]' |
| | | maxWidthClassName='!max-w-[405px]' |
| | | height='calc(100vh - 65px)' |
| | | headerClassName='!border-b-black/5' |
| | | body={ |
| | | <div className='h-full pt-3'> |
| | | {isLoading |
| | | ? <div className='flex h-full items-center'> |
| | | <Loading type='app' /> |
| | | </div> |
| | | : (<div className='flex flex-col h-full'> |
| | | <div className='grow h-0 overflow-y-auto px-6'> |
| | | {isInfoActive ? infoUI : settingUI} |
| | | </div> |
| | | {!readonly && !isInfoActive && ( |
| | | <div className='mt-2 flex shrink-0 justify-end space-x-2 rounded-b-[10px] border-t border-divider-regular bg-components-panel-bg px-6 py-4'> |
| | | <Button className='flex h-8 items-center !px-3 !text-[13px] font-medium ' onClick={onHide}>{t('common.operation.cancel')}</Button> |
| | | <Button className='flex h-8 items-center !px-3 !text-[13px] font-medium' variant='primary' disabled={!isValid} onClick={() => onSave?.(addDefaultValue(tempSetting, formSchemas))}>{t('common.operation.save')}</Button> |
| | | <div className='mt-2 shrink-0 flex justify-end py-4 px-6 space-x-2 rounded-b-[10px] bg-gray-50 border-t border-black/5'> |
| | | <Button className='flex items-center h-8 !px-3 !text-[13px] font-medium !text-gray-700' onClick={onHide}>{t('common.operation.cancel')}</Button> |
| | | <Button className='flex items-center h-8 !px-3 !text-[13px] font-medium' variant='primary' disabled={!isValid} onClick={() => onSave?.(addDefaultValue(tempSetting, formSchemas))}>{t('common.operation.save')}</Button> |
| | | </div> |
| | | )} |
| | | </div>)} |
| | | </div> |
| | | </div> |
| | | </> |
| | | )} |
| | | </> |
| | | </Drawer> |
| | | } |
| | | isShowMask={false} |
| | | clickOutsideNotOpen={false} |
| | | /> |
| | | ) |
| | | } |
| | | export default React.memo(SettingBuiltInTool) |
| | |
| | | import ConfigContext from '@/context/debug-configuration' |
| | | import { useModalContext } from '@/context/modal-context' |
| | | import { useToastContext } from '@/app/components/base/toast' |
| | | import s from '@/app/components/app/configuration/config-prompt/style.module.css' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | import s from '@/app/components/app/configuration/config-prompt/style.module.css' |
| | | type Props = { |
| | | className?: string |
| | | type: 'first-prompt' | 'next-iteration' |
| | |
| | | return ( |
| | | <div className={cn(className, s.gradientBorder, 'relative')}> |
| | | <div className='rounded-xl bg-white'> |
| | | <div className={cn(s.boxHeader, 'flex h-11 items-center justify-between rounded-tl-xl rounded-tr-xl bg-white pb-1 pl-4 pr-3 pt-2 hover:shadow-xs')}> |
| | | <div className={cn(s.boxHeader, 'flex justify-between items-center h-11 pt-2 pr-3 pb-1 pl-4 rounded-tl-xl rounded-tr-xl bg-white hover:shadow-xs')}> |
| | | <div className='text-sm font-semibold uppercase text-indigo-800'>{t(`appDebug.agent.${isFirstPrompt ? 'firstPrompt' : 'nextIteration'}`)}</div> |
| | | <div className={cn(s.optionWrap, 'items-center space-x-1')}> |
| | | {!isCopied |
| | | ? ( |
| | | <Clipboard className='h-6 w-6 cursor-pointer p-1 text-gray-500' onClick={() => { |
| | | <Clipboard className='h-6 w-6 p-1 text-gray-500 cursor-pointer' onClick={() => { |
| | | copy(value) |
| | | setIsCopied(true) |
| | | }} /> |
| | |
| | | )} |
| | | </div> |
| | | </div> |
| | | <div className={cn(editorHeight, ' min-h-[102px] overflow-y-auto px-4 text-sm text-gray-700')}> |
| | | <div className={cn(editorHeight, ' px-4 min-h-[102px] overflow-y-auto text-sm text-gray-700')}> |
| | | <PromptEditor |
| | | className={editorHeight} |
| | | value={value} |
| | |
| | | user: '', |
| | | assistant: '', |
| | | }, |
| | | onEditRole: noop, |
| | | onEditRole: () => { }, |
| | | }} |
| | | queryBlock={{ |
| | | show: false, |
| | | selectable: false, |
| | | }} |
| | | onChange={onChange} |
| | | onBlur={noop} |
| | | onBlur={() => { }} |
| | | /> |
| | | </div> |
| | | <div className='flex pb-2 pl-4'> |
| | | <div className="h-[18px] rounded-md bg-gray-100 px-1 text-xs leading-[18px] text-gray-500">{value.length}</div> |
| | | <div className='pl-4 pb-2 flex'> |
| | | <div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{value.length}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | const SelectItem: FC<ItemProps> = ({ text, value, Icon, isChecked, description, onClick, disabled }) => { |
| | | return ( |
| | | <div |
| | | className={cn(disabled ? 'opacity-50' : 'cursor-pointer', isChecked ? 'border-[2px] border-indigo-600 shadow-sm' : 'border border-gray-100', 'mb-2 rounded-xl bg-gray-25 p-3 pr-4 hover:bg-gray-50')} |
| | | className={cn(disabled ? 'opacity-50' : 'cursor-pointer', isChecked ? 'border-[2px] border-indigo-600 shadow-sm' : 'border border-gray-100', 'mb-2 p-3 pr-4 rounded-xl bg-gray-25 hover:bg-gray-50')} |
| | | onClick={() => !disabled && onClick(value)} |
| | | > |
| | | <div className='flex items-center justify-between'> |
| | | <div className='flex items-center '> |
| | | <div className='mr-3 rounded-lg bg-indigo-50 p-1'> |
| | | <Icon className='h-4 w-4 text-indigo-600' /> |
| | | <div className='mr-3 p-1 bg-indigo-50 rounded-lg'> |
| | | <Icon className='w-4 h-4 text-indigo-600' /> |
| | | </div> |
| | | <div className='text-sm font-medium leading-5 text-gray-900'>{text}</div> |
| | | <div className='leading-5 text-sm font-medium text-gray-900'>{text}</div> |
| | | </div> |
| | | <Radio isChecked={isChecked} /> |
| | | </div> |
| | | <div className='ml-9 text-xs font-normal leading-[18px] text-gray-500'>{description}</div> |
| | | <div className='ml-9 leading-[18px] text-xs font-normal text-gray-500'>{description}</div> |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | <> |
| | | <div className='my-4 h-[1px] bg-gray-100'></div> |
| | | <div |
| | | className={cn(isAgent ? 'group cursor-pointer hover:bg-primary-50' : 'opacity-30', 'rounded-xl bg-gray-50 p-3 pr-4 ')} |
| | | className={cn(isAgent ? 'group cursor-pointer hover:bg-primary-50' : 'opacity-30', 'p-3 pr-4 rounded-xl bg-gray-50 ')} |
| | | onClick={() => { |
| | | if (isAgent) { |
| | | setOpen(false) |
| | |
| | | > |
| | | <div className='flex items-center justify-between'> |
| | | <div className='flex items-center '> |
| | | <div className='mr-3 rounded-lg bg-gray-200 p-1 group-hover:bg-white'> |
| | | <Settings04 className='h-4 w-4 text-gray-600 group-hover:text-[#155EEF]' /> |
| | | <div className='mr-3 p-1 bg-gray-200 group-hover:bg-white rounded-lg'> |
| | | <Settings04 className='w-4 h-4 text-gray-600 group-hover:text-[#155EEF]' /> |
| | | </div> |
| | | <div className='text-sm font-medium leading-5 text-gray-900 group-hover:text-[#155EEF]'>{t('appDebug.agent.setting.name')}</div> |
| | | <div className='leading-5 text-sm font-medium text-gray-900 group-hover:text-[#155EEF]'>{t('appDebug.agent.setting.name')}</div> |
| | | </div> |
| | | <ArrowUpRight className='h-4 w-4 text-gray-500 group-hover:text-[#155EEF]' /> |
| | | <ArrowUpRight className='w-4 h-4 text-gray-500 group-hover:text-[#155EEF]' /> |
| | | </div> |
| | | <div className='ml-9 text-xs font-normal leading-[18px] text-gray-500'>{t('appDebug.agent.setting.description')}</div> |
| | | <div className='ml-9 leading-[18px] text-xs font-normal text-gray-500'>{t('appDebug.agent.setting.description')}</div> |
| | | </div> |
| | | </> |
| | | ) |
| | |
| | | }} |
| | | > |
| | | <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> |
| | | <div className={cn(open && 'bg-gray-50', 'flex h-8 cursor-pointer select-none items-center space-x-1 rounded-lg border border-black/5 px-3 text-indigo-600')}> |
| | | {isAgent ? <BubbleText className='h-3 w-3' /> : <CuteRobot className='h-3 w-3' />} |
| | | <div className={cn(open && 'bg-gray-50', 'flex items-center h-8 px-3 border border-black/5 rounded-lg cursor-pointer select-none space-x-1 text-indigo-600')}> |
| | | {isAgent ? <BubbleText className='w-3 h-3' /> : <CuteRobot className='w-3 h-3' />} |
| | | <div className='text-xs font-medium'>{t(`appDebug.assistantType.${isAgent ? 'agentAssistant' : 'chatAssistant'}.name`)}</div> |
| | | <RiArrowDownSLine className='h-3 w-3' /> |
| | | <RiArrowDownSLine className='w-3 h-3' /> |
| | | </div> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent style={{ zIndex: 1000 }}> |
| | | <div className='relative left-0.5 w-[480px] rounded-xl border border-black/8 bg-white p-6 shadow-lg'> |
| | | <div className='mb-2 text-sm font-semibold leading-5 text-gray-900'>{t('appDebug.assistantType.name')}</div> |
| | | <div className='relative left-0.5 p-6 bg-white border border-black/8 shadow-lg rounded-xl w-[480px]'> |
| | | <div className='mb-2 leading-5 text-sm font-semibold text-gray-900'>{t('appDebug.assistantType.name')}</div> |
| | | <SelectItem |
| | | Icon={BubbleText} |
| | | value='chat' |
| | |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiSparklingFill, |
| | | } from '@remixicon/react' |
| | | import Button from '@/app/components/base/button' |
| | | import { Generator } from '@/app/components/base/icons/src/vender/other' |
| | | |
| | | export type IAutomaticBtnProps = { |
| | | onClick: () => void |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <Button variant='secondary-accent' size='small' onClick={onClick}> |
| | | <RiSparklingFill className='mr-1 h-3.5 w-3.5' /> |
| | | <span className=''>{t('appDebug.operation.automatic')}</span> |
| | | </Button> |
| | | <div className='flex space-x-1 items-center !h-8 cursor-pointer' |
| | | onClick={onClick} |
| | | > |
| | | <Generator className='w-3.5 h-3.5 text-indigo-600' /> |
| | | <span className='text-xs font-semibold text-indigo-600'>{t('appDebug.operation.automatic')}</span> |
| | | </div> |
| | | ) |
| | | } |
| | | export default React.memo(AutomaticBtn) |
| | |
| | | }> = ({ Icon, text, onClick }) => { |
| | | return ( |
| | | <div |
| | | className='mr-1 mt-2 flex h-7 shrink-0 cursor-pointer items-center rounded-lg bg-components-button-secondary-bg px-2' |
| | | className='mt-2 mr-1 shrink-0 flex h-7 items-center px-2 bg-gray-100 rounded-lg cursor-pointer' |
| | | onClick={onClick} |
| | | > |
| | | <Icon className='h-4 w-4 text-text-tertiary'></Icon> |
| | | <div className='ml-1 text-xs font-medium text-text-secondary'>{text}</div> |
| | | <Icon className='w-4 h-4 text-gray-500'></Icon> |
| | | <div className='ml-1 text-xs font-medium text-gray-700'>{text}</div> |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | const [res, setRes] = React.useState<AutomaticRes | null>(null) |
| | | |
| | | const renderLoading = ( |
| | | <div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3'> |
| | | <div className='w-0 grow flex flex-col items-center justify-center h-full space-y-3'> |
| | | <Loading /> |
| | | <div className='text-[13px] text-text-tertiary'>{t('appDebug.generate.loading')}</div> |
| | | <div className='text-[13px] text-gray-400'>{t('appDebug.generate.loading')}</div> |
| | | </div> |
| | | ) |
| | | |
| | | const renderNoData = ( |
| | | <div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'> |
| | | <Generator className='h-14 w-14 text-text-tertiary' /> |
| | | <div className='text-center text-[13px] font-normal leading-5 text-text-tertiary'> |
| | | <div className='w-0 grow flex flex-col items-center px-8 justify-center h-full space-y-3'> |
| | | <Generator className='w-14 h-14 text-gray-300' /> |
| | | <div className='leading-5 text-center text-[13px] font-normal text-gray-400'> |
| | | <div>{t('appDebug.generate.noDataLine1')}</div> |
| | | <div>{t('appDebug.generate.noDataLine2')}</div> |
| | | </div> |
| | |
| | | <Modal |
| | | isShow={isShow} |
| | | onClose={onClose} |
| | | className='min-w-[1140px] !p-0' |
| | | className='!p-0 min-w-[1140px]' |
| | | closable |
| | | > |
| | | <div className='flex h-[680px] flex-wrap'> |
| | | <div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6'> |
| | | <div className='w-[570px] shrink-0 p-6 h-full overflow-y-auto border-r border-gray-100'> |
| | | <div className='mb-8'> |
| | | <div className={`text-lg font-bold leading-[28px] ${s.textGradient}`}>{t('appDebug.generate.title')}</div> |
| | | <div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.generate.description')}</div> |
| | | <div className={`leading-[28px] text-lg font-bold ${s.textGradient}`}>{t('appDebug.generate.title')}</div> |
| | | <div className='mt-1 text-[13px] font-normal text-gray-500'>{t('appDebug.generate.description')}</div> |
| | | </div> |
| | | <div className='mb-8 flex items-center'> |
| | | <div className='flex items-center mb-8'> |
| | | <ModelIcon |
| | | className='mr-1.5 shrink-0 ' |
| | | className='shrink-0 mr-1.5 ' |
| | | provider={currentProvider} |
| | | modelName={currentModel?.model} |
| | | /> |
| | |
| | | </div> |
| | | <div > |
| | | <div className='flex items-center'> |
| | | <div className='mr-3 shrink-0 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{t('appDebug.generate.tryIt')}</div> |
| | | <div className='h-px grow' style={{ |
| | | <div className='mr-3 shrink-0 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>{t('appDebug.generate.tryIt')}</div> |
| | | <div className='grow h-px' style={{ |
| | | background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))', |
| | | }}></div> |
| | | </div> |
| | |
| | | {/* inputs */} |
| | | <div className='mt-6'> |
| | | <div className='text-[0px]'> |
| | | <div className='mb-2 text-sm font-medium leading-5 text-text-primary'>{t('appDebug.generate.instruction')}</div> |
| | | <div className='mb-2 leading-5 text-sm font-medium text-gray-900'>{t('appDebug.generate.instruction')}</div> |
| | | <Textarea |
| | | className="h-[200px] resize-none" |
| | | placeholder={t('appDebug.generate.instructionPlaceHolder') as string} |
| | |
| | | onClick={onGenerate} |
| | | disabled={isLoading} |
| | | > |
| | | <Generator className='h-4 w-4 text-white' /> |
| | | <Generator className='w-4 h-4 text-white' /> |
| | | <span className='text-xs font-semibold text-white'>{t('appDebug.generate.generate')}</span> |
| | | </Button> |
| | | </div> |
| | |
| | | </div> |
| | | |
| | | {(!isLoading && res) && ( |
| | | <div className='h-full w-0 grow p-6 pb-0'> |
| | | <div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div> |
| | | <div className='w-0 grow p-6 pb-0 h-full'> |
| | | <div className='shrink-0 mb-3 leading-[160%] text-base font-semibold text-gray-800'>{t('appDebug.generate.resTitle')}</div> |
| | | <div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}> |
| | | <ConfigPrompt |
| | | mode={mode} |
| | |
| | | <div className='mt-7'> |
| | | <GroupName name={t('appDebug.feature.groupChat.title')} /> |
| | | <div |
| | | className='mb-1 rounded-xl border-l-[0.5px] border-t-[0.5px] border-effects-highlight bg-background-section-burn p-3' |
| | | className='mb-1 p-3 border-t-[0.5px] border-l-[0.5px] border-effects-highlight rounded-xl bg-background-section-burn' |
| | | > |
| | | <div className='mb-2 flex items-center gap-2'> |
| | | <div className='shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs'> |
| | | <LoveMessage className='h-4 w-4 text-text-primary-on-surface' /> |
| | | <div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-blue-light-blue-light-500'> |
| | | <LoveMessage className='w-4 h-4 text-text-primary-on-surface' /> |
| | | </div> |
| | | <div className='system-sm-semibold flex grow items-center text-text-secondary'> |
| | | <div className='grow flex items-center text-text-secondary system-sm-semibold'> |
| | | {t('appDebug.feature.conversationOpener.title')} |
| | | </div> |
| | | </div> |
| | | <div className='system-xs-regular min-h-8 text-text-tertiary'>{res.opening_statement}</div> |
| | | <div className='min-h-8 text-text-tertiary system-xs-regular'>{res.opening_statement}</div> |
| | | </div> |
| | | </div> |
| | | )} |
| | |
| | | )} |
| | | </div> |
| | | |
| | | <div className='flex justify-end bg-background-default py-4'> |
| | | <div className='flex justify-end py-4 bg-white'> |
| | | <Button onClick={onClose}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' className='ml-2' onClick={() => { |
| | | setShowConfirmOverwrite(true) |
| | |
| | | const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false) |
| | | |
| | | const renderLoading = ( |
| | | <div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3'> |
| | | <div className='w-0 grow flex flex-col items-center justify-center h-full space-y-3'> |
| | | <Loading /> |
| | | <div className='text-[13px] text-gray-400'>{t('appDebug.codegen.loading')}</div> |
| | | </div> |
| | | ) |
| | | const renderNoData = ( |
| | | <div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'> |
| | | <Generator className='h-14 w-14 text-gray-300' /> |
| | | <div className='text-center text-[13px] font-normal leading-5 text-gray-400'> |
| | | <div className='w-0 grow flex flex-col items-center px-8 justify-center h-full space-y-3'> |
| | | <Generator className='w-14 h-14 text-gray-300' /> |
| | | <div className='leading-5 text-center text-[13px] font-normal text-gray-400'> |
| | | <div>{t('appDebug.codegen.noDataLine1')}</div> |
| | | <div>{t('appDebug.codegen.noDataLine2')}</div> |
| | | </div> |
| | |
| | | <Modal |
| | | isShow={isShow} |
| | | onClose={onClose} |
| | | className='min-w-[1140px] !p-0' |
| | | className='!p-0 min-w-[1140px]' |
| | | closable |
| | | > |
| | | <div className='relative flex h-[680px] flex-wrap'> |
| | | <div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-gray-100 p-8'> |
| | | <div className='w-[570px] shrink-0 p-8 h-full overflow-y-auto border-r border-gray-100'> |
| | | <div className='mb-8'> |
| | | <div className={'text-lg font-bold leading-[28px]'}>{t('appDebug.codegen.title')}</div> |
| | | <div className={'leading-[28px] text-lg font-bold'}>{t('appDebug.codegen.title')}</div> |
| | | <div className='mt-1 text-[13px] font-normal text-gray-500'>{t('appDebug.codegen.description')}</div> |
| | | </div> |
| | | <div className='flex items-center'> |
| | | <ModelIcon |
| | | className='mr-1.5 shrink-0' |
| | | className='shrink-0 mr-1.5' |
| | | provider={currentProvider} |
| | | modelName={currentModel?.model} |
| | | /> |
| | |
| | | </div> |
| | | <div className='mt-6'> |
| | | <div className='text-[0px]'> |
| | | <div className='mb-2 text-sm font-medium leading-5 text-gray-900'>{t('appDebug.codegen.instruction')}</div> |
| | | <div className='mb-2 leading-5 text-sm font-medium text-gray-900'>{t('appDebug.codegen.instruction')}</div> |
| | | <textarea |
| | | className="h-[200px] w-full overflow-y-auto rounded-lg bg-gray-50 px-3 py-2 text-sm" |
| | | className="w-full h-[200px] overflow-y-auto px-3 py-2 text-sm bg-gray-50 rounded-lg" |
| | | placeholder={t('appDebug.codegen.instructionPlaceholder') || ''} |
| | | value={instruction} |
| | | onChange={e => setInstruction(e.target.value)} |
| | |
| | | onClick={onGenerate} |
| | | disabled={isLoading} |
| | | > |
| | | <Generator className='h-4 w-4 text-white' /> |
| | | <Generator className='w-4 h-4 text-white' /> |
| | | <span className='text-xs font-semibold text-white'>{t('appDebug.codegen.generate')}</span> |
| | | </Button> |
| | | </div> |
| | |
| | | {isLoading && renderLoading} |
| | | {!isLoading && !res && renderNoData} |
| | | {(!isLoading && res) && ( |
| | | <div className='h-full w-0 grow p-6 pb-0'> |
| | | <div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-gray-800'>{t('appDebug.codegen.resTitle')}</div> |
| | | <div className='w-0 grow p-6 pb-0 h-full'> |
| | | <div className='shrink-0 mb-3 leading-[160%] text-base font-semibold text-gray-800'>{t('appDebug.codegen.resTitle')}</div> |
| | | <div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}> |
| | | <ConfigPrompt |
| | | mode={mode} |
| | |
| | | {res?.code && ( |
| | | <div className='mt-4'> |
| | | <h3 className='mb-2 text-sm font-medium text-gray-900'>{t('appDebug.codegen.generatedCode')}</h3> |
| | | <pre className='overflow-x-auto rounded-lg bg-gray-50 p-4'> |
| | | <pre className='p-4 bg-gray-50 rounded-lg overflow-x-auto'> |
| | | <code className={`language-${res.language}`}> |
| | | {res.code} |
| | | </code> |
| | |
| | | </div> |
| | | )} |
| | | {res?.error && ( |
| | | <div className='mt-4 rounded-lg bg-red-50 p-4'> |
| | | <div className='mt-4 p-4 bg-red-50 rounded-lg'> |
| | | <p className='text-sm text-red-600'>{res.error}</p> |
| | | </div> |
| | | )} |
| | |
| | | )} |
| | | </div> |
| | | |
| | | <div className='flex justify-end bg-white py-4'> |
| | | <div className='flex justify-end py-4 bg-white'> |
| | | <Button onClick={onClose}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' className='ml-2' onClick={() => { |
| | | setShowConfirmOverwrite(true) |
| | |
| | | return null |
| | | |
| | | return ( |
| | | <div className='mt-2 flex items-center gap-2 rounded-xl border-l-[0.5px] border-t-[0.5px] bg-background-section-burn p-2'> |
| | | <div className='mt-2 flex items-center gap-2 p-2 rounded-xl border-t-[0.5px] border-l-[0.5px] bg-background-section-burn'> |
| | | <div className='shrink-0 p-1'> |
| | | <div className='rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-indigo-indigo-600 p-1 shadow-xs'> |
| | | <Document className='h-4 w-4 text-text-primary-on-surface' /> |
| | | <div className='p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-indigo-indigo-600'> |
| | | <Document className='w-4 h-4 text-text-primary-on-surface' /> |
| | | </div> |
| | | </div> |
| | | <div className='flex grow items-center'> |
| | | <div className='system-sm-semibold mr-1 text-text-secondary'>{t('appDebug.feature.documentUpload.title')}</div> |
| | | <div className='grow flex items-center'> |
| | | <div className='mr-1 text-text-secondary system-sm-semibold'>{t('appDebug.feature.documentUpload.title')}</div> |
| | | <Tooltip |
| | | popupContent={ |
| | | <div className='w-[180px]' > |
| | |
| | | } |
| | | /> |
| | | </div> |
| | | <div className='flex shrink-0 items-center'> |
| | | <div className='ml-1 mr-3 h-3.5 w-[1px] bg-divider-subtle'></div> |
| | | <div className='shrink-0 flex items-center'> |
| | | <div className='ml-1 mr-3 w-[1px] h-3.5 bg-divider-subtle'></div> |
| | | <Switch |
| | | defaultValue={isDocumentEnabled} |
| | | onChange={handleChange} |
| | |
| | | import ConfigContext from '@/context/debug-configuration' |
| | | import ConfigPrompt from '@/app/components/app/configuration/config-prompt' |
| | | import ConfigVar from '@/app/components/app/configuration/config-var' |
| | | import type { ModelConfig, PromptVariable } from '@/models/debug' |
| | | import { type ModelConfig, type PromptVariable } from '@/models/debug' |
| | | import type { AppType } from '@/types/app' |
| | | import { ModelModeType } from '@/types/app' |
| | | |
| | |
| | | return ( |
| | | <> |
| | | <div |
| | | className="relative h-0 grow overflow-y-auto px-6 pb-[50px]" |
| | | className="grow h-0 relative px-6 pb-[50px] overflow-y-auto" |
| | | > |
| | | {/* Template */} |
| | | <ConfigPrompt |
| | |
| | | const ContrlBtnGroup: FC<IContrlBtnGroupProps> = ({ onSave, onReset }) => { |
| | | const { t } = useTranslation() |
| | | return ( |
| | | <div className="fixed bottom-0 left-[224px] h-[64px] w-[519px]"> |
| | | <div className={`${s.ctrlBtn} flex h-full items-center gap-2 bg-white pl-4`}> |
| | | <div className="fixed left-[224px] bottom-0 w-[519px] h-[64px]"> |
| | | <div className={`${s.ctrlBtn} flex items-center h-full pl-4 gap-2 bg-white`}> |
| | | <Button variant='primary' onClick={onSave}>{t('appDebug.operation.applyConfig')}</Button> |
| | | <Button onClick={onReset}>{t('appDebug.operation.resetConfig')}</Button> |
| | | </div> |
| | |
| | | <div |
| | | className={ |
| | | cn(className, s.card, |
| | | 'relative flex cursor-pointer items-center rounded-xl border border-gray-200 bg-white px-3 py-2.5') |
| | | 'relative flex items-center rounded-xl px-3 py-2.5 bg-white border border-gray-200 cursor-pointer') |
| | | }> |
| | | <div className='flex items-center space-x-2'> |
| | | <div className={cn(!config.embedding_available && 'opacity-50')}> |
| | | <TypeIcon type="upload_file" /> |
| | | </div> |
| | | <div> |
| | | <div className='mr-1 flex w-[160px] items-center'> |
| | | <div className={cn('overflow-hidden text-ellipsis whitespace-nowrap text-[13px] font-medium leading-[18px] text-gray-800', !config.embedding_available && 'opacity-50')}>{config.name}</div> |
| | | <div className='flex items-center w-[160px] mr-1'> |
| | | <div className={cn('text-[13px] leading-[18px] font-medium text-gray-800 overflow-hidden text-ellipsis whitespace-nowrap', !config.embedding_available && 'opacity-50')}>{config.name}</div> |
| | | {!config.embedding_available && ( |
| | | <Tooltip |
| | | popupContent={t('dataset.unavailableTip')} |
| | | > |
| | | <span className='inline-flex shrink-0 whitespace-nowrap rounded-md border border-gray-200 px-1 text-xs font-normal leading-[18px] text-gray-500'>{t('dataset.unavailable')}</span> |
| | | <span className='shrink-0 inline-flex whitespace-nowrap px-1 border border-gray-200 rounded-md text-gray-500 text-xs font-normal leading-[18px]'>{t('dataset.unavailable')}</span> |
| | | </Tooltip> |
| | | )} |
| | | </div> |
| | | <div className={cn('flex max-w-[150px] text-xs text-gray-500', !config.embedding_available && 'opacity-50')}> |
| | | <div className={cn('max-w-[150px] flex text-xs text-gray-500', !config.embedding_available && 'opacity-50')}> |
| | | {formatNumber(config.word_count)} {t('appDebug.feature.dataSet.words')} · {formatNumber(config.document_count)} {t('appDebug.feature.dataSet.textBlocks')} |
| | | </div> |
| | | </div> |
| | |
| | | import FileIcon from '@/app/components/base/file-icon' |
| | | import { Folder } from '@/app/components/base/icons/src/vender/solid/files' |
| | | import { Globe06 } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' |
| | | import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' |
| | | import Drawer from '@/app/components/base/drawer' |
| | | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' |
| | | import Badge from '@/app/components/base/badge' |
| | | import { useKnowledge } from '@/hooks/use-knowledge' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type ItemProps = { |
| | | className?: string |
| | |
| | | setShowSettingsModal(false) |
| | | } |
| | | |
| | | const [isDeleting, setIsDeleting] = useState(false) |
| | | |
| | | return ( |
| | | <div className={cn( |
| | | 'group relative mb-1 flex h-10 w-full cursor-pointer items-center justify-between rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2 last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover', |
| | | isDeleting && 'border-state-destructive-border hover:bg-state-destructive-hover', |
| | | )}> |
| | | <div className='flex w-0 grow items-center space-x-1.5'> |
| | | <div className='group relative flex items-center mb-1 last-of-type:mb-0 pl-2.5 py-2 pr-3 w-full bg-white rounded-lg border-[0.5px] border-gray-200 shadow-xs'> |
| | | { |
| | | config.data_source_type === DataSourceType.FILE && ( |
| | | <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#E0EAFF] bg-[#F5F8FF]'> |
| | | <Folder className='h-4 w-4 text-[#444CE7]' /> |
| | | <div className='shrink-0 flex items-center justify-center mr-2 w-6 h-6 bg-[#F5F8FF] rounded-md border-[0.5px] border-[#E0EAFF]'> |
| | | <Folder className='w-4 h-4 text-[#444CE7]' /> |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | config.data_source_type === DataSourceType.NOTION && ( |
| | | <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#EAECF5]'> |
| | | <FileIcon type='notion' className='h-4 w-4' /> |
| | | <div className='shrink-0 flex items-center justify-center mr-2 w-6 h-6 rounded-md border-[0.5px] border-[#EAECF5]'> |
| | | <FileIcon type='notion' className='w-4 h-4' /> |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | config.data_source_type === DataSourceType.WEB && ( |
| | | <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-blue-100 bg-[#F5FAFF]'> |
| | | <Globe06 className='h-4 w-4 text-blue-600' /> |
| | | <div className='shrink-0 flex items-center justify-center mr-2 w-6 h-6 bg-[#F5FAFF] border-[0.5px] border-blue-100 rounded-md'> |
| | | <Globe06 className='w-4 h-4 text-blue-600' /> |
| | | </div> |
| | | ) |
| | | } |
| | | <div className='system-sm-medium w-0 grow truncate text-text-secondary' title={config.name}>{config.name}</div> |
| | | </div> |
| | | <div className='ml-2 hidden shrink-0 items-center space-x-1 group-hover:flex'> |
| | | { |
| | | editable && <ActionButton |
| | | onClick={(e) => { |
| | | e.stopPropagation() |
| | | setShowSettingsModal(true) |
| | | }} |
| | | > |
| | | <RiEditLine className='h-4 w-4 shrink-0 text-text-tertiary' /> |
| | | </ActionButton> |
| | | } |
| | | <ActionButton |
| | | onClick={() => onRemove(config.id)} |
| | | state={isDeleting ? ActionButtonState.Destructive : ActionButtonState.Default} |
| | | onMouseEnter={() => setIsDeleting(true)} |
| | | onMouseLeave={() => setIsDeleting(false)} |
| | | > |
| | | <RiDeleteBinLine className={cn('h-4 w-4 shrink-0 text-text-tertiary', isDeleting && 'text-text-destructive')} /> |
| | | </ActionButton> |
| | | </div> |
| | | { |
| | | config.indexing_technique && <Badge |
| | | className='shrink-0 group-hover:hidden' |
| | | <div className='grow'> |
| | | <div className='flex items-center h-[18px]'> |
| | | <div className='grow text-[13px] font-medium text-gray-800 truncate' title={config.name}>{config.name}</div> |
| | | {config.provider === 'external' |
| | | ? <Badge text={t('dataset.externalTag') as string} /> |
| | | : <Badge |
| | | text={formatIndexingTechniqueAndMethod(config.indexing_technique, config.retrieval_model_dict?.search_method)} |
| | | /> |
| | | } |
| | | />} |
| | | </div> |
| | | </div> |
| | | <div className='hidden rounded-lg group-hover:flex items-center justify-end absolute right-0 top-0 bottom-0 pr-2 w-[124px] bg-gradient-to-r from-white/50 to-white to-50%'> |
| | | { |
| | | config.provider === 'external' && <Badge |
| | | className='shrink-0 group-hover:hidden' |
| | | text={t('dataset.externalTag') as string} |
| | | /> |
| | | editable && <div |
| | | className='flex items-center justify-center mr-1 w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' |
| | | onClick={() => setShowSettingsModal(true)} |
| | | > |
| | | <RiEditLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | } |
| | | <Drawer isOpen={showSettingsModal} onClose={() => setShowSettingsModal(false)} footer={null} mask={isMobile} panelClassName='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'> |
| | | <div |
| | | className='group/action flex items-center justify-center w-6 h-6 hover:bg-[#FEE4E2] rounded-md cursor-pointer' |
| | | onClick={() => onRemove(config.id)} |
| | | > |
| | | <RiDeleteBinLine className='w-4 h-4 text-gray-500 group-hover/action:text-[#D92D20]' /> |
| | | </div> |
| | | </div> |
| | | <Drawer isOpen={showSettingsModal} onClose={() => setShowSettingsModal(false)} footer={null} mask={isMobile} panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'> |
| | | <SettingsModal |
| | | currentDataset={config} |
| | | onCancel={() => setShowSettingsModal(false)} |
| | |
| | | const currItem = options.find(item => item.value === value) |
| | | const notSetVar = !currItem |
| | | return ( |
| | | <div className={cn(notSetVar ? 'rounded-bl-xl rounded-br-xl border-[#FEF0C7] bg-[#FEF0C7]' : 'border-components-panel-border-subtle', 'flex h-12 items-center justify-between border-t px-3 ')}> |
| | | <div className='flex shrink-0 items-center space-x-1'> |
| | | <div className={cn(notSetVar ? 'rounded-bl-xl rounded-br-xl bg-[#FEF0C7] border-[#FEF0C7]' : 'border-gray-200', 'flex justify-between items-center h-12 px-3 border-t ')}> |
| | | <div className='flex items-center space-x-1 shrink-0'> |
| | | <div className='p-1'> |
| | | <BracketsX className='h-4 w-4 text-text-accent' /> |
| | | <BracketsX className='w-4 h-4 text-primary-500' /> |
| | | </div> |
| | | <div className='mr-1 text-sm font-medium text-text-secondary'>{t('appDebug.feature.dataSet.queryVariable.title')}</div> |
| | | <div className='mr-1 text-sm font-medium text-gray-800'>{t('appDebug.feature.dataSet.queryVariable.title')}</div> |
| | | <Tooltip |
| | | popupContent={ |
| | | <div className='w-[180px]'> |
New file |
| | |
| | | .trigger:hover .dropdownIcon { |
| | | color: #98A2B3; |
| | | } |
| | |
| | | import React, { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { ChevronDownIcon } from '@heroicons/react/24/outline' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import { |
| | | PortalToFollowElem, |
| | |
| | | } |
| | | |
| | | const VarItem: FC<{ item: Option }> = ({ item }) => ( |
| | | <div className='flex h-[18px] items-center space-x-1 rounded bg-[#EFF8FF] px-1'> |
| | | <div className='flex items-center h-[18px] px-1 bg-[#EFF8FF] rounded space-x-1'> |
| | | <IconTypeIcon type={item.type as IInputTypeIconProps['type']} className='text-[#1570EF]' /> |
| | | <div className='flex text-xs font-medium text-[#1570EF]'> |
| | | <span className='opacity-60'>{'{{'}</span> |
| | |
| | | > |
| | | <PortalToFollowElemTrigger className={cn(triggerClassName)} onClick={() => setOpen(v => !v)}> |
| | | <div className={cn( |
| | | s.trigger, |
| | | className, |
| | | notSetVar ? 'border-[#FEDF89] bg-[#FFFCF5] text-[#DC6803]' : ' border-components-button-secondary-border text-text-accent hover:bg-components-button-secondary-bg', |
| | | open ? 'bg-components-button-secondary-bg' : 'bg-transparent', |
| | | notSetVar ? 'bg-[#FFFCF5] border-[#FEDF89] text-[#DC6803]' : ' hover:bg-gray-50 border-gray-200 text-primary-600', |
| | | open ? 'bg-gray-50' : 'bg-white', |
| | | ` |
| | | flex h-8 cursor-pointer items-center justify-center space-x-1 rounded-lg border px-2 text-[13px] |
| | | font-medium shadow-xs |
| | | flex items-center h-8 justify-center px-2 space-x-1 rounded-lg border shadow-xs cursor-pointer |
| | | text-[13px] font-medium |
| | | `)}> |
| | | <div> |
| | | {value |
| | |
| | | {notSelectedVarTip || t('appDebug.feature.dataSet.queryVariable.choosePlaceholder')} |
| | | </div>)} |
| | | </div> |
| | | <ChevronDownIcon className={cn(open && 'rotate-180 text-text-tertiary', 'h-3.5 w-3.5')} /> |
| | | <ChevronDownIcon className={cn(s.dropdownIcon, open && 'rotate-180 text-[#98A2B3]', 'w-3.5 h-3.5')} /> |
| | | </div> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent style={{ zIndex: 1000 }}> |
| | | {options.length > 0 |
| | | ? (<div className='max-h-[50vh] w-[240px] overflow-y-auto rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg'> |
| | | ? (<div className='w-[240px] max-h-[50vh] overflow-y-auto p-1 border bg-white border-gray-200 rounded-lg shadow-lg'> |
| | | {options.map(({ name, value, type }, index) => ( |
| | | <div |
| | | key={index} |
| | | className='flex cursor-pointer rounded-lg px-3 py-1 hover:bg-state-base-hover' |
| | | className='px-3 py-1 flex rounded-lg hover:bg-gray-50 cursor-pointer' |
| | | onClick={() => { |
| | | onChange(value) |
| | | setOpen(false) |
| | |
| | | ))} |
| | | </div>) |
| | | : ( |
| | | <div className='w-[240px] rounded-lg border border-components-panel-border bg-components-panel-bg p-6 shadow-lg'> |
| | | <div className='mb-1 text-sm font-medium text-text-secondary'>{t('appDebug.feature.dataSet.queryVariable.noVar')}</div> |
| | | <div className='text-xs leading-normal text-text-tertiary'>{t('appDebug.feature.dataSet.queryVariable.noVarTip')}</div> |
| | | <div className='w-[240px] p-6 bg-white border border-gray-200 rounded-lg shadow-lg'> |
| | | <div className='mb-1 text-sm font-medium text-gray-700'>{t('appDebug.feature.dataSet.queryVariable.noVar')}</div> |
| | | <div className='text-xs leading-normal text-gray-500'>{t('appDebug.feature.dataSet.queryVariable.noVarTip')}</div> |
| | | </div> |
| | | )} |
| | | |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useCallback, useMemo } from 'react' |
| | | import React, { useMemo } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { intersectionBy } from 'lodash-es' |
| | | import { useContext } from 'use-context-selector' |
| | | import produce from 'immer' |
| | | import { v4 as uuid4 } from 'uuid' |
| | | import { useFormattingChangedDispatcher } from '../debug/hooks' |
| | | import FeaturePanel from '../base/feature-panel' |
| | | import OperationBtn from '../base/operation-btn' |
| | |
| | | import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' |
| | | import { useSelector as useAppContextSelector } from '@/context/app-context' |
| | | import { hasEditPermissionForDataset } from '@/utils/permission' |
| | | import MetadataFilter from '@/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter' |
| | | import type { |
| | | HandleAddCondition, |
| | | HandleRemoveCondition, |
| | | HandleToggleConditionLogicalOperator, |
| | | HandleUpdateCondition, |
| | | MetadataFilteringModeEnum, |
| | | } from '@/app/components/workflow/nodes/knowledge-retrieval/types' |
| | | import { |
| | | ComparisonOperator, |
| | | LogicalOperator, |
| | | MetadataFilteringVariableType, |
| | | } from '@/app/components/workflow/nodes/knowledge-retrieval/types' |
| | | |
| | | const Icon = ( |
| | | <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | | <path fillRule="evenodd" clipRule="evenodd" d="M12.6667 5.34368C12.6667 5.32614 12.6667 5.31738 12.6659 5.30147C12.6502 4.97229 12.3607 4.68295 12.0315 4.66737C12.0156 4.66662 12.0104 4.66662 12 4.66663H9.8391C9.30248 4.66662 8.85957 4.66661 8.49878 4.69609C8.12405 4.72671 7.77958 4.79242 7.45603 4.95728C6.95426 5.21294 6.54631 5.62089 6.29065 6.12265C6.12579 6.44621 6.06008 6.79068 6.02946 7.16541C5.99999 7.5262 5.99999 7.96911 6 8.50574V15.4942C5.99999 16.0308 5.99999 16.4737 6.02946 16.8345C6.06008 17.2092 6.12579 17.5537 6.29065 17.8773C6.54631 18.379 6.95426 18.787 7.45603 19.0426C7.77958 19.2075 8.12405 19.2732 8.49878 19.3038C8.85958 19.3333 9.30248 19.3333 9.83912 19.3333H14.1609C14.6975 19.3333 15.1404 19.3333 15.5012 19.3038C15.8759 19.2732 16.2204 19.2075 16.544 19.0426C17.0457 18.787 17.4537 18.379 17.7093 17.8773C17.8742 17.5537 17.9399 17.2092 17.9705 16.8345C18 16.4737 18 16.0308 18 15.4942V10.6666C18 10.6562 18 10.6511 17.9993 10.6352C17.9837 10.306 17.6943 10.0164 17.3651 10.0007C17.3492 9.99997 17.3405 9.99997 17.323 9.99997L14.3787 9.99997C14.2105 9.99999 14.0466 10 13.9078 9.98867C13.7555 9.97622 13.5756 9.94684 13.3947 9.85464C13.1438 9.72681 12.9398 9.52284 12.812 9.27195C12.7198 9.09101 12.6904 8.91118 12.678 8.75879C12.6666 8.62001 12.6666 8.45615 12.6667 8.2879L12.6667 5.34368ZM9.33333 12.6666C8.96514 12.6666 8.66667 12.9651 8.66667 13.3333C8.66667 13.7015 8.96514 14 9.33333 14H14.6667C15.0349 14 15.3333 13.7015 15.3333 13.3333C15.3333 12.9651 15.0349 12.6666 14.6667 12.6666H9.33333ZM9.33333 15.3333C8.96514 15.3333 8.66667 15.6318 8.66667 16C8.66667 16.3681 8.96514 16.6666 9.33333 16.6666H13.3333C13.7015 16.6666 14 16.3681 14 16C14 15.6318 13.7015 15.3333 13.3333 15.3333H9.33333Z" fill="#6938EF" /> |
| | | <path d="M16.6053 8.66662C16.8011 8.66662 16.8989 8.66663 16.9791 8.61747C17.0923 8.54806 17.16 8.38452 17.129 8.25538C17.107 8.16394 17.0432 8.10018 16.9155 7.97265L14.694 5.75111C14.5664 5.62345 14.5027 5.55962 14.4112 5.53764C14.2821 5.5066 14.1186 5.57429 14.0492 5.68752C14 5.7677 14 5.86557 14 6.06132L14 8.13327C14 8.31994 14 8.41328 14.0363 8.48459C14.0683 8.54731 14.1193 8.5983 14.182 8.63026C14.2533 8.66659 14.3466 8.66659 14.5333 8.66659L16.6053 8.66662Z" fill="#6938EF" /> |
| | | </svg> |
| | | ) |
| | | |
| | | const DatasetConfig: FC = () => { |
| | | const { t } = useTranslation() |
| | |
| | | showSelectDataSet, |
| | | isAgent, |
| | | datasetConfigs, |
| | | datasetConfigsRef, |
| | | setDatasetConfigs, |
| | | setRerankSettingModalOpen, |
| | | } = useContext(ConfigContext) |
| | |
| | | }) |
| | | }, [dataSet, userProfile?.id]) |
| | | |
| | | const metadataList = useMemo(() => { |
| | | return intersectionBy(...formattedDataset.filter((dataset) => { |
| | | return !!dataset.doc_metadata |
| | | }).map((dataset) => { |
| | | return dataset.doc_metadata! |
| | | }), 'name') |
| | | }, [formattedDataset]) |
| | | |
| | | const handleMetadataFilterModeChange = useCallback((newMode: MetadataFilteringModeEnum) => { |
| | | setDatasetConfigs(produce(datasetConfigsRef.current!, (draft) => { |
| | | draft.metadata_filtering_mode = newMode |
| | | })) |
| | | }, [setDatasetConfigs, datasetConfigsRef]) |
| | | |
| | | const handleAddCondition = useCallback<HandleAddCondition>(({ name, type }) => { |
| | | let operator: ComparisonOperator = ComparisonOperator.is |
| | | |
| | | if (type === MetadataFilteringVariableType.number) |
| | | operator = ComparisonOperator.equal |
| | | |
| | | const newCondition = { |
| | | id: uuid4(), |
| | | name, |
| | | comparison_operator: operator, |
| | | } |
| | | |
| | | const newInputs = produce(datasetConfigsRef.current!, (draft) => { |
| | | if (draft.metadata_filtering_conditions) { |
| | | draft.metadata_filtering_conditions.conditions.push(newCondition) |
| | | } |
| | | else { |
| | | draft.metadata_filtering_conditions = { |
| | | logical_operator: LogicalOperator.and, |
| | | conditions: [newCondition], |
| | | } |
| | | } |
| | | }) |
| | | setDatasetConfigs(newInputs) |
| | | }, [setDatasetConfigs, datasetConfigsRef]) |
| | | |
| | | const handleRemoveCondition = useCallback<HandleRemoveCondition>((id) => { |
| | | const conditions = datasetConfigsRef.current!.metadata_filtering_conditions?.conditions || [] |
| | | const index = conditions.findIndex(c => c.id === id) |
| | | const newInputs = produce(datasetConfigsRef.current!, (draft) => { |
| | | if (index > -1) |
| | | draft.metadata_filtering_conditions?.conditions.splice(index, 1) |
| | | }) |
| | | setDatasetConfigs(newInputs) |
| | | }, [setDatasetConfigs, datasetConfigsRef]) |
| | | |
| | | const handleUpdateCondition = useCallback<HandleUpdateCondition>((id, newCondition) => { |
| | | const conditions = datasetConfigsRef.current!.metadata_filtering_conditions?.conditions || [] |
| | | const index = conditions.findIndex(c => c.id === id) |
| | | const newInputs = produce(datasetConfigsRef.current!, (draft) => { |
| | | if (index > -1) |
| | | draft.metadata_filtering_conditions!.conditions[index] = newCondition |
| | | }) |
| | | setDatasetConfigs(newInputs) |
| | | }, [setDatasetConfigs, datasetConfigsRef]) |
| | | |
| | | const handleToggleConditionLogicalOperator = useCallback<HandleToggleConditionLogicalOperator>(() => { |
| | | const oldLogicalOperator = datasetConfigsRef.current!.metadata_filtering_conditions?.logical_operator |
| | | const newLogicalOperator = oldLogicalOperator === LogicalOperator.and ? LogicalOperator.or : LogicalOperator.and |
| | | const newInputs = produce(datasetConfigsRef.current!, (draft) => { |
| | | draft.metadata_filtering_conditions!.logical_operator = newLogicalOperator |
| | | }) |
| | | setDatasetConfigs(newInputs) |
| | | }, [setDatasetConfigs, datasetConfigsRef]) |
| | | |
| | | const handleMetadataModelChange = useCallback((model: { provider: string; modelId: string; mode?: string }) => { |
| | | const newInputs = produce(datasetConfigsRef.current!, (draft) => { |
| | | draft.metadata_model_config = { |
| | | provider: model.provider, |
| | | name: model.modelId, |
| | | mode: model.mode || 'chat', |
| | | completion_params: draft.metadata_model_config?.completion_params || { temperature: 0.7 }, |
| | | } |
| | | }) |
| | | setDatasetConfigs(newInputs) |
| | | }, [setDatasetConfigs, datasetConfigsRef]) |
| | | |
| | | const handleMetadataCompletionParamsChange = useCallback((newParams: Record<string, any>) => { |
| | | const newInputs = produce(datasetConfigsRef.current!, (draft) => { |
| | | draft.metadata_model_config = { |
| | | ...draft.metadata_model_config!, |
| | | completion_params: newParams, |
| | | } |
| | | }) |
| | | setDatasetConfigs(newInputs) |
| | | }, [setDatasetConfigs, datasetConfigsRef]) |
| | | |
| | | return ( |
| | | <FeaturePanel |
| | | className='mt-2' |
| | | headerIcon={Icon} |
| | | title={t('appDebug.feature.dataSet.title')} |
| | | headerRight={ |
| | | <div className='flex items-center gap-1'> |
| | |
| | | > |
| | | {hasData |
| | | ? ( |
| | | <div className='mt-1 flex flex-wrap justify-between px-3 pb-3'> |
| | | <div className='flex flex-wrap mt-1 px-3 pb-3 justify-between'> |
| | | {formattedDataset.map(item => ( |
| | | <CardItem |
| | | key={item.id} |
| | |
| | | ) |
| | | : ( |
| | | <div className='mt-1 px-3 pb-3'> |
| | | <div className='pb-1 pt-2 text-xs text-text-tertiary'>{t('appDebug.feature.dataSet.noData')}</div> |
| | | <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.feature.dataSet.noData')}</div> |
| | | </div> |
| | | )} |
| | | |
| | | <div className='border-t border-t-divider-subtle py-2'> |
| | | <MetadataFilter |
| | | metadataList={metadataList} |
| | | selectedDatasetsLoaded |
| | | metadataFilterMode={datasetConfigs.metadata_filtering_mode} |
| | | metadataFilteringConditions={datasetConfigs.metadata_filtering_conditions} |
| | | handleAddCondition={handleAddCondition} |
| | | handleMetadataFilterModeChange={handleMetadataFilterModeChange} |
| | | handleRemoveCondition={handleRemoveCondition} |
| | | handleToggleConditionLogicalOperator={handleToggleConditionLogicalOperator} |
| | | handleUpdateCondition={handleUpdateCondition} |
| | | metadataModelConfig={datasetConfigs.metadata_model_config} |
| | | handleMetadataModelChange={handleMetadataModelChange} |
| | | handleMetadataCompletionParamsChange={handleMetadataCompletionParamsChange} |
| | | isCommonVariable |
| | | availableCommonStringVars={promptVariablesToSelect.filter(item => item.type === MetadataFilteringVariableType.string || item.type === MetadataFilteringVariableType.select)} |
| | | availableCommonNumberVars={promptVariablesToSelect.filter(item => item.type === MetadataFilteringVariableType.number)} |
| | | /> |
| | | </div> |
| | | |
| | | {mode === AppType.completion && dataSet.length > 0 && ( |
| | | <ContextVar |
| | |
| | | import { useSelectedDatasetsMode } from '@/app/components/workflow/nodes/knowledge-retrieval/hooks' |
| | | import Switch from '@/app/components/base/switch' |
| | | import Toast from '@/app/components/base/toast' |
| | | import Divider from '@/app/components/base/divider' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | type Props = { |
| | | datasetConfigs: DatasetConfigs |
| | |
| | | onChange, |
| | | isInWorkflow, |
| | | singleRetrievalModelConfig: singleRetrievalConfig = {} as ModelConfig, |
| | | onSingleRetrievalModelChange = noop, |
| | | onSingleRetrievalModelParamsChange = noop, |
| | | onSingleRetrievalModelChange = () => { }, |
| | | onSingleRetrievalModelParamsChange = () => { }, |
| | | selectedDatasets = [], |
| | | }) => { |
| | | const { t } = useTranslation() |
| | |
| | | </div> |
| | | {type === RETRIEVE_TYPE.multiWay && ( |
| | | <> |
| | | <div className='my-2 flex h-6 items-center py-1'> |
| | | <div className='system-xs-semibold-uppercase mr-2 shrink-0 text-text-secondary'> |
| | | <div className='flex items-center my-2 py-1 h-6'> |
| | | <div className='shrink-0 mr-2 system-xs-semibold-uppercase text-text-secondary'> |
| | | {t('dataset.rerankSettings')} |
| | | </div> |
| | | <Divider bgStyle='gradient' className='mx-0 !h-px' /> |
| | | <div className='grow h-[1px] bg-gradient-to-l from-white to-[rgba(16,24,40,0.08)]'></div> |
| | | </div> |
| | | { |
| | | selectedDatasetsMode.inconsistentEmbeddingModel |
| | | && ( |
| | | <div className='system-xs-medium mt-4 text-text-warning'> |
| | | <div className='mt-4 system-xs-medium text-text-warning'> |
| | | {t('dataset.inconsistentEmbeddingModelTip')} |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | selectedDatasetsMode.mixtureInternalAndExternal && ( |
| | | <div className='system-xs-medium mt-4 text-text-warning'> |
| | | <div className='mt-4 system-xs-medium text-text-warning'> |
| | | {t('dataset.mixtureInternalAndExternalTip')} |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | selectedDatasetsMode.allExternal && ( |
| | | <div className='system-xs-medium mt-4 text-text-warning'> |
| | | <div className='mt-4 system-xs-medium text-text-warning'> |
| | | {t('dataset.allExternalTip')} |
| | | </div> |
| | | ) |
| | |
| | | { |
| | | selectedDatasetsMode.mixtureHighQualityAndEconomic |
| | | && ( |
| | | <div className='system-xs-medium mt-4 text-text-warning'> |
| | | <div className='mt-4 system-xs-medium text-text-warning'> |
| | | {t('dataset.mixtureHighQualityAndEconomicTip')} |
| | | </div> |
| | | ) |
| | |
| | | <div |
| | | key={option.value} |
| | | className={cn( |
| | | 'system-sm-medium flex h-8 w-[calc((100%-8px)/2)] cursor-pointer items-center justify-center rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg text-text-secondary', |
| | | 'flex items-center justify-center w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary', |
| | | selectedRerankMode === option.value && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary', |
| | | )} |
| | | onClick={() => handleRerankModeChange(option.value)} |
| | |
| | | /> |
| | | ) |
| | | } |
| | | <div className='system-sm-semibold ml-1 leading-[32px] text-text-secondary'>{t('common.modelProvider.rerankModel.key')}</div> |
| | | <div className='leading-[32px] ml-1 text-text-secondary system-sm-semibold'>{t('common.modelProvider.rerankModel.key')}</div> |
| | | <Tooltip |
| | | popupContent={ |
| | | <div className="w-[200px]"> |
| | |
| | | {isInWorkflow && type === RETRIEVE_TYPE.oneWay && ( |
| | | <div className='mt-4'> |
| | | <div className='flex items-center space-x-0.5'> |
| | | <div className='text-[13px] font-medium leading-[32px] text-text-primary'>{t('common.modelProvider.systemReasoningModel.key')}</div> |
| | | <div className='leading-[32px] text-[13px] font-medium text-gray-900'>{t('common.modelProvider.systemReasoningModel.key')}</div> |
| | | <Tooltip |
| | | popupContent={t('common.modelProvider.systemReasoningModel.tip')} |
| | | /> |
| | |
| | | provider={model?.provider} |
| | | completionParams={model?.completion_params} |
| | | modelId={model?.name} |
| | | setModel={onSingleRetrievalModelChange} |
| | | onCompletionParamsChange={onSingleRetrievalModelParamsChange} |
| | | setModel={onSingleRetrievalModelChange as any} |
| | | onCompletionParamsChange={onSingleRetrievalModelParamsChange as any} |
| | | hideDebugWithMultipleModel |
| | | debugWithMultipleModel={false} |
| | | /> |
| | |
| | | }} |
| | | disabled={disabled} |
| | | > |
| | | <RiEqualizer2Line className='mr-1 h-3.5 w-3.5' /> |
| | | <RiEqualizer2Line className='mr-1 w-3.5 h-3.5' /> |
| | | {t('dataset.retrievalSettings')} |
| | | </Button> |
| | | { |
| | |
| | | /> |
| | | |
| | | <div className='mt-6 flex justify-end'> |
| | | <Button className='mr-2 shrink-0' onClick={() => { |
| | | <Button className='mr-2 flex-shrink-0' onClick={() => { |
| | | setTempDataSetConfigs(datasetConfigs) |
| | | setRerankSettingModalOpen(false) |
| | | }}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' className='shrink-0' onClick={handleSave} >{t('common.operation.save')}</Button> |
| | | <Button variant='primary' className='flex-shrink-0' onClick={handleSave} >{t('common.operation.save')}</Button> |
| | | </div> |
| | | </Modal> |
| | | ) |
| | |
| | | import './weighted-score.css' |
| | | import Slider from '@/app/components/base/slider' |
| | | import cn from '@/utils/classnames' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | const formatNumber = (value: number) => { |
| | | if (value > 0 && value < 1) |
| | |
| | | } |
| | | const WeightedScore = ({ |
| | | value, |
| | | onChange = noop, |
| | | onChange = () => {}, |
| | | }: WeightedScoreProps) => { |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div> |
| | | <div className='space-x-3 rounded-lg border border-components-panel-border px-3 pb-2 pt-5'> |
| | | <div className='px-3 pt-5 pb-2 space-x-3 rounded-lg border border-components-panel-border'> |
| | | <Slider |
| | | className={cn('h-0.5 grow rounded-full !bg-util-colors-teal-teal-500')} |
| | | className={cn('grow h-0.5 !bg-util-colors-teal-teal-500 rounded-full')} |
| | | max={1.0} |
| | | min={0} |
| | | step={0.1} |
| | |
| | | onChange={v => onChange({ value: [v, (10 - v * 10) / 10] })} |
| | | trackClassName='weightedScoreSliderTrack' |
| | | /> |
| | | <div className='mt-3 flex justify-between'> |
| | | <div className='system-xs-semibold-uppercase flex w-[90px] shrink-0 items-center text-util-colors-blue-light-blue-light-500'> |
| | | <div className='flex justify-between mt-3'> |
| | | <div className='shrink-0 flex items-center w-[90px] system-xs-semibold-uppercase text-util-colors-blue-light-blue-light-500'> |
| | | <div className='mr-1 truncate uppercase' title={t('dataset.weightedScore.semantic') || ''}> |
| | | {t('dataset.weightedScore.semantic')} |
| | | </div> |
| | | {formatNumber(value.value[0])} |
| | | </div> |
| | | <div className='system-xs-semibold-uppercase flex w-[90px] shrink-0 items-center justify-end text-util-colors-teal-teal-500'> |
| | | <div className='shrink-0 flex items-center justify-end w-[90px] system-xs-semibold-uppercase text-util-colors-teal-teal-500'> |
| | | {formatNumber(value.value[1])} |
| | | <div className='ml-1 truncate uppercase' title={t('dataset.weightedScore.keyword') || ''}> |
| | | {t('dataset.weightedScore.keyword')} |
| | |
| | | import Link from 'next/link' |
| | | import produce from 'immer' |
| | | import TypeIcon from '../type-icon' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import Modal from '@/app/components/base/modal' |
| | | import type { DataSet } from '@/models/datasets' |
| | | import Button from '@/app/components/base/button' |
| | |
| | | import Loading from '@/app/components/base/loading' |
| | | import Badge from '@/app/components/base/badge' |
| | | import { useKnowledge } from '@/hooks/use-knowledge' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | export type ISelectDataSetProps = { |
| | | isShow: boolean |
| | |
| | | )} |
| | | |
| | | {(loaded && hasNoData) && ( |
| | | <div className='mt-6 flex h-[128px] items-center justify-center space-x-1 rounded-lg border text-[13px]' |
| | | <div className='flex items-center justify-center mt-6 rounded-lg space-x-1 h-[128px] text-[13px] border' |
| | | style={{ |
| | | background: 'rgba(0, 0, 0, 0.02)', |
| | | borderColor: 'rgba(0, 0, 0, 0.02', |
| | | }} |
| | | > |
| | | <span className='text-text-tertiary'>{t('appDebug.feature.dataSet.noDataSet')}</span> |
| | | <Link href={'/datasets/create'} className='font-normal text-text-accent'>{t('appDebug.feature.dataSet.toCreate')}</Link> |
| | | <span className='text-gray-500'>{t('appDebug.feature.dataSet.noDataSet')}</span> |
| | | <Link href="/datasets/create" className='font-normal text-[#155EEF]'>{t('appDebug.feature.dataSet.toCreate')}</Link> |
| | | </div> |
| | | )} |
| | | |
| | | {datasets && datasets?.length > 0 && ( |
| | | <> |
| | | <div ref={listRef} className='mt-7 max-h-[286px] space-y-1 overflow-y-auto'> |
| | | <div ref={listRef} className='mt-7 space-y-1 max-h-[286px] overflow-y-auto'> |
| | | {datasets.map(item => ( |
| | | <div |
| | | key={item.id} |
| | | className={cn( |
| | | 'flex h-10 cursor-pointer items-center justify-between rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2 shadow-xs hover:border-components-panel-border hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', |
| | | selected.some(i => i.id === item.id) && 'border-[1.5px] border-components-option-card-option-selected-border bg-state-accent-hover shadow-xs hover:border-components-option-card-option-selected-border hover:bg-state-accent-hover hover:shadow-xs', |
| | | !item.embedding_available && 'hover:border-components-panel-border-subtle hover:bg-components-panel-on-panel-item-bg hover:shadow-xs', |
| | | )} |
| | | className={cn(s.item, selected.some(i => i.id === item.id) && s.selected, 'flex justify-between items-center h-10 px-2 rounded-lg bg-white border border-gray-200 cursor-pointer', !item.embedding_available && s.disabled)} |
| | | onClick={() => { |
| | | if (!item.embedding_available) |
| | | return |
| | | toggleSelect(item) |
| | | }} |
| | | > |
| | | <div className='mr-1 flex items-center overflow-hidden'> |
| | | <div className={cn('mr-2', !item.embedding_available && 'opacity-30')}> |
| | | <div className='mr-1 flex items-center'> |
| | | <div className={cn('mr-2', !item.embedding_available && 'opacity-50')}> |
| | | <TypeIcon type="upload_file" size='md' /> |
| | | </div> |
| | | <div className={cn('max-w-[200px] truncate text-[13px] font-medium text-text-secondary', !item.embedding_available && '!max-w-[120px] opacity-30')}>{item.name}</div> |
| | | <div className={cn('max-w-[200px] text-[13px] font-medium text-gray-800 overflow-hidden text-ellipsis whitespace-nowrap', !item.embedding_available && 'opacity-50 !max-w-[120px]')}>{item.name}</div> |
| | | {!item.embedding_available && ( |
| | | <span className='ml-1 shrink-0 rounded-md border border-divider-deep px-1 text-xs font-normal leading-[18px] text-text-tertiary'>{t('dataset.unavailable')}</span> |
| | | <span className='ml-1 shrink-0 px-1 border border-gray-200 rounded-md text-gray-500 text-xs font-normal leading-[18px]'>{t('dataset.unavailable')}</span> |
| | | )} |
| | | </div> |
| | | { |
| | | item.indexing_technique && ( |
| | | <Badge |
| | | className='shrink-0' |
| | | text={formatIndexingTechniqueAndMethod(item.indexing_technique, item.retrieval_model_dict?.search_method)} |
| | | /> |
| | | ) |
| | | } |
| | | { |
| | | item.provider === 'external' && ( |
| | | <Badge className='shrink-0' text={t('dataset.externalTag')} /> |
| | | <Badge text={t('dataset.externalTag')} /> |
| | | ) |
| | | } |
| | | </div> |
| | |
| | | </> |
| | | )} |
| | | {loaded && ( |
| | | <div className='mt-8 flex items-center justify-between'> |
| | | <div className='text-sm font-medium text-text-secondary'> |
| | | <div className='flex justify-between items-center mt-8'> |
| | | <div className='text-sm font-medium text-gray-700'> |
| | | {selected.length > 0 && `${selected.length} ${t('appDebug.feature.dataSet.selected')}`} |
| | | </div> |
| | | <div className='flex space-x-2'> |
New file |
| | |
| | | .item { |
| | | box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); |
| | | } |
| | | |
| | | .item:hover, |
| | | .item.selected { |
| | | background: #F5F8FF; |
| | | border-color: #528BFF; |
| | | } |
| | | |
| | | .item.disabled { |
| | | @apply bg-white border-gray-200 cursor-default; |
| | | } |
| | |
| | | import { useTranslation } from 'react-i18next' |
| | | import { isEqual } from 'lodash-es' |
| | | import { RiCloseLine } from '@remixicon/react' |
| | | import { BookOpenIcon } from '@heroicons/react/24/outline' |
| | | import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development' |
| | | import cn from '@/utils/classnames' |
| | | import IndexMethodRadio from '@/app/components/datasets/settings/index-method-radio' |
| | |
| | | const { notify } = useToastContext() |
| | | const ref = useRef(null) |
| | | const isExternal = currentDataset.provider === 'external' |
| | | const [topK, setTopK] = useState(currentDataset?.external_retrieval_model.top_k ?? 2) |
| | | const [scoreThreshold, setScoreThreshold] = useState(currentDataset?.external_retrieval_model.score_threshold ?? 0.5) |
| | | const [scoreThresholdEnabled, setScoreThresholdEnabled] = useState(currentDataset?.external_retrieval_model.score_threshold_enabled ?? false) |
| | | const { setShowAccountSettingModal } = useModalContext() |
| | | const [loading, setLoading] = useState(false) |
| | | const { isCurrentWorkspaceDatasetOperator } = useAppContext() |
| | | const [localeCurrentDataset, setLocaleCurrentDataset] = useState({ ...currentDataset }) |
| | | const [topK, setTopK] = useState(localeCurrentDataset?.external_retrieval_model.top_k ?? 2) |
| | | const [scoreThreshold, setScoreThreshold] = useState(localeCurrentDataset?.external_retrieval_model.score_threshold ?? 0.5) |
| | | const [scoreThresholdEnabled, setScoreThresholdEnabled] = useState(localeCurrentDataset?.external_retrieval_model.score_threshold_enabled ?? false) |
| | | const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>(currentDataset.partial_member_list || []) |
| | | const [memberList, setMemberList] = useState<Member[]>([]) |
| | | |
| | |
| | | setScoreThreshold(data.score_threshold) |
| | | if (data.score_threshold_enabled !== undefined) |
| | | setScoreThresholdEnabled(data.score_threshold_enabled) |
| | | |
| | | setLocaleCurrentDataset({ |
| | | ...localeCurrentDataset, |
| | | external_retrieval_model: { |
| | | ...localeCurrentDataset?.external_retrieval_model, |
| | | ...data, |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | const handleSave = async () => { |
| | |
| | | retrieval_model_dict: retrievalConfig, |
| | | }) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) |
| | | } |
| | | finally { |
| | |
| | | |
| | | return ( |
| | | <div |
| | | className='flex w-full flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl' |
| | | className='overflow-hidden w-full flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl' |
| | | style={{ |
| | | height: 'calc(100vh - 72px)', |
| | | }} |
| | | ref={ref} |
| | | > |
| | | <div className='flex h-14 shrink-0 items-center justify-between border-b border-divider-regular pl-6 pr-5'> |
| | | <div className='flex flex-col text-base font-semibold text-text-primary'> |
| | | <div className='shrink-0 flex justify-between items-center pl-6 pr-5 h-14 border-b border-b-gray-100'> |
| | | <div className='flex flex-col text-base font-semibold text-gray-900'> |
| | | <div className='leading-6'>{t('datasetSettings.title')}</div> |
| | | </div> |
| | | <div className='flex items-center'> |
| | | <div |
| | | onClick={onCancel} |
| | | className='flex h-6 w-6 cursor-pointer items-center justify-center' |
| | | className='flex justify-center items-center w-6 h-6 cursor-pointer' |
| | | > |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <RiCloseLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | {/* Body */} |
| | | <div className='overflow-y-auto border-b border-divider-regular p-6 pb-[68px] pt-5'> |
| | | <div className='p-6 pt-5 border-b overflow-y-auto pb-[68px]' style={{ |
| | | borderBottom: 'rgba(0, 0, 0, 0.05)', |
| | | }}> |
| | | <div className={cn(rowClass, 'items-center')}> |
| | | <div className={labelClass}> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.name')}</div> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.name')}</div> |
| | | </div> |
| | | <Input |
| | | value={localeCurrentDataset.name} |
| | |
| | | </div> |
| | | <div className={cn(rowClass)}> |
| | | <div className={labelClass}> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.desc')}</div> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.desc')}</div> |
| | | </div> |
| | | <div className='w-full'> |
| | | <Textarea |
| | |
| | | className='resize-none' |
| | | placeholder={t('datasetSettings.form.descPlaceholder') || ''} |
| | | /> |
| | | <a className='mt-2 flex items-center h-[18px] px-3 text-xs text-gray-500' href="https://docs.dify.ai/features/datasets#how-to-write-a-good-dataset-description" target='_blank' rel='noopener noreferrer'> |
| | | <BookOpenIcon className='w-3 h-[18px] mr-1' /> |
| | | {t('datasetSettings.form.descWrite')} |
| | | </a> |
| | | </div> |
| | | </div> |
| | | <div className={rowClass}> |
| | | <div className={labelClass}> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.permissions')}</div> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.permissions')}</div> |
| | | </div> |
| | | <div className='w-full'> |
| | | <PermissionSelector |
| | |
| | | {currentDataset && currentDataset.indexing_technique && ( |
| | | <div className={cn(rowClass)}> |
| | | <div className={labelClass}> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.indexMethod')}</div> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.indexMethod')}</div> |
| | | </div> |
| | | <div className='grow'> |
| | | <IndexMethodRadio |
| | |
| | | {indexMethod === 'high_quality' && ( |
| | | <div className={cn(rowClass)}> |
| | | <div className={labelClass}> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.embeddingModel')}</div> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.embeddingModel')}</div> |
| | | </div> |
| | | <div className='w-full'> |
| | | <div className='h-8 w-full rounded-lg bg-components-input-bg-normal opacity-60'> |
| | | <div className='w-full h-9 rounded-lg bg-gray-100 opacity-60'> |
| | | <ModelSelector |
| | | readonly |
| | | defaultModel={{ |
| | |
| | | modelList={embeddingsModelList} |
| | | /> |
| | | </div> |
| | | <div className='mt-2 w-full text-xs leading-6 text-text-tertiary'> |
| | | <div className='mt-2 w-full text-xs leading-6 text-gray-500'> |
| | | {t('datasetSettings.form.embeddingModelTip')} |
| | | <span className='cursor-pointer text-text-accent' onClick={() => setShowAccountSettingModal({ payload: 'provider' })}>{t('datasetSettings.form.embeddingModelTipLink')}</span> |
| | | <span className='text-[#155eef] cursor-pointer' onClick={() => setShowAccountSettingModal({ payload: 'provider' })}>{t('datasetSettings.form.embeddingModelTipLink')}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <div className={rowClass}><Divider /></div> |
| | | <div className={rowClass}> |
| | | <div className={labelClass}> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.retrievalSetting.title')}</div> |
| | | </div> |
| | | <RetrievalSettings |
| | | topK={topK} |
| | |
| | | <div className={rowClass}><Divider /></div> |
| | | <div className={rowClass}> |
| | | <div className={labelClass}> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.externalKnowledgeAPI')}</div> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.externalKnowledgeAPI')}</div> |
| | | </div> |
| | | <div className='w-full max-w-[480px]'> |
| | | <div className='flex h-full items-center gap-1 rounded-lg bg-components-input-bg-normal px-3 py-2'> |
| | | <ApiConnectionMod className='h-4 w-4 text-text-secondary' /> |
| | | <div className='system-sm-medium overflow-hidden text-ellipsis text-text-secondary'> |
| | | <div className='flex h-full px-3 py-2 items-center gap-1 rounded-lg bg-components-input-bg-normal'> |
| | | <ApiConnectionMod className='w-4 h-4 text-text-secondary' /> |
| | | <div className='overflow-hidden text-text-secondary text-ellipsis system-sm-medium'> |
| | | {currentDataset?.external_knowledge_info.external_knowledge_api_name} |
| | | </div> |
| | | <div className='system-xs-regular text-text-tertiary'>·</div> |
| | | <div className='system-xs-regular text-text-tertiary'>{currentDataset?.external_knowledge_info.external_knowledge_api_endpoint}</div> |
| | | <div className='text-text-tertiary system-xs-regular'>·</div> |
| | | <div className='text-text-tertiary system-xs-regular'>{currentDataset?.external_knowledge_info.external_knowledge_api_endpoint}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div className={rowClass}> |
| | | <div className={labelClass}> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.externalKnowledgeID')}</div> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.externalKnowledgeID')}</div> |
| | | </div> |
| | | <div className='w-full max-w-[480px]'> |
| | | <div className='flex h-full items-center gap-1 rounded-lg bg-components-input-bg-normal px-3 py-2'> |
| | | <div className='system-xs-regular text-text-tertiary'>{currentDataset?.external_knowledge_info.external_knowledge_id}</div> |
| | | <div className='flex h-full px-3 py-2 items-center gap-1 rounded-lg bg-components-input-bg-normal'> |
| | | <div className='text-text-tertiary system-xs-regular'>{currentDataset?.external_knowledge_info.external_knowledge_id}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | : <div className={rowClass}> |
| | | <div className={cn(labelClass, 'w-auto min-w-[168px]')}> |
| | | <div> |
| | | <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div> |
| | | <div className='text-xs font-normal leading-[18px] text-text-tertiary'> |
| | | <a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> |
| | | <div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.retrievalSetting.title')}</div> |
| | | <div className='leading-[18px] text-xs font-normal text-gray-500'> |
| | | <a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-[#155eef]'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> |
| | | {t('datasetSettings.form.retrievalSetting.description')} |
| | | </div> |
| | | </div> |
| | |
| | | </div>} |
| | | </div> |
| | | {isRetrievalChanged && !isHideChangedTip && ( |
| | | <div className='absolute bottom-[76px] left-[30px] right-[30px] z-10 flex h-10 items-center justify-between rounded-lg border border-[#FEF0C7] bg-[#FFFAEB] px-3 shadow-lg'> |
| | | <div className='absolute z-10 left-[30px] right-[30px] bottom-[76px] flex h-10 items-center px-3 rounded-lg border border-[#FEF0C7] bg-[#FFFAEB] shadow-lg justify-between'> |
| | | <div className='flex items-center'> |
| | | <AlertTriangle className='mr-1 h-3 w-3 text-[#F79009]' /> |
| | | <div className='text-xs font-medium leading-[18px] text-gray-700'>{t('appDebug.datasetConfig.retrieveChangeTip')}</div> |
| | | <AlertTriangle className='mr-1 w-3 h-3 text-[#F79009]' /> |
| | | <div className='leading-[18px] text-xs font-medium text-gray-700'>{t('appDebug.datasetConfig.retrieveChangeTip')}</div> |
| | | </div> |
| | | <div className='cursor-pointer p-1' onClick={(e) => { |
| | | <div className='p-1 cursor-pointer' onClick={(e) => { |
| | | setIsHideChangedTip(true) |
| | | e.stopPropagation() |
| | | e.nativeEvent.stopImmediatePropagation() |
| | | }}> |
| | | <RiCloseLine className='h-4 w-4 text-gray-500' /> |
| | | <RiCloseLine className='w-4 h-4 text-gray-500 ' /> |
| | | </div> |
| | | </div> |
| | | )} |
| | | |
| | | <div |
| | | className='sticky bottom-0 z-[5] flex w-full justify-end border-t border-divider-regular bg-background-section px-6 py-4' |
| | | className='sticky z-[5] bottom-0 w-full flex justify-end py-4 px-6 border-t bg-white ' |
| | | style={{ |
| | | borderColor: 'rgba(0, 0, 0, 0.05)', |
| | | }} |
| | | > |
| | | <Button |
| | | onClick={onCancel} |
| | |
| | | return null |
| | | |
| | | return ( |
| | | <div className={cn('z-[1] rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-xs')}> |
| | | <div className='px-4 pb-4 pt-3'> |
| | | <div className={cn('bg-components-panel-on-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle shadow-xs z-[1]')}> |
| | | <div className='px-4 pt-3 pb-4'> |
| | | {promptVariables.map(({ key, name, type, options, max_length, required }, index) => ( |
| | | <div |
| | | key={key} |
| | | className='mb-4 last-of-type:mb-0' |
| | | > |
| | | <div> |
| | | <div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'> |
| | | <div className='h-6 mb-1 flex items-center gap-1 text-text-secondary system-sm-semibold'> |
| | | <div className='truncate'>{name || key}</div> |
| | | {!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>} |
| | | {!required && <span className='text-text-tertiary system-xs-regular'>{t('workflow.panel.optional')}</span>} |
| | | </div> |
| | | <div className='grow'> |
| | | {type === 'string' && ( |
| | |
| | | )} |
| | | {type === 'paragraph' && ( |
| | | <Textarea |
| | | className='h-[120px] grow' |
| | | className='grow h-[120px]' |
| | | placeholder={name} |
| | | value={inputs[key] ? `${inputs[key]}` : ''} |
| | | onChange={(e) => { handleInputValueChange(key, e.target.value) }} |
| | |
| | | onSelect={(i) => { handleInputValueChange(key, i.value as string) }} |
| | | items={(options || []).map(i => ({ name: i, value: i }))} |
| | | allowSearch={false} |
| | | bgClassName='bg-gray-50' |
| | | /> |
| | | )} |
| | | {type === 'number' && ( |
| | |
| | | import { useFeatures } from '@/app/components/base/features/hooks' |
| | | import type { InputForm } from '@/app/components/base/chat/chat/type' |
| | | import { getLastAnswer } from '@/app/components/base/chat/utils' |
| | | import { canFindTool } from '@/utils' |
| | | |
| | | type ChatItemProps = { |
| | | modelAndParameter: ModelAndParameter |
| | |
| | | const allToolIcons = useMemo(() => { |
| | | const icons: Record<string, any> = {} |
| | | modelConfig.agentConfig.tools?.forEach((item: any) => { |
| | | icons[item.tool_name] = collectionList.find((collection: any) => canFindTool(collection.id, item.provider_id))?.icon |
| | | icons[item.tool_name] = collectionList.find((collection: any) => collection.id === item.provider_id)?.icon |
| | | }) |
| | | return icons |
| | | }, [collectionList, modelConfig.agentConfig.tools]) |
| | |
| | | |
| | | import { createContext, useContext } from 'use-context-selector' |
| | | import type { ModelAndParameter } from '../types' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | export type DebugWithMultipleModelContextType = { |
| | | multipleModelConfigs: ModelAndParameter[] |
| | |
| | | } |
| | | const DebugWithMultipleModelContext = createContext<DebugWithMultipleModelContextType>({ |
| | | multipleModelConfigs: [], |
| | | onMultipleModelConfigsChange: noop, |
| | | onDebugWithMultipleModelChange: noop, |
| | | onMultipleModelConfigsChange: () => {}, |
| | | onDebugWithMultipleModelChange: () => {}, |
| | | }) |
| | | |
| | | export const useDebugWithMultipleModelContext = () => useContext(DebugWithMultipleModelContext) |
| | |
| | | |
| | | return ( |
| | | <div |
| | | className={`flex min-w-[320px] flex-col rounded-xl bg-background-section-burn ${className}`} |
| | | className={`flex flex-col min-w-[320px] rounded-xl bg-white border-[0.5px] border-black/5 ${className}`} |
| | | style={style} |
| | | > |
| | | <div className='flex h-10 shrink-0 items-center justify-between border-b-[0.5px] border-divider-regular px-3'> |
| | | <div className='flex h-5 w-6 items-center justify-center font-medium italic text-text-tertiary'> |
| | | <div className='shrink-0 flex items-center justify-between h-10 px-3 border-b-[0.5px] border-b-black/5'> |
| | | <div className='flex items-center justify-center w-6 h-5 font-medium italic text-gray-500'> |
| | | #{index + 1} |
| | | </div> |
| | | <ModelParameterTrigger |
| | |
| | | const inputsForm = modelConfig.configs.prompt_variables.filter(item => item.type !== 'api').map(item => ({ ...item, label: item.name, variable: item.key })) as InputForm[] |
| | | |
| | | return ( |
| | | <div className='flex h-full flex-col'> |
| | | <div className='flex flex-col h-full'> |
| | | <div |
| | | className={` |
| | | relative mb-3 grow overflow-auto px-6 |
| | | grow mb-3 relative px-6 overflow-auto |
| | | `} |
| | | style={{ height: isChatMode ? 'calc(100% - 60px)' : '100%' }} |
| | | > |
| | |
| | | } |
| | | </div> |
| | | {isChatMode && ( |
| | | <div className='shrink-0 px-6 pb-0'> |
| | | <div className='shrink-0 pb-0 px-6'> |
| | | <ChatInputArea |
| | | showFeatureBar |
| | | showFileUpload={false} |
| | |
| | | import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon' |
| | | import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name' |
| | | import { |
| | | type FormValue, |
| | | MODEL_STATUS_TEXT, |
| | | ModelStatusEnum, |
| | | } from '@/app/components/header/account-setting/model-provider-page/declarations' |
| | |
| | | } |
| | | onMultipleModelConfigsChange(true, newModelConfigs) |
| | | } |
| | | const handleParamsChange = (params: FormValue) => { |
| | | const handleParamsChange = (params: any) => { |
| | | const newModelConfigs = [...multipleModelConfigs] |
| | | newModelConfigs[index] = { |
| | | ...newModelConfigs[index], |
| | |
| | | }) => ( |
| | | <div |
| | | className={` |
| | | flex h-8 max-w-[200px] cursor-pointer items-center rounded-lg px-2 |
| | | ${open && 'bg-state-base-hover'} |
| | | flex items-center max-w-[200px] h-8 px-2 rounded-lg cursor-pointer |
| | | ${open && 'bg-gray-100'} |
| | | ${currentModel && currentModel.status !== ModelStatusEnum.active && '!bg-[#FFFAEB]'} |
| | | `} |
| | | > |
| | | { |
| | | currentProvider && ( |
| | | <ModelIcon |
| | | className='mr-1 !h-4 !w-4' |
| | | className='mr-1 !w-4 !h-4' |
| | | provider={currentProvider} |
| | | modelName={currentModel?.model} |
| | | /> |
| | |
| | | } |
| | | { |
| | | !currentProvider && ( |
| | | <div className='mr-1 flex h-4 w-4 items-center justify-center rounded'> |
| | | <CubeOutline className='h-4 w-4 text-text-accent' /> |
| | | <div className='flex items-center justify-center mr-1 w-4 h-4 rounded border border-dashed border-primary-100'> |
| | | <CubeOutline className='w-[11px] h-[11px] text-primary-600' /> |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | currentModel && ( |
| | | <ModelName |
| | | className='mr-0.5 text-text-secondary' |
| | | className='mr-0.5 text-gray-800' |
| | | modelItem={currentModel} |
| | | /> |
| | | ) |
| | | } |
| | | { |
| | | !currentModel && ( |
| | | <div className='mr-0.5 truncate text-[13px] font-medium text-text-accent'> |
| | | <div className='mr-0.5 text-[13px] font-medium text-primary-600 truncate'> |
| | | {t('common.modelProvider.selectModel')} |
| | | </div> |
| | | ) |
| | | } |
| | | <RiArrowDownSLine className={`h-3 w-3 ${(currentModel && currentProvider) ? 'text-text-tertiary' : 'text-text-accent'}`} /> |
| | | <RiArrowDownSLine className={`w-3 h-3 ${(currentModel && currentProvider) ? 'text-gray-800' : 'text-primary-600'}`} /> |
| | | { |
| | | currentModel && currentModel.status !== ModelStatusEnum.active && ( |
| | | <Tooltip popupContent={MODEL_STATUS_TEXT[currentModel.status][language]}> |
| | | <AlertTriangle className='h-4 w-4 text-[#F79009]' /> |
| | | <AlertTriangle className='w-4 h-4 text-[#F79009]' /> |
| | | </Tooltip> |
| | | ) |
| | | } |
| | |
| | | import { useEventEmitterContextContext } from '@/context/event-emitter' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import { useFeatures } from '@/app/components/base/features/hooks' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | type TextGenerationItemProps = { |
| | | modelAndParameter: ModelAndParameter |
| | |
| | | doSend(v.payload.message, v.payload.files) |
| | | }) |
| | | |
| | | const varList = modelConfig.configs.prompt_variables.map((item: any) => { |
| | | return { |
| | | label: item.key, |
| | | value: inputs[item.key], |
| | | } |
| | | }) |
| | | |
| | | return ( |
| | | <TextGeneration |
| | | className='flex h-full flex-col overflow-y-auto border-none' |
| | | className='flex flex-col h-full overflow-y-auto border-none' |
| | | innerClassName='grow flex flex-col' |
| | | contentClassName='grow' |
| | | content={completion} |
| | | isLoading={!completion && isResponding} |
| | | isResponding={isResponding} |
| | |
| | | siteInfo={null} |
| | | messageId={messageId} |
| | | isError={false} |
| | | onRetry={noop} |
| | | inSidePanel |
| | | onRetry={() => { }} |
| | | appId={appId} |
| | | varList={varList} |
| | | /> |
| | | ) |
| | | } |
| | |
| | | import { memo, useCallback, useImperativeHandle, useMemo } from 'react' |
| | | import { |
| | | forwardRef, |
| | | memo, |
| | | useCallback, |
| | | useImperativeHandle, |
| | | useMemo, |
| | | } from 'react' |
| | | import { |
| | | useConfigFromDebugContext, |
| | | useFormattingChangedSubscription, |
| | |
| | | import { useFeatures } from '@/app/components/base/features/hooks' |
| | | import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils' |
| | | import type { InputForm } from '@/app/components/base/chat/chat/type' |
| | | import { canFindTool } from '@/utils' |
| | | import type { FileEntity } from '@/app/components/base/file-uploader/types' |
| | | |
| | | type DebugWithSingleModelProps = { |
| | | checkCanSend?: () => boolean |
| | |
| | | export type DebugWithSingleModelRefType = { |
| | | handleRestart: () => void |
| | | } |
| | | const DebugWithSingleModel = ( |
| | | { |
| | | ref, |
| | | const DebugWithSingleModel = forwardRef<DebugWithSingleModelRefType, DebugWithSingleModelProps>(({ |
| | | checkCanSend, |
| | | }: DebugWithSingleModelProps & { |
| | | ref: React.RefObject<DebugWithSingleModelRefType>; |
| | | }, |
| | | ) => { |
| | | }, ref) => { |
| | | const { userProfile } = useAppContext() |
| | | const { |
| | | modelConfig, |
| | |
| | | ) |
| | | }, [appId, chatList, checkCanSend, completionParams, config, handleSend, inputs, modelConfig.mode, modelConfig.model_id, modelConfig.provider, textGenerationModelList]) |
| | | |
| | | const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => { |
| | | const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)! |
| | | const doRegenerate = useCallback((chatItem: ChatItemInTree) => { |
| | | const question = chatList.find(item => item.id === chatItem.parentMessageId)! |
| | | const parentAnswer = chatList.find(item => item.id === question.parentMessageId) |
| | | doSend(editedQuestion ? editedQuestion.message : question.content, |
| | | editedQuestion ? editedQuestion.files : question.message_files, |
| | | true, |
| | | isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null, |
| | | ) |
| | | doSend(question.content, question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null) |
| | | }, [chatList, doSend]) |
| | | |
| | | const allToolIcons = useMemo(() => { |
| | | const icons: Record<string, any> = {} |
| | | modelConfig.agentConfig.tools?.forEach((item: any) => { |
| | | icons[item.tool_name] = collectionList.find((collection: any) => canFindTool(collection.id, item.provider_id))?.icon |
| | | icons[item.tool_name] = collectionList.find((collection: any) => collection.id === item.provider_id)?.icon |
| | | }) |
| | | return icons |
| | | }, [collectionList, modelConfig.agentConfig.tools]) |
| | |
| | | noSpacing |
| | | /> |
| | | ) |
| | | } |
| | | }) |
| | | |
| | | DebugWithSingleModel.displayName = 'DebugWithSingleModel' |
| | | |
| | |
| | | import PromptLogModal from '@/app/components/base/prompt-log-modal' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | type IDebug = { |
| | | isAPIKeySet: boolean |
| | |
| | | return ( |
| | | <> |
| | | <div className="shrink-0"> |
| | | <div className='flex items-center justify-between px-4 pb-2 pt-3'> |
| | | <div className='system-xl-semibold text-text-primary'>{t('appDebug.inputs.title')}</div> |
| | | <div className='flex items-center justify-between px-4 pt-3 pb-2'> |
| | | <div className='text-text-primary system-xl-semibold'>{t('appDebug.inputs.title')}</div> |
| | | <div className='flex items-center'> |
| | | { |
| | | debugWithMultipleModel |
| | |
| | | onClick={() => onMultipleModelConfigsChange(true, [...multipleModelConfigs, { id: `${Date.now()}`, model: '', provider: '', parameters: {} }])} |
| | | disabled={multipleModelConfigs.length >= 4} |
| | | > |
| | | <RiAddLine className='mr-1 h-3.5 w-3.5' /> |
| | | <RiAddLine className='mr-1 w-3.5 h-3.5' /> |
| | | {t('common.modelProvider.addModel')}({multipleModelConfigs.length}/4) |
| | | </Button> |
| | | <div className='mx-2 h-[14px] w-[1px] bg-divider-regular' /> |
| | | <div className='mx-2 w-[1px] h-[14px] bg-divider-regular' /> |
| | | </> |
| | | ) |
| | | : null |
| | |
| | | popupContent={t('common.operation.refresh')} |
| | | > |
| | | <ActionButton onClick={clearConversation}> |
| | | <RefreshCcw01 className='h-4 w-4' /> |
| | | <RefreshCcw01 className='w-4 h-4' /> |
| | | </ActionButton> |
| | | </TooltipPlus> |
| | | {varList.length > 0 && ( |
| | |
| | | popupContent={t('workflow.panel.userInputField')} |
| | | > |
| | | <ActionButton state={expanded ? ActionButtonState.Active : undefined} onClick={() => setExpanded(!expanded)}> |
| | | <RiEqualizer2Line className='h-4 w-4' /> |
| | | <RiEqualizer2Line className='w-4 h-4' /> |
| | | </ActionButton> |
| | | </TooltipPlus> |
| | | {expanded && <div className='absolute bottom-[-14px] right-[5px] z-10 h-3 w-3 rotate-45 border-l-[0.5px] border-t-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg' />} |
| | | {expanded && <div className='absolute z-10 bottom-[-14px] right-[5px] w-3 h-3 bg-components-panel-on-panel-item-bg border-l-[0.5px] border-t-[0.5px] border-components-panel-border-subtle rotate-45' />} |
| | | </div> |
| | | )} |
| | | </> |
| | |
| | | </div> |
| | | { |
| | | debugWithMultipleModel && ( |
| | | <div className='mt-3 grow overflow-hidden' ref={ref}> |
| | | <div className='grow mt-3 overflow-hidden' ref={ref}> |
| | | <DebugWithMultipleModel |
| | | multipleModelConfigs={multipleModelConfigs} |
| | | onMultipleModelConfigsChange={onMultipleModelConfigsChange} |
| | |
| | | } |
| | | { |
| | | !debugWithMultipleModel && ( |
| | | <div className="flex grow flex-col" ref={ref}> |
| | | <div className="flex flex-col grow" ref={ref}> |
| | | {/* Chat */} |
| | | {mode !== AppType.completion && ( |
| | | <div className='h-0 grow overflow-hidden'> |
| | | <div className='grow h-0 overflow-hidden'> |
| | | <DebugWithSingleModel |
| | | ref={debugWithSingleModelRef} |
| | | checkCanSend={checkCanSend} |
| | |
| | | isInstalledApp={false} |
| | | messageId={messageId} |
| | | isError={false} |
| | | onRetry={noop} |
| | | onRetry={() => { }} |
| | | supportAnnotation |
| | | appId={appId} |
| | | varList={varList} |
| | | siteInfo={null} |
| | | /> |
| | | </div> |
| | | </> |
| | | )} |
| | | {!completionRes && !isResponding && ( |
| | | <div className='flex grow flex-col items-center justify-center gap-2'> |
| | | <RiSparklingFill className='h-12 w-12 text-text-empty-state-icon' /> |
| | | <div className='system-sm-regular text-text-quaternary'>{t('appDebug.noResult')}</div> |
| | | <div className='grow flex flex-col items-center justify-center gap-2'> |
| | | <RiSparklingFill className='w-12 h-12 text-text-empty-state-icon' /> |
| | | <div className='text-text-quaternary system-sm-regular'>{t('appDebug.noResult')}</div> |
| | | </div> |
| | | )} |
| | | </> |
New file |
| | |
| | | /* eslint-disable multiline-ternary */ |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useEffect, useRef, useState } from 'react' |
| | | import { |
| | | RiAddLine, |
| | | RiDeleteBinLine, |
| | | } from '@remixicon/react' |
| | | import { useContext } from 'use-context-selector' |
| | | import produce from 'immer' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useBoolean } from 'ahooks' |
| | | import { ReactSortable } from 'react-sortablejs' |
| | | import cn from '@/utils/classnames' |
| | | import ConfigContext from '@/context/debug-configuration' |
| | | import Panel from '@/app/components/app/configuration/base/feature-panel' |
| | | import Button from '@/app/components/base/button' |
| | | import OperationBtn from '@/app/components/app/configuration/base/operation-btn' |
| | | import { getInputKeys } from '@/app/components/base/block-input' |
| | | import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var' |
| | | import { getNewVar } from '@/utils/var' |
| | | import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight' |
| | | import Toast from '@/app/components/base/toast' |
| | | |
| | | const MAX_QUESTION_NUM = 10 |
| | | |
| | | export type IOpeningStatementProps = { |
| | | value: string |
| | | readonly?: boolean |
| | | onChange?: (value: string) => void |
| | | suggestedQuestions?: string[] |
| | | onSuggestedQuestionsChange?: (value: string[]) => void |
| | | } |
| | | |
| | | // regex to match the {{}} and replace it with a span |
| | | const regex = /\{\{([^}]+)\}\}/g |
| | | |
| | | const OpeningStatement: FC<IOpeningStatementProps> = ({ |
| | | value = '', |
| | | readonly, |
| | | onChange, |
| | | suggestedQuestions = [], |
| | | onSuggestedQuestionsChange = () => { }, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const { |
| | | modelConfig, |
| | | setModelConfig, |
| | | } = useContext(ConfigContext) |
| | | const promptVariables = modelConfig.configs.prompt_variables |
| | | const [notIncludeKeys, setNotIncludeKeys] = useState<string[]>([]) |
| | | |
| | | const hasValue = !!(value || '').trim() |
| | | const inputRef = useRef<HTMLTextAreaElement>(null) |
| | | |
| | | const [isFocus, { setTrue: didSetFocus, setFalse: setBlur }] = useBoolean(false) |
| | | |
| | | const setFocus = () => { |
| | | didSetFocus() |
| | | setTimeout(() => { |
| | | const input = inputRef.current |
| | | if (input) { |
| | | input.focus() |
| | | input.setSelectionRange(input.value.length, input.value.length) |
| | | } |
| | | }, 0) |
| | | } |
| | | |
| | | const [tempValue, setTempValue] = useState(value) |
| | | useEffect(() => { |
| | | setTempValue(value || '') |
| | | }, [value]) |
| | | |
| | | const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(suggestedQuestions || []) |
| | | const notEmptyQuestions = tempSuggestedQuestions.filter(question => !!question && question.trim()) |
| | | const coloredContent = (tempValue || '') |
| | | .replace(/</g, '<') |
| | | .replace(/>/g, '>') |
| | | .replace(regex, varHighlightHTML({ name: '$1' })) // `<span class="${highLightClassName}">{{$1}}</span>` |
| | | .replace(/\n/g, '<br />') |
| | | |
| | | const handleEdit = () => { |
| | | if (readonly) |
| | | return |
| | | setFocus() |
| | | } |
| | | |
| | | const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false) |
| | | |
| | | const handleCancel = () => { |
| | | setBlur() |
| | | setTempValue(value) |
| | | setTempSuggestedQuestions(suggestedQuestions) |
| | | } |
| | | |
| | | const handleConfirm = () => { |
| | | if (!(tempValue || '').trim()) { |
| | | Toast.notify({ |
| | | type: 'error', |
| | | message: t('common.errorMsg.fieldRequired', { |
| | | field: t('appDebug.openingStatement.title'), |
| | | }), |
| | | }) |
| | | return |
| | | } |
| | | const keys = getInputKeys(tempValue) |
| | | const promptKeys = promptVariables.map(item => item.key) |
| | | let notIncludeKeys: string[] = [] |
| | | |
| | | if (promptKeys.length === 0) { |
| | | if (keys.length > 0) |
| | | notIncludeKeys = keys |
| | | } |
| | | else { |
| | | notIncludeKeys = keys.filter(key => !promptKeys.includes(key)) |
| | | } |
| | | |
| | | if (notIncludeKeys.length > 0) { |
| | | setNotIncludeKeys(notIncludeKeys) |
| | | showConfirmAddVar() |
| | | return |
| | | } |
| | | setBlur() |
| | | onChange?.(tempValue) |
| | | onSuggestedQuestionsChange(tempSuggestedQuestions) |
| | | } |
| | | |
| | | const cancelAutoAddVar = () => { |
| | | onChange?.(tempValue) |
| | | hideConfirmAddVar() |
| | | setBlur() |
| | | } |
| | | |
| | | const autoAddVar = () => { |
| | | const newModelConfig = produce(modelConfig, (draft) => { |
| | | draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key, 'string'))] |
| | | }) |
| | | onChange?.(tempValue) |
| | | setModelConfig(newModelConfig) |
| | | hideConfirmAddVar() |
| | | setBlur() |
| | | } |
| | | |
| | | const headerRight = !readonly ? ( |
| | | isFocus ? ( |
| | | <div className='flex items-center space-x-1'> |
| | | <Button |
| | | variant='ghost' |
| | | size='small' |
| | | onClick={handleCancel} |
| | | > |
| | | {t('common.operation.cancel')} |
| | | </Button> |
| | | <Button |
| | | onClick={handleConfirm} |
| | | variant="primary" |
| | | size='small' |
| | | > |
| | | {t('common.operation.save')} |
| | | </Button> |
| | | </div> |
| | | ) : ( |
| | | <OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpener') as string} onClick={handleEdit} /> |
| | | ) |
| | | ) : null |
| | | |
| | | const renderQuestions = () => { |
| | | return isFocus ? ( |
| | | <div> |
| | | <div className='flex items-center py-2'> |
| | | <div className='shrink-0 flex space-x-0.5 leading-[18px] text-xs font-medium text-gray-500'> |
| | | <div className='uppercase'>{t('appDebug.openingStatement.openingQuestion')}</div> |
| | | <div>·</div> |
| | | <div>{tempSuggestedQuestions.length}/{MAX_QUESTION_NUM}</div> |
| | | </div> |
| | | <div className='ml-3 grow w-0 h-px bg-[#243, 244, 246]'></div> |
| | | </div> |
| | | <ReactSortable |
| | | className="space-y-1" |
| | | list={tempSuggestedQuestions.map((name, index) => { |
| | | return { |
| | | id: index, |
| | | name, |
| | | } |
| | | })} |
| | | setList={list => setTempSuggestedQuestions(list.map(item => item.name))} |
| | | handle='.handle' |
| | | ghostClass="opacity-50" |
| | | animation={150} |
| | | > |
| | | {tempSuggestedQuestions.map((question, index) => { |
| | | return ( |
| | | <div className='group relative rounded-lg border border-gray-200 flex items-center pl-2.5 hover:border-gray-300 hover:bg-white' key={index}> |
| | | <div className='handle flex items-center justify-center w-4 h-4 cursor-grab'> |
| | | <svg width="6" height="10" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | | <path fillRule="evenodd" clipRule="evenodd" d="M1 2C1.55228 2 2 1.55228 2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2ZM1 6C1.55228 6 2 5.55228 2 5C2 4.44772 1.55228 4 1 4C0.447715 4 0 4.44772 0 5C0 5.55228 0.447715 6 1 6ZM6 1C6 1.55228 5.55228 2 5 2C4.44772 2 4 1.55228 4 1C4 0.447715 4.44772 0 5 0C5.55228 0 6 0.447715 6 1ZM5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6ZM2 9C2 9.55229 1.55228 10 1 10C0.447715 10 0 9.55229 0 9C0 8.44771 0.447715 8 1 8C1.55228 8 2 8.44771 2 9ZM5 10C5.55228 10 6 9.55229 6 9C6 8.44771 5.55228 8 5 8C4.44772 8 4 8.44771 4 9C4 9.55229 4.44772 10 5 10Z" fill="#98A2B3" /> |
| | | </svg> |
| | | </div> |
| | | <input |
| | | type="input" |
| | | value={question || ''} |
| | | onChange={(e) => { |
| | | const value = e.target.value |
| | | setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => { |
| | | if (index === i) |
| | | return value |
| | | |
| | | return item |
| | | })) |
| | | }} |
| | | className={'w-full overflow-x-auto pl-1.5 pr-8 text-sm leading-9 text-gray-900 border-0 grow h-9 bg-transparent focus:outline-none cursor-pointer rounded-lg'} |
| | | /> |
| | | |
| | | <div |
| | | className='block absolute top-1/2 translate-y-[-50%] right-1.5 p-1 rounded-md cursor-pointer hover:bg-[#FEE4E2] hover:text-[#D92D20]' |
| | | onClick={() => { |
| | | setTempSuggestedQuestions(tempSuggestedQuestions.filter((_, i) => index !== i)) |
| | | }} |
| | | > |
| | | <RiDeleteBinLine className='w-3.5 h-3.5' /> |
| | | </div> |
| | | </div> |
| | | ) |
| | | })}</ReactSortable> |
| | | {tempSuggestedQuestions.length < MAX_QUESTION_NUM && ( |
| | | <div |
| | | onClick={() => { setTempSuggestedQuestions([...tempSuggestedQuestions, '']) }} |
| | | className='mt-1 flex items-center h-9 px-3 gap-2 rounded-lg cursor-pointer text-gray-400 bg-gray-100 hover:bg-gray-200'> |
| | | <RiAddLine className='w-4 h-4' /> |
| | | <div className='text-gray-500 text-[13px]'>{t('appDebug.variableConfig.addOption')}</div> |
| | | </div> |
| | | )} |
| | | </div> |
| | | ) : ( |
| | | <div className='mt-1.5 flex flex-wrap'> |
| | | {notEmptyQuestions.map((question, index) => { |
| | | return ( |
| | | <div key={index} className='mt-1 mr-1 max-w-full truncate last:mr-0 shrink-0 leading-8 items-center px-2.5 rounded-lg border border-gray-200 shadow-xs bg-white text-[13px] font-normal text-gray-900 cursor-pointer'> |
| | | {question} |
| | | </div> |
| | | ) |
| | | })} |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | return ( |
| | | <Panel |
| | | className={cn(isShowConfirmAddVar && 'h-[220px]', 'relative mt-4 !bg-gray-25')} |
| | | title={t('appDebug.openingStatement.title')} |
| | | headerIcon={ |
| | | <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | | <path fillRule="evenodd" clipRule="evenodd" d="M8.33353 1.33301C4.83572 1.33301 2.00019 4.16854 2.00019 7.66634C2.00019 8.37301 2.11619 9.05395 2.3307 9.69036C2.36843 9.80229 2.39063 9.86853 2.40507 9.91738L2.40979 9.93383L2.40729 9.93903C2.39015 9.97437 2.36469 10.0218 2.31705 10.11L1.2158 12.1484C1.14755 12.2746 1.07633 12.4064 1.02735 12.5209C0.978668 12.6348 0.899813 12.8437 0.938613 13.0914C0.984094 13.3817 1.15495 13.6373 1.40581 13.7903C1.61981 13.9208 1.843 13.9279 1.96683 13.9264C2.09141 13.925 2.24036 13.9095 2.38314 13.8947L5.81978 13.5395C5.87482 13.5338 5.9036 13.5309 5.92468 13.5292L5.92739 13.529L5.93564 13.532C5.96154 13.5413 5.99666 13.5548 6.0573 13.5781C6.76459 13.8506 7.53244 13.9997 8.33353 13.9997C11.8313 13.9997 14.6669 11.1641 14.6669 7.66634C14.6669 4.16854 11.8313 1.33301 8.33353 1.33301ZM5.9799 5.72116C6.73142 5.08698 7.73164 5.27327 8.33144 5.96584C8.93125 5.27327 9.91854 5.09365 10.683 5.72116C11.4474 6.34867 11.5403 7.41567 10.9501 8.16572C10.5845 8.6304 9.6668 9.47911 9.02142 10.0576C8.78435 10.2702 8.66582 10.3764 8.52357 10.4192C8.40154 10.456 8.26134 10.456 8.13931 10.4192C7.99706 10.3764 7.87853 10.2702 7.64147 10.0576C6.99609 9.47911 6.07839 8.6304 5.71276 8.16572C5.12259 7.41567 5.22839 6.35534 5.9799 5.72116Z" fill="#E74694" /> |
| | | </svg> |
| | | } |
| | | headerRight={headerRight} |
| | | hasHeaderBottomBorder={!hasValue} |
| | | isFocus={isFocus} |
| | | > |
| | | <div className='text-gray-700 text-sm'> |
| | | {(hasValue || (!hasValue && isFocus)) ? ( |
| | | <> |
| | | {isFocus |
| | | ? ( |
| | | <div> |
| | | <textarea |
| | | ref={inputRef} |
| | | value={tempValue} |
| | | rows={3} |
| | | onChange={e => setTempValue(e.target.value)} |
| | | className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none " |
| | | placeholder={t('appDebug.openingStatement.placeholder') as string} |
| | | > |
| | | </textarea> |
| | | </div> |
| | | ) |
| | | : ( |
| | | <div dangerouslySetInnerHTML={{ |
| | | __html: coloredContent, |
| | | }}></div> |
| | | )} |
| | | {renderQuestions()} |
| | | </>) : ( |
| | | <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div> |
| | | )} |
| | | |
| | | {isShowConfirmAddVar && ( |
| | | <ConfirmAddVar |
| | | varNameArr={notIncludeKeys} |
| | | onConfirm={autoAddVar} |
| | | onCancel={cancelAutoAddVar} |
| | | onHide={hideConfirmAddVar} |
| | | /> |
| | | )} |
| | | |
| | | </div> |
| | | </Panel> |
| | | ) |
| | | } |
| | | export default React.memo(OpeningStatement) |
| | |
| | | noBodySpacing |
| | | > |
| | | {!isHideTip && ( |
| | | <div className='flex h-9 items-center justify-between rounded-b-xl bg-[#FFFAEB] px-3 text-xs text-gray-700'> |
| | | <div className='flex justify-between items-center h-9 px-3 rounded-b-xl bg-[#FFFAEB] text-xs text-gray-700'> |
| | | <div className='flex items-center space-x-2'> |
| | | <div>{warningIcon}</div> |
| | | <div>{t('appDebug.feature.moreLikeThis.tip')}</div> |
| | | </div> |
| | | <div className='flex h-4 w-4 cursor-pointer items-center justify-center' onClick={() => setIsHideTip(true)}> |
| | | <XMarkIcon className="h-3 w-3" /> |
| | | <div className='flex items-center justify-center w-4 h-4 cursor-pointer' onClick={() => setIsHideTip(true)}> |
| | | <XMarkIcon className="w-3 h-3" /> |
| | | </div> |
| | | </div> |
| | | )} |
| | |
| | | import type { FC } from 'react' |
| | | import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' |
| | | import useSWR from 'swr' |
| | | import { basePath } from '@/utils/var' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useContext } from 'use-context-selector' |
| | | import { usePathname } from 'next/navigation' |
| | |
| | | } from '@/app/components/app/configuration/debug/hooks' |
| | | import type { ModelAndParameter } from '@/app/components/app/configuration/debug/types' |
| | | import Button from '@/app/components/base/button' |
| | | import Divider from '@/app/components/base/divider' |
| | | import Loading from '@/app/components/base/loading' |
| | | import AppPublisher from '@/app/components/app/app-publisher/features-wrapper' |
| | | import type { |
| | |
| | | useTextGenerationCurrentProviderAndModelAndModelList, |
| | | } from '@/app/components/header/account-setting/model-provider-page/hooks' |
| | | import { fetchCollectionList } from '@/service/tools' |
| | | import type { Collection } from '@/app/components/tools/types' |
| | | import { type Collection } from '@/app/components/tools/types' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | import { |
| | | getMultipleRetrievalConfig, |
| | |
| | | import { SupportUploadFileTypes } from '@/app/components/workflow/types' |
| | | import NewFeaturePanel from '@/app/components/base/features/new-feature-panel' |
| | | import { fetchFileUploadConfig } from '@/service/common' |
| | | import { |
| | | correctModelProvider, |
| | | correctToolProvider, |
| | | } from '@/utils' |
| | | import PluginDependency from '@/app/components/workflow/plugin-dependency' |
| | | import { supportFunctionCall } from '@/utils/tool-call' |
| | | |
| | | type PublishConfig = { |
| | | modelConfig: ModelConfig |
| | |
| | | }))) |
| | | const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) |
| | | |
| | | const latestPublishedAt = useMemo(() => appDetail?.model_config?.updated_at, [appDetail]) |
| | | const latestPublishedAt = useMemo(() => appDetail?.model_config.updated_at, [appDetail]) |
| | | const [formattingChanged, setFormattingChanged] = useState(false) |
| | | const { setShowAccountSettingModal } = useModalContext() |
| | | const [hasFetchedDetail, setHasFetchedDetail] = useState(false) |
| | |
| | | const setCompletionParams = (value: FormValue) => { |
| | | const params = { ...value } |
| | | |
| | | // eslint-disable-next-line ts/no-use-before-define |
| | | // eslint-disable-next-line @typescript-eslint/no-use-before-define |
| | | if ((!params.stop || params.stop.length === 0) && (modeModeTypeRef.current === ModelModeType.completion)) { |
| | | params.stop = getTempStop() |
| | | setTempStop([]) |
| | |
| | | } |
| | | |
| | | const [modelConfig, doSetModelConfig] = useState<ModelConfig>({ |
| | | provider: 'langgenius/openai/openai', |
| | | provider: 'openai', |
| | | model_id: 'gpt-3.5-turbo', |
| | | mode: ModelModeType.unset, |
| | | configs: { |
| | |
| | | dataSets: [], |
| | | agentConfig: DEFAULT_AGENT_SETTING, |
| | | }) |
| | | |
| | | const isAgent = mode === 'agent-chat' |
| | | |
| | | const isOpenAI = modelConfig.provider === 'langgenius/openai/openai' |
| | | const isOpenAI = modelConfig.provider === 'openai' |
| | | |
| | | const [collectionList, setCollectionList] = useState<Collection[]>([]) |
| | | const [datasetConfigs, doSetDatasetConfigs] = useState<DatasetConfigs>({ |
| | | useEffect(() => { |
| | | |
| | | }, []) |
| | | const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>({ |
| | | retrieval_model: RETRIEVE_TYPE.multiWay, |
| | | reranking_model: { |
| | | reranking_provider_name: '', |
| | |
| | | datasets: [], |
| | | }, |
| | | }) |
| | | const datasetConfigsRef = useRef(datasetConfigs) |
| | | const setDatasetConfigs = useCallback((newDatasetConfigs: DatasetConfigs) => { |
| | | doSetDatasetConfigs(newDatasetConfigs) |
| | | datasetConfigsRef.current = newDatasetConfigs |
| | | }, []) |
| | | |
| | | const setModelConfig = (newModelConfig: ModelConfig) => { |
| | | doSetModelConfig(newModelConfig) |
| | |
| | | }, [modelModeType]) |
| | | |
| | | const [dataSets, setDataSets] = useState<DataSet[]>([]) |
| | | const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key |
| | | const contextVar = modelConfig.configs.prompt_variables.find((item: any) => item.is_context_var)?.key |
| | | const hasSetContextVar = !!contextVar |
| | | const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false) |
| | | const selectedIds = dataSets.map(item => item.id) |
| | |
| | | formattingChangedDispatcher() |
| | | let newDatasets = data |
| | | if (data.find(item => !item.name)) { // has not loaded selected dataset |
| | | const newSelected = produce(data, (draft) => { |
| | | const newSelected = produce(data, (draft: any) => { |
| | | data.forEach((item, index) => { |
| | | if (!item.name) { // not fetched database |
| | | const newItem = dataSets.find(i => i.id === item.id) |
| | |
| | | }) |
| | | |
| | | setDatasetConfigs({ |
| | | ...datasetConfigsRef.current, |
| | | ...retrievalConfig, |
| | | reranking_model: { |
| | | reranking_provider_name: retrievalConfig?.reranking_model?.provider || '', |
| | |
| | | }, |
| | | ) |
| | | |
| | | const isFunctionCall = supportFunctionCall(currModel?.features) |
| | | const isFunctionCall = (() => { |
| | | const features = currModel?.features |
| | | if (!features) |
| | | return false |
| | | return features.includes(ModelFeatureEnum.toolCall) || features.includes(ModelFeatureEnum.multiToolCall) |
| | | })() |
| | | |
| | | // Fill old app data missing model mode. |
| | | useEffect(() => { |
| | |
| | | const [canReturnToSimpleMode, setCanReturnToSimpleMode] = useState(true) |
| | | const setPromptMode = async (mode: PromptMode) => { |
| | | if (mode === PromptMode.advanced) { |
| | | // eslint-disable-next-line ts/no-use-before-define |
| | | // eslint-disable-next-line @typescript-eslint/no-use-before-define |
| | | await migrateToDefaultPrompt() |
| | | setCanReturnToSimpleMode(true) |
| | | } |
| | |
| | | useEffect(() => { |
| | | (async () => { |
| | | const collectionList = await fetchCollectionList() |
| | | if (basePath) { |
| | | collectionList.forEach((item) => { |
| | | if (typeof item.icon == 'string' && !item.icon.includes(basePath)) |
| | | item.icon = `${basePath}${item.icon}` |
| | | }) |
| | | } |
| | | setCollectionList(collectionList) |
| | | fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => { |
| | | setMode(res.mode) |
| | |
| | | if (modelConfig.chat_prompt_config && modelConfig.chat_prompt_config.prompt.length > 0) |
| | | setChatPromptConfig(modelConfig.chat_prompt_config) |
| | | else |
| | | setChatPromptConfig(clone(DEFAULT_CHAT_PROMPT_CONFIG)) |
| | | setChatPromptConfig(clone(DEFAULT_CHAT_PROMPT_CONFIG) as any) |
| | | setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any) |
| | | setCanReturnToSimpleMode(false) |
| | | } |
| | |
| | | if (modelConfig.retriever_resource) |
| | | setCitationConfig(modelConfig.retriever_resource) |
| | | |
| | | if (modelConfig.annotation_reply) { |
| | | let annotationConfig = modelConfig.annotation_reply |
| | | if (modelConfig.annotation_reply.enabled) { |
| | | annotationConfig = { |
| | | ...modelConfig.annotation_reply, |
| | | embedding_model: { |
| | | ...modelConfig.annotation_reply.embedding_model, |
| | | embedding_provider_name: correctModelProvider(modelConfig.annotation_reply.embedding_model.embedding_provider_name), |
| | | }, |
| | | } |
| | | } |
| | | setAnnotationConfig(annotationConfig, true) |
| | | } |
| | | if (modelConfig.annotation_reply) |
| | | setAnnotationConfig(modelConfig.annotation_reply, true) |
| | | |
| | | if (modelConfig.sensitive_word_avoidance) |
| | | setModerationConfig(modelConfig.sensitive_word_avoidance) |
| | |
| | | |
| | | const config = { |
| | | modelConfig: { |
| | | provider: correctModelProvider(model.provider), |
| | | provider: model.provider, |
| | | model_id: model.name, |
| | | mode: model.mode, |
| | | configs: { |
| | |
| | | annotation_reply: modelConfig.annotation_reply, |
| | | external_data_tools: modelConfig.external_data_tools, |
| | | dataSets: datasets || [], |
| | | // eslint-disable-next-line multiline-ternary |
| | | agentConfig: res.mode === 'agent-chat' ? { |
| | | max_iteration: DEFAULT_AGENT_SETTING.max_iteration, |
| | | ...modelConfig.agent_mode, |
| | |
| | | tools: modelConfig.agent_mode?.tools.filter((tool: any) => { |
| | | return !tool.dataset |
| | | }).map((tool: any) => { |
| | | const toolInCollectionList = collectionList.find(c => tool.provider_id === c.id) |
| | | return { |
| | | ...tool, |
| | | isDeleted: res.deleted_tools?.some((deletedTool: any) => deletedTool.id === tool.id && deletedTool.tool_name === tool.tool_name), |
| | | notAuthor: toolInCollectionList?.is_team_authorization === false, |
| | | ...(tool.provider_type === 'builtin' ? { |
| | | provider_id: correctToolProvider(tool.provider_name, !!toolInCollectionList), |
| | | provider_name: correctToolProvider(tool.provider_name, !!toolInCollectionList), |
| | | } : {}), |
| | | isDeleted: res.deleted_tools?.includes(tool.tool_name), |
| | | notAuthor: collectionList.find(c => tool.provider_id === c.id)?.is_team_authorization === false, |
| | | } |
| | | }), |
| | | } : DEFAULT_AGENT_SETTING, |
| | |
| | | |
| | | syncToPublishedConfig(config) |
| | | setPublishedConfig(config) |
| | | const retrievalConfig = getMultipleRetrievalConfig({ |
| | | ...modelConfig.dataset_configs, |
| | | reranking_model: modelConfig.dataset_configs.reranking_model && { |
| | | provider: modelConfig.dataset_configs.reranking_model.reranking_provider_name, |
| | | model: modelConfig.dataset_configs.reranking_model.reranking_model_name, |
| | | }, |
| | | }, datasets, datasets, { |
| | | const retrievalConfig = getMultipleRetrievalConfig(modelConfig.dataset_configs, datasets, datasets, { |
| | | provider: currentRerankProvider?.provider, |
| | | model: currentRerankModel?.model, |
| | | }) |
| | |
| | | retrieval_model: RETRIEVE_TYPE.multiWay, |
| | | ...modelConfig.dataset_configs, |
| | | ...retrievalConfig, |
| | | ...(retrievalConfig.reranking_model ? { |
| | | reranking_model: { |
| | | reranking_model_name: retrievalConfig.reranking_model.model, |
| | | reranking_provider_name: correctModelProvider(retrievalConfig.reranking_model.provider), |
| | | }, |
| | | } : {}), |
| | | }) |
| | | setHasFetchedDetail(true) |
| | | }) |
| | |
| | | } |
| | | |
| | | if (isLoading) { |
| | | return <div className='flex h-full items-center justify-center'> |
| | | return <div className='flex items-center justify-center h-full'> |
| | | <Loading type='area' /> |
| | | </div> |
| | | } |
| | |
| | | dataSets, |
| | | setDataSets, |
| | | datasetConfigs, |
| | | datasetConfigsRef, |
| | | setDatasetConfigs, |
| | | hasSetContextVar, |
| | | isShowVisionConfig, |
| | |
| | | > |
| | | <FeaturesProvider features={featuresData}> |
| | | <> |
| | | <div className="flex h-full flex-col"> |
| | | <div className='relative flex h-[200px] grow pt-14'> |
| | | <div className="flex flex-col h-full"> |
| | | <div className='relative flex grow h-[200px] pt-14'> |
| | | {/* Header */} |
| | | <div className='bg-default-subtle absolute left-0 top-0 h-14 w-full'> |
| | | <div className='flex h-14 items-center justify-between px-6'> |
| | | <div className='absolute top-0 left-0 w-full bg-white h-14'> |
| | | <div className='flex items-center justify-between px-6 h-14'> |
| | | <div className='flex items-center'> |
| | | <div className='system-xl-semibold text-text-primary'>{t('appDebug.orchestrate')}</div> |
| | | <div className='flex h-[14px] items-center space-x-1 text-xs'> |
| | | <div className='text-base font-semibold leading-6 text-gray-900'>{t('appDebug.orchestrate')}</div> |
| | | <div className='flex items-center h-[14px] space-x-1 text-xs'> |
| | | {isAdvancedMode && ( |
| | | <div className='system-xs-medium-uppercase ml-1 flex h-5 items-center rounded-md border border-components-button-secondary-border px-1.5 uppercase text-text-tertiary'>{t('appDebug.promptMode.advanced')}</div> |
| | | <div className='ml-1 flex items-center h-5 px-1.5 border border-gray-100 rounded-md text-[11px] font-medium text-gray-500 uppercase'>{t('appDebug.promptMode.advanced')}</div> |
| | | )} |
| | | </div> |
| | | </div> |
| | |
| | | debugWithMultipleModel={debugWithMultipleModel} |
| | | onDebugWithMultipleModelChange={handleDebugWithMultipleModelChange} |
| | | /> |
| | | <Divider type='vertical' className='mx-2 h-[14px]' /> |
| | | <div className='mx-2 w-[1px] h-[14px] bg-gray-200'></div> |
| | | </> |
| | | )} |
| | | {isMobile && ( |
| | | <Button className='mr-2 !h-8 !text-[13px] font-medium' onClick={showDebugPanel}> |
| | | <Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}> |
| | | <span className='mr-1'>{t('appDebug.operation.debugConfig')}</span> |
| | | <CodeBracketIcon className="h-4 w-4 text-text-tertiary" /> |
| | | <CodeBracketIcon className="w-4 h-4 text-gray-500" /> |
| | | </Button> |
| | | )} |
| | | <AppPublisher {...{ |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div className={`flex h-full w-full shrink-0 flex-col sm:w-1/2 ${debugWithMultipleModel && 'max-w-[560px]'}`}> |
| | | <div className={`w-full sm:w-1/2 shrink-0 flex flex-col h-full ${debugWithMultipleModel && 'max-w-[560px]'}`}> |
| | | <Config /> |
| | | </div> |
| | | {!isMobile && <div className="relative flex h-full w-1/2 grow flex-col overflow-y-auto " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}> |
| | | <div className='flex grow flex-col rounded-tl-2xl border-l-[0.5px] border-t-[0.5px] border-components-panel-border bg-chatbot-bg '> |
| | | {!isMobile && <div className="relative flex flex-col w-1/2 h-full overflow-y-auto grow " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}> |
| | | <div className='grow flex flex-col border-t-[0.5px] border-l-[0.5px] rounded-tl-2xl border-components-panel-border bg-chatbot-bg '> |
| | | <Debug |
| | | isAPIKeySet={isAPIKeySet} |
| | | onSetting={() => setShowAccountSettingModal({ payload: 'provider' })} |
| | |
| | | /> |
| | | )} |
| | | {isMobile && ( |
| | | <Drawer showClose isOpen={isShowDebugPanel} onClose={hideDebugPanel} mask footer={null}> |
| | | <Drawer showClose isOpen={isShowDebugPanel} onClose={hideDebugPanel} mask footer={null} panelClassname='!bg-gray-50'> |
| | | <Debug |
| | | isAPIKeySet={isAPIKeySet} |
| | | onSetting={() => setShowAccountSettingModal({ payload: 'provider' })} |
| | |
| | | onAutoAddPromptVariable={handleAddPromptVariable} |
| | | /> |
| | | )} |
| | | <PluginDependency /> |
| | | </> |
| | | </FeaturesProvider> |
| | | </ConfigContext.Provider> |
| | |
| | | if (!show) |
| | | return null |
| | | return ( |
| | | <div className='mb-3 rounded-xl border border-[#FEF0C7] bg-[#FFFAEB] px-4 py-3' > |
| | | <div className='mb-2 text-xs font-bold leading-[18px] text-[#DC6803]'>{t('appDebug.promptMode.advancedWarning.title')}</div> |
| | | <div className='flex items-center justify-between'> |
| | | <div className='mb-3 py-3 px-4 border border-[#FEF0C7] rounded-xl bg-[#FFFAEB]' > |
| | | <div className='mb-2 text-xs leading-[18px] font-bold text-[#DC6803]'>{t('appDebug.promptMode.advancedWarning.title')}</div> |
| | | <div className='flex justify-between items-center'> |
| | | <div className='text-xs leading-[18px] '> |
| | | <span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span> |
| | | <a |
| | | className='font-medium text-[#155EEF]' |
| | | href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? '/guides/features/prompt-engineering' : 'features/prompt-engineering'}`} |
| | | href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/guides/application-design/prompt-engineering' : 'features/prompt-engineering'}`} |
| | | target='_blank' rel='noopener noreferrer' |
| | | > |
| | | {t('appDebug.promptMode.advancedWarning.learnMore')} |
| | |
| | | <div className='flex items-center space-x-1'> |
| | | <div |
| | | onClick={onReturnToSimpleMode} |
| | | className='flex h-6 shrink-0 cursor-pointer items-center space-x-1 rounded-lg border border-gray-200 bg-indigo-600 px-2 text-xs font-semibold text-white shadow-xs' |
| | | className='shrink-0 flex items-center h-6 px-2 bg-indigo-600 shadow-xs border border-gray-200 rounded-lg text-white text-xs font-semibold cursor-pointer space-x-1' |
| | | > |
| | | <div className='text-xs font-semibold uppercase'>{t('appDebug.promptMode.switchBack')}</div> |
| | | </div> |
| | | <div |
| | | className='flex h-6 cursor-pointer items-center rounded-md border border-gray-200 bg-[#fff] px-2 text-xs font-medium text-primary-600 shadow-xs' |
| | | className='flex items-center h-6 px-2 rounded-md bg-[#fff] border border-gray-200 shadow-xs text-xs font-medium text-primary-600 cursor-pointer' |
| | | onClick={() => setShow(false)} |
| | | >{t('appDebug.promptMode.advancedWarning.ok')}</div> |
| | | </div> |
| | |
| | | } |
| | | |
| | | const onClear = () => { |
| | | const newInputs: Inputs = {} |
| | | const newInputs: Record<string, any> = {} |
| | | promptVariables.forEach((item) => { |
| | | newInputs[item.key] = '' |
| | | }) |
| | |
| | | |
| | | return ( |
| | | <> |
| | | <div className='relative z-[1] mx-3 rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-md'> |
| | | <div className='relative z-[1] mx-3 border-[0.5px] bg-components-panel-on-panel-item-bg border-components-panel-border-subtle rounded-xl shadow-md'> |
| | | <div className={cn('px-4 pt-3', userInputFieldCollapse ? 'pb-3' : 'pb-1')}> |
| | | <div className='flex cursor-pointer items-center gap-0.5 py-0.5' onClick={() => setUserInputFieldCollapse(!userInputFieldCollapse)}> |
| | | <div className='system-md-semibold-uppercase text-text-secondary'>{t('appDebug.inputs.userInputField')}</div> |
| | | {userInputFieldCollapse && <RiArrowRightSLine className='h-4 w-4 text-text-secondary'/>} |
| | | {!userInputFieldCollapse && <RiArrowDownSLine className='h-4 w-4 text-text-secondary'/>} |
| | | <div className='flex items-center gap-0.5 py-0.5 cursor-pointer' onClick={() => setUserInputFieldCollapse(!userInputFieldCollapse)}> |
| | | <div className='text-text-secondary system-md-semibold-uppercase'>{t('appDebug.inputs.userInputField')}</div> |
| | | {userInputFieldCollapse && <RiArrowRightSLine className='w-4 h-4 text-text-secondary'/>} |
| | | {!userInputFieldCollapse && <RiArrowDownSLine className='w-4 h-4 text-text-secondary'/>} |
| | | </div> |
| | | {!userInputFieldCollapse && ( |
| | | <div className='system-xs-regular mt-1 text-text-tertiary'>{t('appDebug.inputs.completionVarTip')}</div> |
| | | <div className='mt-1 text-text-tertiary system-xs-regular'>{t('appDebug.inputs.completionVarTip')}</div> |
| | | )} |
| | | </div> |
| | | {!userInputFieldCollapse && promptVariables.length > 0 && ( |
| | | <div className='px-4 pb-4 pt-3'> |
| | | <div className='px-4 pt-3 pb-4'> |
| | | {promptVariables.map(({ key, name, type, options, max_length, required }, index) => ( |
| | | <div |
| | | key={key} |
| | | className='mb-4 last-of-type:mb-0' |
| | | > |
| | | <div> |
| | | <div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'> |
| | | <div className='h-6 mb-1 flex items-center gap-1 text-text-secondary system-sm-semibold'> |
| | | <div className='truncate'>{name || key}</div> |
| | | {!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>} |
| | | {!required && <span className='text-text-tertiary system-xs-regular'>{t('workflow.panel.optional')}</span>} |
| | | </div> |
| | | <div className='grow'> |
| | | {type === 'string' && ( |
| | |
| | | )} |
| | | {type === 'paragraph' && ( |
| | | <Textarea |
| | | className='h-[120px] grow' |
| | | className='grow h-[120px]' |
| | | placeholder={name} |
| | | value={inputs[key] ? `${inputs[key]}` : ''} |
| | | onChange={(e) => { handleInputValueChange(key, e.target.value) }} |
| | |
| | | </div> |
| | | ))} |
| | | {visionConfig?.enabled && ( |
| | | <div className="mt-3 justify-between xl:flex"> |
| | | <div className="mr-1 w-[120px] shrink-0 py-2 text-sm text-text-primary">{t('common.imageUploader.imageUpload')}</div> |
| | | <div className="mt-3 xl:flex justify-between"> |
| | | <div className="mr-1 py-2 shrink-0 w-[120px] text-sm text-gray-900">{t('common.imageUploader.imageUpload')}</div> |
| | | <div className='grow'> |
| | | <TextGenerationImageUploader |
| | | settings={visionConfig} |
| | |
| | | </div> |
| | | )} |
| | | {!userInputFieldCollapse && ( |
| | | <div className='flex justify-between border-t border-divider-subtle p-4 pt-3'> |
| | | <div className='flex justify-between p-4 pt-3 border-t border-divider-subtle'> |
| | | <Button className='w-[72px]' onClick={onClear}>{t('common.operation.clear')}</Button> |
| | | {canNotRun && ( |
| | | <Tooltip popupContent={t('appDebug.otherError.promptNoBeEmpty')} needsDelay> |
| | |
| | | disabled={canNotRun} |
| | | onClick={() => onSend && onSend()} |
| | | className="w-[96px]"> |
| | | <RiPlayLargeFill className="mr-0.5 h-4 w-4 shrink-0" aria-hidden="true" /> |
| | | <RiPlayLargeFill className="shrink-0 w-4 h-4 mr-0.5" aria-hidden="true" /> |
| | | {t('appDebug.inputs.run')} |
| | | </Button> |
| | | </Tooltip> |
| | |
| | | disabled={canNotRun} |
| | | onClick={() => onSend && onSend()} |
| | | className="w-[96px]"> |
| | | <RiPlayLargeFill className="mr-0.5 h-4 w-4 shrink-0" aria-hidden="true" /> |
| | | <RiPlayLargeFill className="shrink-0 w-4 h-4 mr-0.5" aria-hidden="true" /> |
| | | {t('appDebug.inputs.run')} |
| | | </Button> |
| | | )} |
New file |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useContext } from 'use-context-selector' |
| | | import { usePathname, useRouter } from 'next/navigation' |
| | | import ConfigParamModal from './config-param-modal' |
| | | import Panel from '@/app/components/app/configuration/base/feature-panel' |
| | | import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import { LinkExternal02, Settings04 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import ConfigContext from '@/context/debug-configuration' |
| | | import type { EmbeddingModelConfig } from '@/app/components/app/annotation/type' |
| | | import { fetchAnnotationConfig, updateAnnotationScore } from '@/service/annotation' |
| | | import type { AnnotationReplyConfig as AnnotationReplyConfigType } from '@/models/debug' |
| | | |
| | | type Props = { |
| | | onEmbeddingChange: (embeddingModel: EmbeddingModelConfig) => void |
| | | onScoreChange: (score: number, embeddingModel?: EmbeddingModelConfig) => void |
| | | } |
| | | |
| | | export const Item: FC<{ title: string; tooltip: string; children: JSX.Element }> = ({ |
| | | title, |
| | | tooltip, |
| | | children, |
| | | }) => { |
| | | return ( |
| | | <div> |
| | | <div className='flex items-center space-x-1'> |
| | | <div>{title}</div> |
| | | <Tooltip |
| | | popupContent={ |
| | | <div className='max-w-[200px] leading-[18px] text-[13px] font-medium text-gray-800'>{tooltip}</div> |
| | | } |
| | | /> |
| | | </div> |
| | | <div>{children}</div> |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | const AnnotationReplyConfig: FC<Props> = ({ |
| | | onEmbeddingChange, |
| | | onScoreChange, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const router = useRouter() |
| | | const pathname = usePathname() |
| | | const matched = pathname.match(/\/app\/([^/]+)/) |
| | | const appId = (matched?.length && matched[1]) ? matched[1] : '' |
| | | const { |
| | | annotationConfig, |
| | | } = useContext(ConfigContext) |
| | | |
| | | const [isShowEdit, setIsShowEdit] = React.useState(false) |
| | | |
| | | return ( |
| | | <> |
| | | <Panel |
| | | className="mt-4" |
| | | headerIcon={ |
| | | <MessageFast className='w-4 h-4 text-[#444CE7]' /> |
| | | } |
| | | title={t('appDebug.feature.annotation.title')} |
| | | headerRight={ |
| | | <div className='flex items-center'> |
| | | <div |
| | | className='flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200' |
| | | onClick={() => { setIsShowEdit(true) }} |
| | | > |
| | | <Settings04 className="w-[14px] h-[14px]" /> |
| | | <div className='text-xs font-medium'> |
| | | |
| | | {t('common.operation.params')} |
| | | </div> |
| | | </div> |
| | | <div |
| | | className='ml-1 flex items-center h-7 px-3 space-x-1 leading-[18px] text-xs font-medium text-gray-700 rounded-md cursor-pointer hover:bg-gray-200' |
| | | onClick={() => { |
| | | router.push(`/app/${appId}/annotations`) |
| | | }}> |
| | | <div>{t('appDebug.feature.annotation.cacheManagement')}</div> |
| | | <LinkExternal02 className='w-3.5 h-3.5' /> |
| | | </div> |
| | | </div> |
| | | } |
| | | noBodySpacing |
| | | /> |
| | | {isShowEdit && ( |
| | | <ConfigParamModal |
| | | appId={appId} |
| | | isShow |
| | | onHide={() => { |
| | | setIsShowEdit(false) |
| | | }} |
| | | onSave={async (embeddingModel, score) => { |
| | | const annotationConfig = await fetchAnnotationConfig(appId) as AnnotationReplyConfigType |
| | | let isEmbeddingModelChanged = false |
| | | if ( |
| | | embeddingModel.embedding_model_name !== annotationConfig.embedding_model.embedding_model_name |
| | | || embeddingModel.embedding_provider_name !== annotationConfig.embedding_model.embedding_provider_name |
| | | ) { |
| | | await onEmbeddingChange(embeddingModel) |
| | | isEmbeddingModelChanged = true |
| | | } |
| | | |
| | | if (score !== annotationConfig.score_threshold) { |
| | | await updateAnnotationScore(appId, annotationConfig.id, score) |
| | | if (isEmbeddingModelChanged) |
| | | onScoreChange(score, embeddingModel) |
| | | |
| | | else |
| | | onScoreChange(score) |
| | | } |
| | | |
| | | setIsShowEdit(false) |
| | | }} |
| | | annotationConfig={annotationConfig} |
| | | /> |
| | | )} |
| | | </> |
| | | ) |
| | | } |
| | | export default React.memo(AnnotationReplyConfig) |
New file |
| | |
| | | 'use client' |
| | | |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import GroupName from '../base/group-name' |
| | | import Moderation from './moderation' |
| | | import Annotation from './annotation/config-param' |
| | | import type { EmbeddingModelConfig } from '@/app/components/app/annotation/type' |
| | | |
| | | export type ToolboxProps = { |
| | | showModerationSettings: boolean |
| | | showAnnotation: boolean |
| | | onEmbeddingChange: (embeddingModel: EmbeddingModelConfig) => void |
| | | onScoreChange: (score: number, embeddingModel?: EmbeddingModelConfig) => void |
| | | } |
| | | |
| | | const Toolbox: FC<ToolboxProps> = ({ |
| | | showModerationSettings, |
| | | showAnnotation, |
| | | onEmbeddingChange, |
| | | onScoreChange, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className='mt-7'> |
| | | <GroupName name={t('appDebug.feature.toolbox.title')} /> |
| | | { |
| | | showModerationSettings && ( |
| | | <Moderation /> |
| | | ) |
| | | } |
| | | { |
| | | showAnnotation && ( |
| | | <Annotation |
| | | onEmbeddingChange={onEmbeddingChange} |
| | | onScoreChange={onScoreChange} |
| | | /> |
| | | ) |
| | | } |
| | | </div> |
| | | ) |
| | | } |
| | | export default React.memo(Toolbox) |
| | |
| | | } from '@/models/common' |
| | | import { useToastContext } from '@/app/components/base/toast' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | const systemTypes = ['api'] |
| | | type ExternalDataToolModalProps = { |
| | |
| | | return |
| | | } |
| | | |
| | | if (localeData.variable && !/[a-zA-Z_]\w{0,29}/g.test(localeData.variable)) { |
| | | if (localeData.variable && !/[a-zA-Z_][a-zA-Z0-9_]{0,29}/g.test(localeData.variable)) { |
| | | notify({ type: 'error', message: t('appDebug.varKeyError.notValid', { key: t('appDebug.feature.tools.modal.variableName.title') }) }) |
| | | return |
| | | } |
| | |
| | | return ( |
| | | <Modal |
| | | isShow |
| | | onClose={noop} |
| | | className='!w-[640px] !max-w-none !p-8 !pb-6' |
| | | onClose={() => { }} |
| | | className='!p-8 !pb-6 !max-w-none !w-[640px]' |
| | | > |
| | | <div className='mb-2 text-xl font-semibold text-gray-900'> |
| | | {`${action} ${t('appDebug.variableConfig.apiBasedVar')}`} |
| | | </div> |
| | | <div className='py-2'> |
| | | <div className='text-sm font-medium leading-9 text-gray-900'> |
| | | <div className='leading-9 text-sm font-medium text-gray-900'> |
| | | {t('common.apiBasedExtension.type')} |
| | | </div> |
| | | <SimpleSelect |
| | |
| | | /> |
| | | </div> |
| | | <div className='py-2'> |
| | | <div className='text-sm font-medium leading-9 text-gray-900'> |
| | | <div className='leading-9 text-sm font-medium text-gray-900'> |
| | | {t('appDebug.feature.tools.modal.name.title')} |
| | | </div> |
| | | <div className='flex items-center'> |
| | | <input |
| | | value={localeData.label || ''} |
| | | onChange={e => handleValueChange({ label: e.target.value })} |
| | | className='mr-2 block h-9 grow appearance-none rounded-lg bg-gray-100 px-3 text-sm text-gray-900 outline-none' |
| | | className='grow block mr-2 px-3 h-9 bg-gray-100 rounded-lg text-sm text-gray-900 outline-none appearance-none' |
| | | placeholder={t('appDebug.feature.tools.modal.name.placeholder') || ''} |
| | | /> |
| | | <AppIcon size='large' |
| | | onClick={() => { setShowEmojiPicker(true) }} |
| | | className='!h-9 !w-9 cursor-pointer rounded-lg border-[0.5px] border-black/5 ' |
| | | className='!w-9 !h-9 rounded-lg border-[0.5px] border-black/5 cursor-pointer ' |
| | | icon={localeData.icon} |
| | | background={localeData.icon_background} |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div className='py-2'> |
| | | <div className='text-sm font-medium leading-9 text-gray-900'> |
| | | <div className='leading-9 text-sm font-medium text-gray-900'> |
| | | {t('appDebug.feature.tools.modal.variableName.title')} |
| | | </div> |
| | | <input |
| | | value={localeData.variable || ''} |
| | | onChange={e => handleValueChange({ variable: e.target.value })} |
| | | className='block h-9 w-full appearance-none rounded-lg bg-gray-100 px-3 text-sm text-gray-900 outline-none' |
| | | className='block px-3 w-full h-9 bg-gray-100 rounded-lg text-sm text-gray-900 outline-none appearance-none' |
| | | placeholder={t('appDebug.feature.tools.modal.variableName.placeholder') || ''} |
| | | /> |
| | | </div> |
| | | { |
| | | localeData.type === 'api' && ( |
| | | <div className='py-2'> |
| | | <div className='flex h-9 items-center justify-between text-sm font-medium text-gray-900'> |
| | | <div className='flex justify-between items-center h-9 text-sm font-medium text-gray-900'> |
| | | {t('common.apiBasedExtension.selector.title')} |
| | | <a |
| | | href={t('common.apiBasedExtension.linkUrl') || '/'} |
| | | target='_blank' rel='noopener noreferrer' |
| | | className='group flex items-center text-xs font-normal text-gray-500 hover:text-primary-600' |
| | | > |
| | | <BookOpen01 className='mr-1 h-3 w-3 text-gray-500 group-hover:text-primary-600' /> |
| | | <BookOpen01 className='mr-1 w-3 h-3 text-gray-500 group-hover:text-primary-600' /> |
| | | {t('common.apiBasedExtension.link')} |
| | | </a> |
| | | </div> |
| | |
| | | /> |
| | | ) |
| | | } |
| | | <div className='mt-6 flex items-center justify-end'> |
| | | <div className='flex items-center justify-end mt-6'> |
| | | <Button |
| | | onClick={onCancel} |
| | | className='mr-2' |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className='mt-3 rounded-xl bg-gray-50 px-3'> |
| | | <div className='flex h-12 items-center'> |
| | | <div className='flex grow items-center'> |
| | | <div className='mt-3 px-3 rounded-xl bg-gray-50'> |
| | | <div className='flex items-center h-12'> |
| | | <div className='grow flex items-center'> |
| | | <div |
| | | className={` |
| | | group mr-1 flex h-6 w-6 items-center justify-center rounded-md |
| | | ${externalDataToolsConfig.length && 'hover:bg-white hover:shadow-xs'} |
| | | group flex items-center justify-center mr-1 w-6 h-6 rounded-md |
| | | ${externalDataToolsConfig.length && 'hover:shadow-xs hover:bg-white'} |
| | | `} |
| | | onClick={() => setExpanded(v => !v)} |
| | | > |
| | | { |
| | | externalDataToolsConfig.length |
| | | ? <Tool03 className='h-4 w-4 text-[#444CE7] group-hover:hidden' /> |
| | | : <Tool03 className='h-4 w-4 text-[#444CE7]' /> |
| | | ? <Tool03 className='group-hover:hidden w-4 h-4 text-[#444CE7]' /> |
| | | : <Tool03 className='w-4 h-4 text-[#444CE7]' /> |
| | | } |
| | | { |
| | | !!externalDataToolsConfig.length && ( |
| | | <RiArrowDownSLine className={`hidden h-4 w-4 cursor-pointer text-primary-600 group-hover:block ${expanded ? 'rotate-180' : 'rotate-0'}`} /> |
| | | <RiArrowDownSLine className={`hidden group-hover:block w-4 h-4 text-primary-600 cursor-pointer ${expanded ? 'rotate-180' : 'rotate-0'}`} /> |
| | | ) |
| | | } |
| | | </div> |
| | |
| | | !expanded && !!externalDataToolsConfig.length && ( |
| | | <> |
| | | <div className='mr-3 text-xs text-gray-500'>{t('appDebug.feature.tools.toolsInUse', { count: externalDataToolsConfig.length })}</div> |
| | | <div className='mr-1 h-3.5 w-[1px] bg-gray-200' /> |
| | | <div className='mr-1 w-[1px] h-3.5 bg-gray-200' /> |
| | | </> |
| | | ) |
| | | } |
| | | <div |
| | | className='flex h-7 cursor-pointer items-center px-3 text-xs font-medium text-gray-700' |
| | | className='flex items-center h-7 px-3 text-xs font-medium text-gray-700 cursor-pointer' |
| | | onClick={() => handleOpenExternalDataToolModal({}, -1)} |
| | | > |
| | | <RiAddLine className='mr-[5px] h-3.5 w-3.5 ' /> |
| | | <RiAddLine className='mr-[5px] w-3.5 h-3.5 ' /> |
| | | {t('common.operation.add')} |
| | | </div> |
| | | </div> |
| | |
| | | externalDataToolsConfig.map((item, index: number) => ( |
| | | <div |
| | | key={`${index}-${item.type}-${item.label}-${item.variable}`} |
| | | className='group mb-1 flex items-center rounded-lg border-[0.5px] border-gray-200 bg-white px-2.5 py-2 shadow-xs last-of-type:mb-0' |
| | | className='group flex items-center mb-1 last-of-type:mb-0 px-2.5 py-2 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs' |
| | | > |
| | | <div className='flex grow items-center'> |
| | | <div className='grow flex items-center'> |
| | | <AppIcon size='large' |
| | | className='mr-2 !h-6 !w-6 rounded-md border-[0.5px] border-black/5' |
| | | className='mr-2 !w-6 !h-6 rounded-md border-[0.5px] border-black/5' |
| | | icon={item.icon} |
| | | background={item.icon_background} |
| | | /> |
| | |
| | | </Tooltip> |
| | | </div> |
| | | <div |
| | | className='mr-1 hidden h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-black/5 group-hover:flex' |
| | | className='hidden group-hover:flex items-center justify-center mr-1 w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' |
| | | onClick={() => handleOpenExternalDataToolModal(item, index)} |
| | | > |
| | | <Settings01 className='h-4 w-4 text-gray-500' /> |
| | | <Settings01 className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | <div |
| | | className='group/action hidden h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-[#FEE4E2] group-hover:flex' |
| | | className='hidden group/action group-hover:flex items-center justify-center w-6 h-6 hover:bg-[#FEE4E2] rounded-md cursor-pointer' |
| | | onClick={() => setExternalDataToolsConfig([...externalDataToolsConfig.slice(0, index), ...externalDataToolsConfig.slice(index + 1)])} |
| | | > |
| | | <RiDeleteBinLine className='h-4 w-4 text-gray-500 group-hover/action:text-[#D92D20]' /> |
| | | <RiDeleteBinLine className='w-4 h-4 text-gray-500 group-hover/action:text-[#D92D20]' /> |
| | | </div> |
| | | <div className='ml-2 mr-3 hidden h-3.5 w-[1px] bg-gray-200 group-hover:block' /> |
| | | <div className='hidden group-hover:block ml-2 mr-3 w-[1px] h-3.5 bg-gray-200' /> |
| | | <Switch |
| | | size='l' |
| | | defaultValue={item.enabled} |
| | |
| | | const { t } = useTranslation() |
| | | const { app: appBasicInfo } = app |
| | | return ( |
| | | <div className={cn('group relative flex h-[132px] cursor-pointer flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 shadow-xs hover:shadow-lg')}> |
| | | <div className='flex shrink-0 grow-0 items-center gap-3 pb-2'> |
| | | <div className={cn('p-4 h-[132px] relative overflow-hidden flex flex-col group bg-components-panel-on-panel-item-bg border-[0.5px] border-components-panel-border rounded-xl shadow-xs hover:shadow-lg cursor-pointer')}> |
| | | <div className='flex items-center gap-3 pb-2 grow-0 shrink-0'> |
| | | <div className='relative shrink-0'> |
| | | <AppIcon |
| | | size='large' |
| | |
| | | imageUrl={appBasicInfo.icon_url} |
| | | /> |
| | | <AppTypeIcon wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-[4px] border border-divider-regular outline outline-components-panel-on-panel-item-bg' |
| | | className='h-3 w-3' type={appBasicInfo.mode} /> |
| | | className='w-3 h-3' type={appBasicInfo.mode} /> |
| | | </div> |
| | | <div className='flex grow flex-col gap-1'> |
| | | <div className='grow flex flex-col gap-1'> |
| | | <div className='line-clamp-1'> |
| | | <span className='system-md-semibold text-text-secondary' title={appBasicInfo.name}>{appBasicInfo.name}</span> |
| | | </div> |
| | | <AppTypeLabel className='system-2xs-medium-uppercase text-text-tertiary' type={app.app.mode} /> |
| | | </div> |
| | | </div> |
| | | <div className="system-xs-regular py-1 text-text-tertiary"> |
| | | <div className="py-1 system-xs-regular text-text-tertiary"> |
| | | <div className='line-clamp-3'> |
| | | {app.description} |
| | | </div> |
| | | </div> |
| | | <div className={cn('absolute bottom-0 left-0 right-0 hidden bg-gradient-to-t from-components-panel-gradient-2 from-[60.27%] to-transparent p-4 pt-8 group-hover:flex')}> |
| | | <div className={cn('flex h-8 w-full items-center space-x-2')}> |
| | | <div className={cn('hidden absolute bottom-0 left-0 right-0 p-4 pt-8 group-hover:flex bg-gradient-to-t from-[60.27%] from-components-panel-gradient-2 to-transparent')}> |
| | | <div className={cn('flex items-center w-full space-x-2 h-8')}> |
| | | <Button variant='primary' className='grow' onClick={() => onCreate()}> |
| | | <PlusIcon className='mr-1 h-4 w-4' /> |
| | | <PlusIcon className='w-4 h-4 mr-1' /> |
| | | <span className='text-xs'>{t('app.newApp.useTemplate')}</span> |
| | | </Button> |
| | | </div> |
| | |
| | | import Input from '@/app/components/base/input' |
| | | import type { AppMode } from '@/types/app' |
| | | import { DSLImportMode } from '@/models/app' |
| | | import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' |
| | | |
| | | type AppsProps = { |
| | | onSuccess?: () => void |
| | |
| | | |
| | | const [currApp, setCurrApp] = React.useState<App | null>(null) |
| | | const [isShowCreateModal, setIsShowCreateModal] = React.useState(false) |
| | | const { handleCheckPluginDependencies } = usePluginDependencies() |
| | | const onCreate: CreateAppModalProps['onConfirm'] = async ({ |
| | | name, |
| | | icon_type, |
| | |
| | | icon_background, |
| | | description, |
| | | }) => { |
| | | const { export_data, mode } = await fetchAppDetail( |
| | | const { export_data } = await fetchAppDetail( |
| | | currApp?.app.id as string, |
| | | ) |
| | | try { |
| | |
| | | }) |
| | | if (onSuccess) |
| | | onSuccess() |
| | | if (app.app_id) |
| | | await handleCheckPluginDependencies(app.app_id) |
| | | localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') |
| | | getRedirection(isCurrentWorkspaceEditor, { id: app.app_id!, mode }, push) |
| | | getRedirection(isCurrentWorkspaceEditor, { id: app.app_id }, push) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className='flex h-full flex-col'> |
| | | <div className='flex items-center justify-between border-b border-divider-burn py-3'> |
| | | <div className='h-full flex flex-col'> |
| | | <div className='flex justify-between items-center py-3 border-b border-divider-burn'> |
| | | <div className='min-w-[180px] pl-5'> |
| | | <span className='title-xl-semi-bold text-text-primary'>{t('app.newApp.startFromTemplate')}</span> |
| | | </div> |
| | | <div className='flex max-w-[548px] flex-1 items-center rounded-xl border border-components-panel-border bg-components-panel-bg-blur p-1.5 shadow-md'> |
| | | <div className='flex-1 max-w-[548px] p-1.5 flex items-center rounded-xl shadow-md bg-components-panel-bg-blur border border-components-panel-border'> |
| | | <AppTypeSelector value={currentType} onChange={setCurrentType} /> |
| | | <div className='h-[14px]'> |
| | | <Divider type='vertical' /> |
| | |
| | | <Input |
| | | showClearIcon |
| | | wrapperClassName='w-full flex-1' |
| | | className='bg-transparent hover:border-transparent hover:bg-transparent focus:border-transparent focus:bg-transparent focus:shadow-none' |
| | | className='bg-transparent hover:bg-transparent focus:bg-transparent hover:border-transparent focus:border-transparent focus:shadow-none' |
| | | placeholder={t('app.newAppFromTemplate.searchAllTemplate') as string} |
| | | value={keywords} |
| | | onChange={e => handleKeywordsChange(e.target.value)} |
| | | onClear={() => handleKeywordsChange('')} |
| | | /> |
| | | </div> |
| | | <div className='h-8 w-[180px]'></div> |
| | | <div className='w-[180px] h-8'></div> |
| | | </div> |
| | | <div className='relative flex flex-1 overflow-y-auto'> |
| | | {!searchKeywords && <div className='h-full w-[200px] p-4'> |
| | | <Sidebar current={currCategory as AppCategories} categories={categories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> |
| | | {!searchKeywords && <div className='w-[200px] h-full p-4'> |
| | | <Sidebar current={currCategory as AppCategories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> |
| | | </div>} |
| | | <div className='h-full flex-1 shrink-0 grow overflow-auto border-l border-divider-burn p-6 pt-2'> |
| | | <div className='flex-1 h-full overflow-auto shrink-0 grow p-6 pt-2 border-l border-divider-burn'> |
| | | {searchFilteredList && searchFilteredList.length > 0 && <> |
| | | <div className='pb-1 pt-4'> |
| | | <div className='pt-4 pb-1'> |
| | | {searchKeywords |
| | | ? <p className='title-md-semi-bold text-text-tertiary'>{searchFilteredList.length > 1 ? t('app.newApp.foundResults', { count: searchFilteredList.length }) : t('app.newApp.foundResult', { count: searchFilteredList.length })}</p> |
| | | : <div className='flex h-[22px] items-center'> |
| | | <AppCategoryLabel category={currCategory as AppCategories} className='title-md-semi-bold text-text-primary' /> |
| | | </div>} |
| | | : <AppCategoryLabel category={currCategory as AppCategories} className='title-md-semi-bold text-text-primary' />} |
| | | </div> |
| | | <div |
| | | className={cn( |
| | | 'grid shrink-0 grid-cols-1 content-start gap-3 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6', |
| | | 'grid content-start shrink-0 gap-3 grid-cols-1 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6', |
| | | )}> |
| | | {searchFilteredList.map(app => ( |
| | | <AppCard |
| | |
| | | |
| | | function NoTemplateFound() { |
| | | const { t } = useTranslation() |
| | | return <div className='w-full rounded-lg bg-workflow-process-bg p-4'> |
| | | <div className='mb-2 inline-flex h-8 w-8 items-center justify-center rounded-lg bg-components-card-bg shadow-lg'> |
| | | <RiRobot2Line className='h-5 w-5 text-text-tertiary' /> |
| | | return <div className='p-4 rounded-lg w-full bg-workflow-process-bg'> |
| | | <div className='w-8 h-8 rounded-lg inline-flex items-center justify-center mb-2 shadow-lg bg-components-card-bg'> |
| | | <RiRobot2Line className='w-5 h-5 text-text-tertiary' /> |
| | | </div> |
| | | <p className='title-md-semi-bold text-text-primary'>{t('app.newApp.noTemplateFound')}</p> |
| | | <p className='system-sm-regular text-text-tertiary'>{t('app.newApp.noTemplateFoundTip')}</p> |
| | |
| | | 'use client' |
| | | import { RiStickyNoteAddLine, RiThumbUpLine } from '@remixicon/react' |
| | | import { RiAppsFill, RiChatSmileAiFill, RiExchange2Fill, RiPassPendingFill, RiQuillPenAiFill, RiSpeakAiFill, RiStickyNoteAddLine, RiTerminalBoxFill, RiThumbUpFill } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import classNames from '@/utils/classnames' |
| | | import Divider from '@/app/components/base/divider' |
| | | |
| | | export enum AppCategories { |
| | | RECOMMENDED = 'Recommended', |
| | | ASSISTANT = 'Assistant', |
| | | AGENT = 'Agent', |
| | | HR = 'HR', |
| | | PROGRAMMING = 'Programming', |
| | | WORKFLOW = 'Workflow', |
| | | WRITING = 'Writing', |
| | | } |
| | | |
| | | type SidebarProps = { |
| | | current: AppCategories | string |
| | | categories: string[] |
| | | onClick?: (category: AppCategories | string) => void |
| | | current: AppCategories |
| | | onClick?: (category: AppCategories) => void |
| | | onCreateFromBlank?: () => void |
| | | } |
| | | |
| | | export default function Sidebar({ current, categories, onClick, onCreateFromBlank }: SidebarProps) { |
| | | export default function Sidebar({ current, onClick, onCreateFromBlank }: SidebarProps) { |
| | | const { t } = useTranslation() |
| | | return <div className="flex h-full w-full flex-col"> |
| | | <ul className='pt-0.5'> |
| | | return <div className="w-full h-full flex flex-col"> |
| | | <ul> |
| | | <CategoryItem category={AppCategories.RECOMMENDED} active={current === AppCategories.RECOMMENDED} onClick={onClick} /> |
| | | </ul> |
| | | <div className='system-xs-medium-uppercase mb-0.5 mt-3 px-3 pb-1 pt-2 text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div> |
| | | <ul className='flex grow flex-col gap-0.5'> |
| | | {categories.map(category => (<CategoryItem key={category} category={category} active={current === category} onClick={onClick} />))} |
| | | <div className='px-3 pt-2 pb-1 system-xs-medium-uppercase text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div> |
| | | <ul className='flex-grow flex flex-col gap-0.5'> |
| | | <CategoryItem category={AppCategories.ASSISTANT} active={current === AppCategories.ASSISTANT} onClick={onClick} /> |
| | | <CategoryItem category={AppCategories.AGENT} active={current === AppCategories.AGENT} onClick={onClick} /> |
| | | <CategoryItem category={AppCategories.HR} active={current === AppCategories.HR} onClick={onClick} /> |
| | | <CategoryItem category={AppCategories.PROGRAMMING} active={current === AppCategories.PROGRAMMING} onClick={onClick} /> |
| | | <CategoryItem category={AppCategories.WORKFLOW} active={current === AppCategories.WORKFLOW} onClick={onClick} /> |
| | | <CategoryItem category={AppCategories.WRITING} active={current === AppCategories.WRITING} onClick={onClick} /> |
| | | </ul> |
| | | <Divider bgStyle='gradient' /> |
| | | <div className='flex cursor-pointer items-center gap-1 px-3 py-1 text-text-tertiary' onClick={onCreateFromBlank}> |
| | | <RiStickyNoteAddLine className='h-3.5 w-3.5' /> |
| | | <div className='px-3 py-1 flex items-center gap-1 text-text-tertiary cursor-pointer' onClick={onCreateFromBlank}> |
| | | <RiStickyNoteAddLine className='w-3.5 h-3.5' /> |
| | | <span className='system-xs-regular'>{t('app.newApp.startFromBlank')}</span> |
| | | </div> |
| | | </div> |
| | |
| | | |
| | | type CategoryItemProps = { |
| | | active: boolean |
| | | category: AppCategories | string |
| | | onClick?: (category: AppCategories | string) => void |
| | | category: AppCategories |
| | | onClick?: (category: AppCategories) => void |
| | | } |
| | | function CategoryItem({ category, active, onClick }: CategoryItemProps) { |
| | | return <li |
| | | className={classNames('p-1 pl-3 h-8 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')} |
| | | className={classNames('p-1 pl-3 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')} |
| | | onClick={() => { onClick?.(category) }}> |
| | | {category === AppCategories.RECOMMENDED && <div className='inline-flex h-5 w-5 items-center justify-center rounded-md'> |
| | | <RiThumbUpLine className='h-4 w-4 text-components-menu-item-text group-[.active]:text-components-menu-item-text-active' /> |
| | | </div>} |
| | | <div className='w-5 h-5 inline-flex items-center justify-center rounded-md border border-divider-regular bg-components-icon-bg-midnight-solid group-[.active]:bg-components-icon-bg-blue-solid'> |
| | | <AppCategoryIcon category={category} /> |
| | | </div> |
| | | <AppCategoryLabel category={category} |
| | | className={classNames('system-sm-medium text-components-menu-item-text group-[.active]:text-components-menu-item-text-active group-hover:text-components-menu-item-text-hover', active && 'system-sm-semibold')} /> |
| | | </li > |
| | | } |
| | | |
| | | type AppCategoryLabelProps = { |
| | | category: AppCategories | string |
| | | category: AppCategories |
| | | className?: string |
| | | } |
| | | export function AppCategoryLabel({ category, className }: AppCategoryLabelProps) { |
| | | const { t } = useTranslation() |
| | | return <span className={className}>{category === AppCategories.RECOMMENDED ? t('app.newAppFromTemplate.sidebar.Recommended') : category}</span> |
| | | return <span className={className}>{t(`app.newAppFromTemplate.sidebar.${category}`)}</span> |
| | | } |
| | | |
| | | type AppCategoryIconProps = { |
| | | category: AppCategories |
| | | } |
| | | function AppCategoryIcon({ category }: AppCategoryIconProps) { |
| | | if (category === AppCategories.AGENT) |
| | | return <RiSpeakAiFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' /> |
| | | if (category === AppCategories.ASSISTANT) |
| | | return <RiChatSmileAiFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' /> |
| | | if (category === AppCategories.HR) |
| | | return <RiPassPendingFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' /> |
| | | if (category === AppCategories.PROGRAMMING) |
| | | return <RiTerminalBoxFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' /> |
| | | if (category === AppCategories.RECOMMENDED) |
| | | return <RiThumbUpFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' /> |
| | | if (category === AppCategories.WRITING) |
| | | return <RiQuillPenAiFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' /> |
| | | if (category === AppCategories.WORKFLOW) |
| | | return <RiExchange2Fill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' /> |
| | | return <RiAppsFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' /> |
| | | } |
| | |
| | | 'use client' |
| | | import { useCallback } from 'react' |
| | | import { useKeyPress } from 'ahooks' |
| | | import AppList from './app-list' |
| | | import FullScreenModal from '@/app/components/base/fullscreen-modal' |
| | | |
| | |
| | | } |
| | | |
| | | const CreateAppTemplateDialog = ({ show, onSuccess, onClose, onCreateFromBlank }: CreateAppDialogProps) => { |
| | | const handleEscKeyPress = useCallback(() => { |
| | | if (show) |
| | | onClose() |
| | | }, [show, onClose]) |
| | | |
| | | useKeyPress('esc', handleEscKeyPress) |
| | | |
| | | return ( |
| | | <FullScreenModal |
| | | open={show} |
New file |
| | |
| | | import { Fragment, useCallback } from 'react' |
| | | import type { ReactNode } from 'react' |
| | | import { Dialog, Transition } from '@headlessui/react' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type DialogProps = { |
| | | className?: string |
| | | children: ReactNode |
| | | show: boolean |
| | | onClose?: () => void |
| | | } |
| | | |
| | | const NewAppDialog = ({ |
| | | className, |
| | | children, |
| | | show, |
| | | onClose, |
| | | }: DialogProps) => { |
| | | const close = useCallback(() => onClose?.(), [onClose]) |
| | | return ( |
| | | <Transition appear show={show} as={Fragment}> |
| | | <Dialog as="div" className="relative z-40" onClose={close}> |
| | | <Transition.Child |
| | | as={Fragment} |
| | | enter="ease-out duration-300" |
| | | enterFrom="opacity-0" |
| | | enterTo="opacity-100" |
| | | leave="ease-in duration-200" |
| | | leaveFrom="opacity-100" |
| | | leaveTo="opacity-0" |
| | | > |
| | | <div className="fixed inset-0 bg-black bg-opacity-25" /> |
| | | </Transition.Child> |
| | | |
| | | <div className="fixed inset-0"> |
| | | <div className="flex flex-col items-center justify-center min-h-full pt-[56px]"> |
| | | <Transition.Child |
| | | as={Fragment} |
| | | enter="ease-out duration-300" |
| | | enterFrom="opacity-0 scale-95" |
| | | enterTo="opacity-100 scale-100" |
| | | leave="ease-in duration-200" |
| | | leaveFrom="opacity-100 scale-100" |
| | | leaveTo="opacity-0 scale-95" |
| | | > |
| | | <Dialog.Panel className={cn('grow relative w-full h-[calc(100vh-56px)] p-0 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-t-xl', className)}> |
| | | {children} |
| | | </Dialog.Panel> |
| | | </Transition.Child> |
| | | </div> |
| | | </div> |
| | | </Dialog> |
| | | </Transition > |
| | | ) |
| | | } |
| | | |
| | | export default NewAppDialog |
| | |
| | | 'use client' |
| | | |
| | | import { useCallback, useEffect, useRef, useState } from 'react' |
| | | import { useCallback, useRef, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | |
| | | import { useRouter, useSearchParams } from 'next/navigation' |
| | | import { useRouter } from 'next/navigation' |
| | | import { useContext, useContextSelector } from 'use-context-selector' |
| | | import { RiArrowRightLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react' |
| | | import Link from 'next/link' |
| | |
| | | import Button from '@/app/components/base/button' |
| | | import Divider from '@/app/components/base/divider' |
| | | import cn from '@/utils/classnames' |
| | | import { WEB_PREFIX } from '@/config' |
| | | import AppsContext, { useAppContext } from '@/context/app-context' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import { ToastContext } from '@/app/components/base/toast' |
| | | import type { AppMode } from '@/types/app' |
| | | import { AppModes } from '@/types/app' |
| | | import { createApp } from '@/service/apps' |
| | | import Input from '@/app/components/base/input' |
| | | import Textarea from '@/app/components/base/textarea' |
| | |
| | | import { NEED_REFRESH_APP_LIST_KEY } from '@/config' |
| | | import { getRedirection } from '@/utils/app-redirection' |
| | | import FullScreenModal from '@/app/components/base/fullscreen-modal' |
| | | import useTheme from '@/hooks/use-theme' |
| | | |
| | | type CreateAppProps = { |
| | | onSuccess: () => void |
| | |
| | | const { isCurrentWorkspaceEditor } = useAppContext() |
| | | |
| | | const isCreatingRef = useRef(false) |
| | | |
| | | const searchParams = useSearchParams() |
| | | |
| | | useEffect(() => { |
| | | const category = searchParams.get('category') |
| | | if (category && AppModes.includes(category as AppMode)) |
| | | setAppMode(category as AppMode) |
| | | }, [searchParams]) |
| | | |
| | | const onCreate = useCallback(async () => { |
| | | if (!appMode) { |
| | |
| | | localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') |
| | | getRedirection(isCurrentWorkspaceEditor, app, push) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | isCreatingRef.current = false |
| | |
| | | handleCreateApp() |
| | | }) |
| | | return <> |
| | | <div className='flex h-full justify-center overflow-y-auto overflow-x-hidden'> |
| | | <div className='flex flex-1 shrink-0 justify-end'> |
| | | <div className='flex justify-center h-full overflow-y-auto overflow-x-hidden'> |
| | | <div className='flex-1 shrink-0 flex justify-end'> |
| | | <div className='px-10'> |
| | | <div className='h-6 w-full 2xl:h-[139px]' /> |
| | | <div className='pb-6 pt-1'> |
| | | <div className='w-full h-6 2xl:h-[139px]' /> |
| | | <div className='pt-1 pb-6'> |
| | | <span className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.startFromBlank')}</span> |
| | | </div> |
| | | <div className='mb-2 leading-6'> |
| | | <div className='leading-6 mb-2'> |
| | | <span className='system-sm-semibold text-text-secondary'>{t('app.newApp.chooseAppType')}</span> |
| | | </div> |
| | | <div className='flex w-[660px] flex-col gap-4'> |
| | | <div className='flex flex-col w-[660px] gap-4'> |
| | | <div> |
| | | <div className='mb-2'> |
| | | <span className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.newApp.forBeginners')}</span> |
| | |
| | | active={appMode === 'chat'} |
| | | title={t('app.types.chatbot')} |
| | | description={t('app.newApp.chatbotShortDescription')} |
| | | icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-blue-solid'> |
| | | <ChatBot className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> |
| | | icon={<div className='w-6 h-6 bg-components-icon-bg-blue-solid rounded-md flex items-center justify-center'> |
| | | <ChatBot className='w-4 h-4 text-components-avatar-shape-fill-stop-100' /> |
| | | </div>} |
| | | onClick={() => { |
| | | setAppMode('chat') |
| | |
| | | active={appMode === 'agent-chat'} |
| | | title={t('app.types.agent')} |
| | | description={t('app.newApp.agentShortDescription')} |
| | | icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-violet-solid'> |
| | | <Logic className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> |
| | | icon={<div className='w-6 h-6 bg-components-icon-bg-violet-solid rounded-md flex items-center justify-center'> |
| | | <Logic className='w-4 h-4 text-components-avatar-shape-fill-stop-100' /> |
| | | </div>} |
| | | onClick={() => { |
| | | setAppMode('agent-chat') |
| | |
| | | active={appMode === 'completion'} |
| | | title={t('app.newApp.completeApp')} |
| | | description={t('app.newApp.completionShortDescription')} |
| | | icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-teal-solid'> |
| | | <ListSparkle className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> |
| | | icon={<div className='w-6 h-6 bg-components-icon-bg-teal-solid rounded-md flex items-center justify-center'> |
| | | <ListSparkle className='w-4 h-4 text-components-avatar-shape-fill-stop-100' /> |
| | | </div>} |
| | | onClick={() => { |
| | | setAppMode('completion') |
| | |
| | | </div> |
| | | <div className='flex flex-row gap-2'> |
| | | <AppTypeCard |
| | | beta |
| | | active={appMode === 'advanced-chat'} |
| | | title={t('app.types.advanced')} |
| | | description={t('app.newApp.advancedShortDescription')} |
| | | icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-blue-light-solid'> |
| | | <BubbleTextMod className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> |
| | | icon={<div className='w-6 h-6 bg-components-icon-bg-blue-light-solid rounded-md flex items-center justify-center'> |
| | | <BubbleTextMod className='w-4 h-4 text-components-avatar-shape-fill-stop-100' /> |
| | | </div>} |
| | | onClick={() => { |
| | | setAppMode('advanced-chat') |
| | | }} /> |
| | | <AppTypeCard |
| | | beta |
| | | active={appMode === 'workflow'} |
| | | title={t('app.types.workflow')} |
| | | description={t('app.newApp.workflowShortDescription')} |
| | | icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-indigo-solid'> |
| | | <RiExchange2Fill className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> |
| | | icon={<div className='w-6 h-6 bg-components-icon-bg-indigo-solid rounded-md flex items-center justify-center'> |
| | | <RiExchange2Fill className='w-4 h-4 text-components-avatar-shape-fill-stop-100' /> |
| | | </div>} |
| | | onClick={() => { |
| | | setAppMode('workflow') |
| | |
| | | </div> |
| | | </div> |
| | | <Divider style={{ margin: 0 }} /> |
| | | <div className='flex items-center space-x-3'> |
| | | <div className='flex space-x-3 items-center'> |
| | | <div className='flex-1'> |
| | | <div className='mb-1 flex h-6 items-center'> |
| | | <div className='h-6 flex items-center mb-1'> |
| | | <label className='system-sm-semibold text-text-secondary'>{t('app.newApp.captionName')}</label> |
| | | </div> |
| | | <Input |
| | |
| | | />} |
| | | </div> |
| | | <div> |
| | | <div className='mb-1 flex h-6 items-center'> |
| | | <div className='h-6 flex items-center mb-1'> |
| | | <label className='system-sm-semibold text-text-secondary'>{t('app.newApp.captionDescription')}</label> |
| | | <span className='system-xs-regular ml-1 text-text-tertiary'>({t('app.newApp.optional')})</span> |
| | | <span className='system-xs-regular text-text-tertiary ml-1'>({t('app.newApp.optional')})</span> |
| | | </div> |
| | | <Textarea |
| | | className='resize-none' |
| | |
| | | /> |
| | | </div> |
| | | </div> |
| | | {isAppsFull && <AppsFull className='mt-4' loc='app-create' />} |
| | | <div className='flex items-center justify-between pb-10 pt-5'> |
| | | <div className='system-xs-regular flex cursor-pointer items-center gap-1 text-text-tertiary' onClick={onCreateFromTemplate}> |
| | | <div className='pt-5 pb-10 flex justify-between items-center'> |
| | | <div className='flex gap-1 items-center system-xs-regular text-text-tertiary cursor-pointer' onClick={onCreateFromTemplate}> |
| | | <span>{t('app.newApp.noIdeaTip')}</span> |
| | | <div className='p-[1px]'> |
| | | <RiArrowRightLine className='h-3.5 w-3.5' /> |
| | | <RiArrowRightLine className='w-3.5 h-3.5' /> |
| | | </div> |
| | | </div> |
| | | <div className='flex gap-2'> |
| | |
| | | <Button disabled={isAppsFull || !name} className='gap-1' variant="primary" onClick={handleCreateApp}> |
| | | <span>{t('app.newApp.Create')}</span> |
| | | <div className='flex gap-0.5'> |
| | | <RiCommandLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' /> |
| | | <RiCornerDownLeftLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' /> |
| | | <RiCommandLine size={14} className='p-0.5 system-kbd bg-components-kbd-bg-white rounded-sm' /> |
| | | <RiCornerDownLeftLine size={14} className='p-0.5 system-kbd bg-components-kbd-bg-white rounded-sm' /> |
| | | </div> |
| | | </Button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div className='relative flex h-full flex-1 shrink justify-start overflow-hidden'> |
| | | <div className='absolute left-0 right-0 top-0 h-6 border-b border-b-divider-subtle 2xl:h-[139px]'></div> |
| | | <div className='flex-1 shrink h-full flex justify-start relative overflow-hidden'> |
| | | <div className='h-6 2xl:h-[139px] absolute left-0 top-0 right-0 border-b border-b-divider-subtle'></div> |
| | | <div className='max-w-[760px] border-x border-x-divider-subtle'> |
| | | <div className='h-6 2xl:h-[139px]' /> |
| | | <AppPreview mode={appMode} /> |
| | | <div className='absolute left-0 right-0 border-b border-b-divider-subtle'></div> |
| | | <div className='flex h-[448px] w-[664px] items-center justify-center' style={{ background: 'repeating-linear-gradient(135deg, transparent, transparent 2px, rgba(16,24,40,0.04) 4px,transparent 3px, transparent 6px)' }}> |
| | | <div className='w-[664px] h-[448px] flex items-center justify-center' style={{ background: 'repeating-linear-gradient(135deg, transparent, transparent 2px, rgba(16,24,40,0.04) 4px,transparent 3px, transparent 6px)' }}> |
| | | <AppScreenShot show={appMode === 'chat'} mode='chat' /> |
| | | <AppScreenShot show={appMode === 'advanced-chat'} mode='advanced-chat' /> |
| | | <AppScreenShot show={appMode === 'agent-chat'} mode='agent-chat' /> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | { |
| | | isAppsFull && ( |
| | | <div className='px-8 py-2'> |
| | | <AppsFull loc='app-create' /> |
| | | </div> |
| | | ) |
| | | } |
| | | </> |
| | | } |
| | | type CreateAppDialogProps = CreateAppProps & { |
| | |
| | | export default CreateAppModal |
| | | |
| | | type AppTypeCardProps = { |
| | | icon: React.JSX.Element |
| | | icon: JSX.Element |
| | | beta?: boolean |
| | | title: string |
| | | description: string |
| | | active: boolean |
| | | onClick: () => void |
| | | } |
| | | function AppTypeCard({ icon, title, description, active, onClick }: AppTypeCardProps) { |
| | | function AppTypeCard({ icon, title, beta = false, description, active, onClick }: AppTypeCardProps) { |
| | | const { t } = useTranslation() |
| | | return <div |
| | | className={ |
| | | cn(`relative box-content h-[84px] w-[191px] cursor-pointer rounded-xl |
| | | border-[0.5px] border-components-option-card-option-border |
| | | bg-components-panel-on-panel-item-bg p-3 shadow-xs hover:shadow-md`, active |
| | | ? 'shadow-md outline outline-[1.5px] outline-components-option-card-option-selected-border' |
| | | cn(`w-[191px] h-[84px] p-3 border-[0.5px] relative box-content |
| | | rounded-xl border-components-option-card-option-border |
| | | bg-components-panel-on-panel-item-bg shadow-xs cursor-pointer hover:shadow-md`, active |
| | | ? 'outline outline-[1.5px] outline-components-option-card-option-selected-border shadow-md' |
| | | : '') |
| | | } |
| | | onClick={onClick} |
| | | > |
| | | {beta && <div className='px-[5px] py-[3px] |
| | | rounded-[5px] min-w-[18px] absolute top-3 right-3 |
| | | border border-divider-deep system-2xs-medium-uppercase text-text-tertiary'>{t('common.menus.status')}</div>} |
| | | {icon} |
| | | <div className='system-sm-semibold mb-0.5 mt-2 text-text-secondary'>{title}</div> |
| | | <div className='system-sm-semibold text-text-secondary mt-2 mb-0.5'>{title}</div> |
| | | <div className='system-xs-regular text-text-tertiary'>{description}</div> |
| | | </div> |
| | | } |
| | |
| | | 'chat': { |
| | | title: t('app.types.chatbot'), |
| | | description: t('app.newApp.chatbotUserDescription'), |
| | | link: 'https://docs.dify.ai/guides/application-orchestrate/readme', |
| | | link: 'https://docs.dify.ai/guides/application-orchestrate/conversation-application?fallback=true', |
| | | }, |
| | | 'advanced-chat': { |
| | | title: t('app.types.advanced'), |
| | | description: t('app.newApp.advancedUserDescription'), |
| | | link: 'https://docs.dify.ai/en/guides/workflow/README', |
| | | link: 'https://docs.dify.ai/guides/workflow', |
| | | }, |
| | | 'agent-chat': { |
| | | title: t('app.types.agent'), |
| | | description: t('app.newApp.agentUserDescription'), |
| | | link: 'https://docs.dify.ai/en/guides/application-orchestrate/agent', |
| | | link: 'https://docs.dify.ai/guides/application-orchestrate/agent', |
| | | }, |
| | | 'completion': { |
| | | title: t('app.newApp.completeApp'), |
| | |
| | | 'workflow': { |
| | | title: t('app.types.workflow'), |
| | | description: t('app.newApp.workflowUserDescription'), |
| | | link: 'https://docs.dify.ai/en/guides/workflow/README', |
| | | link: 'https://docs.dify.ai/guides/workflow', |
| | | }, |
| | | } |
| | | const previewInfo = modeToPreviewInfoMap[mode] |
| | | return <div className='px-8 py-4'> |
| | | <h4 className='system-sm-semibold-uppercase text-text-secondary'>{previewInfo.title}</h4> |
| | | <div className='system-xs-regular mt-1 min-h-8 max-w-96 text-text-tertiary'> |
| | | <div className='mt-1 system-xs-regular text-text-tertiary max-w-96 min-h-8'> |
| | | <span>{previewInfo.description}</span> |
| | | {previewInfo.link && <Link target='_blank' href={previewInfo.link} className='ml-1 text-text-accent'>{t('app.newApp.learnMore')}</Link>} |
| | | {previewInfo.link && <Link target='_blank' href={previewInfo.link} className='text-text-accent ml-1'>{t('app.newApp.learnMore')}</Link>} |
| | | </div> |
| | | </div> |
| | | } |
| | | |
| | | function AppScreenShot({ mode, show }: { mode: AppMode; show: boolean }) { |
| | | const { theme } = useTheme() |
| | | const theme = useContextSelector(AppsContext, state => state.theme) |
| | | const modeToImageMap = { |
| | | 'chat': 'Chatbot', |
| | | 'advanced-chat': 'Chatflow', |
| | |
| | | 'workflow': 'Workflow', |
| | | } |
| | | return <picture> |
| | | <source media="(resolution: 1x)" srcSet={`${WEB_PREFIX}/screenshots/${theme}/${modeToImageMap[mode]}.png`} /> |
| | | <source media="(resolution: 2x)" srcSet={`${WEB_PREFIX}/screenshots/${theme}/${modeToImageMap[mode]}@2x.png`} /> |
| | | <source media="(resolution: 3x)" srcSet={`${WEB_PREFIX}/screenshots/${theme}/${modeToImageMap[mode]}@3x.png`} /> |
| | | <source media="(resolution: 1x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}.png`} /> |
| | | <source media="(resolution: 2x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}@2x.png`} /> |
| | | <source media="(resolution: 3x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}@3x.png`} /> |
| | | <Image className={show ? '' : 'hidden'} |
| | | src={`${WEB_PREFIX}/screenshots/${theme}/${modeToImageMap[mode]}.png`} |
| | | src={`/screenshots/${theme}/${modeToImageMap[mode]}.png`} |
| | | alt='App Screen Shot' |
| | | width={664} height={448} /> |
| | | </picture> |
| | |
| | | import { useRouter } from 'next/navigation' |
| | | import { useContext } from 'use-context-selector' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react' |
| | | import { useDebounceFn, useKeyPress } from 'ahooks' |
| | | import { RiCloseLine } from '@remixicon/react' |
| | | import Uploader from './uploader' |
| | | import Button from '@/app/components/base/button' |
| | | import Input from '@/app/components/base/input' |
| | |
| | | import { NEED_REFRESH_APP_LIST_KEY } from '@/config' |
| | | import { getRedirection } from '@/utils/app-redirection' |
| | | import cn from '@/utils/classnames' |
| | | import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | type CreateFromDSLModalProps = { |
| | | show: boolean |
| | |
| | | const [showErrorModal, setShowErrorModal] = useState(false) |
| | | const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() |
| | | const [importId, setImportId] = useState<string>() |
| | | const { handleCheckPluginDependencies } = usePluginDependencies() |
| | | |
| | | const readFile = (file: File) => { |
| | | const reader = new FileReader() |
| | |
| | | |
| | | if (!response) |
| | | return |
| | | const { id, status, app_id, app_mode, imported_dsl_version, current_dsl_version } = response |
| | | |
| | | const { id, status, app_id, imported_dsl_version, current_dsl_version } = response |
| | | if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { |
| | | if (onSuccess) |
| | | onSuccess() |
| | |
| | | children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'), |
| | | }) |
| | | localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') |
| | | if (app_id) |
| | | await handleCheckPluginDependencies(app_id) |
| | | getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push) |
| | | getRedirection(isCurrentWorkspaceEditor, { id: app_id }, push) |
| | | } |
| | | else if (status === DSLImportStatus.PENDING) { |
| | | setVersions({ |
| | |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | } |
| | | // eslint-disable-next-line unused-imports/no-unused-vars |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | isCreatingRef.current = false |
| | | } |
| | | |
| | | const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 }) |
| | | |
| | | useKeyPress(['meta.enter', 'ctrl.enter'], () => { |
| | | if (show && !isAppsFull && ((currentTab === CreateFromDSLModalTab.FROM_FILE && currentFile) || (currentTab === CreateFromDSLModalTab.FROM_URL && dslUrlValue))) |
| | | handleCreateApp() |
| | | }) |
| | | |
| | | useKeyPress('esc', () => { |
| | | if (show && !showErrorModal) |
| | | onClose() |
| | | }) |
| | | |
| | | const onDSLConfirm: MouseEventHandler = async () => { |
| | | try { |
| | |
| | | import_id: importId, |
| | | }) |
| | | |
| | | const { status, app_id, app_mode } = response |
| | | const { status, app_id } = response |
| | | |
| | | if (status === DSLImportStatus.COMPLETED) { |
| | | if (onSuccess) |
| | |
| | | type: 'success', |
| | | message: t('app.newApp.appCreated'), |
| | | }) |
| | | if (app_id) |
| | | await handleCheckPluginDependencies(app_id) |
| | | localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') |
| | | getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push) |
| | | getRedirection(isCurrentWorkspaceEditor, { id: app_id }, push) |
| | | } |
| | | else if (status === DSLImportStatus.FAILED) { |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | } |
| | | // eslint-disable-next-line unused-imports/no-unused-vars |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | |
| | | return ( |
| | | <> |
| | | <Modal |
| | | className='w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl' |
| | | className='p-0 w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl' |
| | | isShow={show} |
| | | onClose={noop} |
| | | onClose={() => { }} |
| | | > |
| | | <div className='title-2xl-semi-bold flex items-center justify-between pb-3 pl-6 pr-5 pt-6 text-text-primary'> |
| | | <div className='flex items-center justify-between pt-6 pl-6 pr-5 pb-3 text-text-primary title-2xl-semi-bold'> |
| | | {t('app.importFromDSL')} |
| | | <div |
| | | className='flex h-8 w-8 cursor-pointer items-center' |
| | | className='flex items-center w-8 h-8 cursor-pointer' |
| | | onClick={() => onClose()} |
| | | > |
| | | <RiCloseLine className='h-5 w-5 text-text-tertiary' /> |
| | | <RiCloseLine className='w-5 h-5 text-text-tertiary' /> |
| | | </div> |
| | | </div> |
| | | <div className='system-md-semibold flex h-9 items-center space-x-6 border-b border-divider-subtle px-6 text-text-tertiary'> |
| | | <div className='flex items-center px-6 h-9 space-x-6 system-md-semibold text-text-tertiary border-b border-divider-subtle'> |
| | | { |
| | | tabs.map(tab => ( |
| | | <div |
| | | key={tab.key} |
| | | className={cn( |
| | | 'relative flex h-full cursor-pointer items-center', |
| | | 'relative flex items-center h-full cursor-pointer', |
| | | currentTab === tab.key && 'text-text-primary', |
| | | )} |
| | | onClick={() => setCurrentTab(tab.key)} |
| | |
| | | {tab.label} |
| | | { |
| | | currentTab === tab.key && ( |
| | | <div className='absolute bottom-0 h-[2px] w-full bg-util-colors-blue-brand-blue-brand-600'></div> |
| | | <div className='absolute bottom-0 w-full h-[2px] bg-util-colors-blue-brand-blue-brand-600'></div> |
| | | ) |
| | | } |
| | | </div> |
| | |
| | | { |
| | | currentTab === CreateFromDSLModalTab.FROM_URL && ( |
| | | <div> |
| | | <div className='system-md-semibold mb-1 text-text-secondary'>DSL URL</div> |
| | | <div className='mb-1 system-md-semibold leading6'>DSL URL</div> |
| | | <Input |
| | | placeholder={t('app.importFromDSLUrlPlaceholder') || ''} |
| | | value={dslUrlValue} |
| | |
| | | )} |
| | | <div className='flex justify-end px-6 py-5'> |
| | | <Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button> |
| | | <Button |
| | | disabled={buttonDisabled} |
| | | variant="primary" |
| | | onClick={handleCreateApp} |
| | | className="gap-1" |
| | | > |
| | | <span>{t('app.newApp.Create')}</span> |
| | | <div className='flex gap-0.5'> |
| | | <RiCommandLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' /> |
| | | <RiCornerDownLeftLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' /> |
| | | </div> |
| | | </Button> |
| | | <Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button> |
| | | </div> |
| | | </Modal> |
| | | <Modal |
| | |
| | | onClose={() => setShowErrorModal(false)} |
| | | className='w-[480px]' |
| | | > |
| | | <div className='flex flex-col items-start gap-2 self-stretch pb-4'> |
| | | <div className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.appCreateDSLErrorTitle')}</div> |
| | | <div className='system-md-regular flex grow flex-col text-text-secondary'> |
| | | <div className='flex pb-4 flex-col items-start gap-2 self-stretch'> |
| | | <div className='text-text-primary title-2xl-semi-bold'>{t('app.newApp.appCreateDSLErrorTitle')}</div> |
| | | <div className='flex flex-grow flex-col text-text-secondary system-md-regular'> |
| | | <div>{t('app.newApp.appCreateDSLErrorPart1')}</div> |
| | | <div>{t('app.newApp.appCreateDSLErrorPart2')}</div> |
| | | <br /> |
| | |
| | | <div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div> |
| | | </div> |
| | | </div> |
| | | <div className='flex items-start justify-end gap-2 self-stretch pt-6'> |
| | | <div className='flex pt-6 justify-end items-start gap-2 self-stretch'> |
| | | <Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button> |
| | | <Button variant='primary' destructive onClick={onDSLConfirm}>{t('app.newApp.Confirm')}</Button> |
| | | </div> |
| | |
| | | import React, { useEffect, useRef, useState } from 'react' |
| | | import { |
| | | RiDeleteBinLine, |
| | | RiUploadCloud2Line, |
| | | } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useContext } from 'use-context-selector' |
| | |
| | | import cn from '@/utils/classnames' |
| | | import { Yaml as YamlIcon } from '@/app/components/base/icons/src/public/files' |
| | | import { ToastContext } from '@/app/components/base/toast' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | import { UploadCloud01 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import Button from '@/app/components/base/button' |
| | | |
| | | export type Props = { |
| | | file: File | undefined |
| | |
| | | style={{ display: 'none' }} |
| | | type="file" |
| | | id="fileUploader" |
| | | accept='.yaml,.yml' |
| | | accept='.yml' |
| | | onChange={fileChangeHandle} |
| | | /> |
| | | <div ref={dropRef}> |
| | | {!file && ( |
| | | <div className={cn('flex h-12 items-center rounded-[10px] border border-dashed border-components-dropzone-border bg-components-dropzone-bg text-sm font-normal', dragging && 'border-components-dropzone-border-accent bg-components-dropzone-bg-accent')}> |
| | | <div className='flex w-full items-center justify-center space-x-2'> |
| | | <RiUploadCloud2Line className='h-6 w-6 text-text-tertiary' /> |
| | | <div className='text-text-tertiary'> |
| | | <div className={cn('flex items-center h-12 rounded-xl bg-gray-50 border border-dashed border-gray-200 text-sm font-normal', dragging && 'bg-[#F5F8FF] border border-[#B2CCFF]')}> |
| | | <div className='w-full flex items-center justify-center space-x-2'> |
| | | <UploadCloud01 className='w-6 h-6 mr-2' /> |
| | | <div className='text-gray-500'> |
| | | {t('datasetCreation.stepOne.uploader.button')} |
| | | <span className='cursor-pointer pl-1 text-text-accent' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span> |
| | | <span className='pl-1 text-[#155eef] cursor-pointer' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span> |
| | | </div> |
| | | </div> |
| | | {dragging && <div ref={dragRef} className='absolute left-0 top-0 h-full w-full' />} |
| | | {dragging && <div ref={dragRef} className='absolute w-full h-full top-0 left-0' />} |
| | | </div> |
| | | )} |
| | | {file && ( |
| | | <div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', ' hover:bg-components-panel-on-panel-item-bg-hover')}> |
| | | <div className='flex items-center justify-center p-3'> |
| | | <YamlIcon className="h-6 w-6 shrink-0" /> |
| | | <div className={cn('flex items-center rounded-lg bg-components-panel-on-panel-item-bg border-[0.5px] border-components-panel-border shadow-xs group', 'hover:bg-[#F5F8FF] hover:border-[#B2CCFF]')}> |
| | | <div className='flex p-3 justify-center items-center'> |
| | | <YamlIcon className="w-6 h-6 shrink-0" /> |
| | | </div> |
| | | <div className='flex grow flex-col items-start gap-0.5 py-1 pr-2'> |
| | | <span className='font-inter max-w-[calc(100%_-_30px)] overflow-hidden text-ellipsis whitespace-nowrap text-[12px] font-medium leading-4 text-text-secondary'>{file.name}</span> |
| | | <div className='font-inter flex h-3 items-center gap-1 self-stretch text-[10px] font-medium uppercase leading-3 text-text-tertiary'> |
| | | <div className='flex py-1 pr-2 grow flex-col items-start gap-0.5'> |
| | | <span className='max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-text-secondary font-inter text-[12px] font-medium leading-4'>{file.name}</span> |
| | | <div className='flex h-3 items-center gap-1 self-stretch text-text-tertiary font-inter text-[10px] font-medium leading-3 uppercase'> |
| | | <span>YAML</span> |
| | | <span className='text-text-quaternary'>·</span> |
| | | <span>{formatFileSize(file.size)}</span> |
| | | </div> |
| | | </div> |
| | | <div className='hidden items-center pr-3 group-hover:flex'> |
| | | <ActionButton onClick={removeFile}> |
| | | <RiDeleteBinLine className='h-4 w-4 text-text-tertiary' /> |
| | | </ActionButton> |
| | | <div className='hidden group-hover:flex items-center'> |
| | | <Button onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button> |
| | | <div className='mx-2 w-px h-4 bg-gray-200' /> |
| | | <div className='p-2 cursor-pointer' onClick={removeFile}> |
| | | <RiDeleteBinLine className='w-4 h-4 text-text-tertiary' /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | )} |
| | |
| | | 'use client' |
| | | import React, { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiCloseLine } from '@remixicon/react' |
| | | import AppIconPicker from '../../base/app-icon-picker' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import Modal from '@/app/components/base/modal' |
| | | import Button from '@/app/components/base/button' |
| | |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import AppsFull from '@/app/components/billing/apps-full-in-dialog' |
| | | import type { AppIconType } from '@/types/app' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | export type DuplicateAppModalProps = { |
| | | appName: string |
| | |
| | | <> |
| | | <Modal |
| | | isShow={show} |
| | | onClose={noop} |
| | | className={cn('relative !max-w-[480px]', 'px-8')} |
| | | onClose={() => { }} |
| | | className={cn(s.modal, '!max-w-[480px]', 'px-8')} |
| | | > |
| | | <div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onHide}> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | </div> |
| | | <div className='relative mb-9 mt-3 text-xl font-semibold leading-[30px] text-text-primary'>{t('app.duplicateTitle')}</div> |
| | | <div className='system-sm-regular mb-9 text-text-secondary'> |
| | | <div className='system-md-medium mb-2'>{t('explore.appCustomize.subTitle')}</div> |
| | | <span className={s.close} onClick={onHide} /> |
| | | <div className={s.title}>{t('app.duplicateTitle')}</div> |
| | | <div className={s.content}> |
| | | <div className={s.subTitle}>{t('explore.appCustomize.subTitle')}</div> |
| | | <div className='flex items-center justify-between space-x-2'> |
| | | <AppIcon |
| | | size='large' |
| | |
| | | className='h-10' |
| | | /> |
| | | </div> |
| | | {isAppsFull && <AppsFull className='mt-4' loc='app-duplicate-create' />} |
| | | {isAppsFull && <AppsFull loc='app-duplicate-create' />} |
| | | </div> |
| | | <div className='flex flex-row-reverse'> |
| | | <Button disabled={isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{t('app.duplicate')}</Button> |
| | | <Button disabled={isAppsFull} className='w-24 ml-2' variant='primary' onClick={submit}>{t('app.duplicate')}</Button> |
| | | <Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button> |
| | | </div> |
| | | </Modal> |
New file |
| | |
| | | .modal { |
| | | position: relative; |
| | | } |
| | | |
| | | .modal .close { |
| | | position: absolute; |
| | | right: 16px; |
| | | top: 25px; |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 8px; |
| | | background: center no-repeat url(~@/app/components/datasets/create/assets/close.svg); |
| | | background-size: 16px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .modal .title { |
| | | @apply mb-9; |
| | | font-weight: 600; |
| | | font-size: 20px; |
| | | line-height: 30px; |
| | | color: #101828; |
| | | } |
| | | |
| | | .modal .content { |
| | | @apply mb-9; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | line-height: 20px; |
| | | color: #101828; |
| | | } |
| | | |
| | | .subTitle { |
| | | margin-bottom: 8px; |
| | | font-weight: 500; |
| | | } |
| | |
| | | { value: PageType.log, text: t('appLog.title') }, |
| | | { value: PageType.annotation, text: t('appAnnotation.title') }, |
| | | ] |
| | | }, [appDetail?.mode, t]) |
| | | }, [appDetail]) |
| | | |
| | | if (!appDetail) { |
| | | return ( |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className='flex h-full flex-col px-6 pt-3'> |
| | | <div className='pt-3 px-6 h-full flex flex-col'> |
| | | {appDetail.mode !== 'workflow' && ( |
| | | <TabSlider |
| | | className='shrink-0' |
| | |
| | | options={options} |
| | | /> |
| | | )} |
| | | <div className={cn('h-0 grow', appDetail.mode !== 'workflow' && 'mt-3')}> |
| | | <div className={cn('grow h-0', appDetail.mode !== 'workflow' && 'mt-3')}> |
| | | {pageType === PageType.log && appDetail.mode !== 'workflow' && (<Log appDetail={appDetail} />)} |
| | | {pageType === PageType.annotation && (<Annotation appDetail={appDetail} />)} |
| | | {pageType === PageType.log && appDetail.mode === 'workflow' && (<WorkflowLog appDetail={appDetail} />)} |
| | |
| | | if (!data) |
| | | return null |
| | | return ( |
| | | <div className='mb-2 flex flex-row flex-wrap items-center gap-2'> |
| | | <div className='flex flex-row flex-wrap gap-2 items-center mb-2'> |
| | | <Chip |
| | | className='min-w-[150px]' |
| | | panelClassName='w-[270px]' |
| | |
| | | /> |
| | | {isChatMode && ( |
| | | <> |
| | | <div className='h-3.5 w-px bg-divider-regular'></div> |
| | | <div className='w-px h-3.5 bg-divider-regular'></div> |
| | | <Sort |
| | | order={queryParams.sort_by?.startsWith('-') ? '-' : ''} |
| | | value={queryParams.sort_by?.replace('-', '') || 'created_at'} |
| | |
| | | const pathname = usePathname() |
| | | const pathSegments = pathname.split('/') |
| | | pathSegments.pop() |
| | | return <div className='flex h-full items-center justify-center'> |
| | | <div className='box-border h-fit w-[560px] rounded-2xl bg-background-section-burn px-5 py-4'> |
| | | <span className='system-md-semibold text-text-secondary'>{t('appLog.table.empty.element.title')}<ThreeDotsIcon className='relative -left-1.5 -top-3 inline' /></span> |
| | | <div className='system-sm-regular mt-2 text-text-tertiary'> |
| | | return <div className='flex items-center justify-center h-full'> |
| | | <div className='bg-background-section-burn w-[560px] h-fit box-border px-5 py-4 rounded-2xl'> |
| | | <span className='text-text-secondary system-md-semibold'>{t('appLog.table.empty.element.title')}<ThreeDotsIcon className='inline relative -top-3 -left-1.5' /></span> |
| | | <div className='mt-2 text-text-tertiary system-sm-regular'> |
| | | <Trans |
| | | i18nKey="appLog.table.empty.element.content" |
| | | components={{ shareLink: <Link href={`${pathSegments.join('/')}/overview`} className='text-util-colors-blue-blue-600' />, testLink: <Link href={appUrl} className='text-util-colors-blue-blue-600' target='_blank' rel='noopener noreferrer' /> }} |
| | |
| | | const total = isChatMode ? chatConversations?.total : completionConversations?.total |
| | | |
| | | return ( |
| | | <div className='flex h-full grow flex-col'> |
| | | <p className='system-sm-regular shrink-0 text-text-tertiary'>{t('appLog.description')}</p> |
| | | <div className='flex max-h-[calc(100%-16px)] flex-1 grow flex-col py-4'> |
| | | <div className='grow flex flex-col h-full'> |
| | | <p className='shrink-0 text-text-tertiary system-sm-regular'>{t('appLog.description')}</p> |
| | | <div className='grow max-h-[calc(100%-16px)] flex flex-col py-4 flex-1'> |
| | | <Filter isChatMode={isChatMode} appId={appDetail.id} queryParams={queryParams} setQueryParams={setQueryParams} /> |
| | | {total === undefined |
| | | ? <Loading type='app' /> |
| | |
| | | import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils' |
| | | import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' |
| | | import cn from '@/utils/classnames' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | dayjs.extend(utc) |
| | | dayjs.extend(timezone) |
| | |
| | | const HandThumbIconWithCount: FC<{ count: number; iconType: 'up' | 'down' }> = ({ count, iconType }) => { |
| | | const classname = iconType === 'up' ? 'text-primary-600 bg-primary-50' : 'text-red-600 bg-red-50' |
| | | const Icon = iconType === 'up' ? HandThumbUpIcon : HandThumbDownIcon |
| | | return <div className={`inline-flex w-fit items-center rounded-md p-1 text-xs ${classname} mr-1 last:mr-0`}> |
| | | <Icon className={'mr-0.5 h-3 w-3 rounded-md'} /> |
| | | return <div className={`inline-flex items-center w-fit rounded-md p-1 text-xs ${classname} mr-1 last:mr-0`}> |
| | | <Icon className={'h-3 w-3 mr-0.5 rounded-md'} /> |
| | | {count > 0 ? count : null} |
| | | </div> |
| | | } |
| | | |
| | | const statusTdRender = (statusCount: StatusCount) => { |
| | | if (!statusCount) |
| | | return null |
| | | |
| | | if (statusCount.partial_success + statusCount.failed === 0) { |
| | | return ( |
| | | <div className='system-xs-semibold-uppercase inline-flex items-center gap-1'> |
| | | <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'> |
| | | <Indicator color={'green'} /> |
| | | <span className='text-util-colors-green-green-600'>Success</span> |
| | | </div> |
| | |
| | | } |
| | | else if (statusCount.failed === 0) { |
| | | return ( |
| | | <div className='system-xs-semibold-uppercase inline-flex items-center gap-1'> |
| | | <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'> |
| | | <Indicator color={'green'} /> |
| | | <span className='text-util-colors-green-green-600'>Partial Success</span> |
| | | </div> |
| | |
| | | } |
| | | else { |
| | | return ( |
| | | <div className='system-xs-semibold-uppercase inline-flex items-center gap-1'> |
| | | <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'> |
| | | <Indicator color={'red'} /> |
| | | <span className='text-util-colors-red-red-600'>{statusCount.failed} {`${statusCount.failed > 1 ? 'Failures' : 'Failure'}`}</span> |
| | | </div> |
| | |
| | | }, []) |
| | | |
| | | return ( |
| | | <div ref={ref} className='flex h-full flex-col rounded-xl border-[0.5px] border-components-panel-border'> |
| | | <div ref={ref} className='rounded-xl border-[0.5px] border-components-panel-border h-full flex flex-col'> |
| | | {/* Panel Header */} |
| | | <div className='flex shrink-0 items-center gap-2 rounded-t-xl bg-components-panel-bg pb-2 pl-4 pr-3 pt-3'> |
| | | <div className='shrink-0 pl-4 pt-3 pr-3 pb-2 flex items-center gap-2 bg-components-panel-bg rounded-t-xl'> |
| | | <div className='shrink-0'> |
| | | <div className='system-xs-semibold-uppercase mb-0.5 text-text-primary'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div> |
| | | <div className='mb-0.5 text-text-primary system-xs-semibold-uppercase'>{isChatMode ? t('appLog.detail.conversationId') : t('appLog.detail.time')}</div> |
| | | {isChatMode && ( |
| | | <div className='system-2xs-regular-uppercase flex items-center text-text-secondary'> |
| | | <div className='flex items-center text-text-secondary system-2xs-regular-uppercase'> |
| | | <Tooltip |
| | | popupContent={detail.id} |
| | | > |
| | |
| | | </div> |
| | | )} |
| | | {!isChatMode && ( |
| | | <div className='system-2xs-regular-uppercase text-text-secondary'>{formatTime(detail.created_at, t('appLog.dateTimeFormat') as string)}</div> |
| | | <div className='text-text-secondary system-2xs-regular-uppercase'>{formatTime(detail.created_at, t('appLog.dateTimeFormat') as string)}</div> |
| | | )} |
| | | </div> |
| | | <div className='flex grow flex-wrap items-center justify-end gap-y-1'> |
| | | <div className='grow flex items-center flex-wrap gap-y-1 justify-end'> |
| | | {!isAdvanced && <ModelInfo model={detail.model_config.model} />} |
| | | </div> |
| | | <ActionButton size='l' onClick={onClose}> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <RiCloseLine className='w-4 h-4 text-text-tertiary' /> |
| | | </ActionButton> |
| | | </div> |
| | | {/* Panel Body */} |
| | | <div className='shrink-0 px-1 pt-1'> |
| | | <div className='rounded-t-xl bg-background-section-burn p-3 pb-2'> |
| | | <div className='shrink-0 pt-1 px-1'> |
| | | <div className='p-3 pb-2 rounded-t-xl bg-background-section-burn'> |
| | | {(varList.length > 0 || (!isChatMode && message_files.length > 0)) && ( |
| | | <VarPanel |
| | | varList={varList} |
| | |
| | | )} |
| | | </div> |
| | | </div> |
| | | <div className='mx-1 mb-1 grow overflow-auto rounded-b-xl bg-background-section-burn'> |
| | | <div className='grow mx-1 mb-1 bg-background-section-burn rounded-b-xl overflow-auto'> |
| | | {!isChatMode |
| | | ? <div className="px-6 py-4"> |
| | | <div className='flex h-[18px] items-center space-x-3'> |
| | | <div className='system-xs-semibold-uppercase text-text-tertiary'>{t('appLog.table.header.output')}</div> |
| | | <div className='h-[1px] grow' style={{ |
| | | <div className='text-text-tertiary system-xs-semibold-uppercase'>{t('appLog.table.header.output')}</div> |
| | | <div className='grow h-[1px]' style={{ |
| | | background: 'linear-gradient(270deg, rgba(243, 244, 246, 0) 0%, rgb(243, 244, 246) 100%)', |
| | | }}></div> |
| | | </div> |
| | |
| | | content={detail.message.answer} |
| | | messageId={detail.message.id} |
| | | isError={false} |
| | | onRetry={noop} |
| | | onRetry={() => { }} |
| | | isInstalledApp={false} |
| | | supportFeedback |
| | | feedback={detail.message.feedbacks.find((item: any) => item.from_source === 'admin')} |
| | | onFeedback={feedback => onFeedback(detail.message.id, feedback)} |
| | | supportAnnotation |
| | | isShowTextToSpeech |
| | | appId={appDetail?.id} |
| | | varList={varList} |
| | | siteInfo={null} |
| | | /> |
| | | </div> |
| | | : threadChatItems.length < 8 |
| | | ? <div className="mb-4 pt-4"> |
| | | ? <div className="pt-4 mb-4"> |
| | | <Chat |
| | | config={{ |
| | | appId: appDetail?.id, |
| | | text_to_speech: { |
| | | enabled: true, |
| | | }, |
| | | questionEditEnable: false, |
| | | supportAnnotation: true, |
| | | annotation_reply: { |
| | | enabled: true, |
| | |
| | | dataLength={threadChatItems.length} |
| | | next={fetchData} |
| | | hasMore={hasMore} |
| | | loader={<div className='system-xs-regular text-center text-text-tertiary'>{t('appLog.detail.loading')}...</div>} |
| | | loader={<div className='text-center text-text-tertiary system-xs-regular'>{t('appLog.detail.loading')}...</div>} |
| | | // endMessage={<div className='text-center'>Nothing more to show</div>} |
| | | // below props only if you need pull down functionality |
| | | refreshFunction={fetchData} |
| | |
| | | text_to_speech: { |
| | | enabled: true, |
| | | }, |
| | | questionEditEnable: false, |
| | | supportAnnotation: true, |
| | | annotation_reply: { |
| | | enabled: true, |
| | |
| | | notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) |
| | | return true |
| | | } |
| | | catch { |
| | | catch (err) { |
| | | notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) |
| | | return false |
| | | } |
| | |
| | | notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) |
| | | return true |
| | | } |
| | | catch { |
| | | catch (err) { |
| | | notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) |
| | | return false |
| | | } |
| | |
| | | notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) |
| | | return true |
| | | } |
| | | catch { |
| | | catch (err) { |
| | | notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) |
| | | return false |
| | | } |
| | |
| | | notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) |
| | | return true |
| | | } |
| | | catch { |
| | | catch (err) { |
| | | notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') }) |
| | | return false |
| | | } |
| | |
| | | const [currentConversation, setCurrentConversation] = useState<ChatConversationGeneralDetail | CompletionConversationGeneralDetail | undefined>() // Currently selected conversation |
| | | const isChatMode = appDetail.mode !== 'completion' // Whether the app is a chat app |
| | | const isChatflow = appDetail.mode === 'advanced-chat' // Whether the app is a chatflow app |
| | | const { setShowPromptLogModal, setShowAgentLogModal, setShowMessageLogModal } = useAppStore(useShallow(state => ({ |
| | | const { setShowPromptLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({ |
| | | setShowPromptLogModal: state.setShowPromptLogModal, |
| | | setShowAgentLogModal: state.setShowAgentLogModal, |
| | | setShowMessageLogModal: state.setShowMessageLogModal, |
| | | }))) |
| | | |
| | | // Annotated data needs to be highlighted |
| | |
| | | return ( |
| | | <Tooltip |
| | | popupContent={ |
| | | <span className='inline-flex items-center text-xs text-text-tertiary'> |
| | | <RiEditFill className='mr-1 h-3 w-3' />{`${t('appLog.detail.annotationTip', { user: annotation?.account?.name })} ${formatTime(annotation?.created_at || dayjs().unix(), 'MM-DD hh:mm A')}`} |
| | | <span className='text-xs text-text-tertiary inline-flex items-center'> |
| | | <RiEditFill className='w-3 h-3 mr-1' />{`${t('appLog.detail.annotationTip', { user: annotation?.account?.name })} ${formatTime(annotation?.created_at || dayjs().unix(), 'MM-DD hh:mm A')}`} |
| | | </span> |
| | | } |
| | | popupClassName={(isHighlight && !isChatMode) ? '' : '!hidden'} |
| | |
| | | setCurrentConversation(undefined) |
| | | setShowPromptLogModal(false) |
| | | setShowAgentLogModal(false) |
| | | setShowMessageLogModal(false) |
| | | } |
| | | |
| | | if (!logs) |
| | |
| | | <table className={cn('mt-2 w-full min-w-[440px] border-collapse border-0')}> |
| | | <thead className='system-xs-medium-uppercase text-text-tertiary'> |
| | | <tr> |
| | | <td className='w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1'></td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.endUser')}</td> |
| | | {isChatflow && <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.status')}</td>} |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{isChatMode ? t('appLog.table.header.messageCount') : t('appLog.table.header.output')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.userRate')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.adminRate')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.updatedTime')}</td> |
| | | <td className='whitespace-nowrap rounded-r-lg bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.time')}</td> |
| | | <td className='pl-2 pr-1 w-5 rounded-l-lg bg-background-section-burn whitespace-nowrap'></td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.endUser')}</td> |
| | | {isChatflow && <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.status')}</td>} |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{isChatMode ? t('appLog.table.header.messageCount') : t('appLog.table.header.output')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.userRate')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.adminRate')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.updatedTime')}</td> |
| | | <td className='pl-3 py-1.5 rounded-r-lg bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.time')}</td> |
| | | </tr> |
| | | </thead> |
| | | <tbody className="system-sm-regular text-text-secondary"> |
| | | <tbody className="text-text-secondary system-sm-regular"> |
| | | {logs.data.map((log: any) => { |
| | | const endUser = log.from_end_user_session_id || log.from_account_name |
| | | const leftValue = get(log, isChatMode ? 'name' : 'message.inputs.query') || (!isChatMode ? (get(log, 'message.query') || get(log, 'message.inputs.default_input')) : '') || '' |
| | | const rightValue = get(log, isChatMode ? 'message_count' : 'message.answer') |
| | | return <tr |
| | | key={log.id} |
| | | className={cn('cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover', currentConversation?.id !== log.id ? '' : 'bg-background-default-hover')} |
| | | className={cn('border-b border-divider-subtle hover:bg-background-default-hover cursor-pointer', currentConversation?.id !== log.id ? '' : 'bg-background-default-hover')} |
| | | onClick={() => { |
| | | setShowDrawer(true) |
| | | setCurrentConversation(log) |
| | | }}> |
| | | <td className='h-4'> |
| | | {!log.read_at && ( |
| | | <div className='flex items-center p-3 pr-0.5'> |
| | | <span className='inline-block h-1.5 w-1.5 rounded bg-util-colors-blue-blue-500'></span> |
| | | <div className='p-3 pr-0.5 flex items-center'> |
| | | <span className='inline-block bg-util-colors-blue-blue-500 h-1.5 w-1.5 rounded'></span> |
| | | </div> |
| | | )} |
| | | </td> |
| | | <td className='w-[160px] p-3 pr-2' style={{ maxWidth: isChatMode ? 300 : 200 }}> |
| | | <td className='p-3 pr-2 w-[160px]' style={{ maxWidth: isChatMode ? 300 : 200 }}> |
| | | {renderTdValue(leftValue || t('appLog.table.empty.noChat'), !leftValue, isChatMode && log.annotated)} |
| | | </td> |
| | | <td className='p-3 pr-2'>{renderTdValue(endUser || defaultValue, !endUser)}</td> |
| | | {isChatflow && <td className='w-[160px] p-3 pr-2' style={{ maxWidth: isChatMode ? 300 : 200 }}> |
| | | {isChatflow && <td className='p-3 pr-2 w-[160px]' style={{ maxWidth: isChatMode ? 300 : 200 }}> |
| | | {statusTdRender(log.status_count)} |
| | | </td>} |
| | | <td className='p-3 pr-2' style={{ maxWidth: isChatMode ? 100 : 200 }}> |
| | |
| | | onClose={onCloseDrawer} |
| | | mask={isMobile} |
| | | footer={null} |
| | | panelClassName='mt-16 mx-2 sm:mr-2 mb-4 !p-0 !max-w-[640px] rounded-xl bg-components-panel-bg' |
| | | panelClassname='mt-16 mx-2 sm:mr-2 mb-4 !p-0 !max-w-[640px] rounded-xl bg-components-panel-bg' |
| | | > |
| | | <DrawerContext.Provider value={{ |
| | | onClose: onCloseDrawer, |
| | |
| | | |
| | | return ( |
| | | <div className={cn('flex items-center rounded-lg')}> |
| | | <div className='mr-px flex h-8 shrink-0 items-center gap-1 rounded-l-lg bg-components-input-bg-normal pl-1.5 pr-2'> |
| | | <div className='shrink-0 flex items-center gap-1 mr-px h-8 pl-1.5 pr-2 rounded-l-lg bg-components-input-bg-normal'> |
| | | <ModelIcon |
| | | className='!h-5 !w-5' |
| | | className='!w-5 !h-5' |
| | | provider={currentProvider} |
| | | modelName={currentModel?.model} |
| | | /> |
| | |
| | | className='block' |
| | | > |
| | | <div className={cn( |
| | | 'cursor-pointer rounded-r-lg bg-components-button-tertiary-bg p-2 hover:bg-components-button-tertiary-bg-hover', |
| | | 'p-2 rounded-r-lg bg-components-button-tertiary-bg hover:bg-components-button-tertiary-bg-hover cursor-pointer', |
| | | open && 'bg-components-button-tertiary-bg-hover', |
| | | )}> |
| | | <RiInformation2Line className='h-4 w-4 text-text-tertiary' /> |
| | | </div> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-[1002]'> |
| | | <div className='relative w-[280px] overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg px-4 pb-2 pt-3 shadow-xl'> |
| | | <div className='system-sm-semibold-uppercase mb-1 h-6 text-text-secondary'>{t('appLog.detail.modelParams')}</div> |
| | | <div className='relative w-[280px] pt-3 px-4 pb-2 bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadow-xl overflow-hidden'> |
| | | <div className='mb-1 h-6 text-text-secondary system-sm-semibold-uppercase'>{t('appLog.detail.modelParams')}</div> |
| | | <div className='py-1'> |
| | | {['temperature', 'top_p', 'presence_penalty', 'max_tokens', 'stop'].map((param: string, index: number) => { |
| | | return <div className='flex justify-between py-1.5' key={index}> |
| | | <span className='system-xs-medium-uppercase text-text-tertiary'>{PARAM_MAP[param as keyof typeof PARAM_MAP]}</span> |
| | | <span className='system-xs-medium-uppercase text-text-secondary'>{getParamValue(param)}</span> |
| | | <span className='text-text-tertiary system-xs-medium-uppercase'>{PARAM_MAP[param as keyof typeof PARAM_MAP]}</span> |
| | | <span className='text-text-secondary system-xs-medium-uppercase'>{getParamValue(param)}</span> |
| | | </div> |
| | | })} |
| | | </div> |
| | |
| | | return ( |
| | | <div className='rounded-[10px] border border-divider-subtle bg-chat-bubble-bg'> |
| | | <div |
| | | className={cn('flex cursor-pointer items-center gap-1 border-b border-divider-subtle px-3 pb-2 pt-2.5 text-text-secondary', isCollapse && 'border-0 pb-2.5')} |
| | | className={cn('flex items-center gap-1 px-3 pt-2.5 pb-2 border-b border-divider-subtle text-text-secondary cursor-pointer', isCollapse && 'pb-2.5 border-0')} |
| | | onClick={toggleCollapse} |
| | | > |
| | | <Variable02 className='h-4 w-4' /> |
| | | <div className='system-md-medium grow'>{t('appLog.detail.variables')}</div> |
| | | <Variable02 className='w-4 h-4' /> |
| | | <div className='grow system-md-medium'>{t('appLog.detail.variables')}</div> |
| | | { |
| | | isCollapse |
| | | ? <RiArrowRightSLine className='h-4 w-4' /> |
| | | : <RiArrowDownSLine className='h-4 w-4' /> |
| | | ? <RiArrowRightSLine className='w-4 h-4' /> |
| | | : <RiArrowDownSLine className='w-4 h-4' /> |
| | | } |
| | | </div> |
| | | {!isCollapse && ( |
| | | <div className='flex max-h-[500px] flex-col gap-2 overflow-y-auto p-3'> |
| | | <div className='p-3 flex flex-col gap-2 max-h-[500px] overflow-y-auto'> |
| | | {varList.map(({ label, value }, index) => ( |
| | | <div key={index} className='system-xs-medium flex py-2'> |
| | | <div className='flex w-[128px] shrink-0 text-text-accent'> |
| | | <div key={index} className='flex py-2 system-xs-medium'> |
| | | <div className='shrink-0 w-[128px] flex text-text-accent'> |
| | | <span className='shrink-0 opacity-60'>{'{{'}</span> |
| | | <span className='truncate'>{label}</span> |
| | | <span className='shrink-0 opacity-60'>{'}}'}</span> |
| | | </div> |
| | | <div className='whitespace-pre-wrap pl-2.5 text-text-secondary'>{value}</div> |
| | | <div className='pl-2.5 whitespace-pre-wrap text-text-secondary'>{value}</div> |
| | | </div> |
| | | ))} |
| | | |
| | | {message_files.length > 0 && ( |
| | | <div className='mt-1 flex py-2'> |
| | | <div className='system-xs-medium w-[128px] shrink-0 text-text-tertiary'>{t('appLog.detail.uploadImages')}</div> |
| | | <div className='shrink-0 w-[128px] system-xs-medium text-text-tertiary'>{t('appLog.detail.uploadImages')}</div> |
| | | <div className="flex space-x-2"> |
| | | {message_files.map((url, index) => ( |
| | | <div |
| | | key={index} |
| | | className="ml-2.5 h-16 w-16 cursor-pointer rounded-lg bg-cover bg-center bg-no-repeat" |
| | | className="ml-2.5 w-16 h-16 rounded-lg bg-no-repeat bg-cover bg-center cursor-pointer" |
| | | style={{ backgroundImage: `url(${url})` }} |
| | | onClick={() => setImagePreviewUrl(url)} |
| | | /> |
| | |
| | | return null |
| | | |
| | | return ( |
| | | <div className={cn('border-components-panel-border bg-components-panel-bg', 'relative mb-6 rounded-2xl border p-8 shadow-md ')}> |
| | | <div className={cn('text-[24px] font-semibold text-text-primary', isCloud ? 'flex h-8 items-center space-x-1' : 'mb-6 leading-8')}> |
| | | <div className={cn('bg-[#EFF4FF] border-[#D1E0FF]', 'mb-6 relative rounded-2xl shadow-md border p-8 ')}> |
| | | <div className={cn('text-[24px] text-gray-800 font-semibold', isCloud ? 'flex items-center h-8 space-x-1' : 'leading-8 mb-6')}> |
| | | {isCloud && <em-emoji id={'😀'} />} |
| | | {isCloud |
| | | ? ( |
| | |
| | | )} |
| | | </div> |
| | | {isCloud && ( |
| | | <div className='mt-1 text-sm font-normal text-text-tertiary'>{t(`appOverview.apiKeyInfo.cloud.${'trial'}.description`)}</div> |
| | | <div className='mt-1 text-sm text-gray-600 font-normal'>{t(`appOverview.apiKeyInfo.cloud.${'trial'}.description`)}</div> |
| | | )} |
| | | <Button |
| | | variant='primary' |
| | | className='mt-2 space-x-2' |
| | | className='space-x-2' |
| | | onClick={() => setShowAccountSettingModal({ payload: 'provider' })} |
| | | > |
| | | <div className='text-sm font-medium'>{t('appOverview.apiKeyInfo.setAPIBtn')}</div> |
| | | <LinkExternal02 className='h-4 w-4' /> |
| | | <LinkExternal02 className='w-4 h-4' /> |
| | | </Button> |
| | | {!isCloud && ( |
| | | <a |
| | | className='mt-2 flex h-[26px] items-center space-x-1 p-1 text-xs font-medium text-[#155EEF]' |
| | | className='mt-2 flex items-center h-[26px] text-xs font-medium text-[#155EEF] p-1 space-x-1' |
| | | href='https://cloud.dify.ai/apps' |
| | | target='_blank' rel='noopener noreferrer' |
| | | > |
| | | <div>{t('appOverview.apiKeyInfo.tryCloud')}</div> |
| | | <LinkExternal02 className='h-3 w-3' /> |
| | | <LinkExternal02 className='w-3 h-3' /> |
| | | </a> |
| | | )} |
| | | <div |
| | | onClick={() => setIsShow(false)} |
| | | className='absolute right-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center '> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | className='absolute right-4 top-4 flex items-center justify-center w-8 h-8 cursor-pointer '> |
| | | <RiCloseLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | </div> |
| | | ) |
New file |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | export type IProgressProps = { |
| | | className?: string |
| | | value: number // percent |
| | | } |
| | | |
| | | const Progress: FC<IProgressProps> = ({ |
| | | className, |
| | | value, |
| | | }) => { |
| | | const exhausted = value === 100 |
| | | return ( |
| | | <div className={cn(className, 'relative grow h-2 flex bg-gray-200 rounded-md overflow-hidden')}> |
| | | <div |
| | | className={cn(s.bar, exhausted && s['bar-error'], 'absolute top-0 left-0 right-0 bottom-0')} |
| | | style={{ width: `${value}%` }} |
| | | /> |
| | | {Array(10).fill(0).map((i, k) => ( |
| | | <div key={k} className={s['bar-item']} /> |
| | | ))} |
| | | </div> |
| | | ) |
| | | } |
| | | export default React.memo(Progress) |
New file |
| | |
| | | .bar { |
| | | background: linear-gradient(90deg, rgba(41, 112, 255, 0.9) 0%, rgba(21, 94, 239, 0.9) 100%); |
| | | } |
| | | |
| | | .bar-error { |
| | | background: linear-gradient(90deg, rgba(240, 68, 56, 0.72) 0%, rgba(217, 45, 32, 0.9) 100%); |
| | | } |
| | | |
| | | .bar-item { |
| | | width: 10%; |
| | | border-right: 1px solid rgba(255, 255, 255, 0.5); |
| | | } |
| | | |
| | | .bar-item:last-of-type { |
| | | border-right: 0; |
| | | } |
| | |
| | | 'use client' |
| | | import type { HTMLProps } from 'react' |
| | | import React, { useMemo, useState } from 'react' |
| | | import { |
| | | Cog8ToothIcon, |
| | | DocumentTextIcon, |
| | | PaintBrushIcon, |
| | | RocketLaunchIcon, |
| | | } from '@heroicons/react/24/outline' |
| | | import { usePathname, useRouter } from 'next/navigation' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiBookOpenLine, |
| | | RiEqualizer2Line, |
| | | RiExternalLinkLine, |
| | | RiPaintBrushLine, |
| | | RiWindowLine, |
| | | } from '@remixicon/react' |
| | | import SettingsModal from './settings' |
| | | import EmbeddedModal from './embedded' |
| | | import CustomizeModal from './customize' |
| | |
| | | import AppBasic from '@/app/components/app-sidebar/basic' |
| | | import { asyncRunSafe, randomString } from '@/utils' |
| | | import Button from '@/app/components/base/button' |
| | | import Tag from '@/app/components/base/tag' |
| | | import Switch from '@/app/components/base/switch' |
| | | import Divider from '@/app/components/base/divider' |
| | | import CopyFeedback from '@/app/components/base/copy-feedback' |
| | |
| | | import type { AppDetailResponse } from '@/models/app' |
| | | import { useAppContext } from '@/context/app-context' |
| | | import type { AppSSO } from '@/types/app' |
| | | import Indicator from '@/app/components/header/indicator' |
| | | |
| | | export type IAppCardProps = { |
| | | className?: string |
| | | appInfo: AppDetailResponse & Partial<AppSSO> |
| | | isInPanel?: boolean |
| | | cardType?: 'api' | 'webapp' |
| | | customBgColor?: string |
| | | onChangeStatus: (val: boolean) => Promise<void> |
| | |
| | | onGenerateCode?: () => Promise<void> |
| | | } |
| | | |
| | | const EmbedIcon = ({ className = '' }: HTMLProps<HTMLDivElement>) => { |
| | | return <div className={`${style.codeBrowserIcon} ${className}`}></div> |
| | | } |
| | | |
| | | function AppCard({ |
| | | appInfo, |
| | | isInPanel, |
| | | cardType = 'webapp', |
| | | customBgColor, |
| | | onChangeStatus, |
| | |
| | | const OPERATIONS_MAP = useMemo(() => { |
| | | const operationsMap = { |
| | | webapp: [ |
| | | { opName: t('appOverview.overview.appInfo.launch'), opIcon: RiExternalLinkLine }, |
| | | { opName: t('appOverview.overview.appInfo.preview'), opIcon: RocketLaunchIcon }, |
| | | { opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: PaintBrushIcon }, |
| | | ] as { opName: string; opIcon: any }[], |
| | | api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: RiBookOpenLine }], |
| | | api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: DocumentTextIcon }], |
| | | app: [], |
| | | } |
| | | if (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: RiWindowLine }) |
| | | |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: RiPaintBrushLine }) |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: EmbedIcon }) |
| | | |
| | | if (isCurrentWorkspaceEditor) |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: RiEqualizer2Line }) |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: Cog8ToothIcon }) |
| | | |
| | | return operationsMap |
| | | }, [isCurrentWorkspaceEditor, appInfo, t]) |
| | |
| | | const appUrl = `${app_base_url}/${appMode}/${access_token}` |
| | | const apiUrl = appInfo?.api_base_url |
| | | |
| | | let bgColor = 'bg-primary-50 bg-opacity-40' |
| | | if (cardType === 'api') |
| | | bgColor = 'bg-purple-50' |
| | | |
| | | const genClickFuncByName = (opName: string) => { |
| | | switch (opName) { |
| | | case t('appOverview.overview.appInfo.launch'): |
| | | case t('appOverview.overview.appInfo.preview'): |
| | | return () => { |
| | | window.open(appUrl, '_blank') |
| | | } |
| | |
| | | return ( |
| | | <div |
| | | className={ |
| | | `${isInPanel ? 'border-l-[0.5px] border-t' : 'border-[0.5px] shadow-xs'} w-full max-w-full rounded-xl border-effects-highlight ${className ?? ''}`} |
| | | `shadow-xs border-[0.5px] rounded-lg border-gray-200 ${className ?? ''}`} |
| | | > |
| | | <div className={`${customBgColor ?? 'bg-background-default'} rounded-xl`}> |
| | | <div className='flex w-full flex-col items-start justify-center gap-3 self-stretch border-b-[0.5px] border-divider-subtle p-3'> |
| | | <div className='flex w-full items-center gap-3 self-stretch'> |
| | | <div className={`px-6 py-5 ${customBgColor ?? bgColor} rounded-lg`}> |
| | | <div className="mb-2.5 flex flex-row items-start justify-between"> |
| | | <AppBasic |
| | | iconType={cardType} |
| | | icon={appInfo.icon} |
| | |
| | | : t('appOverview.overview.apiInfo.explanation') |
| | | } |
| | | /> |
| | | <div className='flex items-center gap-1'> |
| | | <Indicator color={runningStatus ? 'green' : 'yellow'} /> |
| | | <div className={`${runningStatus ? 'text-text-success' : 'text-text-warning'} system-xs-semibold-uppercase`}> |
| | | <div className="flex flex-row items-center h-9"> |
| | | <Tag className="mr-2" color={runningStatus ? 'green' : 'yellow'}> |
| | | {runningStatus |
| | | ? t('appOverview.overview.status.running') |
| | | : t('appOverview.overview.status.disable')} |
| | | </div> |
| | | </div> |
| | | </Tag> |
| | | <Switch defaultValue={runningStatus} onChange={onChangeStatus} disabled={toggleDisabled} /> |
| | | </div> |
| | | <div className='flex flex-col items-start justify-center self-stretch'> |
| | | <div className="system-xs-medium pb-1 text-text-tertiary"> |
| | | </div> |
| | | <div className="flex flex-col justify-center py-2"> |
| | | <div className="py-1"> |
| | | <div className="pb-1 text-xs text-gray-500"> |
| | | {isApp |
| | | ? t('appOverview.overview.appInfo.accessibleAddress') |
| | | : t('appOverview.overview.apiInfo.accessibleAddress')} |
| | | </div> |
| | | <div className="inline-flex h-9 w-full items-center gap-0.5 rounded-lg bg-components-input-bg-normal p-1 pl-2"> |
| | | <div className="flex h-4 min-w-0 flex-1 items-start justify-start gap-2 px-1"> |
| | | <div className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-medium text-text-secondary"> |
| | | <div className="w-full h-9 pl-2 pr-0.5 py-0.5 bg-black bg-opacity-2 rounded-lg border border-black border-opacity-5 justify-start items-center inline-flex"> |
| | | <div className="h-4 px-2 justify-start items-start gap-2 flex flex-1 min-w-0"> |
| | | <div className="text-gray-700 text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap"> |
| | | {isApp ? appUrl : apiUrl} |
| | | </div> |
| | | </div> |
| | | <Divider type="vertical" className="!h-3.5 shrink-0 !mx-0.5" /> |
| | | {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} selectorId={randomString(8)} className={'hover:bg-gray-200'} />} |
| | | <CopyFeedback |
| | | content={isApp ? appUrl : apiUrl} |
| | | className={'!size-6'} |
| | | className={'hover:bg-gray-200'} |
| | | /> |
| | | {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} className='z-50 !size-6 rounded-md hover:bg-state-base-hover' selectorId={randomString(8)} />} |
| | | {isApp && <Divider type="vertical" className="!mx-0.5 !h-3.5 shrink-0" />} |
| | | {/* button copy link/ button regenerate */} |
| | | {showConfirmDelete && ( |
| | | <Confirm |
| | |
| | | popupContent={t('appOverview.overview.appInfo.regenerate') || ''} |
| | | > |
| | | <div |
| | | className="h-6 w-6 cursor-pointer rounded-md hover:bg-state-base-hover" |
| | | className="w-8 h-8 ml-0.5 cursor-pointer hover:bg-gray-200 rounded-lg" |
| | | onClick={() => setShowConfirmDelete(true)} |
| | | > |
| | | <div |
| | | className={ |
| | | `h-full w-full ${style.refreshIcon} ${genLoading ? style.generateLogo : ''}`} |
| | | `w-full h-full ${style.refreshIcon} ${genLoading ? style.generateLogo : ''}`} |
| | | ></div> |
| | | </div> |
| | | </Tooltip> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div className={'flex items-center gap-1 self-stretch p-3'}> |
| | | {!isApp && <SecretKeyButton appId={appInfo.id} />} |
| | | <div className={'pt-2 flex flex-row items-center flex-wrap gap-y-2'}> |
| | | {!isApp && <SecretKeyButton className='flex-shrink-0 !h-8 bg-white mr-2' textCls='!text-gray-700 font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />} |
| | | {OPERATIONS_MAP[cardType].map((op) => { |
| | | const disabled |
| | | = op.opName === t('appOverview.overview.appInfo.settings.entry') |
| | |
| | | : !runningStatus |
| | | return ( |
| | | <Button |
| | | className="mr-1 min-w-[88px]" |
| | | size="small" |
| | | variant={'ghost'} |
| | | className="mr-2" |
| | | key={op.opName} |
| | | onClick={genClickFuncByName(op.opName)} |
| | | disabled={disabled} |
| | |
| | | } |
| | | popupClassName={disabled ? 'mt-[-8px]' : '!hidden'} |
| | | > |
| | | <div className="flex items-center justify-center gap-[1px]"> |
| | | <op.opIcon className="h-3.5 w-3.5" /> |
| | | <div className={`${runningStatus ? 'text-text-tertiary' : 'text-components-button-ghost-text-disabled'} system-xs-medium px-[3px]`}>{op.opName}</div> |
| | | <div className="flex flex-row items-center"> |
| | | <op.opIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" /> |
| | | <span className="text-[13px]">{op.opName}</span> |
| | | </div> |
| | | </Tooltip> |
| | | </Button> |
| | |
| | | const sumData = isAvg ? (sum(yData) / yData.length) : sum(yData) |
| | | |
| | | return ( |
| | | <div className={`flex w-full flex-col rounded-xl bg-components-chart-bg px-6 py-4 shadow-xs ${className ?? ''}`}> |
| | | <div className={`flex flex-col w-full px-6 py-4 border-[0.5px] rounded-lg border-gray-200 shadow-xs ${className ?? ''}`}> |
| | | <div className='mb-3'> |
| | | <Basic name={title} type={timePeriod} hoverTip={explanation} /> |
| | | </div> |
| | | <div className='mb-4 flex-1'> |
| | | <Basic |
| | | isExtraInLine={CHART_TYPE_CONFIG[chartType].showTokens} |
| | | name={chartType !== 'costs' ? (`${sumData.toLocaleString()} ${unit}`) : `${sumData < 1000 ? sumData : (`${formatNumber(Math.round(sumData / 1000))}k`)}`} |
| | | name={chartType !== 'costs' ? (sumData.toLocaleString() + unit) : `${sumData < 1000 ? sumData : (`${formatNumber(Math.round(sumData / 1000))}k`)}`} |
| | | type={!CHART_TYPE_CONFIG[chartType].showTokens |
| | | ? '' |
| | | : <span>{t('appOverview.analysis.tokenUsage.consumed')} Tokens<span className='text-sm'> |
| | | <span className='ml-1 text-text-tertiary'>(</span> |
| | | <span className='text-orange-400'>~{sum(statistics.map(item => Number.parseFloat(get(item, 'total_price', '0')))).toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 4 })}</span> |
| | | <span className='text-text-tertiary'>)</span> |
| | | <span className='ml-1 text-gray-500'>(</span> |
| | | <span className='text-orange-400'>~{sum(statistics.map(item => parseFloat(get(item, 'total_price', '0')))).toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 4 })}</span> |
| | | <span className='text-gray-500'>)</span> |
| | | </span></span>} |
| | | textStyle={{ main: `!text-3xl !font-normal ${sumData === 0 ? '!text-text-quaternary' : ''}` }} /> |
| | | textStyle={{ main: `!text-3xl !font-normal ${sumData === 0 ? '!text-gray-300' : ''}` }} /> |
| | | </div> |
| | | <ReactECharts option={options} style={{ height: 160 }} /> |
| | | </div> |
| | |
| | | isAvg |
| | | unit={t('appOverview.analysis.tokenPS') as string} |
| | | {...(noDataFlag && { yMax: 100 })} |
| | | className="min-w-0" |
| | | /> |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | const StepNum: FC<{ children: React.ReactNode }> = ({ children }) => |
| | | <div className='mr-3 flex h-7 w-7 shrink-0 items-center justify-center rounded-2xl bg-util-colors-blue-blue-50 text-text-accent'> |
| | | <div className='h-7 w-7 flex justify-center items-center flex-shrink-0 mr-3 text-primary-600 bg-primary-50 rounded-2xl'> |
| | | {children} |
| | | </div> |
| | | |
| | |
| | | description={t(`${prefixCustomize}.explanation`)} |
| | | isShow={isShow} |
| | | onClose={onClose} |
| | | className='w-[640px] !max-w-2xl' |
| | | className='!max-w-2xl w-[640px]' |
| | | closable={true} |
| | | > |
| | | <div className='mt-4 w-full rounded-lg border-[0.5px] border-components-panel-border px-6 py-5'> |
| | | <Tag bordered={true} hideBg={true} className='border-text-accent-secondary uppercase text-text-accent-secondary'>{t(`${prefixCustomize}.way`)} 1</Tag> |
| | | <p className='system-sm-medium my-2 text-text-secondary'>{t(`${prefixCustomize}.way1.name`)}</p> |
| | | <div className='w-full mt-4 px-6 py-5 border-gray-200 rounded-lg border-[0.5px]'> |
| | | <Tag bordered={true} hideBg={true} className='text-primary-600 border-primary-600 uppercase'>{t(`${prefixCustomize}.way`)} 1</Tag> |
| | | <p className='my-2 text-base font-medium text-gray-800'>{t(`${prefixCustomize}.way1.name`)}</p> |
| | | <div className='flex py-4'> |
| | | <StepNum>1</StepNum> |
| | | <div className='flex flex-col'> |
| | | <div className='text-text-primary'>{t(`${prefixCustomize}.way1.step1`)}</div> |
| | | <div className='mb-2 mt-1 text-xs text-text-tertiary'>{t(`${prefixCustomize}.way1.step1Tip`)}</div> |
| | | <div className='text-gray-900'>{t(`${prefixCustomize}.way1.step1`)}</div> |
| | | <div className='text-gray-500 text-xs mt-1 mb-2'>{t(`${prefixCustomize}.way1.step1Tip`)}</div> |
| | | <a href={`https://github.com/langgenius/${isChatApp ? 'webapp-conversation' : 'webapp-text-generator'}`} target='_blank' rel='noopener noreferrer'> |
| | | <Button><GithubIcon className='mr-2 text-text-secondary' />{t(`${prefixCustomize}.way1.step1Operation`)}</Button> |
| | | <Button><GithubIcon className='text-gray-800 mr-2' />{t(`${prefixCustomize}.way1.step1Operation`)}</Button> |
| | | </a> |
| | | </div> |
| | | </div> |
| | | <div className='flex pt-4'> |
| | | <StepNum>2</StepNum> |
| | | <div className='flex flex-col'> |
| | | <div className='text-text-primary'>{t(`${prefixCustomize}.way1.step3`)}</div> |
| | | <div className='mb-2 mt-1 text-xs text-text-tertiary'>{t(`${prefixCustomize}.way1.step2Tip`)}</div> |
| | | <div className='text-gray-900'>{t(`${prefixCustomize}.way1.step3`)}</div> |
| | | <div className='text-gray-500 text-xs mt-1 mb-2'>{t(`${prefixCustomize}.way1.step2Tip`)}</div> |
| | | <a href="https://vercel.com/docs/concepts/deployments/git/vercel-for-github" target='_blank' rel='noopener noreferrer'> |
| | | <Button> |
| | | <div className='mr-1.5 border-b-[12px] border-l-[7px] border-r-[7px] border-t-0 border-solid border-text-primary border-l-transparent border-r-transparent border-t-transparent'></div> |
| | | <div className='mr-1.5 border-solid border-t-0 border-r-[7px] border-l-[7px] border-b-[12px] border-r-transparent border-b-black border-l-transparent border-t-transparent'></div> |
| | | <span>{t(`${prefixCustomize}.way1.step2Operation`)}</span> |
| | | </Button> |
| | | </a> |
| | |
| | | </div> |
| | | <div className='flex py-4'> |
| | | <StepNum>3</StepNum> |
| | | <div className='flex w-full flex-col overflow-hidden'> |
| | | <div className='text-text-primary'>{t(`${prefixCustomize}.way1.step3`)}</div> |
| | | <div className='mb-2 mt-1 text-xs text-text-tertiary'>{t(`${prefixCustomize}.way1.step3Tip`)}</div> |
| | | <pre className='box-border select-text overflow-x-scroll rounded-lg border-[0.5px] border-components-panel-border bg-background-section px-4 py-3 text-xs font-medium text-text-secondary'> |
| | | <div className='flex flex-col w-full overflow-hidden'> |
| | | <div className='text-gray-900'>{t(`${prefixCustomize}.way1.step3`)}</div> |
| | | <div className='text-gray-500 text-xs mt-1 mb-2'>{t(`${prefixCustomize}.way1.step3Tip`)}</div> |
| | | <pre className='overflow-x-scroll box-border py-3 px-4 bg-gray-100 text-xs font-medium rounded-lg select-text'> |
| | | NEXT_PUBLIC_APP_ID={`'${appId}'`} <br /> |
| | | NEXT_PUBLIC_APP_KEY={'\'<Web API Key From Dify>\''} <br /> |
| | | NEXT_PUBLIC_API_URL={`'${api_base_url}'`} |
| | |
| | | </div> |
| | | |
| | | </div> |
| | | <div className='mt-4 w-full rounded-lg border-[0.5px] border-components-panel-border px-6 py-5'> |
| | | <Tag bordered={true} hideBg={true} className='border-text-accent-secondary uppercase text-text-accent-secondary'>{t(`${prefixCustomize}.way`)} 2</Tag> |
| | | <p className='system-sm-medium my-2 text-text-secondary'>{t(`${prefixCustomize}.way2.name`)}</p> |
| | | <div className='w-full mt-4 px-6 py-5 border-gray-200 rounded-lg border-[0.5px]'> |
| | | <Tag bordered={true} hideBg={true} className='text-primary-600 border-primary-600 uppercase'>{t(`${prefixCustomize}.way`)} 2</Tag> |
| | | <p className='mt-2 text-base font-medium text-gray-800'>{t(`${prefixCustomize}.way2.name`)}</p> |
| | | <Button |
| | | className='mt-2' |
| | | onClick={() => |
| | | window.open( |
| | | `https://docs.dify.ai/${locale !== LanguagesSupported[1] |
| | | ? 'user-guide/launching-dify-apps/developing-with-apis' |
| | | : `${locale.toLowerCase()}/guides/application-publishing/developing-with-apis` |
| | | : `v/${locale.toLowerCase()}/guides/application-publishing/developing-with-apis` |
| | | }`, |
| | | '_blank', |
| | | ) |
| | | } |
| | | > |
| | | <span className='text-sm text-text-secondary'>{t(`${prefixCustomize}.way2.operation`)}</span> |
| | | <ArrowTopRightOnSquareIcon className='ml-1 h-4 w-4 shrink-0 text-text-secondary' /> |
| | | <span className='text-sm text-gray-800'>{t(`${prefixCustomize}.way2.operation`)}</span> |
| | | <ArrowTopRightOnSquareIcon className='w-4 h-4 ml-1 text-gray-800 shrink-0' /> |
| | | </Button> |
| | | </div> |
| | | </Modal> |
| | |
| | | import React, { useEffect, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiClipboardFill, |
| | | RiClipboardLine, |
| | | } from '@remixicon/react' |
| | | import copy from 'copy-to-clipboard' |
| | | import style from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import Modal from '@/app/components/base/modal' |
| | | import copyStyle from '@/app/components/base/copy-btn/style.module.css' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import { useAppContext } from '@/context/app-context' |
| | | import { IS_CE_EDITION } from '@/config' |
| | | import type { SiteInfo } from '@/models/share' |
| | | import { useThemeContext } from '@/app/components/base/chat/embedded-chatbot/theme/theme-context' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type Props = { |
| | | siteInfo?: SiteInfo |
| | |
| | | : ''}${IS_CE_EDITION |
| | | ? `, |
| | | baseUrl: '${url}'` |
| | | : ''}, |
| | | systemVariables: { |
| | | // user_id: 'YOU CAN DEFINE USER ID HERE', |
| | | // conversation_id: 'YOU CAN DEFINE CONVERSATION ID HERE, IT MUST BE A VALID UUID', |
| | | }, |
| | | : ''} |
| | | } |
| | | </script> |
| | | <script |
| | |
| | | } |
| | | |
| | | const navigateToChromeUrl = () => { |
| | | window.open('https://chrome.google.com/webstore/detail/dify-chatbot/ceehdapohffmjmkdcifjofadiaoeggaf', '_blank', 'noopener,noreferrer') |
| | | window.open('https://chrome.google.com/webstore/detail/dify-chatbot/ceehdapohffmjmkdcifjofadiaoeggaf', '_blank') |
| | | } |
| | | |
| | | useEffect(() => { |
| | |
| | | title={t(`${prefixEmbedded}.title`)} |
| | | isShow={isShow} |
| | | onClose={onClose} |
| | | className="w-[640px] !max-w-2xl" |
| | | className="!max-w-2xl w-[640px]" |
| | | wrapperClassName={className} |
| | | closable={true} |
| | | > |
| | | <div className="system-sm-medium mb-4 mt-8 text-text-primary"> |
| | | <div className="mb-4 mt-8 text-gray-900 text-[14px] font-medium leading-tight"> |
| | | {t(`${prefixEmbedded}.explanation`)} |
| | | </div> |
| | | <div className="flex flex-wrap items-center justify-between gap-y-2"> |
| | |
| | | })} |
| | | </div> |
| | | {option === 'chromePlugin' && ( |
| | | <div className="mt-6 w-full"> |
| | | <div className={cn('inline-flex w-full items-center justify-center gap-2 rounded-lg py-3', |
| | | 'shrink-0 cursor-pointer bg-primary-600 text-white hover:bg-primary-600/75 hover:shadow-sm')}> |
| | | <div className={`relative h-4 w-4 ${style.pluginInstallIcon}`}></div> |
| | | <div className="font-['Inter'] text-sm font-medium leading-tight text-white" onClick={navigateToChromeUrl}>{t(`${prefixEmbedded}.chromePlugin`)}</div> |
| | | <div className="w-full mt-6"> |
| | | <div className={cn('gap-2 py-3 justify-center items-center inline-flex w-full rounded-lg', |
| | | 'bg-primary-600 hover:bg-primary-600/75 hover:shadow-md cursor-pointer text-white hover:shadow-sm flex-shrink-0')}> |
| | | <div className={`w-4 h-4 relative ${style.pluginInstallIcon}`}></div> |
| | | <div className="text-white text-sm font-medium font-['Inter'] leading-tight" onClick={navigateToChromeUrl}>{t(`${prefixEmbedded}.chromePlugin`)}</div> |
| | | </div> |
| | | </div> |
| | | )} |
| | | <div className={cn('inline-flex w-full flex-col items-start justify-start rounded-lg border-[0.5px] border-components-panel-border bg-background-section', |
| | | <div className={cn('w-full bg-gray-100 rounded-lg flex-col justify-start items-start inline-flex', |
| | | 'mt-6')}> |
| | | <div className="inline-flex items-center justify-start gap-2 self-stretch rounded-t-lg bg-background-section-burn py-1 pl-3 pr-1"> |
| | | <div className="system-sm-medium shrink-0 grow text-text-secondary"> |
| | | <div className="inline-flex items-center self-stretch justify-start gap-2 py-1 pl-3 pr-1 border border-black rounded-tl-lg rounded-tr-lg bg-gray-50 border-opacity-5"> |
| | | <div className="grow shrink basis-0 text-slate-700 text-[13px] font-medium leading-none"> |
| | | {t(`${prefixEmbedded}.${option}`)} |
| | | </div> |
| | | <div className="flex items-center justify-center gap-1 p-2 rounded-lg"> |
| | | <Tooltip |
| | | popupContent={ |
| | | (isCopied[option] |
| | | ? t(`${prefixEmbedded}.copied`) |
| | | : t(`${prefixEmbedded}.copy`)) || '' |
| | | } |
| | | popupContent={(isCopied[option] ? t(`${prefixEmbedded}.copied`) : t(`${prefixEmbedded}.copy`)) || ''} |
| | | > |
| | | <ActionButton> |
| | | <div |
| | | onClick={onClickCopy} |
| | | > |
| | | {isCopied[option] && <RiClipboardFill className='h-4 w-4' />} |
| | | {!isCopied[option] && <RiClipboardLine className='h-4 w-4' />} |
| | | <div className="w-8 h-8 rounded-lg cursor-pointer hover:bg-gray-100"> |
| | | <div onClick={onClickCopy} className={`w-full h-full ${copyStyle.copyIcon} ${isCopied[option] ? copyStyle.copied : ''}`}></div> |
| | | </div> |
| | | </ActionButton> |
| | | </Tooltip> |
| | | </div> |
| | | <div className="flex w-full items-start justify-start gap-2 overflow-x-auto p-3"> |
| | | <div className="shrink grow basis-0 font-mono text-[13px] leading-tight text-text-secondary"> |
| | | </div> |
| | | <div className="flex items-start justify-start w-full gap-2 p-3 overflow-x-auto"> |
| | | <div className="grow shrink basis-0 text-slate-700 text-[13px] leading-tight font-mono"> |
| | | <pre className='select-text'>{OPTION_MAP[option].getContent(appBaseUrl, accessToken, themeBuilder.theme?.primaryColor ?? '#1C64F2', isTestEnv)}</pre> |
| | | </div> |
| | | </div> |
| | |
| | | return check |
| | | } |
| | | |
| | | const validatePrivacyPolicy = (privacyPolicy: string | null) => { |
| | | if (privacyPolicy === null || privacyPolicy?.length === 0) |
| | | return true |
| | | |
| | | return privacyPolicy.startsWith('http://') || privacyPolicy.startsWith('https://') |
| | | } |
| | | |
| | | if (inputInfo !== null) { |
| | | if (!validateColorHex(inputInfo.chatColorTheme)) { |
| | | notify({ type: 'error', message: t(`${prefixSettings}.invalidHexMessage`) }) |
| | | return |
| | | } |
| | | if (!validatePrivacyPolicy(inputInfo.privacyPolicy)) { |
| | | notify({ type: 'error', message: t(`${prefixSettings}.invalidPrivacyPolicy`) }) |
| | | return |
| | | } |
| | | } |
| | |
| | | className='max-w-[520px] p-0' |
| | | > |
| | | {/* header */} |
| | | <div className='pb-3 pl-6 pr-5 pt-5'> |
| | | <div className='pl-6 pt-5 pr-5 pb-3'> |
| | | <div className='flex items-center gap-1'> |
| | | <div className='title-2xl-semi-bold grow text-text-primary'>{t(`${prefixSettings}.title`)}</div> |
| | | <div className='grow text-text-primary title-2xl-semi-bold'>{t(`${prefixSettings}.title`)}</div> |
| | | <ActionButton className='shrink-0' onClick={onHide}> |
| | | <RiCloseLine className='h-4 w-4' /> |
| | | <RiCloseLine className='w-4 h-4' /> |
| | | </ActionButton> |
| | | </div> |
| | | <div className='system-xs-regular mt-0.5 text-text-tertiary'> |
| | | <div className='mt-0.5 text-text-tertiary system-xs-regular'> |
| | | <span>{t(`${prefixSettings}.modalTip`)}</span> |
| | | <Link href={`${locale === LanguagesSupported[1] ? 'https://docs.dify.ai/zh-hans/guides/application-publishing/launch-your-webapp-quickly#she-zhi-ni-de-ai-zhan-dian' : 'https://docs.dify.ai/en/guides/application-publishing/launch-your-webapp-quickly/README'}`} target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> |
| | | <Link href={`${locale === LanguagesSupported[1] ? 'https://docs.dify.ai/zh-hans/guides/application-publishing/launch-your-webapp-quickly#she-zhi-ni-de-ai-zhan-dian' : 'https://docs.dify.ai/guides/application-publishing/launch-your-webapp-quickly#setting-up-your-ai-site'}`} target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> |
| | | </div> |
| | | </div> |
| | | {/* form body */} |
| | | <div className='space-y-5 px-6 py-3'> |
| | | <div className='px-6 py-3 space-y-5'> |
| | | {/* name & icon */} |
| | | <div className='flex gap-4'> |
| | | <div className='grow'> |
| | | <div className={cn('system-sm-semibold mb-1 py-1 text-text-secondary')}>{t(`${prefixSettings}.webName`)}</div> |
| | | <div className={cn('mb-1 py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.webName`)}</div> |
| | | <Input |
| | | className='w-full' |
| | | value={inputInfo.title} |
| | |
| | | </div> |
| | | {/* description */} |
| | | <div className='relative'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.webDesc`)}</div> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.webDesc`)}</div> |
| | | <Textarea |
| | | className='mt-1' |
| | | value={inputInfo.desc} |
| | | onChange={e => onDesChange(e.target.value)} |
| | | placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string} |
| | | /> |
| | | <p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>{t(`${prefixSettings}.webDescTip`)}</p> |
| | | <p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.webDescTip`)}</p> |
| | | </div> |
| | | <Divider className="my-0 h-px" /> |
| | | <Divider className="h-px my-0" /> |
| | | {/* answer icon */} |
| | | {isChat && ( |
| | | <div className='w-full'> |
| | | <div className='flex items-center justify-between'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t('app.answerIcon.title')}</div> |
| | | <div className='flex justify-between items-center'> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t('app.answerIcon.title')}</div> |
| | | <Switch |
| | | defaultValue={inputInfo.use_icon_as_answer_icon} |
| | | onChange={v => setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })} |
| | | /> |
| | | </div> |
| | | <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t('app.answerIcon.description')}</p> |
| | | <p className='pb-0.5 text-text-tertiary body-xs-regular'>{t('app.answerIcon.description')}</p> |
| | | </div> |
| | | )} |
| | | {/* language */} |
| | | <div className='flex items-center'> |
| | | <div className={cn('system-sm-semibold grow py-1 text-text-secondary')}>{t(`${prefixSettings}.language`)}</div> |
| | | <div className={cn('grow py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.language`)}</div> |
| | | <SimpleSelect |
| | | wrapperClassName='w-[200px]' |
| | | items={languages.filter(item => item.supported)} |
| | | defaultValue={language} |
| | | onSelect={item => setLanguage(item.value as Language)} |
| | | notClearable |
| | | /> |
| | | </div> |
| | | {/* theme color */} |
| | | {isChat && ( |
| | | <div className='flex items-center'> |
| | | <div className='grow'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.chatColorTheme`)}</div> |
| | | <div className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.chatColorThemeDesc`)}</div> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.chatColorTheme`)}</div> |
| | | <div className='pb-0.5 body-xs-regular text-text-tertiary'>{t(`${prefixSettings}.chatColorThemeDesc`)}</div> |
| | | </div> |
| | | <div className='shrink-0'> |
| | | <Input |
| | |
| | | onChange={onChange('chatColorTheme')} |
| | | placeholder='E.g #A020F0' |
| | | /> |
| | | <div className='flex items-center justify-between'> |
| | | <div className='flex justify-between items-center'> |
| | | <p className={cn('body-xs-regular text-text-tertiary')}>{t(`${prefixSettings}.chatColorThemeInverted`)}</p> |
| | | <Switch defaultValue={inputInfo.chatColorThemeInverted} onChange={v => setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}></Switch> |
| | | </div> |
| | |
| | | )} |
| | | {/* workflow detail */} |
| | | <div className='w-full'> |
| | | <div className='flex items-center justify-between'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.workflow.subTitle`)}</div> |
| | | <div className='flex justify-between items-center'> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.workflow.subTitle`)}</div> |
| | | <Switch |
| | | disabled={!(appInfo.mode === 'workflow' || appInfo.mode === 'advanced-chat')} |
| | | defaultValue={inputInfo.show_workflow_steps} |
| | | onChange={v => setInputInfo({ ...inputInfo, show_workflow_steps: v })} |
| | | /> |
| | | </div> |
| | | <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.workflow.showDesc`)}</p> |
| | | <p className='pb-0.5 text-text-tertiary body-xs-regular'>{t(`${prefixSettings}.workflow.showDesc`)}</p> |
| | | </div> |
| | | {/* SSO */} |
| | | {systemFeatures.enable_web_sso_switch_component && ( |
| | | <> |
| | | <Divider className="my-0 h-px" /> |
| | | <Divider className="h-px my-0" /> |
| | | <div className='w-full'> |
| | | <p className='system-xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${prefixSettings}.sso.label`)}</p> |
| | | <div className='flex items-center justify-between'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.sso.title`)}</div> |
| | | <p className='mb-1 system-xs-medium-uppercase text-text-tertiary'>{t(`${prefixSettings}.sso.label`)}</p> |
| | | <div className='flex justify-between items-center'> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.sso.title`)}</div> |
| | | <Tooltip |
| | | disabled={systemFeatures.sso_enforced_for_web} |
| | | popupContent={ |
| | |
| | | <Switch disabled={!systemFeatures.sso_enforced_for_web || !isCurrentWorkspaceEditor} defaultValue={systemFeatures.sso_enforced_for_web && inputInfo.enable_sso} onChange={v => setInputInfo({ ...inputInfo, enable_sso: v })}></Switch> |
| | | </Tooltip> |
| | | </div> |
| | | <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.sso.description`)}</p> |
| | | <p className='pb-0.5 body-xs-regular text-text-tertiary'>{t(`${prefixSettings}.sso.description`)}</p> |
| | | </div> |
| | | </> |
| | | )} |
| | | {/* more settings switch */} |
| | | <Divider className="my-0 h-px" /> |
| | | <Divider className="h-px my-0" /> |
| | | {!isShowMore && ( |
| | | <div className='flex cursor-pointer items-center' onClick={() => setIsShowMore(true)}> |
| | | <div className='flex items-center cursor-pointer' onClick={() => setIsShowMore(true)}> |
| | | <div className='grow'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.entry`)}</div> |
| | | <p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>{t(`${prefixSettings}.more.copyRightPlaceholder`)} & {t(`${prefixSettings}.more.privacyPolicyPlaceholder`)}</p> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.entry`)}</div> |
| | | <p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.more.copyRightPlaceholder`)} & {t(`${prefixSettings}.more.privacyPolicyPlaceholder`)}</p> |
| | | </div> |
| | | <RiArrowRightSLine className='ml-1 h-4 w-4 shrink-0 text-text-secondary' /> |
| | | <RiArrowRightSLine className='shrink-0 ml-1 w-4 h-4 text-text-secondary'/> |
| | | </div> |
| | | )} |
| | | {/* more settings */} |
| | |
| | | {/* copyright */} |
| | | <div className='w-full'> |
| | | <div className='flex items-center'> |
| | | <div className='flex grow items-center'> |
| | | <div className={cn('system-sm-semibold mr-1 py-1 text-text-secondary')}>{t(`${prefixSettings}.more.copyright`)}</div> |
| | | <div className='grow flex items-center'> |
| | | <div className={cn('mr-1 py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.copyright`)}</div> |
| | | {/* upgrade button */} |
| | | {enableBilling && isFreePlan && ( |
| | | <div className='h-[18px] select-none'> |
| | | <div className='select-none h-[18px]'> |
| | | <PremiumBadge size='s' color='blue' allowHover={true} onClick={handlePlanClick}> |
| | | <SparklesSoft className='flex h-3.5 w-3.5 items-center py-[1px] pl-[3px] text-components-premium-badge-indigo-text-stop-0' /> |
| | | <SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' /> |
| | | <div className='system-xs-medium'> |
| | | <span className='p-1'> |
| | | {t('billing.upgradeBtn.encourageShort')} |
| | |
| | | /> |
| | | </Tooltip> |
| | | </div> |
| | | <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.more.copyrightTip`)}</p> |
| | | <p className='pb-0.5 text-text-tertiary body-xs-regular'>{t(`${prefixSettings}.more.copyrightTip`)}</p> |
| | | {inputInfo.copyrightSwitchValue && ( |
| | | <Input |
| | | className='mt-2 h-10' |
| | |
| | | </div> |
| | | {/* privacy policy */} |
| | | <div className='w-full'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.privacyPolicy`)}</div> |
| | | <p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.privacyPolicy`)}</div> |
| | | <p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}> |
| | | <Trans |
| | | i18nKey={`${prefixSettings}.more.privacyPolicyTip`} |
| | | components={{ privacyPolicyLink: <Link href={'https://dify.ai/privacy'} target='_blank' rel='noopener noreferrer' className='text-text-accent' /> }} |
| | | components={{ privacyPolicyLink: <Link href={'https://docs.dify.ai/user-agreement/privacy-policy'} target='_blank' rel='noopener noreferrer' className='text-text-accent' /> }} |
| | | /> |
| | | </p> |
| | | <Input |
| | |
| | | </div> |
| | | {/* custom disclaimer */} |
| | | <div className='w-full'> |
| | | <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.customDisclaimer`)}</div> |
| | | <p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>{t(`${prefixSettings}.more.customDisclaimerTip`)}</p> |
| | | <div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.customDisclaimer`)}</div> |
| | | <p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}>{t(`${prefixSettings}.more.customDisclaimerTip`)}</p> |
| | | <Textarea |
| | | className='mt-1' |
| | | value={inputInfo.customDisclaimer} |
| | |
| | | )} |
| | | </div> |
| | | {/* footer */} |
| | | <div className='flex justify-end p-6 pt-5'> |
| | | <div className='p-6 pt-5 flex justify-end'> |
| | | <Button className='mr-2' onClick={onHide}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button> |
| | | </div> |
| | | |
| | | </Modal > |
| | | {showAppIconPicker && ( |
| | | <div onClick={e => e.stopPropagation()}> |
| | | <AppIconPicker |
| | | onSelect={(payload) => { |
| | | setAppIcon(payload) |
| | |
| | | setShowAppIconPicker(false) |
| | | }} |
| | | /> |
| | | </div> |
| | | )} |
| | | </Modal> |
| | | </> |
| | | |
| | | ) |
| | | } |
| | | export default React.memo(SettingsModal) |
| | |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiCloseLine } from '@remixicon/react' |
| | | import AppIconPicker from '../../base/app-icon-picker' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import Checkbox from '@/app/components/base/checkbox' |
| | | import Button from '@/app/components/base/button' |
| | |
| | | import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | type SwitchAppModalProps = { |
| | | show: boolean |
| | |
| | | removeOriginal ? replace : push, |
| | | ) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | } |
| | |
| | | return ( |
| | | <> |
| | | <Modal |
| | | className={cn('w-[600px] max-w-[600px] p-8')} |
| | | className={cn('p-8 max-w-[600px] w-[600px]', s.bg)} |
| | | isShow={show} |
| | | onClose={noop} |
| | | onClose={() => { }} |
| | | > |
| | | <div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onClose}> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onClose}> |
| | | <RiCloseLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | <div className='h-12 w-12 rounded-xl border-[0.5px] border-divider-regular bg-background-default-burn p-3 shadow-xl'> |
| | | <AlertTriangle className='h-6 w-6 text-[rgb(247,144,9)]' /> |
| | | <div className='w-12 h-12 p-3 bg-white rounded-xl border-[0.5px] border-gray-100 shadow-xl'> |
| | | <AlertTriangle className='w-6 h-6 text-[rgb(247,144,9)]' /> |
| | | </div> |
| | | <div className='relative mt-3 text-xl font-semibold leading-[30px] text-text-primary'>{t('app.switch')}</div> |
| | | <div className='my-1 text-sm leading-5 text-text-tertiary'> |
| | | <div className='relative mt-3 text-xl font-semibold leading-[30px] text-gray-900'>{t('app.switch')}</div> |
| | | <div className='my-1 text-gray-500 text-sm leading-5'> |
| | | <span>{t('app.switchTipStart')}</span> |
| | | <span className='font-medium text-text-secondary'>{t('app.switchTip')}</span> |
| | | <span className='text-gray-700 font-medium'>{t('app.switchTip')}</span> |
| | | <span>{t('app.switchTipEnd')}</span> |
| | | </div> |
| | | <div className='pb-4'> |
| | | <div className='py-2 text-sm font-medium leading-[20px] text-text-primary'>{t('app.switchLabel')}</div> |
| | | <div className='py-2 text-sm font-medium leading-[20px] text-gray-900'>{t('app.switchLabel')}</div> |
| | | <div className='flex items-center justify-between space-x-2'> |
| | | <AppIcon |
| | | size='large' |
| | |
| | | value={name} |
| | | onChange={e => setName(e.target.value)} |
| | | placeholder={t('app.newApp.appNamePlaceholder') || ''} |
| | | className='h-10 grow' |
| | | className='grow h-10' |
| | | /> |
| | | </div> |
| | | {showAppIconPicker && <AppIconPicker |
| | |
| | | />} |
| | | </div> |
| | | {isAppsFull && <AppsFull loc='app-switch' />} |
| | | <div className='flex items-center justify-between pt-6'> |
| | | <div className='pt-6 flex justify-between items-center'> |
| | | <div className='flex items-center'> |
| | | <Checkbox className='shrink-0' checked={removeOriginal} onCheck={() => setRemoveOriginal(!removeOriginal)} /> |
| | | <div className="ml-2 cursor-pointer text-sm leading-5 text-text-secondary" onClick={() => setRemoveOriginal(!removeOriginal)}>{t('app.removeOriginal')}</div> |
| | | <div className="ml-2 text-sm leading-5 text-gray-700 cursor-pointer" onClick={() => setRemoveOriginal(!removeOriginal)}>{t('app.removeOriginal')}</div> |
| | | </div> |
| | | <div className='flex items-center'> |
| | | <Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button> |
New file |
| | |
| | | .bg { |
| | | background: linear-gradient(180deg, rgba(247, 144, 9, 0.05) 0%, rgba(247, 144, 9, 0.00) 24.41%), #F9FAFB; |
| | | } |
New file |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import { format } from '@/service/base' |
| | | |
| | | export type ITextGenerationProps = { |
| | | value: string |
| | | className?: string |
| | | } |
| | | |
| | | const TextGeneration: FC<ITextGenerationProps> = ({ |
| | | value, |
| | | className, |
| | | }) => { |
| | | return ( |
| | | <div |
| | | className={className} |
| | | dangerouslySetInnerHTML={{ |
| | | __html: format(value), |
| | | }} |
| | | > |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | export default React.memo(TextGeneration) |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React, { useEffect, useState } from 'react' |
| | | import React, { useEffect, useRef, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiBookmark3Line, |
| | | RiClipboardLine, |
| | | RiFileList3Line, |
| | | RiPlayList2Line, |
| | | RiReplay15Line, |
| | | RiSparklingFill, |
| | | RiSparklingLine, |
| | | RiThumbDownLine, |
| | | RiThumbUpLine, |
| | | } from '@remixicon/react' |
| | | import copy from 'copy-to-clipboard' |
| | | import { useParams } from 'next/navigation' |
| | | import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' |
| | | import { useBoolean } from 'ahooks' |
| | | import { HashtagIcon } from '@heroicons/react/24/solid' |
| | | import ResultTab from './result-tab' |
| | | import cn from '@/utils/classnames' |
| | | import { Markdown } from '@/app/components/base/markdown' |
| | | import Loading from '@/app/components/base/loading' |
| | | import Toast from '@/app/components/base/toast' |
| | | import AudioBtn from '@/app/components/base/audio-btn' |
| | | import type { FeedbackType } from '@/app/components/base/chat/chat/type' |
| | | import { fetchMoreLikeThis, updateFeedback } from '@/service/share' |
| | | import { File02 } from '@/app/components/base/icons/src/vender/line/files' |
| | | import { Bookmark } from '@/app/components/base/icons/src/vender/line/general' |
| | | import { Stars02 } from '@/app/components/base/icons/src/vender/line/weather' |
| | | import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows' |
| | | import AnnotationCtrlBtn from '@/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-btn' |
| | | import { fetchTextGenerationMessage } from '@/service/debug' |
| | | import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | import WorkflowProcessItem from '@/app/components/base/chat/chat/answer/workflow-process' |
| | | import type { WorkflowProcess } from '@/app/components/base/chat/types' |
| | | import type { SiteInfo } from '@/models/share' |
| | | import { useChatContext } from '@/app/components/base/chat/chat/context' |
| | | import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' |
| | | import NewAudioButton from '@/app/components/base/new-audio-button' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | const MAX_DEPTH = 3 |
| | | |
| | |
| | | taskId?: string |
| | | controlClearMoreLikeThis?: number |
| | | supportFeedback?: boolean |
| | | supportAnnotation?: boolean |
| | | isShowTextToSpeech?: boolean |
| | | appId?: string |
| | | varList?: { label: string; value: string | number | object }[] |
| | | innerClassName?: string |
| | | contentClassName?: string |
| | | footerClassName?: string |
| | | hideProcessDetail?: boolean |
| | | siteInfo: SiteInfo | null |
| | | inSidePanel?: boolean |
| | | } |
| | | |
| | | export const SimpleBtn = ({ className, isDisabled, onClick, children }: { |
| | | className?: string |
| | | isDisabled?: boolean |
| | | onClick?: () => void |
| | | children: React.ReactNode |
| | | }) => ( |
| | | <div |
| | | className={cn(isDisabled ? 'border-gray-100 text-gray-300' : 'border-gray-200 text-gray-700 cursor-pointer hover:border-gray-300 hover:shadow-sm', 'flex items-center h-7 px-3 rounded-md border text-xs font-medium', className)} |
| | | onClick={() => !isDisabled && onClick?.()} |
| | | > |
| | | {children} |
| | | </div> |
| | | ) |
| | | |
| | | export const copyIcon = ( |
| | | <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | |
| | | taskId, |
| | | controlClearMoreLikeThis, |
| | | supportFeedback, |
| | | supportAnnotation, |
| | | isShowTextToSpeech, |
| | | appId, |
| | | varList, |
| | | innerClassName, |
| | | contentClassName, |
| | | hideProcessDetail, |
| | | siteInfo, |
| | | inSidePanel, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const params = useParams() |
| | | const isTop = depth === 1 |
| | | const ref = useRef(null) |
| | | const [completionRes, setCompletionRes] = useState('') |
| | | const [childMessageId, setChildMessageId] = useState<string | null>(null) |
| | | const hasChild = !!childMessageId |
| | | const [childFeedback, setChildFeedback] = useState<FeedbackType>({ |
| | | rating: null, |
| | | }) |
| | |
| | | setChildFeedback(childFeedback) |
| | | } |
| | | |
| | | const [isShowReplyModal, setIsShowReplyModal] = useState(false) |
| | | const question = (varList && varList?.length > 0) ? varList?.map(({ label, value }) => `${label}:${value}`).join('&') : '' |
| | | const [isQuerying, { setTrue: startQuerying, setFalse: stopQuerying }] = useBoolean(false) |
| | | |
| | | const childProps = { |
| | |
| | | controlClearMoreLikeThis, |
| | | isWorkflow, |
| | | siteInfo, |
| | | taskId, |
| | | } |
| | | |
| | | const handleMoreLikeThis = async () => { |
| | |
| | | setChildMessageId(res.id) |
| | | stopQuerying() |
| | | } |
| | | |
| | | const mainStyle = (() => { |
| | | const res: React.CSSProperties = !isTop |
| | | ? { |
| | | background: depth % 2 === 0 ? 'linear-gradient(90.07deg, #F9FAFB 0.05%, rgba(249, 250, 251, 0) 99.93%)' : '#fff', |
| | | } |
| | | : {} |
| | | |
| | | if (hasChild) |
| | | res.boxShadow = '0px 1px 2px rgba(16, 24, 40, 0.05)' |
| | | |
| | | return res |
| | | })() |
| | | |
| | | useEffect(() => { |
| | | if (controlClearMoreLikeThis) { |
| | |
| | | setShowPromptLogModal(true) |
| | | } |
| | | |
| | | const [currentTab, setCurrentTab] = useState<string>('DETAIL') |
| | | const showResultTabs = !!workflowProcessData?.resultText || !!workflowProcessData?.files?.length |
| | | const switchTab = async (tab: string) => { |
| | | setCurrentTab(tab) |
| | | } |
| | | useEffect(() => { |
| | | if (workflowProcessData?.resultText || !!workflowProcessData?.files?.length) |
| | | switchTab('RESULT') |
| | | else |
| | | switchTab('DETAIL') |
| | | }, [workflowProcessData?.files?.length, workflowProcessData?.resultText]) |
| | | |
| | | return ( |
| | | const ratingContent = ( |
| | | <> |
| | | <div className={cn('relative', !isTop && 'mt-3', className)}> |
| | | {isLoading && ( |
| | | <div className={cn('flex h-10 items-center', !inSidePanel && 'rounded-2xl border-t border-divider-subtle bg-chat-bubble-bg')}><Loading type='area' /></div> |
| | | {!isWorkflow && !isError && messageId && !feedback?.rating && ( |
| | | <SimpleBtn className="!px-0"> |
| | | <> |
| | | <div |
| | | onClick={() => { |
| | | onFeedback?.({ |
| | | rating: 'like', |
| | | }) |
| | | }} |
| | | className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'> |
| | | <HandThumbUpIcon width={16} height={16} /> |
| | | </div> |
| | | <div |
| | | onClick={() => { |
| | | onFeedback?.({ |
| | | rating: 'dislike', |
| | | }) |
| | | }} |
| | | className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'> |
| | | <HandThumbDownIcon width={16} height={16} /> |
| | | </div> |
| | | </> |
| | | </SimpleBtn> |
| | | )} |
| | | {!isLoading && ( |
| | | <> |
| | | {/* result content */} |
| | | <div className={cn( |
| | | 'relative', |
| | | !inSidePanel && 'rounded-2xl border-t border-divider-subtle bg-chat-bubble-bg', |
| | | )}> |
| | | {workflowProcessData && ( |
| | | <> |
| | | <div className={cn( |
| | | 'p-3', |
| | | showResultTabs && 'border-b border-divider-subtle', |
| | | )}> |
| | | {taskId && ( |
| | | <div className={cn('system-2xs-medium-uppercase mb-2 flex items-center text-text-accent-secondary', isError && 'text-text-destructive')}> |
| | | <RiPlayList2Line className='mr-1 h-3 w-3' /> |
| | | <span>{t('share.generation.execution')}</span> |
| | | <span className='px-1'>·</span> |
| | | <span>{taskId}</span> |
| | | {!isWorkflow && !isError && messageId && feedback?.rating === 'like' && ( |
| | | <div |
| | | onClick={() => { |
| | | onFeedback?.({ |
| | | rating: null, |
| | | }) |
| | | }} |
| | | className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-primary-600 border border-primary-200 bg-primary-100 hover:border-primary-300 hover:bg-primary-200'> |
| | | <HandThumbUpIcon width={16} height={16} /> |
| | | </div> |
| | | )} |
| | | {!isWorkflow && !isError && messageId && feedback?.rating === 'dislike' && ( |
| | | <div |
| | | onClick={() => { |
| | | onFeedback?.({ |
| | | rating: null, |
| | | }) |
| | | }} |
| | | className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-red-600 border border-red-200 bg-red-100 hover:border-red-300 hover:bg-red-200'> |
| | | <HandThumbDownIcon width={16} height={16} /> |
| | | </div> |
| | | )} |
| | | </> |
| | | ) |
| | | |
| | | const [currentTab, setCurrentTab] = useState<string>('DETAIL') |
| | | |
| | | return ( |
| | | <div ref={ref} className={cn(isTop ? `rounded-xl border ${!isError ? 'border-gray-200 bg-chat-bubble-bg' : 'border-[#FECDCA] bg-[#FEF3F2]'} ` : 'rounded-br-xl !mt-0', className)} |
| | | style={isTop |
| | | ? { |
| | | boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)', |
| | | } |
| | | : {}} |
| | | > |
| | | {isLoading |
| | | ? ( |
| | | <div className='flex items-center h-10'><Loading type='area' /></div> |
| | | ) |
| | | : ( |
| | | <div |
| | | className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4', innerClassName)} |
| | | style={mainStyle} |
| | | > |
| | | {(isTop && taskId) && ( |
| | | <div className='mb-2 text-gray-500 border border-gray-200 box-border flex items-center rounded-md italic text-[11px] pl-1 pr-1.5 font-medium w-fit group-hover:opacity-100'> |
| | | <HashtagIcon className='w-3 h-3 text-gray-400 fill-current mr-1 stroke-current stroke-1' /> |
| | | {taskId} |
| | | </div>) |
| | | } |
| | | <div className={`flex ${contentClassName}`}> |
| | | <div className='grow w-0'> |
| | | {siteInfo && workflowProcessData && ( |
| | | <WorkflowProcessItem |
| | | data={workflowProcessData} |
| | |
| | | readonly={!siteInfo.show_workflow_steps} |
| | | /> |
| | | )} |
| | | {showResultTabs && ( |
| | | <div className='flex items-center space-x-6 px-1'> |
| | | <div |
| | | className={cn( |
| | | 'system-sm-semibold-uppercase cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary', |
| | | currentTab === 'RESULT' && 'border-util-colors-blue-brand-blue-brand-600 text-text-primary', |
| | | )} |
| | | onClick={() => switchTab('RESULT')} |
| | | >{t('runLog.result')}</div> |
| | | <div |
| | | className={cn( |
| | | 'system-sm-semibold-uppercase cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary', |
| | | currentTab === 'DETAIL' && 'border-util-colors-blue-brand-blue-brand-600 text-text-primary', |
| | | )} |
| | | onClick={() => switchTab('DETAIL')} |
| | | >{t('runLog.detail')}</div> |
| | | </div> |
| | | )} |
| | | </div> |
| | | {!isError && ( |
| | | <ResultTab data={workflowProcessData} content={content} currentTab={currentTab} /> |
| | | )} |
| | | </> |
| | | )} |
| | | {!workflowProcessData && taskId && ( |
| | | <div className={cn('system-2xs-medium-uppercase sticky left-0 top-0 flex w-full items-center rounded-t-2xl bg-components-actionbar-bg p-4 pb-3 text-text-accent-secondary', isError && 'text-text-destructive')}> |
| | | <RiPlayList2Line className='mr-1 h-3 w-3' /> |
| | | <span>{t('share.generation.execution')}</span> |
| | | <span className='px-1'>·</span> |
| | | <span>{`${taskId}${depth > 1 ? `-${depth - 1}` : ''}`}</span> |
| | | </div> |
| | | {workflowProcessData && !isError && ( |
| | | <ResultTab data={workflowProcessData} content={content} currentTab={currentTab} onCurrentTabChange={setCurrentTab} /> |
| | | )} |
| | | {isError && ( |
| | | <div className='body-lg-regular p-4 pt-0 text-text-quaternary'>{t('share.generation.batchFailed.outputPlaceholder')}</div> |
| | | <div className='text-gray-400 text-sm'>{t('share.generation.batchFailed.outputPlaceholder')}</div> |
| | | )} |
| | | {!workflowProcessData && !isError && (typeof content === 'string') && ( |
| | | <div className={cn('p-4', taskId && 'pt-0')}> |
| | | <Markdown content={content} /> |
| | | </div> |
| | | )} |
| | | </div> |
| | | {/* meta data */} |
| | | <div className={cn( |
| | | 'system-xs-regular relative mt-1 h-4 px-4 text-text-quaternary', |
| | | isMobile && ((childMessageId || isQuerying) && depth < 3) && 'pl-10', |
| | | )}> |
| | | {!isWorkflow && <span>{content?.length} {t('common.unit.char')}</span>} |
| | | {/* action buttons */} |
| | | <div className='absolute bottom-1 right-2 flex items-center'> |
| | | {!isInWebApp && !isInstalledApp && !isResponding && ( |
| | | <div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'> |
| | | <ActionButton disabled={isError || !messageId} onClick={handleOpenLogModal}> |
| | | <RiFileList3Line className='h-4 w-4' /> |
| | | {/* <div>{t('common.operation.log')}</div> */} |
| | | </ActionButton> |
| | | </div> |
| | | )} |
| | | <div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'> |
| | | {moreLikeThis && ( |
| | | <ActionButton state={depth === MAX_DEPTH ? ActionButtonState.Disabled : ActionButtonState.Default} disabled={depth === MAX_DEPTH} onClick={handleMoreLikeThis}> |
| | | <RiSparklingLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | )} |
| | | {isShowTextToSpeech && ( |
| | | <NewAudioButton |
| | | id={messageId!} |
| | | voice={config?.text_to_speech?.voice} |
| | | /> |
| | | )} |
| | | |
| | | <div className='flex items-center justify-between mt-3'> |
| | | <div className='flex items-center'> |
| | | { |
| | | !isInWebApp && !isInstalledApp && !isResponding && ( |
| | | <SimpleBtn |
| | | isDisabled={isError || !messageId} |
| | | className={cn(isMobile && '!px-1.5', 'space-x-1 mr-1')} |
| | | onClick={handleOpenLogModal}> |
| | | <File02 className='w-3.5 h-3.5' /> |
| | | {!isMobile && <div>{t('common.operation.log')}</div>} |
| | | </SimpleBtn> |
| | | ) |
| | | } |
| | | {((currentTab === 'RESULT' && workflowProcessData?.resultText) || !isWorkflow) && ( |
| | | <ActionButton disabled={isError || !messageId} onClick={() => { |
| | | <SimpleBtn |
| | | isDisabled={isError || !messageId} |
| | | className={cn(isMobile && '!px-1.5', 'space-x-1')} |
| | | onClick={() => { |
| | | const copyContent = isWorkflow ? workflowProcessData?.resultText : content |
| | | if (typeof copyContent === 'string') |
| | | copy(copyContent) |
| | |
| | | copy(JSON.stringify(copyContent)) |
| | | Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) |
| | | }}> |
| | | <RiClipboardLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | <RiClipboardLine className='w-3.5 h-3.5' /> |
| | | {!isMobile && <div>{t('common.operation.copy')}</div>} |
| | | </SimpleBtn> |
| | | )} |
| | | {isInWebApp && isError && ( |
| | | <ActionButton onClick={onRetry}> |
| | | <RiReplay15Line className='h-4 w-4' /> |
| | | </ActionButton> |
| | | )} |
| | | {isInWebApp && !isWorkflow && ( |
| | | <ActionButton disabled={isError || !messageId} onClick={() => { onSave?.(messageId as string) }}> |
| | | <RiBookmark3Line className='h-4 w-4' /> |
| | | </ActionButton> |
| | | )} |
| | | </div> |
| | | {(supportFeedback || isInWebApp) && !isWorkflow && !isError && messageId && ( |
| | | <div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'> |
| | | {!feedback?.rating && ( |
| | | |
| | | {isInWebApp && ( |
| | | <> |
| | | <ActionButton onClick={() => onFeedback?.({ rating: 'like' })}> |
| | | <RiThumbUpLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | <ActionButton onClick={() => onFeedback?.({ rating: 'dislike' })}> |
| | | <RiThumbDownLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | {!isWorkflow && ( |
| | | <SimpleBtn |
| | | isDisabled={isError || !messageId} |
| | | className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')} |
| | | onClick={() => { onSave?.(messageId as string) }} |
| | | > |
| | | <Bookmark className='w-3.5 h-3.5' /> |
| | | {!isMobile && <div>{t('common.operation.save')}</div>} |
| | | </SimpleBtn> |
| | | )} |
| | | {(moreLikeThis && depth < MAX_DEPTH) && ( |
| | | <SimpleBtn |
| | | isDisabled={isError || !messageId} |
| | | className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')} |
| | | onClick={handleMoreLikeThis} |
| | | > |
| | | <Stars02 className='w-3.5 h-3.5' /> |
| | | {!isMobile && <div>{t('appDebug.feature.moreLikeThis.title')}</div>} |
| | | </SimpleBtn> |
| | | )} |
| | | {isError && ( |
| | | <SimpleBtn |
| | | onClick={onRetry} |
| | | className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')} |
| | | > |
| | | <RefreshCcw01 className='w-3.5 h-3.5' /> |
| | | {!isMobile && <div>{t('share.generation.batchFailed.retry')}</div>} |
| | | </SimpleBtn> |
| | | )} |
| | | {!isError && messageId && !isWorkflow && ( |
| | | <div className="mx-3 w-[1px] h-[14px] bg-gray-200"></div> |
| | | )} |
| | | {ratingContent} |
| | | </> |
| | | )} |
| | | {feedback?.rating === 'like' && ( |
| | | <ActionButton state={ActionButtonState.Active} onClick={() => onFeedback?.({ rating: null })}> |
| | | <RiThumbUpLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | |
| | | {supportAnnotation && ( |
| | | <> |
| | | <div className='ml-2 mr-1 h-[14px] w-[1px] bg-gray-200'></div> |
| | | <AnnotationCtrlBtn |
| | | appId={appId!} |
| | | messageId={messageId!} |
| | | className='ml-1' |
| | | query={question} |
| | | answer={content} |
| | | // not support cache. So can not be cached |
| | | cached={false} |
| | | onAdded={() => { |
| | | |
| | | }} |
| | | onEdit={() => setIsShowReplyModal(true)} |
| | | onRemoved={() => { }} |
| | | /> |
| | | </> |
| | | )} |
| | | {feedback?.rating === 'dislike' && ( |
| | | <ActionButton state={ActionButtonState.Destructive} onClick={() => onFeedback?.({ rating: null })}> |
| | | <RiThumbDownLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | )} |
| | | |
| | | <EditReplyModal |
| | | appId={appId!} |
| | | messageId={messageId!} |
| | | isShow={isShowReplyModal} |
| | | onHide={() => setIsShowReplyModal(false)} |
| | | query={question} |
| | | answer={content} |
| | | onAdded={() => { }} |
| | | onEdited={() => { }} |
| | | createdAt={0} |
| | | onRemove={() => { }} |
| | | onlyEditResponse |
| | | /> |
| | | |
| | | {supportFeedback && ( |
| | | <div className='ml-1'> |
| | | {ratingContent} |
| | | </div> |
| | | )} |
| | | </div> |
| | | </div> |
| | | {/* more like this elements */} |
| | | {!isTop && ( |
| | | <div className={cn( |
| | | 'absolute top-[-32px] flex h-[33px] w-4 justify-center', |
| | | isMobile ? 'left-[17px]' : 'left-[50%] translate-x-[-50%]', |
| | | )}> |
| | | <div className='h-full w-0.5 bg-divider-regular'></div> |
| | | <div className={cn( |
| | | 'absolute left-0 flex h-4 w-4 items-center justify-center rounded-2xl border-[0.5px] border-divider-subtle bg-util-colors-blue-blue-500 shadow-xs', |
| | | isMobile ? 'top-[3.5px]' : 'top-2', |
| | | )}> |
| | | <RiSparklingFill className='h-3 w-3 text-text-primary-on-surface' /> |
| | | </div> |
| | | </div> |
| | | )} |
| | | |
| | | {isShowTextToSpeech && ( |
| | | <> |
| | | <div className='ml-2 mr-2 h-[14px] w-[1px] bg-gray-200'></div> |
| | | <AudioBtn |
| | | id={messageId!} |
| | | className={'mr-1'} |
| | | voice={config?.text_to_speech?.voice} |
| | | /> |
| | | </> |
| | | )} |
| | | </div> |
| | | <div> |
| | | {!workflowProcessData && ( |
| | | <div className='text-xs text-gray-500'>{content?.length} {t('common.unit.char')}</div> |
| | | )} |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | )} |
| | | |
| | | {((childMessageId || isQuerying) && depth < 3) && ( |
| | | <div className='pl-4'> |
| | | <GenerationItem {...childProps as any} /> |
| | | </div> |
| | | )} |
| | | </> |
| | | |
| | | </div> |
| | | ) |
| | | } |
| | | export default React.memo(GenerationItem) |
| | |
| | | import { |
| | | memo, |
| | | useEffect, |
| | | } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import cn from '@/utils/classnames' |
| | | import { Markdown } from '@/app/components/base/markdown' |
| | | import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' |
| | | import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' |
| | |
| | | data, |
| | | content, |
| | | currentTab, |
| | | onCurrentTabChange, |
| | | }: { |
| | | data?: WorkflowProcess |
| | | content: any |
| | | currentTab: string |
| | | onCurrentTabChange: (tab: string) => void |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | |
| | | const switchTab = async (tab: string) => { |
| | | onCurrentTabChange(tab) |
| | | } |
| | | useEffect(() => { |
| | | if (data?.resultText || !!data?.files?.length) |
| | | switchTab('RESULT') |
| | | else |
| | | switchTab('DETAIL') |
| | | }, [data?.files?.length, data?.resultText]) |
| | | |
| | | return ( |
| | | <> |
| | | <div className='grow relative flex flex-col'> |
| | | {(data?.resultText || !!data?.files?.length) && ( |
| | | <div className='shrink-0 flex items-center mb-2 border-b-[0.5px] border-[rgba(0,0,0,0.05)]'> |
| | | <div |
| | | className={cn( |
| | | 'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer', |
| | | currentTab === 'RESULT' && '!border-[rgb(21,94,239)] text-gray-700', |
| | | )} |
| | | onClick={() => switchTab('RESULT')} |
| | | >{t('runLog.result')}</div> |
| | | <div |
| | | className={cn( |
| | | 'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer', |
| | | currentTab === 'DETAIL' && '!border-[rgb(21,94,239)] text-gray-700', |
| | | )} |
| | | onClick={() => switchTab('DETAIL')} |
| | | >{t('runLog.detail')}</div> |
| | | </div> |
| | | )} |
| | | <div className={cn('grow bg-white')}> |
| | | {currentTab === 'RESULT' && ( |
| | | <div className='space-y-3 p-4'> |
| | | <> |
| | | {data?.resultText && <Markdown content={data?.resultText || ''} />} |
| | | {!!data?.files?.length && ( |
| | | <div className='flex flex-col gap-2'> |
| | | {data?.files.map((item: any) => ( |
| | | <div key={item.varName} className='system-xs-regular flex flex-col gap-1'> |
| | | <div key={item.varName} className='flex flex-col gap-1 system-xs-regular'> |
| | | <div className='py-1 text-text-tertiary '>{item.varName}</div> |
| | | <FileList |
| | | files={item.list} |
| | |
| | | ))} |
| | | </div> |
| | | )} |
| | | </div> |
| | | </> |
| | | )} |
| | | {currentTab === 'DETAIL' && content && ( |
| | | <div className='p-4'> |
| | | <div className='mt-1'> |
| | | <CodeEditor |
| | | readOnly |
| | | title={<div>JSON OUTPUT</div>} |
| | |
| | | /> |
| | | </div> |
| | | )} |
| | | </> |
| | | </div> |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | |
| | | 'use client' |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import { |
| | | RiClipboardLine, |
| | | RiDeleteBinLine, |
| | | } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import copy from 'copy-to-clipboard' |
| | | import NoData from './no-data' |
| | | import cn from '@/utils/classnames' |
| | | import type { SavedMessage } from '@/models/debug' |
| | | import { Markdown } from '@/app/components/base/markdown' |
| | | import { SimpleBtn, copyIcon } from '@/app/components/app/text-generate/item' |
| | | import Toast from '@/app/components/base/toast' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | import NewAudioButton from '@/app/components/base/new-audio-button' |
| | | import AudioBtn from '@/app/components/base/audio-btn' |
| | | |
| | | export type ISavedItemsProps = { |
| | | className?: string |
| | |
| | | onRemove: (id: string) => void |
| | | onStartCreateContent: () => void |
| | | } |
| | | |
| | | const removeIcon = ( |
| | | <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | | <path d="M5.25 1.75H8.75M1.75 3.5H12.25M11.0833 3.5L10.6742 9.63625C10.6129 10.5569 10.5822 11.0172 10.3833 11.3663C10.2083 11.6735 9.94422 11.9206 9.62597 12.0748C9.26448 12.25 8.80314 12.25 7.88045 12.25H6.11955C5.19686 12.25 4.73552 12.25 4.37403 12.0748C4.05577 11.9206 3.79172 11.6735 3.61666 11.3663C3.41781 11.0172 3.38713 10.5569 3.32575 9.63625L2.91667 3.5M5.83333 6.125V9.04167M8.16667 6.125V9.04167" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" /> |
| | | </svg> |
| | | ) |
| | | |
| | | const SavedItems: FC<ISavedItemsProps> = ({ |
| | | className, |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className={cn('space-y-4', className)}> |
| | | <div className={cn(className, 'space-y-3')}> |
| | | {list.length === 0 |
| | | ? ( |
| | | <div className='px-6'> |
| | | <NoData onStartCreateContent={onStartCreateContent} /> |
| | | </div> |
| | | ) |
| | | : (<> |
| | | {list.map(({ id, answer }) => ( |
| | | <div key={id} className='relative'> |
| | | <div className={cn( |
| | | 'rounded-2xl bg-background-section-burn p-4', |
| | | )}> |
| | | <div |
| | | key={id} |
| | | className='p-4 rounded-xl bg-gray-50' |
| | | style={{ |
| | | boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)', |
| | | }} |
| | | > |
| | | <Markdown content={answer} /> |
| | | </div> |
| | | <div className='system-xs-regular mt-1 h-4 px-4 text-text-quaternary'> |
| | | <span>{answer.length} {t('common.unit.char')}</span> |
| | | </div> |
| | | <div className='absolute bottom-1 right-2'> |
| | | <div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'> |
| | | {isShowTextToSpeech && <NewAudioButton value={answer}/>} |
| | | <ActionButton onClick={() => { |
| | | <div className='flex items-center justify-between mt-3'> |
| | | <div className='flex items-center space-x-2'> |
| | | <SimpleBtn |
| | | className='space-x-1' |
| | | onClick={() => { |
| | | copy(answer) |
| | | Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) |
| | | }}> |
| | | <RiClipboardLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | <ActionButton onClick={() => { |
| | | {copyIcon} |
| | | <div>{t('common.operation.copy')}</div> |
| | | </SimpleBtn> |
| | | |
| | | <SimpleBtn |
| | | className='space-x-1' |
| | | onClick={() => { |
| | | onRemove(id) |
| | | }}> |
| | | <RiDeleteBinLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | {removeIcon} |
| | | <div>{t('common.operation.remove')}</div> |
| | | </SimpleBtn> |
| | | |
| | | {isShowTextToSpeech && ( |
| | | <> |
| | | <div className='ml-2 mr-2 h-[14px] w-[1px] bg-gray-200'></div> |
| | | <AudioBtn |
| | | value={answer} |
| | | noCache={false} |
| | | className={'mr-1'} |
| | | /> |
| | | </> |
| | | )} |
| | | </div> |
| | | <div className='text-xs text-gray-500'>{answer?.length} {t('common.unit.char')}</div> |
| | | </div> |
| | | </div> |
| | | ))} |
| | |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiAddLine, |
| | | RiBookmark3Line, |
| | | } from '@remixicon/react' |
| | | import { PlusIcon } from '@heroicons/react/24/outline' |
| | | import Button from '@/app/components/base/button' |
| | | export type INoDataProps = { |
| | | onStartCreateContent: () => void |
| | | } |
| | | |
| | | const markIcon = ( |
| | | <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | | <path d="M4.16699 6.5C4.16699 5.09987 4.16699 4.3998 4.43948 3.86502C4.67916 3.39462 5.06161 3.01217 5.53202 2.77248C6.0668 2.5 6.76686 2.5 8.16699 2.5H11.8337C13.2338 2.5 13.9339 2.5 14.4686 2.77248C14.939 3.01217 15.3215 3.39462 15.5612 3.86502C15.8337 4.3998 15.8337 5.09987 15.8337 6.5V17.5L10.0003 14.1667L4.16699 17.5V6.5Z" stroke="#667085" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> |
| | | </svg> |
| | | ) |
| | | |
| | | const lightIcon = ( |
| | | <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="inline relative -top-3 -left-1.5"><path d="M5 6.5V5M8.93934 7.56066L10 6.5M10.0103 11.5H11.5103" stroke="#374151" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path></svg> |
| | | ) |
| | | |
| | | const NoData: FC<INoDataProps> = ({ |
| | | onStartCreateContent, |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className='rounded-xl bg-background-section-burn p-6 '> |
| | | <div className='flex h-10 w-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg-alt shadow-lg backdrop-blur-sm'> |
| | | <RiBookmark3Line className='h-4 w-4 text-text-accent'/> |
| | | <div className='mt-[60px] px-5 py-4 rounded-2xl bg-gray-50 '> |
| | | <div className='flex items-center justify-center w-11 h-11 border border-gray-100 rounded-lg'> |
| | | {markIcon} |
| | | </div> |
| | | <div className='mt-3'> |
| | | <span className='system-xl-semibold text-text-secondary'>{t('share.generation.savedNoData.title')}</span> |
| | | <div className='mt-2'> |
| | | <span className='text-gray-700 font-semibold'>{t('share.generation.savedNoData.title')}</span> |
| | | {lightIcon} |
| | | </div> |
| | | <div className='system-sm-regular mt-1 text-text-tertiary'> |
| | | <div className='mt-2 text-gray-500 text-[13px] font-normal'> |
| | | {t('share.generation.savedNoData.description')} |
| | | </div> |
| | | <Button |
| | | variant='primary' |
| | | className='mt-3' |
| | | className='mt-4' |
| | | onClick={onStartCreateContent} |
| | | > |
| | | <RiAddLine className='mr-1 h-4 w-4' /> |
| | | <div className='flex items-center space-x-2 text-primary-600 text-[13px] font-medium'> |
| | | <PlusIcon className='w-4 h-4' /> |
| | | <span>{t('share.generation.savedNoData.startCreateContent')}</span> |
| | | </div> |
| | | </Button> |
| | | </div> |
| | | ) |
| | |
| | | PortalToFollowElemTrigger, |
| | | } from '@/app/components/base/portal-to-follow-elem' |
| | | import { BubbleTextMod, ChatBot, ListSparkle, Logic } from '@/app/components/base/icons/src/vender/solid/communication' |
| | | import type { AppMode } from '@/types/app' |
| | | import { type AppMode } from '@/types/app' |
| | | export type AppSelectorProps = { |
| | | value: Array<AppMode> |
| | | onChange: (value: AppSelectorProps['value']) => void |
| | |
| | | className='block' |
| | | > |
| | | <div className={cn( |
| | | 'flex cursor-pointer items-center justify-between space-x-1 rounded-md px-2 hover:bg-state-base-hover', |
| | | 'flex items-center justify-between rounded-md cursor-pointer px-2 space-x-1 hover:bg-state-base-hover', |
| | | )}> |
| | | <AppTypeSelectTrigger values={value} /> |
| | | {value && value.length > 0 && <div className='h-4 w-4' onClick={(e) => { |
| | | {value && value.length > 0 && <div className='w-4 h-4' onClick={(e) => { |
| | | e.stopPropagation() |
| | | onChange([]) |
| | | }}> |
| | | <RiCloseCircleFill className='h-3.5 w-3.5 cursor-pointer text-text-quaternary hover:text-text-tertiary' /> |
| | | <RiCloseCircleFill className='w-3.5 h-3.5 text-text-quaternary hover:text-text-tertiary cursor-pointer' /> |
| | | </div>} |
| | | </div> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent className='z-[1002]'> |
| | | <ul className='relative w-[240px] rounded-xl border border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[5px]'> |
| | | <ul className='relative p-1 w-[240px] bg-components-panel-bg-blur backdrop-blur-[5px] rounded-xl shadow-lg border border-components-panel-border'> |
| | | {allTypes.map(mode => ( |
| | | <AppTypeSelectorItem key={mode} type={mode} |
| | | checked={Boolean(value.length > 0 && value?.indexOf(mode) !== -1)} |
| | |
| | | const { t } = useTranslation() |
| | | if (!values || values.length === 0) { |
| | | return <div className={cn( |
| | | 'flex h-8 items-center justify-between gap-1', |
| | | 'flex items-center justify-between gap-1 h-8', |
| | | )}> |
| | | <RiFilter3Line className='h-4 w-4 text-text-tertiary' /> |
| | | <div className='system-sm-medium min-w-[65px] grow text-center text-text-tertiary'>{t('app.typeSelector.all')}</div> |
| | | <RiArrowDownSLine className='h-4 w-4 text-text-tertiary' /> |
| | | <RiFilter3Line className='w-4 h-4 text-text-tertiary' /> |
| | | <div className='grow min-w-[65px] text-center system-sm-medium text-text-tertiary'>{t('app.typeSelector.all')}</div> |
| | | <RiArrowDownSLine className='w-4 h-4 text-text-tertiary' /> |
| | | </div> |
| | | } |
| | | if (values.length === 1) { |
| | | return <div className={cn( |
| | | 'flex h-8 flex-nowrap items-center justify-between gap-1', |
| | | 'flex items-center justify-between gap-1 h-8 flex-nowrap', |
| | | )}> |
| | | <AppTypeIcon type={values[0]} /> |
| | | <div className='line-clamp-1 flex flex-1 items-center text-center'> |
| | | <div className='flex flex-1 items-center text-center line-clamp-1'> |
| | | <AppTypeLabel type={values[0]} className="system-sm-medium text-components-menu-item-text" /> |
| | | </div> |
| | | </div> |
| | | } |
| | | return <div className={cn( |
| | | 'relative flex h-8 items-center justify-between -space-x-2', |
| | | 'flex items-center justify-between h-8 -space-x-2 relative', |
| | | )}> |
| | | {values.map((mode, index) => (<AppTypeIcon key={mode} type={mode} wrapperClassName='border border-components-panel-on-panel-item-bg' style={{ zIndex: 5 - index }} />))} |
| | | </div> |
| | |
| | | onClick: () => void |
| | | } |
| | | function AppTypeSelectorItem({ checked, type, onClick }: AppTypeSelectorItemProps) { |
| | | return <li className='flex cursor-pointer items-center space-x-2 rounded-lg py-1 pl-2 pr-1 hover:bg-state-base-hover' onClick={onClick}> |
| | | return <li className='flex items-center space-x-2 pl-2 py-1 pr-1 rounded-lg cursor-pointer hover:bg-state-base-hover' onClick={onClick}> |
| | | <Checkbox checked={checked} /> |
| | | <AppTypeIcon type={type} /> |
| | | <div className='grow p-1 pl-0'> |
| | |
| | | } |
| | | |
| | | export function AppTypeIcon({ type, className, wrapperClassName, style }: AppTypeIconProps) { |
| | | const wrapperClassNames = cn('inline-flex h-5 w-5 items-center justify-center rounded-md border border-divider-regular', wrapperClassName) |
| | | const iconClassNames = cn('h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100', className) |
| | | const wrapperClassNames = cn('w-5 h-5 inline-flex items-center justify-center rounded-md border border-divider-regular', wrapperClassName) |
| | | const iconClassNames = cn('w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100', className) |
| | | if (type === 'chat') { |
| | | return <div style={style} className={cn(wrapperClassNames, 'bg-components-icon-bg-blue-solid')}> |
| | | <ChatBot className={iconClassNames} /> |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className='relative flex grow flex-col pt-3'> |
| | | <span className='absolute right-3 top-4 z-20 cursor-pointer p-1' onClick={onClose}> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <div className='grow relative flex flex-col pt-3'> |
| | | <span className='absolute right-3 top-4 p-1 cursor-pointer z-20' onClick={onClose}> |
| | | <RiCloseLine className='w-4 h-4 text-text-tertiary' /> |
| | | </span> |
| | | <h1 className='system-xl-semibold shrink-0 px-4 py-1 text-text-primary'>{t('appLog.runDetail.workflowTitle')}</h1> |
| | | <h1 className='shrink-0 px-4 py-1 text-text-primary system-xl-semibold'>{t('appLog.runDetail.workflowTitle')}</h1> |
| | | <Run runID={runID}/> |
| | | </div> |
| | | ) |
| | |
| | | import type { FC } from 'react' |
| | | import React from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import dayjs from 'dayjs' |
| | | import { RiCalendarLine } from '@remixicon/react' |
| | | import quarterOfYear from 'dayjs/plugin/quarterOfYear' |
| | | import type { QueryParam } from './index' |
| | | import Chip from '@/app/components/base/chip' |
| | | import Input from '@/app/components/base/input' |
| | | dayjs.extend(quarterOfYear) |
| | | |
| | | const today = dayjs() |
| | | |
| | | export const TIME_PERIOD_MAPPING: { [key: string]: { value: number; name: string } } = { |
| | | 1: { value: 0, name: 'today' }, |
| | | 2: { value: 7, name: 'last7days' }, |
| | | 3: { value: 28, name: 'last4weeks' }, |
| | | 4: { value: today.diff(today.subtract(3, 'month'), 'day'), name: 'last3months' }, |
| | | 5: { value: today.diff(today.subtract(12, 'month'), 'day'), name: 'last12months' }, |
| | | 6: { value: today.diff(today.startOf('month'), 'day'), name: 'monthToDate' }, |
| | | 7: { value: today.diff(today.startOf('quarter'), 'day'), name: 'quarterToDate' }, |
| | | 8: { value: today.diff(today.startOf('year'), 'day'), name: 'yearToDate' }, |
| | | 9: { value: -1, name: 'allTime' }, |
| | | } |
| | | |
| | | type IFilterProps = { |
| | | queryParams: QueryParam |
| | |
| | | const Filter: FC<IFilterProps> = ({ queryParams, setQueryParams }: IFilterProps) => { |
| | | const { t } = useTranslation() |
| | | return ( |
| | | <div className='mb-2 flex flex-row flex-wrap gap-2'> |
| | | <div className='flex flex-row flex-wrap gap-2 mb-2'> |
| | | <Chip |
| | | value={queryParams.status || 'all'} |
| | | onSelect={(item) => { |
| | |
| | | { value: 'failed', name: 'Fail' }, |
| | | { value: 'stopped', name: 'Stop' }, |
| | | ]} |
| | | /> |
| | | <Chip |
| | | className='min-w-[150px]' |
| | | panelClassName='w-[270px]' |
| | | leftIcon={<RiCalendarLine className='h-4 w-4 text-text-secondary' />} |
| | | value={queryParams.period} |
| | | onSelect={(item) => { |
| | | setQueryParams({ ...queryParams, period: item.value }) |
| | | }} |
| | | onClear={() => setQueryParams({ ...queryParams, period: '9' })} |
| | | items={Object.entries(TIME_PERIOD_MAPPING).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} |
| | | /> |
| | | <Input |
| | | wrapperClassName='w-[200px]' |
| | |
| | | import useSWR from 'swr' |
| | | import { usePathname } from 'next/navigation' |
| | | import { useDebounce } from 'ahooks' |
| | | import { omit } from 'lodash-es' |
| | | import dayjs from 'dayjs' |
| | | import utc from 'dayjs/plugin/utc' |
| | | import timezone from 'dayjs/plugin/timezone' |
| | | import { Trans, useTranslation } from 'react-i18next' |
| | | import Link from 'next/link' |
| | | import List from './list' |
| | | import Filter, { TIME_PERIOD_MAPPING } from './filter' |
| | | import Filter from './filter' |
| | | import Pagination from '@/app/components/base/pagination' |
| | | import Loading from '@/app/components/base/loading' |
| | | import { fetchWorkflowLogs } from '@/service/log' |
| | | import { APP_PAGE_LIMIT } from '@/config' |
| | | import type { App, AppMode } from '@/types/app' |
| | | import { useAppContext } from '@/context/app-context' |
| | | |
| | | dayjs.extend(utc) |
| | | dayjs.extend(timezone) |
| | | |
| | | export type ILogsProps = { |
| | | appDetail: App |
| | | } |
| | | |
| | | export type QueryParam = { |
| | | period: string |
| | | status?: string |
| | | keyword?: string |
| | | } |
| | |
| | | const pathname = usePathname() |
| | | const pathSegments = pathname.split('/') |
| | | pathSegments.pop() |
| | | return <div className='flex h-full items-center justify-center'> |
| | | <div className='box-border h-fit w-[560px] rounded-2xl bg-background-section-burn px-5 py-4'> |
| | | <span className='system-md-semibold text-text-secondary'>{t('appLog.table.empty.element.title')}<ThreeDotsIcon className='relative -left-1.5 -top-3 inline' /></span> |
| | | <div className='system-sm-regular mt-2 text-text-tertiary'> |
| | | return <div className='flex items-center justify-center h-full'> |
| | | <div className='bg-background-section-burn w-[560px] h-fit box-border px-5 py-4 rounded-2xl'> |
| | | <span className='text-text-secondary system-md-semibold'>{t('appLog.table.empty.element.title')}<ThreeDotsIcon className='inline relative -top-3 -left-1.5' /></span> |
| | | <div className='mt-2 text-text-tertiary system-sm-regular'> |
| | | <Trans |
| | | i18nKey="appLog.table.empty.element.content" |
| | | components={{ shareLink: <Link href={`${pathSegments.join('/')}/overview`} className='text-util-colors-blue-blue-600' />, testLink: <Link href={appUrl} className='text-util-colors-blue-blue-600' target='_blank' rel='noopener noreferrer' /> }} |
| | |
| | | |
| | | const Logs: FC<ILogsProps> = ({ appDetail }) => { |
| | | const { t } = useTranslation() |
| | | const { userProfile: { timezone } } = useAppContext() |
| | | const [queryParams, setQueryParams] = useState<QueryParam>({ status: 'all', period: '2' }) |
| | | const [queryParams, setQueryParams] = useState<QueryParam>({ status: 'all' }) |
| | | const [currPage, setCurrPage] = React.useState<number>(0) |
| | | const debouncedQueryParams = useDebounce(queryParams, { wait: 500 }) |
| | | const [limit, setLimit] = React.useState<number>(APP_PAGE_LIMIT) |
| | |
| | | limit, |
| | | ...(debouncedQueryParams.status !== 'all' ? { status: debouncedQueryParams.status } : {}), |
| | | ...(debouncedQueryParams.keyword ? { keyword: debouncedQueryParams.keyword } : {}), |
| | | ...((debouncedQueryParams.period !== '9') |
| | | ? { |
| | | created_at__after: dayjs().subtract(TIME_PERIOD_MAPPING[debouncedQueryParams.period].value, 'day').startOf('day').tz(timezone).format('YYYY-MM-DDTHH:mm:ssZ'), |
| | | created_at__before: dayjs().endOf('day').tz(timezone).format('YYYY-MM-DDTHH:mm:ssZ'), |
| | | } |
| | | : {}), |
| | | ...omit(debouncedQueryParams, ['period', 'status']), |
| | | } |
| | | |
| | | const getWebAppType = (appType: AppMode) => { |
| | |
| | | const total = workflowLogs?.total |
| | | |
| | | return ( |
| | | <div className='flex h-full flex-col'> |
| | | <h1 className='system-xl-semibold text-text-primary'>{t('appLog.workflowTitle')}</h1> |
| | | <p className='system-sm-regular text-text-tertiary'>{t('appLog.workflowSubtitle')}</p> |
| | | <div className='flex max-h-[calc(100%-16px)] flex-1 flex-col py-4'> |
| | | <div className='flex flex-col h-full'> |
| | | <h1 className='text-text-primary system-xl-semibold'>{t('appLog.workflowTitle')}</h1> |
| | | <p className='text-text-tertiary system-sm-regular'>{t('appLog.workflowSubtitle')}</p> |
| | | <div className='flex flex-col py-4 flex-1 max-h-[calc(100%-16px)]'> |
| | | <Filter queryParams={queryParams} setQueryParams={setQueryParams} /> |
| | | {/* workflow log */} |
| | | {total === undefined |
| | |
| | | const statusTdRender = (status: string) => { |
| | | if (status === 'succeeded') { |
| | | return ( |
| | | <div className='system-xs-semibold-uppercase inline-flex items-center gap-1'> |
| | | <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'> |
| | | <Indicator color={'green'} /> |
| | | <span className='text-util-colors-green-green-600'>Success</span> |
| | | </div> |
| | |
| | | } |
| | | if (status === 'failed') { |
| | | return ( |
| | | <div className='system-xs-semibold-uppercase inline-flex items-center gap-1'> |
| | | <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'> |
| | | <Indicator color={'red'} /> |
| | | <span className='text-util-colors-red-red-600'>Fail</span> |
| | | </div> |
| | |
| | | } |
| | | if (status === 'stopped') { |
| | | return ( |
| | | <div className='system-xs-semibold-uppercase inline-flex items-center gap-1'> |
| | | <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'> |
| | | <Indicator color={'yellow'} /> |
| | | <span className='text-util-colors-warning-warning-600'>Stop</span> |
| | | </div> |
| | |
| | | } |
| | | if (status === 'running') { |
| | | return ( |
| | | <div className='system-xs-semibold-uppercase inline-flex items-center gap-1'> |
| | | <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'> |
| | | <Indicator color={'blue'} /> |
| | | <span className='text-util-colors-blue-light-blue-light-600'>Running</span> |
| | | </div> |
| | |
| | | } |
| | | if (status === 'partial-succeeded') { |
| | | return ( |
| | | <div className='system-xs-semibold-uppercase inline-flex items-center gap-1'> |
| | | <div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'> |
| | | <Indicator color={'green'} /> |
| | | <span className='text-util-colors-green-green-600'>Partial Success</span> |
| | | </div> |
| | |
| | | <table className={cn('mt-2 w-full min-w-[440px] border-collapse border-0')}> |
| | | <thead className='system-xs-medium-uppercase text-text-tertiary'> |
| | | <tr> |
| | | <td className='w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1'></td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.startTime')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.status')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.runtime')}</td> |
| | | <td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.tokens')}</td> |
| | | <td className='whitespace-nowrap rounded-r-lg bg-background-section-burn py-1.5 pl-3'>{t('appLog.table.header.user')}</td> |
| | | <td className='pl-2 pr-1 w-5 rounded-l-lg bg-background-section-burn whitespace-nowrap'></td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.startTime')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.status')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.runtime')}</td> |
| | | <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.tokens')}</td> |
| | | <td className='pl-3 py-1.5 rounded-r-lg bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.user')}</td> |
| | | </tr> |
| | | </thead> |
| | | <tbody className="system-sm-regular text-text-secondary"> |
| | | <tbody className="text-text-secondary system-sm-regular"> |
| | | {logs.data.map((log: WorkflowAppLogDetail) => { |
| | | const endUser = log.created_by_end_user ? log.created_by_end_user.session_id : log.created_by_account ? log.created_by_account.name : defaultValue |
| | | return <tr |
| | | key={log.id} |
| | | className={cn('cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover', currentLog?.id !== log.id ? '' : 'bg-background-default-hover')} |
| | | className={cn('border-b border-divider-subtle hover:bg-background-default-hover cursor-pointer', currentLog?.id !== log.id ? '' : 'bg-background-default-hover')} |
| | | onClick={() => { |
| | | setCurrentLog(log) |
| | | setShowDrawer(true) |
| | | }}> |
| | | <td className='h-4'> |
| | | {!log.read_at && ( |
| | | <div className='flex items-center p-3 pr-0.5'> |
| | | <span className='inline-block h-1.5 w-1.5 rounded bg-util-colors-blue-blue-500'></span> |
| | | <div className='p-3 pr-0.5 flex items-center'> |
| | | <span className='inline-block bg-util-colors-blue-blue-500 h-1.5 w-1.5 rounded'></span> |
| | | </div> |
| | | )} |
| | | </td> |
| | | <td className='w-[160px] p-3 pr-2'>{formatTime(log.created_at, t('appLog.dateTimeFormat') as string)}</td> |
| | | <td className='p-3 pr-2 w-[160px]'>{formatTime(log.created_at, t('appLog.dateTimeFormat') as string)}</td> |
| | | <td className='p-3 pr-2'>{statusTdRender(log.workflow_run.status)}</td> |
| | | <td className='p-3 pr-2'> |
| | | <div className={cn( |
| | |
| | | onClose={onCloseDrawer} |
| | | mask={isMobile} |
| | | footer={null} |
| | | panelClassName='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[600px] rounded-xl border border-components-panel-border' |
| | | panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[600px] rounded-xl border border-components-panel-border' |
| | | > |
| | | <DetailPanel onClose={onCloseDrawer} runID={currentLog?.workflow_run.id || ''} /> |
| | | </Drawer> |
| | |
| | | |
| | | @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 |
| | | @apply inline-flex justify-center items-center cursor-pointer text-text-tertiary |
| | | hover:text-text-secondary |
| | | hover:bg-state-base-hover |
| | | } |
| | | |
| | | .action-btn-disabled { |
| | |
| | | } |
| | | |
| | | .action-btn.action-btn-active { |
| | | @apply text-text-accent bg-state-accent-active hover:bg-state-accent-active-alt |
| | | @apply |
| | | text-text-accent |
| | | bg-state-accent-active |
| | | hover:bg-state-accent-active-alt |
| | | } |
| | | |
| | | .action-btn.action-btn-disabled { |
| | | @apply text-text-disabled |
| | | @apply |
| | | text-text-disabled |
| | | } |
| | | |
| | | .action-btn.action-btn-destructive { |
| | | @apply text-text-destructive bg-state-destructive-hover |
| | | @apply |
| | | text-text-destructive |
| | | bg-state-destructive-hover |
| | | } |
| | | |
| | | } |
| | |
| | | Active = 'active', |
| | | Disabled = 'disabled', |
| | | Default = '', |
| | | Hover = 'hover', |
| | | } |
| | | |
| | | const actionButtonVariants = cva( |
| | |
| | | ) |
| | | |
| | | export type ActionButtonProps = { |
| | | size?: 'xs' | 's' | 'm' | 'l' | 'xl' |
| | | size?: 'xs' | 'm' | 'l' | 'xl' |
| | | state?: ActionButtonState |
| | | styleCss?: CSSProperties |
| | | } & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof actionButtonVariants> |
| | |
| | | return 'action-btn-active' |
| | | case ActionButtonState.Disabled: |
| | | return 'action-btn-disabled' |
| | | case ActionButtonState.Hover: |
| | | return 'action-btn-hover' |
| | | default: |
| | | return '' |
| | | } |
| | |
| | | const [list, setList] = useState<AgentIteration[]>([]) |
| | | |
| | | const tools = useMemo(() => { |
| | | const res = uniq(flatten(runDetail?.iterations.map((iteration) => { |
| | | const res = uniq(flatten(runDetail?.iterations.map((iteration: any) => { |
| | | return iteration.tool_calls.map((tool: any) => tool.tool_name).filter(Boolean) |
| | | })).filter(Boolean)) |
| | | return res |
| | |
| | | }, [appDetail, conversationID, messageID]) |
| | | |
| | | return ( |
| | | <div className='relative flex grow flex-col'> |
| | | <div className='grow relative flex flex-col'> |
| | | {/* tab */} |
| | | <div className='flex shrink-0 items-center border-b-[0.5px] border-divider-regular px-4'> |
| | | <div className='shrink-0 flex items-center px-4 border-b-[0.5px] border-[rgba(0,0,0,0.05)]'> |
| | | <div |
| | | className={cn( |
| | | 'mr-6 cursor-pointer border-b-2 border-transparent py-3 text-[13px] font-semibold leading-[18px] text-text-tertiary', |
| | | currentTab === 'DETAIL' && '!border-[rgb(21,94,239)] text-text-secondary', |
| | | 'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer', |
| | | currentTab === 'DETAIL' && '!border-[rgb(21,94,239)] text-gray-700', |
| | | )} |
| | | onClick={() => switchTab('DETAIL')} |
| | | >{t('runLog.detail')}</div> |
| | | <div |
| | | className={cn( |
| | | 'mr-6 cursor-pointer border-b-2 border-transparent py-3 text-[13px] font-semibold leading-[18px] text-text-tertiary', |
| | | currentTab === 'TRACING' && '!border-[rgb(21,94,239)] text-text-secondary', |
| | | 'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer', |
| | | currentTab === 'TRACING' && '!border-[rgb(21,94,239)] text-gray-700', |
| | | )} |
| | | onClick={() => switchTab('TRACING')} |
| | | >{t('runLog.tracing')}</div> |
| | | </div> |
| | | {/* panel detail */} |
| | | <div className={cn('h-0 grow overflow-y-auto rounded-b-2xl bg-components-panel-bg', currentTab !== 'DETAIL' && '!bg-background-section')}> |
| | | <div className={cn('grow bg-white h-0 overflow-y-auto rounded-b-2xl', currentTab !== 'DETAIL' && '!bg-gray-50')}> |
| | | {loading && ( |
| | | <div className='flex h-full items-center justify-center bg-components-panel-bg'> |
| | | <div className='flex h-full items-center justify-center bg-white'> |
| | | <Loading /> |
| | | </div> |
| | | )} |
| | |
| | | |
| | | return ( |
| | | <div |
| | | className={cn('relative z-10 flex flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg py-3 shadow-xl')} |
| | | className={cn('relative flex flex-col py-3 bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl z-10')} |
| | | style={{ |
| | | width: 480, |
| | | position: 'fixed', |
| | |
| | | }} |
| | | ref={ref} |
| | | > |
| | | <h1 className='text-md shrink-0 px-4 py-1 font-semibold text-text-primary'>{t('appLog.runDetail.workflowTitle')}</h1> |
| | | <span className='absolute right-3 top-4 z-20 cursor-pointer p-1' onClick={onCancel}> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <h1 className='shrink-0 px-4 py-1 text-md font-semibold text-gray-900'>{t('appLog.runDetail.workflowTitle')}</h1> |
| | | <span className='absolute right-3 top-4 p-1 cursor-pointer z-20' onClick={onCancel}> |
| | | <RiCloseLine className='w-4 h-4 text-gray-500' /> |
| | | </span> |
| | | <AgentLogDetail |
| | | conversationID={currentLogItem.conversationId} |
| | |
| | | import { useTranslation } from 'react-i18next' |
| | | import type { FC } from 'react' |
| | | import ToolCall from './tool-call' |
| | | import Divider from '@/app/components/base/divider' |
| | | import type { AgentIteration } from '@/models/log' |
| | | import cn from '@/utils/classnames' |
| | | import type { AgentIteration } from '@/models/log' |
| | | |
| | | type Props = { |
| | | isFinal: boolean |
| | |
| | | <div className={cn('px-4 py-2')}> |
| | | <div className='flex items-center'> |
| | | {isFinal && ( |
| | | <div className='mr-3 shrink-0 text-xs font-semibold leading-[18px] text-text-tertiary'>{t('appLog.agentLogDetail.finalProcessing')}</div> |
| | | <div className='shrink-0 mr-3 text-gray-500 text-xs leading-[18px] font-semibold'>{t('appLog.agentLogDetail.finalProcessing')}</div> |
| | | )} |
| | | {!isFinal && ( |
| | | <div className='mr-3 shrink-0 text-xs font-semibold leading-[18px] text-text-tertiary'>{`${t('appLog.agentLogDetail.iteration').toUpperCase()} ${index}`}</div> |
| | | <div className='shrink-0 mr-3 text-gray-500 text-xs leading-[18px] font-semibold'>{`${t('appLog.agentLogDetail.iteration').toUpperCase()} ${index}`}</div> |
| | | )} |
| | | <Divider bgStyle='gradient' className='mx-0 h-[1px] grow'/> |
| | | <div className='grow h-[1px] bg-gradient-to-r from-[#f3f4f6] to-gray-50'></div> |
| | | </div> |
| | | <ToolCall |
| | | isLLM |
| | |
| | | const { formatTime } = useTimestamp() |
| | | |
| | | return ( |
| | | <div className='bg-components-panel-bg py-2'> |
| | | <div className='bg-white py-2'> |
| | | <div className='px-4 py-2'> |
| | | <StatusPanel |
| | | status='succeeded' |
| | |
| | | error={error} |
| | | /> |
| | | </div> |
| | | <div className='flex flex-col gap-2 px-4 py-2'> |
| | | <div className='px-4 py-2 flex flex-col gap-2'> |
| | | <CodeEditor |
| | | readOnly |
| | | title={<div>INPUT</div>} |
| | |
| | | /> |
| | | </div> |
| | | <div className='px-4 py-2'> |
| | | <div className='h-[0.5px] bg-divider-regular opacity-5' /> |
| | | <div className='h-[0.5px] bg-black opacity-5' /> |
| | | </div> |
| | | <div className='px-4 py-2'> |
| | | <div className='relative'> |
| | | <div className='h-6 text-xs font-medium leading-6 text-text-tertiary'>{t('runLog.meta.title')}</div> |
| | | <div className='h-6 leading-6 text-gray-500 text-xs font-medium'>{t('runLog.meta.title')}</div> |
| | | <div className='py-1'> |
| | | <div className='flex'> |
| | | <div className='w-[104px] shrink-0 truncate px-2 py-[5px] text-xs leading-[18px] text-text-tertiary'>{t('runLog.meta.status')}</div> |
| | | <div className='grow px-2 py-[5px] text-xs leading-[18px] text-text-primary'> |
| | | <div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.status')}</div> |
| | | <div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'> |
| | | <span>SUCCESS</span> |
| | | </div> |
| | | </div> |
| | | <div className='flex'> |
| | | <div className='w-[104px] shrink-0 truncate px-2 py-[5px] text-xs leading-[18px] text-text-tertiary'>{t('runLog.meta.executor')}</div> |
| | | <div className='grow px-2 py-[5px] text-xs leading-[18px] text-text-primary'> |
| | | <div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.executor')}</div> |
| | | <div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'> |
| | | <span>{created_by || 'N/A'}</span> |
| | | </div> |
| | | </div> |
| | | <div className='flex'> |
| | | <div className='w-[104px] shrink-0 truncate px-2 py-[5px] text-xs leading-[18px] text-text-tertiary'>{t('runLog.meta.startTime')}</div> |
| | | <div className='grow px-2 py-[5px] text-xs leading-[18px] text-text-primary'> |
| | | <div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.startTime')}</div> |
| | | <div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'> |
| | | <span>{formatTime(Date.parse(created_at) / 1000, t('appLog.dateTimeFormat') as string)}</span> |
| | | </div> |
| | | </div> |
| | | <div className='flex'> |
| | | <div className='w-[104px] shrink-0 truncate px-2 py-[5px] text-xs leading-[18px] text-text-tertiary'>{t('runLog.meta.time')}</div> |
| | | <div className='grow px-2 py-[5px] text-xs leading-[18px] text-text-primary'> |
| | | <div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.time')}</div> |
| | | <div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'> |
| | | <span>{`${elapsed_time?.toFixed(3)}s`}</span> |
| | | </div> |
| | | </div> |
| | | <div className='flex'> |
| | | <div className='w-[104px] shrink-0 truncate px-2 py-[5px] text-xs leading-[18px] text-text-tertiary'>{t('runLog.meta.tokens')}</div> |
| | | <div className='grow px-2 py-[5px] text-xs leading-[18px] text-text-primary'> |
| | | <div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('runLog.meta.tokens')}</div> |
| | | <div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'> |
| | | <span>{`${total_tokens || 0} Tokens`}</span> |
| | | </div> |
| | | </div> |
| | | <div className='flex'> |
| | | <div className='w-[104px] shrink-0 truncate px-2 py-[5px] text-xs leading-[18px] text-text-tertiary'>{t('appLog.agentLogDetail.agentMode')}</div> |
| | | <div className='grow px-2 py-[5px] text-xs leading-[18px] text-text-primary'> |
| | | <div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('appLog.agentLogDetail.agentMode')}</div> |
| | | <div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'> |
| | | <span>{agentMode === 'function_call' ? t('appDebug.agent.agentModeType.functionCall') : t('appDebug.agent.agentModeType.ReACT')}</span> |
| | | </div> |
| | | </div> |
| | | <div className='flex'> |
| | | <div className='w-[104px] shrink-0 truncate px-2 py-[5px] text-xs leading-[18px] text-text-tertiary'>{t('appLog.agentLogDetail.toolUsed')}</div> |
| | | <div className='grow px-2 py-[5px] text-xs leading-[18px] text-text-primary'> |
| | | <div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('appLog.agentLogDetail.toolUsed')}</div> |
| | | <div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'> |
| | | <span>{tools?.length ? tools?.join(', ') : 'Null'}</span> |
| | | </div> |
| | | </div> |
| | | <div className='flex'> |
| | | <div className='w-[104px] shrink-0 truncate px-2 py-[5px] text-xs leading-[18px] text-text-tertiary'>{t('appLog.agentLogDetail.iterations')}</div> |
| | | <div className='grow px-2 py-[5px] text-xs leading-[18px] text-text-primary'> |
| | | <div className='shrink-0 w-[104px] px-2 py-[5px] text-gray-500 text-xs leading-[18px] truncate'>{t('appLog.agentLogDetail.iterations')}</div> |
| | | <div className='grow px-2 py-[5px] text-gray-900 text-xs leading-[18px]'> |
| | | <span>{iterations}</span> |
| | | </div> |
| | | </div> |
| | |
| | | if (time < 1) |
| | | return `${(time * 1000).toFixed(3)} ms` |
| | | if (time > 60) |
| | | return `${Number.parseInt(Math.round(time / 60).toString())} m ${(time % 60).toFixed(3)} s` |
| | | return `${parseInt(Math.round(time / 60).toString())} m ${(time % 60).toFixed(3)} s` |
| | | return `${time.toFixed(3)} s` |
| | | } |
| | | |
| | |
| | | if (tokens < 1000) |
| | | return tokens |
| | | if (tokens >= 1000 && tokens < 1000000) |
| | | return `${Number.parseFloat((tokens / 1000).toFixed(3))}K` |
| | | return `${parseFloat((tokens / 1000).toFixed(3))}K` |
| | | if (tokens >= 1000000) |
| | | return `${Number.parseFloat((tokens / 1000000).toFixed(3))}M` |
| | | return `${parseFloat((tokens / 1000000).toFixed(3))}M` |
| | | } |
| | | |
| | | return ( |
| | | <div className={cn('py-1')}> |
| | | <div className={cn('group rounded-2xl border border-components-panel-border bg-background-default shadow-xs transition-all hover:shadow-md')}> |
| | | <div className={cn('group transition-all bg-white border border-gray-100 rounded-2xl shadow-xs hover:shadow-md')}> |
| | | <div |
| | | className={cn( |
| | | 'flex cursor-pointer items-center py-3 pl-[6px] pr-3', |
| | | 'flex items-center py-3 pl-[6px] pr-3 cursor-pointer', |
| | | !collapseState && '!pb-2', |
| | | )} |
| | | onClick={() => setCollapseState(!collapseState)} |
| | | > |
| | | <ChevronRight |
| | | className={cn( |
| | | 'mr-1 h-3 w-3 shrink-0 text-text-quaternary transition-all group-hover:text-text-tertiary', |
| | | 'shrink-0 w-3 h-3 mr-1 text-gray-400 transition-all group-hover:text-gray-500', |
| | | !collapseState && 'rotate-90', |
| | | )} |
| | | /> |
| | | <BlockIcon className={cn('mr-2 shrink-0')} type={isLLM ? BlockEnum.LLM : BlockEnum.Tool} toolIcon={toolCall.tool_icon} /> |
| | | <BlockIcon className={cn('shrink-0 mr-2')} type={isLLM ? BlockEnum.LLM : BlockEnum.Tool} toolIcon={toolCall.tool_icon} /> |
| | | <div className={cn( |
| | | 'grow truncate text-[13px] font-semibold leading-[16px] text-text-secondary', |
| | | 'grow text-gray-700 text-[13px] leading-[16px] font-semibold truncate', |
| | | )} title={toolName}>{toolName}</div> |
| | | <div className='shrink-0 text-xs leading-[18px] text-text-tertiary'> |
| | | <div className='shrink-0 text-gray-500 text-xs leading-[18px]'> |
| | | {toolCall.time_cost && ( |
| | | <span>{getTime(toolCall.time_cost || 0)}</span> |
| | | )} |
| | |
| | | )} |
| | | </div> |
| | | {toolCall.status === 'success' && ( |
| | | <RiCheckboxCircleLine className='ml-2 h-3.5 w-3.5 shrink-0 text-[#12B76A]' /> |
| | | <RiCheckboxCircleLine className='shrink-0 ml-2 w-3.5 h-3.5 text-[#12B76A]' /> |
| | | )} |
| | | {toolCall.status === 'error' && ( |
| | | <RiErrorWarningLine className='ml-2 h-3.5 w-3.5 shrink-0 text-[#F04438]' /> |
| | | <RiErrorWarningLine className='shrink-0 ml-2 w-3.5 h-3.5 text-[#F04438]' /> |
| | | )} |
| | | </div> |
| | | {!collapseState && ( |
| | | <div className='pb-2'> |
| | | <div className={cn('px-[10px] py-1')}> |
| | | {toolCall.status === 'error' && ( |
| | | <div className='rounded-lg border-[0.5px] border-[rbga(0,0,0,0.05)] bg-[#fef3f2] px-3 py-[10px] text-xs leading-[18px] text-[#d92d20] shadow-xs'>{toolCall.error}</div> |
| | | <div className='px-3 py-[10px] bg-[#fef3f2] rounded-lg border-[0.5px] border-[rbga(0,0,0,0.05)] text-xs leading-[18px] text-[#d92d20] shadow-xs'>{toolCall.error}</div> |
| | | )} |
| | | </div> |
| | | {toolCall.tool_input && ( |
| | |
| | | |
| | | const TracingPanel: FC<TracingPanelProps> = ({ list }) => { |
| | | return ( |
| | | <div className='bg-background-section'> |
| | | <div className='bg-gray-50'> |
| | | {list.map((iteration, index) => ( |
| | | <Iteration |
| | | key={index} |
| | |
| | | style={{ background: background || '#D5F5F6' }} |
| | | > |
| | | {isValidImageIcon |
| | | ? <img src={imageUrl} className="h-full w-full rounded-full" alt="answer icon" /> |
| | | ? <img src={imageUrl} className="w-full h-full rounded-full" alt="answer icon" /> |
| | | : (icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' /> |
| | | } |
| | | </div> |
| | |
| | | import { createRef, useEffect, useState } from 'react' |
| | | import Cropper, { type Area, type CropperProps } from 'react-easy-crop' |
| | | import classNames from 'classnames' |
| | | import { useTranslation } from 'react-i18next' |
| | | |
| | | import { ImagePlus } from '../icons/src/vender/line/images' |
| | | import { useDraggableUploader } from './hooks' |
| | |
| | | cropShape, |
| | | onImageInput, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const [inputImage, setInputImage] = useState<{ file: File; url: string }>() |
| | | const [isAnimatedImage, setIsAnimatedImage] = useState<boolean>(false) |
| | | useEffect(() => { |
| | |
| | | { |
| | | !inputImage |
| | | ? <> |
| | | <ImagePlus className="pointer-events-none mb-3 h-[30px] w-[30px]" /> |
| | | <div className="mb-[2px] text-sm font-medium"> |
| | | <span className="pointer-events-none">{t('common.imageInput.dropImageHere')} </span> |
| | | <button className="text-components-button-primary-bg" onClick={() => inputRef.current?.click()}>{t('common.imageInput.browse')}</button> |
| | | <ImagePlus className="w-[30px] h-[30px] mb-3 pointer-events-none" /> |
| | | <div className="text-sm font-medium mb-[2px]"> |
| | | <span className="pointer-events-none">Drop your image here, or </span> |
| | | <button className="text-components-button-primary-bg" onClick={() => inputRef.current?.click()}>browse</button> |
| | | <input |
| | | ref={inputRef} type="file" className="hidden" |
| | | onClick={e => ((e.target as HTMLInputElement).value = '')} |
| | |
| | | onChange={handleLocalFileInput} |
| | | /> |
| | | </div> |
| | | <div className="pointer-events-none">{t('common.imageInput.supportedFormats')}</div> |
| | | <div className="text-xs pointer-events-none">Supports PNG, JPG, JPEG, WEBP and GIF</div> |
| | | </> |
| | | : handleShowImage() |
| | | } |
| | |
| | | import type { AppIconType, ImageFile } from '@/types/app' |
| | | import cn from '@/utils/classnames' |
| | | import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | export type AppIconEmojiSelection = { |
| | | type: 'emoji' |
| | |
| | | } |
| | | |
| | | return <Modal |
| | | onClose={noop} |
| | | onClose={() => { }} |
| | | isShow |
| | | closable={false} |
| | | wrapperClassName={className} |
| | | className={cn(s.container, '!w-[362px] !p-0')} |
| | | > |
| | | {!DISABLE_UPLOAD_IMAGE_AS_ICON && <div className="w-full p-2 pb-0"> |
| | | <div className='flex items-center justify-center gap-2 rounded-xl bg-background-body p-1'> |
| | | {!DISABLE_UPLOAD_IMAGE_AS_ICON && <div className="p-2 pb-0 w-full"> |
| | | <div className='p-1 flex items-center justify-center gap-2 bg-background-body rounded-xl'> |
| | | {tabs.map(tab => ( |
| | | <button |
| | | key={tab.key} |
| | | className={` |
| | | flex h-8 flex-1 shrink-0 items-center justify-center rounded-xl p-2 text-sm font-medium |
| | | p-2 flex-1 flex justify-center items-center h-8 rounded-xl text-sm shrink-0 font-medium |
| | | ${activeTab === tab.key && 'bg-components-main-nav-nav-button-bg-active shadow-md'} |
| | | `} |
| | | onClick={() => setActiveTab(tab.key as AppIconType)} |
| | |
| | | <ImageInput className={activeTab === 'image' ? 'block' : 'hidden'} onImageInput={handleImageInput} /> |
| | | |
| | | <Divider className='m-0' /> |
| | | <div className='flex w-full items-center justify-center gap-2 p-3'> |
| | | <div className='w-full flex items-center justify-center p-3 gap-2'> |
| | | <Button className='w-full' onClick={() => onClose?.()}> |
| | | {t('app.iconPicker.cancel')} |
| | | </Button> |
| | |
| | | onClick={onClick} |
| | | > |
| | | {isValidImageIcon |
| | | |
| | | ? <img src={imageUrl} className="h-full w-full" alt="app icon" /> |
| | | // eslint-disable-next-line @next/next/no-img-element |
| | | ? <img src={imageUrl} className="w-full h-full" alt="app icon" /> |
| | | : (innerIcon || ((icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />)) |
| | | } |
| | | </span> |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className='flex h-screen w-screen items-center justify-center'> |
| | | <h1 className='mr-5 h-[50px] pr-5 text-[24px] font-medium leading-[50px]' |
| | | <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)', |
| | | }}>{code}</h1> |
| | |
| | | import AudioPlayer from '@/app/components/base/audio-btn/audio' |
| | | declare global { |
| | | // eslint-disable-next-line ts/consistent-type-definitions |
| | | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions |
| | | interface AudioPlayerManager { |
| | | instance: AudioPlayerManager |
| | | } |
| | |
| | | private audioPlayers: AudioPlayer | null = null |
| | | private msgId: string | undefined |
| | | |
| | | private constructor() { |
| | | } |
| | | |
| | | public static getInstance(): AudioPlayerManager { |
| | | if (!AudioPlayerManager.instance) { |
| | | AudioPlayerManager.instance = new AudioPlayerManager() |
| | |
| | | return AudioPlayerManager.instance |
| | | } |
| | | |
| | | public getAudioPlayer(url: string, isPublic: boolean, id: string | undefined, msgContent: string | null | undefined, voice: string | undefined, callback: ((event: string) => void) | null): AudioPlayer { |
| | | public getAudioPlayer(url: string, isPublic: boolean, id: string | undefined, msgContent: string | null | undefined, voice: string | undefined, callback: ((event: string) => {}) | null): AudioPlayer { |
| | | if (this.msgId && this.msgId === id && this.audioPlayers) { |
| | | this.audioPlayers.setCallback(callback) |
| | | return this.audioPlayers |
| | |
| | | this.audioPlayers.cacheBuffers = [] |
| | | this.audioPlayers.sourceBuffer?.abort() |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | } |
| | | } |
| | | |
| | |
| | | import { textToAudioStream } from '@/service/share' |
| | | |
| | | declare global { |
| | | // eslint-disable-next-line ts/consistent-type-definitions |
| | | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions |
| | | interface Window { |
| | | ManagedMediaSource: any |
| | | } |
| | |
| | | isLoadData = false |
| | | url: string |
| | | isPublic: boolean |
| | | callback: ((event: string) => void) | null |
| | | callback: ((event: string) => {}) | null |
| | | |
| | | constructor(streamUrl: string, isPublic: boolean, msgId: string | undefined, msgContent: string | null | undefined, voice: string | undefined, callback: ((event: string) => void) | null) { |
| | | constructor(streamUrl: string, isPublic: boolean, msgId: string | undefined, msgContent: string | null | undefined, voice: string | undefined, callback: ((event: string) => {}) | null) { |
| | | this.audioContext = new AudioContext() |
| | | this.msgId = msgId |
| | | this.msgContent = msgContent |
| | |
| | | }) |
| | | } |
| | | |
| | | public setCallback(callback: ((event: string) => void) | null) { |
| | | public setCallback(callback: ((event: string) => {}) | null) { |
| | | this.callback = callback |
| | | if (callback) { |
| | | this.audio.addEventListener('ended', () => { |
| | |
| | | this.receiveAudioData(value) |
| | | } |
| | | } |
| | | catch { |
| | | catch (error) { |
| | | this.isLoadData = false |
| | | this.callback && this.callback('error') |
| | | } |
| | |
| | | this.audioContext.suspend() |
| | | } |
| | | |
| | | private cancer() { |
| | | |
| | | } |
| | | |
| | | private receiveAudioData(unit8Array: Uint8Array) { |
| | | if (!unit8Array) { |
| | | this.finishStream() |
| | |
| | | |
| | | const params = useParams() |
| | | const pathname = usePathname() |
| | | const audio_finished_call = (event: string): void => { |
| | | const audio_finished_call = (event: string): any => { |
| | | switch (event) { |
| | | case 'ended': |
| | | setAudioState('ended') |
| | |
| | | > |
| | | <button |
| | | disabled={audioState === 'loading'} |
| | | className={`box-border flex h-6 w-6 cursor-pointer items-center justify-center ${isAudition ? 'p-0.5' : 'rounded-md bg-white p-0'}`} |
| | | className={`box-border w-6 h-6 flex items-center justify-center cursor-pointer ${isAudition ? 'p-0.5' : 'p-0 rounded-md bg-white'}`} |
| | | onClick={handleToggle} |
| | | > |
| | | {audioState === 'loading' |
| | | ? ( |
| | | <div className='flex h-full w-full items-center justify-center rounded-md'> |
| | | <div className='w-full h-full rounded-md flex items-center justify-center'> |
| | | <Loading /> |
| | | </div> |
| | | ) |
| | | : ( |
| | | <div className={'flex h-full w-full items-center justify-center rounded-md hover:bg-gray-50'}> |
| | | <div className={`h-4 w-4 ${(audioState === 'playing') ? s.pauseIcon : s.playIcon}`}></div> |
| | | <div className={`w-full h-full rounded-md flex items-center justify-center ${!isAudition ? 'hover:bg-gray-50' : 'hover:bg-gray-50'}`}> |
| | | <div className={`w-4 h-4 ${(audioState === 'playing') ? s.pauseIcon : s.playIcon}`}></div> |
| | | </div> |
| | | )} |
| | | </button> |
New file |
| | |
| | | .audioPlayer { |
| | | display: flex; |
| | | flex-direction: row; |
| | | align-items: center; |
| | | background-color: #ffffff; |
| | | border-radius: 10px; |
| | | padding: 8px; |
| | | min-width: 240px; |
| | | max-width: 420px; |
| | | max-height: 40px; |
| | | backdrop-filter: blur(5px); |
| | | border: 1px solid rgba(16, 24, 40, 0.08); |
| | | box-shadow: 0 1px 2px rgba(9, 9, 11, 0.05); |
| | | gap: 8px; |
| | | } |
| | | |
| | | .playButton { |
| | | display: inline-flex; |
| | | width: 16px; |
| | | height: 16px; |
| | | border-radius: 50%; |
| | | background-color: #296DFF; |
| | | color: white; |
| | | border: none; |
| | | cursor: pointer; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: background-color 0.1s; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .playButton:hover { |
| | | background-color: #3367d6; |
| | | } |
| | | |
| | | .playButton:disabled { |
| | | background-color: #bdbdbf; |
| | | } |
| | | |
| | | .audioControls { |
| | | flex-grow: 1; |
| | | |
| | | } |
| | | |
| | | .progressBarContainer { |
| | | height: 32px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .waveform { |
| | | position: relative; |
| | | display: flex; |
| | | cursor: pointer; |
| | | height: 24px; |
| | | width: 100%; |
| | | flex-grow: 1; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .progressBar { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | opacity: 0.5; |
| | | border-radius: 2px; |
| | | flex: none; |
| | | order: 55; |
| | | flex-grow: 0; |
| | | height: 100%; |
| | | background-color: rgba(66, 133, 244, 0.3); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .timeDisplay { |
| | | /* position: absolute; */ |
| | | color: #296DFF; |
| | | border-radius: 2px; |
| | | order: 0; |
| | | height: 100%; |
| | | width: 50px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | /* .currentTime { |
| | | position: absolute; |
| | | bottom: calc(100% + 5px); |
| | | transform: translateX(-50%); |
| | | background-color: rgba(255,255,255,.8); |
| | | padding: 2px 4px; |
| | | border-radius:10px; |
| | | box-shadow: 0 1px 5px rgba(0, 0, 0, 0.08); |
| | | } */ |
| | | |
| | | .duration { |
| | | background-color: rgba(255, 255, 255, 0.8); |
| | | padding: 2px 4px; |
| | | border-radius: 10px; |
| | | } |
| | | |
| | | .source_unavailable { |
| | | border: none; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 100%; |
| | | height: 100%; |
| | | position: absolute; |
| | | color: #bdbdbf; |
| | | } |
| | | |
| | | .playButton svg path, |
| | | .playButton svg rect{ |
| | | fill:currentColor; |
| | | } |
| | |
| | | import React, { useCallback, useEffect, useRef, useState } from 'react' |
| | | import { t } from 'i18next' |
| | | import { |
| | | RiPauseCircleFill, |
| | | RiPlayLargeFill, |
| | | } from '@remixicon/react' |
| | | import styles from './AudioPlayer.module.css' |
| | | import Toast from '@/app/components/base/toast' |
| | | import useTheme from '@/hooks/use-theme' |
| | | import { Theme } from '@/types/app' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type AudioPlayerProps = { |
| | | src: string |
| | |
| | | const [hasStartedPlaying, setHasStartedPlaying] = useState(false) |
| | | const [hoverTime, setHoverTime] = useState(0) |
| | | const [isAudioAvailable, setIsAudioAvailable] = useState(true) |
| | | const { theme } = useTheme() |
| | | |
| | | useEffect(() => { |
| | | const audio = audioRef.current |
| | |
| | | audio.load() |
| | | |
| | | // Delayed generation of waveform data |
| | | // eslint-disable-next-line ts/no-use-before-define |
| | | // eslint-disable-next-line @typescript-eslint/no-use-before-define |
| | | const timer = setTimeout(() => generateWaveformData(src), 1000) |
| | | |
| | | return () => { |
| | |
| | | setWaveformData(normalizedWaveform) |
| | | setIsAudioAvailable(true) |
| | | } |
| | | catch { |
| | | catch (error) { |
| | | const waveform: number[] = [] |
| | | let prevValue = Math.random() |
| | | |
| | |
| | | let color |
| | | |
| | | if (index * barWidth <= playedWidth) |
| | | color = theme === Theme.light ? '#296DFF' : '#84ABFF' |
| | | color = '#296DFF' |
| | | else if ((index * barWidth / width) * duration <= hoverTime) |
| | | color = theme === Theme.light ? 'rgba(21,90,239,.40)' : 'rgba(200, 206, 218, 0.28)' |
| | | color = 'rgba(21,90,239,.40)' |
| | | else |
| | | color = theme === Theme.light ? 'rgba(21,90,239,.20)' : 'rgba(200, 206, 218, 0.14)' |
| | | color = 'rgba(21,90,239,.20)' |
| | | |
| | | const barHeight = value * height |
| | | const rectX = index * barWidth |
| | |
| | | ctx.fillRect(rectX, rectY, rectWidth, rectHeight) |
| | | } |
| | | }) |
| | | }, [currentTime, duration, hoverTime, theme, waveformData]) |
| | | }, [currentTime, duration, hoverTime, waveformData]) |
| | | |
| | | useEffect(() => { |
| | | drawWaveform() |
| | |
| | | }, [duration]) |
| | | |
| | | return ( |
| | | <div className='flex h-9 min-w-[240px] max-w-[420px] items-end gap-2 rounded-[10px] border border-components-panel-border-subtle bg-components-chat-input-audio-bg-alt p-2 shadow-xs backdrop-blur-sm'> |
| | | <div className={styles.audioPlayer}> |
| | | <audio ref={audioRef} src={src} preload="auto"/> |
| | | <button className='inline-flex shrink-0 cursor-pointer items-center justify-center border-none text-text-accent transition-all hover:text-text-accent-secondary disabled:text-components-button-primary-bg-disabled' onClick={togglePlay} disabled={!isAudioAvailable}> |
| | | <button className={styles.playButton} onClick={togglePlay} disabled={!isAudioAvailable}> |
| | | {isPlaying |
| | | ? ( |
| | | <RiPauseCircleFill className='h-5 w-5' /> |
| | | <svg viewBox="0 0 24 24" width="16" height="16"> |
| | | <rect x="7" y="6" width="3" height="12" rx="1.5" ry="1.5"/> |
| | | <rect x="15" y="6" width="3" height="12" rx="1.5" ry="1.5"/> |
| | | </svg> |
| | | ) |
| | | : ( |
| | | <RiPlayLargeFill className='h-5 w-5' /> |
| | | <svg viewBox="0 0 24 24" width="16" height="16"> |
| | | <path d="M8 5v14l11-7z" fill="currentColor"/> |
| | | </svg> |
| | | )} |
| | | </button> |
| | | <div className={cn(isAudioAvailable && 'grow')} hidden={!isAudioAvailable}> |
| | | <div className='flex h-8 items-center justify-center'> |
| | | <div className={isAudioAvailable ? styles.audioControls : styles.audioControls_disabled} hidden={!isAudioAvailable}> |
| | | <div className={styles.progressBarContainer}> |
| | | <canvas |
| | | ref={canvasRef} |
| | | className='relative flex h-6 w-full grow cursor-pointer items-center justify-center' |
| | | className={styles.waveform} |
| | | onClick={handleCanvasInteraction} |
| | | onMouseMove={handleMouseMove} |
| | | onMouseDown={handleCanvasInteraction} |
| | | /> |
| | | <div className='system-xs-medium inline-flex min-w-[50px] items-center justify-center text-text-accent-secondary'> |
| | | <span className='rounded-[10px] px-0.5 py-1'>{formatTime(duration)}</span> |
| | | {/* <div className={styles.currentTime} style={{ left: `${(currentTime / duration) * 81}%`, bottom: '29px' }}> |
| | | {formatTime(currentTime)} |
| | | </div> */} |
| | | <div className={styles.timeDisplay}> |
| | | <span className={styles.duration}>{formatTime(duration)}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div className='absolute left-0 top-0 flex h-full w-full items-center justify-center text-text-quaternary' hidden={isAudioAvailable}>{t('common.operation.audioSourceUnavailable')}</div> |
| | | <div className={styles.source_unavailable} hidden={isAudioAvailable}>{t('common.operation.audioSourceUnavailable')}</div> |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | import { useEffect, useRef } from 'react' |
| | | import { forwardRef, useEffect, useRef } from 'react' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type AutoHeightTextareaProps = |
| | | & React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement> |
| | | & { outerClassName?: string } |
| | | |
| | | const AutoHeightTextarea = ( |
| | | const AutoHeightTextarea = forwardRef<HTMLTextAreaElement, AutoHeightTextareaProps>( |
| | | ( |
| | | { |
| | | ref: outRef, |
| | | outerClassName, |
| | | value, |
| | | className, |
| | |
| | | autoFocus, |
| | | disabled, |
| | | ...rest |
| | | }: AutoHeightTextareaProps & { |
| | | ref: React.RefObject<HTMLTextAreaElement>; |
| | | }, |
| | | outRef, |
| | | ) => { |
| | | const innerRef = useRef<HTMLTextAreaElement>(null) |
| | | const ref = outRef || innerRef |
| | |
| | | } |
| | | }, [autoFocus, disabled, ref]) |
| | | return ( |
| | | (<div className={outerClassName}> |
| | | <div className={outerClassName}> |
| | | <div className='relative'> |
| | | <div className={cn(className, 'invisible whitespace-pre-wrap break-all')}> |
| | | {!value ? placeholder : `${value}`.replace(/\n$/, '\n ')} |
| | |
| | | <textarea |
| | | ref={ref} |
| | | placeholder={placeholder} |
| | | className={cn(className, 'absolute inset-0 h-full w-full resize-none appearance-none border-none outline-none disabled:bg-transparent')} |
| | | className={cn(className, 'disabled:bg-transparent absolute inset-0 outline-none border-none appearance-none resize-none w-full h-full')} |
| | | value={value} |
| | | disabled={disabled} |
| | | {...rest} |
| | | /> |
| | | </div> |
| | | </div>) |
| | | </div> |
| | | ) |
| | | } |
| | | }, |
| | | ) |
| | | |
| | | AutoHeightTextarea.displayName = 'AutoHeightTextarea' |
| | | |
| | |
| | | import { useEffect, useRef } from 'react' |
| | | import { forwardRef, useEffect, useRef } from 'react' |
| | | import cn from '@/utils/classnames' |
| | | import { sleep } from '@/utils' |
| | | |
| | |
| | | onKeyUp?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void |
| | | } |
| | | |
| | | const AutoHeightTextarea = ( |
| | | { |
| | | ref: outerRef, |
| | | value, |
| | | onChange, |
| | | placeholder, |
| | | className, |
| | | wrapperClassName, |
| | | minHeight = 36, |
| | | maxHeight = 96, |
| | | autoFocus, |
| | | controlFocus, |
| | | onKeyDown, |
| | | onKeyUp, |
| | | }: IProps & { |
| | | ref: React.RefObject<unknown>; |
| | | }, |
| | | const AutoHeightTextarea = forwardRef( |
| | | ( |
| | | { value, onChange, placeholder, className, wrapperClassName, 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) |
| | |
| | | }, [controlFocus]) |
| | | |
| | | return ( |
| | | (<div className={`relative ${wrapperClassName}`}> |
| | | <div className={cn(className, 'invisible overflow-y-auto whitespace-pre-wrap break-all')} style={{ |
| | | <div className={`relative ${wrapperClassName}`}> |
| | | <div className={cn(className, 'invisible whitespace-pre-wrap break-all overflow-y-auto')} style={{ |
| | | minHeight, |
| | | maxHeight, |
| | | paddingRight: (value && value.trim().length > 10000) ? 140 : 130, |
| | |
| | | onKeyUp={onKeyUp} |
| | | value={value} |
| | | /> |
| | | </div>) |
| | | </div> |
| | | ) |
| | | } |
| | | }, |
| | | ) |
| | | |
| | | AutoHeightTextarea.displayName = 'AutoHeightTextarea' |
| | | |
| | |
| | | style={style} |
| | | > |
| | | <div |
| | | className={cn(textClassName, 'scale-[0.4] text-center text-white')} |
| | | className={cn(textClassName, 'text-center text-white scale-[0.4]')} |
| | | style={style} |
| | | > |
| | | {name[0].toLocaleUpperCase()} |
| | |
| | | import type { ReactNode } from 'react' |
| | | import { memo } from 'react' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type BadgeProps = { |
| | | className?: string |
| | | text?: ReactNode |
| | | children?: ReactNode |
| | | text?: string |
| | | children?: React.ReactNode |
| | | uppercase?: boolean |
| | | hasRedCornerMark?: boolean |
| | | } |
| | | |
| | | const Badge = ({ |
| | |
| | | text, |
| | | children, |
| | | uppercase = true, |
| | | hasRedCornerMark, |
| | | }: BadgeProps) => { |
| | | return ( |
| | | <div |
| | | className={cn( |
| | | 'relative inline-flex h-5 items-center rounded-[5px] border border-divider-deep px-[5px] leading-3 text-text-tertiary', |
| | | 'inline-flex items-center px-[5px] h-5 rounded-[5px] border border-divider-deep leading-3 text-text-tertiary', |
| | | uppercase ? 'system-2xs-medium-uppercase' : 'system-xs-medium', |
| | | className, |
| | | )} |
| | | > |
| | | {hasRedCornerMark && ( |
| | | <div className='absolute right-[-2px] top-[-2px] h-1.5 w-1.5 rounded-[2px] border border-components-badge-status-light-error-border-inner bg-components-badge-status-light-error-bg shadow-sm'> |
| | | </div> |
| | | )} |
| | | {children || text} |
| | | </div> |
| | | ) |
| | |
| | | {textAreaContent} |
| | | {/* footer */} |
| | | {!readonly && ( |
| | | <div className='flex pb-2 pl-4'> |
| | | <div className="h-[18px] rounded-md bg-gray-100 px-1 text-xs leading-[18px] text-gray-500">{currentValue?.length}</div> |
| | | <div className='pl-4 pb-2 flex'> |
| | | <div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{currentValue?.length}</div> |
| | | </div> |
| | | )} |
| | | |
| | |
| | | onClick, |
| | | }) => { |
| | | return ( |
| | | <div className={cn(className, 'cursor-pointer select-none rounded-md p-1 hover:bg-state-base-hover')} onClick={onClick}> |
| | | <RiAddLine className='h-4 w-4 text-text-tertiary' /> |
| | | <div className={cn(className, 'p-1 rounded-md cursor-pointer hover:bg-state-base-hover select-none')} onClick={onClick}> |
| | | <RiAddLine className='w-4 h-4 text-text-tertiary' /> |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | |
| | | afterEach(cleanup) |
| | | // https://testing-library.com/docs/queries/about |
| | | describe('Button', () => { |
| | | describe('Button text', () => { |
| | | test('Button text should be same as children', async () => { |
| | | const { getByRole, container } = render(<Button>Click me</Button>) |
| | | expect(getByRole('button').textContent).toBe('Click me') |
| | | expect(container.querySelector('button')?.textContent).toBe('Click me') |
| | | }) |
| | | }) |
| | | |
| | | describe('Button loading', () => { |
| | | test('Loading button text should include same as children', async () => { |
| | | const { getByRole } = render(<Button loading>Click me</Button>) |
| | | expect(getByRole('button').textContent?.includes('Loading')).toBe(true) |
| | | }) |
| | | test('Not loading button text should include same as children', async () => { |
| | | const { getByRole } = render(<Button loading={false}>Click me</Button>) |
| | | expect(getByRole('button').textContent?.includes('Loading')).toBe(false) |
| | | }) |
| | | |
| | | test('Loading button should have loading classname', async () => { |
| | | const animClassName = 'anim-breath' |
| | | const { getByRole } = render(<Button loading spinnerClassName={animClassName}>Click me</Button>) |
| | | expect(getByRole('button').getElementsByClassName('animate-spin')[0]?.className).toContain(animClassName) |
| | | }) |
| | | }) |
| | | |
| | |
| | | expect(getByRole('button').className).toContain('btn-warning') |
| | | }) |
| | | |
| | | test('Button should have secondary variant', async () => { |
| | | const { getByRole } = render(<Button variant='secondary'>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-secondary') |
| | | }) |
| | | |
| | | test('Button should have secondary-accent variant', async () => { |
| | | const { getByRole } = render(<Button variant='secondary-accent'>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-secondary-accent') |
| | | }) |
| | | test('Button should have ghost variant', async () => { |
| | | const { getByRole } = render(<Button variant='ghost'>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-ghost') |
| | | }) |
| | | test('Button should have ghost-accent variant', async () => { |
| | | const { getByRole } = render(<Button variant='ghost-accent'>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-ghost-accent') |
| | | }) |
| | | |
| | | test('Button disabled should have disabled variant', async () => { |
| | | const { getByRole } = render(<Button disabled>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-disabled') |
| | | }) |
| | | }) |
| | | |
| | | describe('Button size', () => { |
| | | test('Button should have default size', async () => { |
| | | const { getByRole } = render(<Button>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-medium') |
| | | }) |
| | | |
| | | test('Button should have small size', async () => { |
| | | const { getByRole } = render(<Button size='small'>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-small') |
| | | }) |
| | | |
| | | test('Button should have medium size', async () => { |
| | | const { getByRole } = render(<Button size='medium'>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-medium') |
| | | }) |
| | | |
| | | test('Button should have large size', async () => { |
| | | const { getByRole } = render(<Button size='large'>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-large') |
| | | }) |
| | | }) |
| | | |
| | | describe('Button destructive', () => { |
| | | test('Button should have destructive classname', async () => { |
| | | const { getByRole } = render(<Button destructive>Click me</Button>) |
| | | expect(getByRole('button').className).toContain('btn-destructive') |
| | | }) |
| | | }) |
| | | |
| | |
| | | const { getByRole } = render(<Button onClick={onClick}>Click me</Button>) |
| | | fireEvent.click(getByRole('button')) |
| | | expect(onClick).toHaveBeenCalled() |
| | | }) |
| | | }) |
| | | }) |
| | |
| | | variant: 'primary', |
| | | children: ( |
| | | <> |
| | | <RocketLaunchIcon className="mr-1.5 h-4 w-4 stroke-[1.8px]" /> |
| | | <RocketLaunchIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" /> |
| | | Launch |
| | | </> |
| | | ), |
| | |
| | | destructive?: boolean |
| | | loading?: boolean |
| | | styleCss?: CSSProperties |
| | | spinnerClassName?: string |
| | | } & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> |
| | | |
| | | const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( |
| | | ({ className, variant, size, destructive, loading, styleCss, children, spinnerClassName, ...props }, ref) => { |
| | | ({ className, variant, size, destructive, loading, styleCss, children, ...props }, ref) => { |
| | | return ( |
| | | <button |
| | | type='button' |
| | |
| | | {...props} |
| | | > |
| | | {children} |
| | | {loading && <Spinner loading={loading} className={classNames('!text-white !h-3 !w-3 !border-2 !ml-1', spinnerClassName)} />} |
| | | {loading && <Spinner loading={loading} className='!text-white !h-3 !w-3 !border-2 !ml-1' />} |
| | | </button> |
| | | ) |
| | | }, |
| | |
| | | import { get } from 'lodash-es' |
| | | import { get } from 'lodash' |
| | | import { buildChatItemTree, getThreadMessages } from '../utils' |
| | | import type { ChatItemInTree } from '../types' |
| | | import branchedTestMessages from './branchedTestMessages.json' |
| | |
| | | expect(tree7).toMatchSnapshot() |
| | | }) |
| | | |
| | | const partialMessages2 = partialMessages as ChatItemInTree[] |
| | | const partialMessages2 = (partialMessages as ChatItemInTree[]) |
| | | const tree8 = buildChatItemTree(partialMessages2) |
| | | it('should work with partial messages 2', () => { |
| | | expect(tree8).toMatchSnapshot() |
| | |
| | | import { useCallback, useEffect, useMemo, useState } from 'react' |
| | | import { useCallback, useEffect, useMemo } from 'react' |
| | | import Chat from '../chat' |
| | | import type { |
| | | ChatConfig, |
| | |
| | | import { useChat } from '../chat/hooks' |
| | | import { getLastAnswer, isValidGeneratedAnswer } from '../utils' |
| | | import { useChatWithHistoryContext } from './context' |
| | | import { InputVarType } from '@/app/components/workflow/types' |
| | | import { TransferMethod } from '@/types/app' |
| | | import InputsForm from '@/app/components/base/chat/chat-with-history/inputs-form' |
| | | import Header from './header' |
| | | import ConfigPanel from './config-panel' |
| | | import { |
| | | fetchSuggestedQuestions, |
| | | getUrl, |
| | | stopChatMessageResponding, |
| | | } from '@/service/share' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | import AnswerIcon from '@/app/components/base/answer-icon' |
| | | import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions' |
| | | import { Markdown } from '@/app/components/base/markdown' |
| | | import cn from '@/utils/classnames' |
| | | import type { FileEntity } from '../../file-uploader/types' |
| | | |
| | | const ChatWrapper = () => { |
| | | const { |
| | |
| | | appPrevChatTree, |
| | | currentConversationId, |
| | | currentConversationItem, |
| | | currentConversationInputs, |
| | | inputsForms, |
| | | newConversationInputs, |
| | | newConversationInputsRef, |
| | | handleNewConversationCompleted, |
| | | isMobile, |
| | | isInstalledApp, |
| | |
| | | currentChatInstanceRef, |
| | | appData, |
| | | themeBuilder, |
| | | sidebarCollapseState, |
| | | clearChatList, |
| | | setClearChatList, |
| | | setIsResponding, |
| | | } = useChatWithHistoryContext() |
| | | const appConfig = useMemo(() => { |
| | | const config = appParams || {} |
| | |
| | | setTargetMessageId, |
| | | handleSend, |
| | | handleStop, |
| | | isResponding: respondingState, |
| | | isResponding, |
| | | suggestedQuestions, |
| | | } = useChat( |
| | | appConfig, |
| | | { |
| | | inputs: (currentConversationId ? currentConversationInputs : newConversationInputs) as any, |
| | | inputs: (currentConversationId ? currentConversationItem?.inputs : newConversationInputs) as any, |
| | | inputsForm: inputsForms, |
| | | }, |
| | | appPrevChatTree, |
| | | taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId), |
| | | clearChatList, |
| | | setClearChatList, |
| | | ) |
| | | const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputsRef?.current |
| | | const inputDisabled = useMemo(() => { |
| | | let hasEmptyInput = '' |
| | | let fileIsUploading = false |
| | | const requiredVars = inputsForms.filter(({ required }) => required) |
| | | if (requiredVars.length) { |
| | | requiredVars.forEach(({ variable, label, type }) => { |
| | | if (hasEmptyInput) |
| | | return |
| | | |
| | | if (fileIsUploading) |
| | | return |
| | | |
| | | if (!inputsFormValue?.[variable]) |
| | | hasEmptyInput = label as string |
| | | |
| | | if ((type === InputVarType.singleFile || type === InputVarType.multiFiles) && inputsFormValue?.[variable]) { |
| | | const files = inputsFormValue[variable] |
| | | if (Array.isArray(files)) |
| | | fileIsUploading = files.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId) |
| | | else |
| | | fileIsUploading = files.transferMethod === TransferMethod.local_file && !files.uploadedId |
| | | } |
| | | }) |
| | | } |
| | | if (hasEmptyInput) |
| | | return true |
| | | |
| | | if (fileIsUploading) |
| | | return true |
| | | return false |
| | | }, [inputsFormValue, inputsForms]) |
| | | |
| | | useEffect(() => { |
| | | if (currentChatInstanceRef.current) |
| | |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, []) |
| | | |
| | | useEffect(() => { |
| | | setIsResponding(respondingState) |
| | | }, [respondingState, setIsResponding]) |
| | | |
| | | const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => { |
| | | const data: any = { |
| | | query: message, |
| | | files, |
| | | inputs: currentConversationId ? currentConversationInputs : newConversationInputs, |
| | | inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, |
| | | conversation_id: currentConversationId, |
| | | parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null, |
| | | } |
| | |
| | | isPublicAPI: !isInstalledApp, |
| | | }, |
| | | ) |
| | | }, [chatList, handleNewConversationCompleted, handleSend, currentConversationId, currentConversationInputs, newConversationInputs, isInstalledApp, appId]) |
| | | }, [ |
| | | chatList, |
| | | handleNewConversationCompleted, |
| | | handleSend, |
| | | currentConversationId, |
| | | currentConversationItem, |
| | | newConversationInputs, |
| | | isInstalledApp, |
| | | appId, |
| | | ]) |
| | | |
| | | const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => { |
| | | const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)! |
| | | const doRegenerate = useCallback((chatItem: ChatItemInTree) => { |
| | | const question = chatList.find(item => item.id === chatItem.parentMessageId)! |
| | | const parentAnswer = chatList.find(item => item.id === question.parentMessageId) |
| | | doSend(editedQuestion ? editedQuestion.message : question.content, |
| | | editedQuestion ? editedQuestion.files : question.message_files, |
| | | true, |
| | | isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null, |
| | | ) |
| | | doSend(question.content, question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null) |
| | | }, [chatList, doSend]) |
| | | |
| | | const messageList = useMemo(() => { |
| | | if (currentConversationId) |
| | | return chatList |
| | | return chatList.filter(item => !item.isOpeningStatement) |
| | | }, [chatList, currentConversationId]) |
| | | |
| | | const [collapsed, setCollapsed] = useState(!!currentConversationId) |
| | | |
| | | const chatNode = useMemo(() => { |
| | | if (!inputsForms.length) |
| | | return null |
| | | if (isMobile) { |
| | | if (!currentConversationId) |
| | | return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} /> |
| | | return null |
| | | if (inputsForms.length) { |
| | | return ( |
| | | <> |
| | | <Header |
| | | isMobile={isMobile} |
| | | title={currentConversationItem?.name || ''} |
| | | /> |
| | | { |
| | | !currentConversationId && ( |
| | | <div className={`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`}> |
| | | <div className='mb-6' /> |
| | | <ConfigPanel /> |
| | | <div |
| | | className='my-6 h-[1px]' |
| | | style={{ background: 'linear-gradient(90deg, rgba(242, 244, 247, 0.00) 0%, #F2F4F7 49.17%, rgba(242, 244, 247, 0.00) 100%)' }} |
| | | /> |
| | | </div> |
| | | ) |
| | | } |
| | | else { |
| | | return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} /> |
| | | </> |
| | | ) |
| | | } |
| | | }, [inputsForms.length, isMobile, currentConversationId, collapsed]) |
| | | |
| | | const welcome = useMemo(() => { |
| | | const welcomeMessage = chatList.find(item => item.isOpeningStatement) |
| | | if (respondingState) |
| | | return null |
| | | if (currentConversationId) |
| | | return null |
| | | if (!welcomeMessage) |
| | | return null |
| | | if (!collapsed && inputsForms.length > 0) |
| | | return null |
| | | if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) { |
| | | return ( |
| | | <div className='flex min-h-[50vh] items-center justify-center px-4 py-12'> |
| | | <div className='flex max-w-[720px] grow gap-4'> |
| | | <AppIcon |
| | | size='xl' |
| | | iconType={appData?.site.icon_type} |
| | | icon={appData?.site.icon} |
| | | background={appData?.site.icon_background} |
| | | imageUrl={appData?.site.icon_url} |
| | | <Header |
| | | isMobile={isMobile} |
| | | title={currentConversationItem?.name || ''} |
| | | /> |
| | | <div className='w-0 grow'> |
| | | <div className='body-lg-regular grow rounded-2xl bg-chat-bubble-bg px-4 py-3 text-text-primary'> |
| | | <Markdown content={welcomeMessage.content} /> |
| | | <SuggestedQuestions item={welcomeMessage} /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | ) |
| | | } |
| | | return ( |
| | | <div className={cn('flex h-[50vh] flex-col items-center justify-center gap-3 py-12')}> |
| | | <AppIcon |
| | | size='xl' |
| | | iconType={appData?.site.icon_type} |
| | | icon={appData?.site.icon} |
| | | background={appData?.site.icon_background} |
| | | imageUrl={appData?.site.icon_url} |
| | | /> |
| | | <div className='max-w-[768px] px-4'> |
| | | <Markdown className='!body-2xl-regular !text-text-tertiary' content={welcomeMessage.content} /> |
| | | </div> |
| | | </div> |
| | | ) |
| | | }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState]) |
| | | }, [ |
| | | currentConversationId, |
| | | inputsForms, |
| | | currentConversationItem, |
| | | isMobile, |
| | | ]) |
| | | |
| | | const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon) |
| | | ? <AnswerIcon |
| | |
| | | |
| | | return ( |
| | | <div |
| | | className='h-full overflow-hidden bg-chatbot-bg' |
| | | className='h-full bg-chatbot-bg overflow-hidden' |
| | | > |
| | | <Chat |
| | | appData={appData} |
| | | config={appConfig} |
| | | chatList={messageList} |
| | | isResponding={respondingState} |
| | | chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[768px] ${isMobile && 'px-4'}`} |
| | | chatList={chatList} |
| | | isResponding={isResponding} |
| | | chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[720px] ${isMobile && 'px-4'}`} |
| | | chatFooterClassName='pb-4' |
| | | chatFooterInnerClassName={`mx-auto w-full max-w-[768px] ${isMobile ? 'px-2' : 'px-4'}`} |
| | | chatFooterInnerClassName={`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`} |
| | | onSend={doSend} |
| | | inputs={currentConversationId ? currentConversationInputs as any : newConversationInputs} |
| | | inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs} |
| | | inputsForm={inputsForms} |
| | | onRegenerate={doRegenerate} |
| | | onStopResponding={handleStop} |
| | | chatNode={ |
| | | <> |
| | | {chatNode} |
| | | {welcome} |
| | | </> |
| | | } |
| | | chatNode={chatNode} |
| | | allToolIcons={appMeta?.tool_icons || {}} |
| | | onFeedback={handleFeedback} |
| | | suggestedQuestions={suggestedQuestions} |
| | |
| | | hideProcessDetail |
| | | themeBuilder={themeBuilder} |
| | | switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)} |
| | | inputDisabled={inputDisabled} |
| | | isMobile={isMobile} |
| | | sidebarCollapseState={sidebarCollapseState} |
| | | /> |
| | | </div> |
| | | ) |
New file |
| | |
| | | import type { FC } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { memo } from 'react' |
| | | import Textarea from '@/app/components/base/textarea' |
| | | |
| | | type InputProps = { |
| | | form: any |
| | | value: string |
| | | onChange: (variable: string, value: string) => void |
| | | } |
| | | const FormInput: FC<InputProps> = ({ |
| | | form, |
| | | value, |
| | | onChange, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const { |
| | | type, |
| | | label, |
| | | required, |
| | | max_length, |
| | | variable, |
| | | } = form |
| | | |
| | | if (type === 'paragraph') { |
| | | return ( |
| | | <Textarea |
| | | value={value} |
| | | className='resize-none' |
| | | onChange={e => onChange(variable, e.target.value)} |
| | | placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`} |
| | | /> |
| | | ) |
| | | } |
| | | |
| | | return ( |
| | | <input |
| | | className='grow h-9 rounded-lg bg-gray-100 px-2.5 outline-none appearance-none' |
| | | value={value || ''} |
| | | maxLength={max_length} |
| | | onChange={e => onChange(variable, e.target.value)} |
| | | placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`} |
| | | /> |
| | | ) |
| | | } |
| | | |
| | | export default memo(FormInput) |
New file |
| | |
| | | import { useCallback } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useChatWithHistoryContext } from '../context' |
| | | import Input from './form-input' |
| | | import { PortalSelect } from '@/app/components/base/select' |
| | | import { InputVarType } from '@/app/components/workflow/types' |
| | | import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' |
| | | |
| | | const Form = () => { |
| | | const { t } = useTranslation() |
| | | const { |
| | | appParams, |
| | | inputsForms, |
| | | newConversationInputs, |
| | | newConversationInputsRef, |
| | | handleNewConversationInputsChange, |
| | | isMobile, |
| | | } = useChatWithHistoryContext() |
| | | |
| | | const handleFormChange = useCallback((variable: string, value: any) => { |
| | | handleNewConversationInputsChange({ |
| | | ...newConversationInputsRef.current, |
| | | [variable]: value, |
| | | }) |
| | | }, [newConversationInputsRef, handleNewConversationInputsChange]) |
| | | |
| | | const renderField = (form: any) => { |
| | | const { |
| | | label, |
| | | required, |
| | | variable, |
| | | options, |
| | | } = form |
| | | |
| | | if (form.type === 'text-input' || form.type === 'paragraph') { |
| | | return ( |
| | | <Input |
| | | form={form} |
| | | value={newConversationInputs[variable]} |
| | | onChange={handleFormChange} |
| | | /> |
| | | ) |
| | | } |
| | | if (form.type === 'number') { |
| | | return ( |
| | | <input |
| | | className="grow h-9 rounded-lg bg-gray-100 px-2.5 outline-none appearance-none" |
| | | type="number" |
| | | value={newConversationInputs[variable] || ''} |
| | | onChange={e => handleFormChange(variable, e.target.value)} |
| | | placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`} |
| | | /> |
| | | ) |
| | | } |
| | | if (form.type === InputVarType.singleFile) { |
| | | return ( |
| | | <FileUploaderInAttachmentWrapper |
| | | value={newConversationInputs[variable] ? [newConversationInputs[variable]] : []} |
| | | onChange={files => handleFormChange(variable, files[0])} |
| | | fileConfig={{ |
| | | allowed_file_types: form.allowed_file_types, |
| | | allowed_file_extensions: form.allowed_file_extensions, |
| | | allowed_file_upload_methods: form.allowed_file_upload_methods, |
| | | number_limits: 1, |
| | | fileUploadConfig: (appParams as any).system_parameters, |
| | | }} |
| | | /> |
| | | ) |
| | | } |
| | | if (form.type === InputVarType.multiFiles) { |
| | | return ( |
| | | <FileUploaderInAttachmentWrapper |
| | | value={newConversationInputs[variable]} |
| | | onChange={files => handleFormChange(variable, files)} |
| | | fileConfig={{ |
| | | allowed_file_types: form.allowed_file_types, |
| | | allowed_file_extensions: form.allowed_file_extensions, |
| | | allowed_file_upload_methods: form.allowed_file_upload_methods, |
| | | number_limits: form.max_length, |
| | | fileUploadConfig: (appParams as any).system_parameters, |
| | | }} |
| | | /> |
| | | ) |
| | | } |
| | | |
| | | return ( |
| | | <PortalSelect |
| | | popupClassName='w-[200px]' |
| | | value={newConversationInputs[variable]} |
| | | items={options.map((option: string) => ({ value: option, name: option }))} |
| | | onSelect={item => handleFormChange(variable, item.value as string)} |
| | | placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`} |
| | | /> |
| | | ) |
| | | } |
| | | |
| | | if (!inputsForms.length) |
| | | return null |
| | | |
| | | return ( |
| | | <div className='mb-4 py-2'> |
| | | { |
| | | inputsForms.map(form => ( |
| | | <div |
| | | key={form.variable} |
| | | className={`flex mb-3 last-of-type:mb-0 text-sm text-gray-900 ${isMobile && '!flex-wrap'}`} |
| | | > |
| | | <div className={`shrink-0 mr-2 py-2 w-[128px] ${isMobile && '!w-full'}`}>{form.label}</div> |
| | | {renderField(form)} |
| | | </div> |
| | | )) |
| | | } |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | export default Form |
New file |
| | |
| | | import { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useChatWithHistoryContext } from '../context' |
| | | import Form from './form' |
| | | import Button from '@/app/components/base/button' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication' |
| | | import { Edit02 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import { Star06 } from '@/app/components/base/icons/src/vender/solid/shapes' |
| | | import LogoSite from '@/app/components/base/logo/logo-site' |
| | | |
| | | const ConfigPanel = () => { |
| | | const { t } = useTranslation() |
| | | const { |
| | | appData, |
| | | inputsForms, |
| | | handleStartChat, |
| | | showConfigPanelBeforeChat, |
| | | isMobile, |
| | | } = useChatWithHistoryContext() |
| | | const [collapsed, setCollapsed] = useState(true) |
| | | const customConfig = appData?.custom_config |
| | | const site = appData?.site |
| | | |
| | | return ( |
| | | <div className='flex flex-col max-h-[80%] w-full max-w-[720px]'> |
| | | <div |
| | | className={` |
| | | grow rounded-xl overflow-y-auto |
| | | ${showConfigPanelBeforeChat && 'border-[0.5px] border-gray-100 shadow-lg'} |
| | | ${!showConfigPanelBeforeChat && collapsed && 'border border-indigo-100'} |
| | | ${!showConfigPanelBeforeChat && !collapsed && 'border-[0.5px] border-gray-100 shadow-lg'} |
| | | `} |
| | | > |
| | | <div |
| | | className={` |
| | | flex flex-wrap px-6 py-4 rounded-t-xl bg-indigo-25 |
| | | ${isMobile && '!px-4 !py-3'} |
| | | `} |
| | | > |
| | | { |
| | | showConfigPanelBeforeChat && ( |
| | | <> |
| | | <div className='flex items-center h-8 text-2xl font-semibold text-gray-800'> |
| | | <AppIcon |
| | | iconType={appData?.site.icon_type} |
| | | icon={appData?.site.icon} |
| | | background='transparent' |
| | | imageUrl={appData?.site.icon_url} |
| | | size='small' |
| | | className="mr-2" |
| | | /> |
| | | {appData?.site.title} |
| | | </div> |
| | | { |
| | | appData?.site.description && ( |
| | | <div className='mt-2 w-full text-sm text-gray-500'> |
| | | {appData?.site.description} |
| | | </div> |
| | | ) |
| | | } |
| | | </> |
| | | ) |
| | | } |
| | | { |
| | | !showConfigPanelBeforeChat && collapsed && ( |
| | | <> |
| | | <Star06 className='mr-1 mt-1 w-4 h-4 text-indigo-600' /> |
| | | <div className='grow py-[3px] text-[13px] text-indigo-600 leading-[18px] font-medium'> |
| | | {t('share.chat.configStatusDes')} |
| | | </div> |
| | | <Button |
| | | variant='secondary-accent' |
| | | size='small' |
| | | className='shrink-0' |
| | | onClick={() => setCollapsed(false)} |
| | | > |
| | | <Edit02 className='mr-1 w-3 h-3' /> |
| | | {t('common.operation.edit')} |
| | | </Button> |
| | | </> |
| | | ) |
| | | } |
| | | { |
| | | !showConfigPanelBeforeChat && !collapsed && ( |
| | | <> |
| | | <Star06 className='mr-1 mt-1 w-4 h-4 text-indigo-600' /> |
| | | <div className='grow py-[3px] text-[13px] text-indigo-600 leading-[18px] font-medium'> |
| | | {t('share.chat.privatePromptConfigTitle')} |
| | | </div> |
| | | </> |
| | | ) |
| | | } |
| | | </div> |
| | | { |
| | | !collapsed && !showConfigPanelBeforeChat && ( |
| | | <div className='p-6 rounded-b-xl'> |
| | | <Form /> |
| | | <div className={`pl-[136px] flex items-center ${isMobile && '!pl-0'}`}> |
| | | <Button |
| | | variant='primary' |
| | | className='mr-2' |
| | | onClick={() => { |
| | | setCollapsed(true) |
| | | handleStartChat() |
| | | }} |
| | | > |
| | | {t('common.operation.save')} |
| | | </Button> |
| | | <Button |
| | | onClick={() => setCollapsed(true)} |
| | | > |
| | | {t('common.operation.cancel')} |
| | | </Button> |
| | | </div> |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | showConfigPanelBeforeChat && ( |
| | | <div className='p-6 rounded-b-xl'> |
| | | <Form /> |
| | | <Button |
| | | className={`${inputsForms.length && !isMobile && 'ml-[136px]'}`} |
| | | variant='primary' |
| | | size='large' |
| | | onClick={handleStartChat} |
| | | > |
| | | <MessageDotsCircle className='mr-2 w-4 h-4 text-white' /> |
| | | {t('share.chat.startChat')} |
| | | </Button> |
| | | </div> |
| | | ) |
| | | } |
| | | </div> |
| | | { |
| | | showConfigPanelBeforeChat && (site || customConfig) && ( |
| | | <div className='mt-4 flex flex-wrap justify-between items-center py-2 text-xs text-gray-400'> |
| | | {site?.privacy_policy |
| | | ? <div className={`flex items-center ${isMobile && 'w-full justify-end'}`}>{t('share.chat.privacyPolicyLeft')} |
| | | <a |
| | | className='text-gray-500 px-1' |
| | | href={site?.privacy_policy} |
| | | target='_blank' rel='noopener noreferrer'>{t('share.chat.privacyPolicyMiddle')}</a> |
| | | {t('share.chat.privacyPolicyRight')} |
| | | </div> |
| | | : <div> |
| | | </div>} |
| | | { |
| | | customConfig?.remove_webapp_brand |
| | | ? null |
| | | : ( |
| | | <div className={`flex items-center justify-end ${isMobile && 'w-full'}`}> |
| | | <div className='flex items-center pr-3 space-x-3'> |
| | | <span className='uppercase'>{t('share.chat.poweredBy')}</span> |
| | | { |
| | | customConfig?.replace_webapp_logo |
| | | ? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' /> |
| | | : <LogoSite className='!h-5' /> |
| | | } |
| | | </div> |
| | | </div> |
| | | ) |
| | | } |
| | | </div> |
| | | ) |
| | | } |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | export default ConfigPanel |
| | |
| | | AppMeta, |
| | | ConversationItem, |
| | | } from '@/models/share' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | export type ChatWithHistoryContextValue = { |
| | | appInfoError?: any |
| | |
| | | appPrevChatTree: ChatItemInTree[] |
| | | pinnedConversationList: AppConversationData['data'] |
| | | conversationList: AppConversationData['data'] |
| | | showConfigPanelBeforeChat: boolean |
| | | newConversationInputs: Record<string, any> |
| | | newConversationInputsRef: RefObject<Record<string, any>> |
| | | handleNewConversationInputsChange: (v: Record<string, any>) => void |
| | | inputsForms: any[] |
| | | handleNewConversation: () => void |
| | | handleStartChat: (callback?: any) => void |
| | | handleStartChat: () => void |
| | | handleChangeConversation: (conversationId: string) => void |
| | | handlePinConversation: (conversationId: string) => void |
| | | handleUnpinConversation: (conversationId: string) => void |
| | |
| | | handleFeedback: (messageId: string, feedback: Feedback) => void |
| | | currentChatInstanceRef: RefObject<{ handleStop: () => void }> |
| | | themeBuilder?: ThemeBuilder |
| | | sidebarCollapseState?: boolean |
| | | handleSidebarCollapse: (state: boolean) => void |
| | | clearChatList?: boolean |
| | | setClearChatList: (state: boolean) => void |
| | | isResponding?: boolean |
| | | setIsResponding: (state: boolean) => void, |
| | | currentConversationInputs: Record<string, any> | null, |
| | | setCurrentConversationInputs: (v: Record<string, any>) => void, |
| | | } |
| | | |
| | | export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({ |
| | |
| | | appPrevChatTree: [], |
| | | pinnedConversationList: [], |
| | | conversationList: [], |
| | | showConfigPanelBeforeChat: false, |
| | | newConversationInputs: {}, |
| | | newConversationInputsRef: { current: {} }, |
| | | handleNewConversationInputsChange: noop, |
| | | handleNewConversationInputsChange: () => {}, |
| | | inputsForms: [], |
| | | handleNewConversation: noop, |
| | | handleStartChat: noop, |
| | | handleChangeConversation: noop, |
| | | handlePinConversation: noop, |
| | | handleUnpinConversation: noop, |
| | | handleDeleteConversation: noop, |
| | | handleNewConversation: () => {}, |
| | | handleStartChat: () => {}, |
| | | handleChangeConversation: () => {}, |
| | | handlePinConversation: () => {}, |
| | | handleUnpinConversation: () => {}, |
| | | handleDeleteConversation: () => {}, |
| | | conversationRenaming: false, |
| | | handleRenameConversation: noop, |
| | | handleNewConversationCompleted: noop, |
| | | handleRenameConversation: () => {}, |
| | | handleNewConversationCompleted: () => {}, |
| | | chatShouldReloadKey: '', |
| | | isMobile: false, |
| | | isInstalledApp: false, |
| | | handleFeedback: noop, |
| | | currentChatInstanceRef: { current: { handleStop: noop } }, |
| | | sidebarCollapseState: false, |
| | | handleSidebarCollapse: noop, |
| | | clearChatList: false, |
| | | setClearChatList: noop, |
| | | isResponding: false, |
| | | setIsResponding: noop, |
| | | currentConversationInputs: {}, |
| | | setCurrentConversationInputs: noop, |
| | | handleFeedback: () => {}, |
| | | currentChatInstanceRef: { current: { handleStop: () => {} } }, |
| | | }) |
| | | export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext) |
| | |
| | | import { useCallback, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiMenuLine, |
| | | } from '@remixicon/react' |
| | | import { useState } from 'react' |
| | | import { useChatWithHistoryContext } from './context' |
| | | import Operation from './header/operation' |
| | | import Sidebar from './sidebar' |
| | | import MobileOperationDropdown from './header/mobile-operation-dropdown' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | import { Message3Fill } from '@/app/components/base/icons/src/public/other' |
| | | import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content' |
| | | import Confirm from '@/app/components/base/confirm' |
| | | import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal' |
| | | import type { ConversationItem } from '@/models/share' |
| | | import { |
| | | Edit05, |
| | | Menu01, |
| | | } from '@/app/components/base/icons/src/vender/line/general' |
| | | |
| | | const HeaderInMobile = () => { |
| | | const { |
| | | appData, |
| | | currentConversationId, |
| | | currentConversationItem, |
| | | pinnedConversationList, |
| | | handleNewConversation, |
| | | handlePinConversation, |
| | | handleUnpinConversation, |
| | | handleDeleteConversation, |
| | | handleRenameConversation, |
| | | conversationRenaming, |
| | | inputsForms, |
| | | } = useChatWithHistoryContext() |
| | | const { t } = useTranslation() |
| | | const isPin = pinnedConversationList.some(item => item.id === currentConversationId) |
| | | const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null) |
| | | const [showRename, setShowRename] = useState<ConversationItem | null>(null) |
| | | const handleOperate = useCallback((type: string) => { |
| | | if (type === 'pin') |
| | | handlePinConversation(currentConversationId) |
| | | |
| | | if (type === 'unpin') |
| | | handleUnpinConversation(currentConversationId) |
| | | |
| | | if (type === 'delete') |
| | | setShowConfirm(currentConversationItem as any) |
| | | |
| | | if (type === 'rename') |
| | | setShowRename(currentConversationItem as any) |
| | | }, [currentConversationId, currentConversationItem, handlePinConversation, handleUnpinConversation]) |
| | | const handleCancelConfirm = useCallback(() => { |
| | | setShowConfirm(null) |
| | | }, []) |
| | | const handleDelete = useCallback(() => { |
| | | if (showConfirm) |
| | | handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm }) |
| | | }, [showConfirm, handleDeleteConversation, handleCancelConfirm]) |
| | | const handleCancelRename = useCallback(() => { |
| | | setShowRename(null) |
| | | }, []) |
| | | const handleRename = useCallback((newName: string) => { |
| | | if (showRename) |
| | | handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename }) |
| | | }, [showRename, handleRenameConversation, handleCancelRename]) |
| | | const [showSidebar, setShowSidebar] = useState(false) |
| | | const [showChatSettings, setShowChatSettings] = useState(false) |
| | | |
| | | return ( |
| | | <> |
| | | <div className='flex shrink-0 items-center gap-1 bg-mask-top2bottom-gray-50-to-transparent px-2 py-3'> |
| | | <ActionButton size='l' className='shrink-0' onClick={() => setShowSidebar(true)}> |
| | | <RiMenuLine className='h-[18px] w-[18px]' /> |
| | | </ActionButton> |
| | | <div className='flex grow items-center justify-center'> |
| | | {!currentConversationId && ( |
| | | <> |
| | | <div className='shrink-0 flex items-center px-3 h-[44px] border-b-[0.5px] border-b-gray-200'> |
| | | <div |
| | | className='shrink-0 flex items-center justify-center w-8 h-8 rounded-lg' |
| | | onClick={() => setShowSidebar(true)} |
| | | > |
| | | <Menu01 className='w-4 h-4 text-gray-700' /> |
| | | </div> |
| | | <div className='grow flex justify-center items-center px-3'> |
| | | <AppIcon |
| | | className='mr-2' |
| | | size='tiny' |
| | |
| | | imageUrl={appData?.site.icon_url} |
| | | background={appData?.site.icon_background} |
| | | /> |
| | | <div className='system-md-semibold truncate text-text-secondary'> |
| | | <div className='py-1 text-base font-semibold text-gray-800 truncate'> |
| | | {appData?.site.title} |
| | | </div> |
| | | </> |
| | | )} |
| | | {currentConversationId && ( |
| | | <Operation |
| | | title={currentConversationItem?.name || ''} |
| | | isPinned={!!isPin} |
| | | togglePin={() => handleOperate(isPin ? 'unpin' : 'pin')} |
| | | isShowDelete |
| | | isShowRenameConversation |
| | | onRenameConversation={() => handleOperate('rename')} |
| | | onDelete={() => handleOperate('delete')} |
| | | /> |
| | | )} |
| | | </div> |
| | | <MobileOperationDropdown |
| | | handleResetChat={handleNewConversation} |
| | | handleViewChatSettings={() => setShowChatSettings(true)} |
| | | hideViewChatSettings={inputsForms.length < 1} |
| | | /> |
| | | <div |
| | | className='shrink-0 flex items-center justify-center w-8 h-8 rounded-lg' |
| | | onClick={handleNewConversation} |
| | | > |
| | | <Edit05 className='w-4 h-4 text-gray-700' /> |
| | | </div> |
| | | {showSidebar && ( |
| | | <div className='fixed inset-0 z-50 flex bg-background-overlay p-1' |
| | | </div> |
| | | { |
| | | showSidebar && ( |
| | | <div className='fixed inset-0 z-50' |
| | | style={{ backgroundColor: 'rgba(35, 56, 118, 0.2)' }} |
| | | onClick={() => setShowSidebar(false)} |
| | | > |
| | | <div className='flex h-full w-[calc(100vw_-_40px)] rounded-xl bg-components-panel-bg shadow-lg backdrop-blur-sm' onClick={e => e.stopPropagation()}> |
| | | <div className='inline-block h-full bg-white' onClick={e => e.stopPropagation()}> |
| | | <Sidebar /> |
| | | </div> |
| | | </div> |
| | | )} |
| | | {showChatSettings && ( |
| | | <div className='fixed inset-0 z-50 flex justify-end bg-background-overlay p-1' |
| | | onClick={() => setShowChatSettings(false)} |
| | | > |
| | | <div className='flex h-full w-[calc(100vw_-_40px)] flex-col rounded-xl bg-components-panel-bg shadow-lg backdrop-blur-sm' onClick={e => e.stopPropagation()}> |
| | | <div className='flex items-center gap-3 rounded-t-2xl border-b border-divider-subtle px-4 py-3'> |
| | | <Message3Fill className='h-6 w-6 shrink-0' /> |
| | | <div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div> |
| | | </div> |
| | | <div className='p-4'> |
| | | <InputsFormContent /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | )} |
| | | {!!showConfirm && ( |
| | | <Confirm |
| | | title={t('share.chat.deleteConversation.title')} |
| | | content={t('share.chat.deleteConversation.content') || ''} |
| | | isShow |
| | | onCancel={handleCancelConfirm} |
| | | onConfirm={handleDelete} |
| | | /> |
| | | )} |
| | | {showRename && ( |
| | | <RenameModal |
| | | isShow |
| | | onClose={handleCancelRename} |
| | | saveLoading={conversationRenaming} |
| | | name={showRename?.name || ''} |
| | | onSave={handleRename} |
| | | /> |
| | | )} |
| | | ) |
| | | } |
| | | </> |
| | | ) |
| | | } |
New file |
| | |
| | | import type { FC } from 'react' |
| | | import { memo } from 'react' |
| | | |
| | | type HeaderProps = { |
| | | title: string |
| | | isMobile: boolean |
| | | } |
| | | const Header: FC<HeaderProps> = ({ |
| | | title, |
| | | isMobile, |
| | | }) => { |
| | | return ( |
| | | <div |
| | | className={` |
| | | sticky top-0 flex items-center px-8 h-16 bg-white/80 text-base font-medium |
| | | text-gray-900 border-b-[0.5px] border-b-gray-100 backdrop-blur-md z-10 |
| | | ${isMobile && '!h-12'} |
| | | `} |
| | | > |
| | | {title} |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | export default memo(Header) |
| | |
| | | Feedback, |
| | | } from '../types' |
| | | import { CONVERSATION_ID_INFO } from '../constants' |
| | | import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams } from '../utils' |
| | | import { buildChatItemTree } from '../utils' |
| | | import { addFileInfos, sortAgentSorts } from '../../../tools/utils' |
| | | import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' |
| | | import { |
| | |
| | | import { useAppFavicon } from '@/hooks/use-app-favicon' |
| | | import { InputVarType } from '@/app/components/workflow/types' |
| | | import { TransferMethod } from '@/types/app' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | function getFormattedChatList(messages: any[]) { |
| | | const newChatList: ChatItem[] = [] |
| | |
| | | id: `question-${item.id}`, |
| | | content: item.query, |
| | | isAnswer: false, |
| | | message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id, upload_file_id: item.upload_file_id }))), |
| | | message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))), |
| | | parentMessageId: item.parent_message_id || undefined, |
| | | }) |
| | | const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] |
| | |
| | | feedback: item.feedback, |
| | | isAnswer: true, |
| | | citation: item.retriever_resources, |
| | | message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id, upload_file_id: item.upload_file_id }))), |
| | | message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))), |
| | | parentMessageId: `question-${item.id}`, |
| | | }) |
| | | }) |
| | |
| | | }, [isInstalledApp, installedAppInfo, appInfo]) |
| | | const appId = useMemo(() => appData?.app_id, [appData]) |
| | | |
| | | const [userId, setUserId] = useState<string>() |
| | | useEffect(() => { |
| | | getProcessedSystemVariablesFromUrlParams().then(({ user_id }) => { |
| | | setUserId(user_id) |
| | | }) |
| | | }, []) |
| | | |
| | | useEffect(() => { |
| | | if (appData?.site.default_language) |
| | | changeLanguage(appData.site.default_language) |
| | | }, [appData]) |
| | | |
| | | const [sidebarCollapseState, setSidebarCollapseState] = useState<boolean>(false) |
| | | const handleSidebarCollapse = useCallback((state: boolean) => { |
| | | if (appId) { |
| | | setSidebarCollapseState(state) |
| | | localStorage.setItem('webappSidebarCollapse', state ? 'collapsed' : 'expanded') |
| | | } |
| | | }, [appId, setSidebarCollapseState]) |
| | | useEffect(() => { |
| | | if (appId) { |
| | | const localState = localStorage.getItem('webappSidebarCollapse') |
| | | setSidebarCollapseState(localState === 'collapsed') |
| | | } |
| | | }, [appId]) |
| | | const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, { |
| | | const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, string>>(CONVERSATION_ID_INFO, { |
| | | defaultValue: {}, |
| | | }) |
| | | const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '', [appId, conversationIdInfo, userId]) |
| | | const currentConversationId = useMemo(() => conversationIdInfo?.[appId || ''] || '', [appId, conversationIdInfo]) |
| | | const handleConversationIdInfoChange = useCallback((changeConversationId: string) => { |
| | | if (appId) { |
| | | let prevValue = conversationIdInfo?.[appId || ''] |
| | | if (typeof prevValue === 'string') |
| | | prevValue = {} |
| | | setConversationIdInfo({ |
| | | ...conversationIdInfo, |
| | | [appId || '']: { |
| | | ...prevValue, |
| | | [userId || 'DEFAULT']: changeConversationId, |
| | | }, |
| | | [appId || '']: changeConversationId, |
| | | }) |
| | | } |
| | | }, [appId, conversationIdInfo, setConversationIdInfo, userId]) |
| | | }, [appId, conversationIdInfo, setConversationIdInfo]) |
| | | const [showConfigPanelBeforeChat, setShowConfigPanelBeforeChat] = useState(true) |
| | | |
| | | const [newConversationId, setNewConversationId] = useState('') |
| | | const chatShouldReloadKey = useMemo(() => { |
| | |
| | | const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100)) |
| | | const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId)) |
| | | |
| | | const [clearChatList, setClearChatList] = useState(false) |
| | | const [isResponding, setIsResponding] = useState(false) |
| | | const appPrevChatTree = useMemo( |
| | | () => (currentConversationId && appChatListData?.data.length) |
| | | ? buildChatItemTree(getFormattedChatList(appChatListData.data)) |
| | |
| | | return conversationItem |
| | | }, [conversationList, currentConversationId, pinnedConversationList]) |
| | | |
| | | const currentConversationLatestInputs = useMemo(() => { |
| | | if (!currentConversationId || !appChatListData?.data.length) |
| | | return newConversationInputsRef.current || {} |
| | | return appChatListData.data.slice().pop().inputs || {} |
| | | }, [appChatListData, currentConversationId]) |
| | | const [currentConversationInputs, setCurrentConversationInputs] = useState<Record<string, any>>(currentConversationLatestInputs || {}) |
| | | useEffect(() => { |
| | | if (currentConversationItem) |
| | | setCurrentConversationInputs(currentConversationLatestInputs || {}) |
| | | }, [currentConversationItem, currentConversationLatestInputs]) |
| | | |
| | | const { notify } = useToastContext() |
| | | const checkInputsRequired = useCallback((silent?: boolean) => { |
| | | let hasEmptyInput = '' |
| | |
| | | |
| | | return true |
| | | }, [inputsForms, notify, t]) |
| | | const handleStartChat = useCallback((callback: any) => { |
| | | const handleStartChat = useCallback(() => { |
| | | if (checkInputsRequired()) { |
| | | setShowConfigPanelBeforeChat(false) |
| | | setShowNewConversationItemInList(true) |
| | | callback?.() |
| | | } |
| | | }, [setShowNewConversationItemInList, checkInputsRequired]) |
| | | const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop }) |
| | | }, [setShowConfigPanelBeforeChat, setShowNewConversationItemInList, checkInputsRequired]) |
| | | const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: () => { } }) |
| | | const handleChangeConversation = useCallback((conversationId: string) => { |
| | | currentChatInstanceRef.current.handleStop() |
| | | setNewConversationId('') |
| | | handleConversationIdInfoChange(conversationId) |
| | | if (conversationId) |
| | | setClearChatList(false) |
| | | }, [handleConversationIdInfoChange, setClearChatList]) |
| | | |
| | | if (conversationId === '' && !checkInputsRequired(true)) |
| | | setShowConfigPanelBeforeChat(true) |
| | | else |
| | | setShowConfigPanelBeforeChat(false) |
| | | }, [handleConversationIdInfoChange, setShowConfigPanelBeforeChat, checkInputsRequired]) |
| | | const handleNewConversation = useCallback(() => { |
| | | currentChatInstanceRef.current.handleStop() |
| | | setShowNewConversationItemInList(true) |
| | | setNewConversationId('') |
| | | |
| | | if (showNewConversationItemInList) { |
| | | handleChangeConversation('') |
| | | } |
| | | else if (currentConversationId) { |
| | | handleConversationIdInfoChange('') |
| | | setShowConfigPanelBeforeChat(true) |
| | | setShowNewConversationItemInList(true) |
| | | handleNewConversationInputsChange({}) |
| | | setClearChatList(true) |
| | | }, [handleChangeConversation, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList]) |
| | | } |
| | | }, [handleChangeConversation, currentConversationId, handleConversationIdInfoChange, setShowConfigPanelBeforeChat, setShowNewConversationItemInList, showNewConversationItemInList, handleNewConversationInputsChange]) |
| | | const handleUpdateConversationList = useCallback(() => { |
| | | mutateAppConversationData() |
| | | mutateAppPinnedConversationData() |
| | |
| | | appPrevChatTree, |
| | | pinnedConversationList, |
| | | conversationList, |
| | | showConfigPanelBeforeChat, |
| | | setShowConfigPanelBeforeChat, |
| | | setShowNewConversationItemInList, |
| | | newConversationInputs, |
| | | newConversationInputsRef, |
| | |
| | | chatShouldReloadKey, |
| | | handleFeedback, |
| | | currentChatInstanceRef, |
| | | sidebarCollapseState, |
| | | handleSidebarCollapse, |
| | | clearChatList, |
| | | setClearChatList, |
| | | isResponding, |
| | | setIsResponding, |
| | | currentConversationInputs, |
| | | setCurrentConversationInputs, |
| | | } |
| | | } |
| | |
| | | } from './context' |
| | | import { useChatWithHistory } from './hooks' |
| | | import Sidebar from './sidebar' |
| | | import Header from './header' |
| | | import HeaderInMobile from './header-in-mobile' |
| | | import ConfigPanel from './config-panel' |
| | | import ChatWrapper from './chat-wrapper' |
| | | import type { InstalledApp } from '@/models/explore' |
| | | import Loading from '@/app/components/base/loading' |
| | | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' |
| | | import { checkOrSetAccessToken } from '@/app/components/share/utils' |
| | | import AppUnavailable from '@/app/components/base/app-unavailable' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type ChatWithHistoryProps = { |
| | | className?: string |
| | |
| | | appInfoError, |
| | | appData, |
| | | appInfoLoading, |
| | | appPrevChatTree, |
| | | showConfigPanelBeforeChat, |
| | | appChatListDataLoading, |
| | | chatShouldReloadKey, |
| | | isMobile, |
| | | themeBuilder, |
| | | sidebarCollapseState, |
| | | } = useChatWithHistoryContext() |
| | | const isSidebarCollapsed = sidebarCollapseState |
| | | |
| | | const chatReady = (!showConfigPanelBeforeChat || !!appPrevChatTree.length) |
| | | const customConfig = appData?.custom_config |
| | | const site = appData?.site |
| | | |
| | | const [showSidePanel, setShowSidePanel] = useState(false) |
| | | |
| | | useEffect(() => { |
| | | themeBuilder?.buildTheme(site?.chat_color_theme, site?.chat_color_theme_inverted) |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className={cn( |
| | | 'flex h-full bg-background-default-burn', |
| | | isMobile && 'flex-col', |
| | | className, |
| | | )}> |
| | | {!isMobile && ( |
| | | <div className={cn( |
| | | 'flex w-[236px] flex-col p-1 pr-0 transition-all duration-200 ease-in-out', |
| | | isSidebarCollapsed && 'w-0 overflow-hidden !p-0', |
| | | )}> |
| | | <div className={`h-full flex bg-white ${className} ${isMobile && 'flex-col'}`}> |
| | | { |
| | | !isMobile && ( |
| | | <Sidebar /> |
| | | </div> |
| | | )} |
| | | {isMobile && ( |
| | | ) |
| | | } |
| | | { |
| | | isMobile && ( |
| | | <HeaderInMobile /> |
| | | )} |
| | | <div className={cn('relative grow p-2', isMobile && 'h-[calc(100%_-_56px)] p-0')}> |
| | | {isSidebarCollapsed && ( |
| | | <div |
| | | className={cn( |
| | | 'absolute top-0 z-20 flex h-full w-[256px] flex-col p-2 transition-all duration-500 ease-in-out', |
| | | showSidePanel ? 'left-0' : 'left-[-248px]', |
| | | )} |
| | | onMouseEnter={() => setShowSidePanel(true)} |
| | | onMouseLeave={() => setShowSidePanel(false)} |
| | | > |
| | | <Sidebar isPanel /> |
| | | ) |
| | | } |
| | | <div className={`grow overflow-hidden ${showConfigPanelBeforeChat && !appPrevChatTree.length && 'flex items-center justify-center'}`}> |
| | | { |
| | | showConfigPanelBeforeChat && !appChatListDataLoading && !appPrevChatTree.length && ( |
| | | <div className={`flex w-full items-center justify-center h-full ${isMobile && 'px-4'}`}> |
| | | <ConfigPanel /> |
| | | </div> |
| | | )} |
| | | <div className={cn('flex h-full flex-col overflow-hidden border-[0,5px] border-components-panel-border-subtle bg-chatbot-bg', isMobile ? 'rounded-t-2xl' : 'rounded-2xl')}> |
| | | {!isMobile && <Header />} |
| | | {appChatListDataLoading && ( |
| | | ) |
| | | } |
| | | { |
| | | appChatListDataLoading && chatReady && ( |
| | | <Loading type='app' /> |
| | | )} |
| | | {!appChatListDataLoading && ( |
| | | ) |
| | | } |
| | | { |
| | | chatReady && !appChatListDataLoading && ( |
| | | <ChatWrapper key={chatShouldReloadKey} /> |
| | | )} |
| | | </div> |
| | | ) |
| | | } |
| | | </div> |
| | | </div> |
| | | ) |
| | |
| | | appPrevChatTree, |
| | | pinnedConversationList, |
| | | conversationList, |
| | | showConfigPanelBeforeChat, |
| | | newConversationInputs, |
| | | newConversationInputsRef, |
| | | handleNewConversationInputsChange, |
| | |
| | | appId, |
| | | handleFeedback, |
| | | currentChatInstanceRef, |
| | | sidebarCollapseState, |
| | | handleSidebarCollapse, |
| | | clearChatList, |
| | | setClearChatList, |
| | | isResponding, |
| | | setIsResponding, |
| | | currentConversationInputs, |
| | | setCurrentConversationInputs, |
| | | } = useChatWithHistory(installedAppInfo) |
| | | |
| | | return ( |
| | |
| | | appPrevChatTree, |
| | | pinnedConversationList, |
| | | conversationList, |
| | | showConfigPanelBeforeChat, |
| | | newConversationInputs, |
| | | newConversationInputsRef, |
| | | handleNewConversationInputsChange, |
| | |
| | | handleFeedback, |
| | | currentChatInstanceRef, |
| | | themeBuilder, |
| | | sidebarCollapseState, |
| | | handleSidebarCollapse, |
| | | clearChatList, |
| | | setClearChatList, |
| | | isResponding, |
| | | setIsResponding, |
| | | currentConversationInputs, |
| | | setCurrentConversationInputs, |
| | | }}> |
| | | <ChatWithHistory className={className} /> |
| | | </ChatWithHistoryContext.Provider> |
| | |
| | | useState, |
| | | } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiEditBoxLine, |
| | | RiExpandRightLine, |
| | | RiLayoutLeft2Line, |
| | | } from '@remixicon/react' |
| | | import { useChatWithHistoryContext } from '../context' |
| | | import List from './list' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | import Button from '@/app/components/base/button' |
| | | import List from '@/app/components/base/chat/chat-with-history/sidebar/list' |
| | | import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown' |
| | | import { Edit05 } from '@/app/components/base/icons/src/vender/line/general' |
| | | import type { ConversationItem } from '@/models/share' |
| | | import Confirm from '@/app/components/base/confirm' |
| | | import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal' |
| | | import DifyLogo from '@/app/components/base/logo/dify-logo' |
| | | import type { ConversationItem } from '@/models/share' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | type Props = { |
| | | isPanel?: boolean |
| | | } |
| | | |
| | | const Sidebar = ({ isPanel }: Props) => { |
| | | const Sidebar = () => { |
| | | const { t } = useTranslation() |
| | | const { |
| | | appData, |
| | | handleNewConversation, |
| | | pinnedConversationList, |
| | | conversationList, |
| | | handleNewConversation, |
| | | currentConversationId, |
| | | handleChangeConversation, |
| | | handlePinConversation, |
| | |
| | | conversationRenaming, |
| | | handleRenameConversation, |
| | | handleDeleteConversation, |
| | | sidebarCollapseState, |
| | | handleSidebarCollapse, |
| | | isMobile, |
| | | isResponding, |
| | | } = useChatWithHistoryContext() |
| | | const isSidebarCollapsed = sidebarCollapseState |
| | | |
| | | const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null) |
| | | const [showRename, setShowRename] = useState<ConversationItem | null>(null) |
| | | |
| | |
| | | }, [showRename, handleRenameConversation, handleCancelRename]) |
| | | |
| | | return ( |
| | | <div className={cn( |
| | | 'flex w-full grow flex-col', |
| | | isPanel && 'rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-bg shadow-lg', |
| | | )}> |
| | | <div className={cn( |
| | | 'flex shrink-0 items-center gap-3 p-3 pr-2', |
| | | )}> |
| | | <div className='shrink-0'> |
| | | <div className='shrink-0 h-full flex flex-col w-[240px] border-r border-r-gray-100'> |
| | | { |
| | | !isMobile && ( |
| | | <div className='shrink-0 flex p-4'> |
| | | <AppIcon |
| | | size='large' |
| | | className='mr-3' |
| | | size='small' |
| | | iconType={appData?.site.icon_type} |
| | | icon={appData?.site.icon} |
| | | background={appData?.site.icon_background} |
| | | imageUrl={appData?.site.icon_url} |
| | | /> |
| | | <div className='py-1 text-base font-semibold text-gray-800'> |
| | | {appData?.site.title} |
| | | </div> |
| | | <div className={cn('system-md-semibold grow truncate text-text-secondary')}>{appData?.site.title}</div> |
| | | {!isMobile && isSidebarCollapsed && ( |
| | | <ActionButton size='l' onClick={() => handleSidebarCollapse(false)}> |
| | | <RiExpandRightLine className='h-[18px] w-[18px]' /> |
| | | </ActionButton> |
| | | )} |
| | | {!isMobile && !isSidebarCollapsed && ( |
| | | <ActionButton size='l' onClick={() => handleSidebarCollapse(true)}> |
| | | <RiLayoutLeft2Line className='h-[18px] w-[18px]' /> |
| | | </ActionButton> |
| | | )} |
| | | </div> |
| | | <div className='shrink-0 px-3 py-4'> |
| | | <Button variant='secondary-accent' disabled={isResponding} className='w-full justify-center' onClick={handleNewConversation}> |
| | | <RiEditBoxLine className='mr-1 h-4 w-4' /> |
| | | ) |
| | | } |
| | | <div className='shrink-0 p-4'> |
| | | <Button |
| | | variant='secondary-accent' |
| | | className='justify-start w-full' |
| | | onClick={handleNewConversation} |
| | | > |
| | | <Edit05 className='mr-2 w-4 h-4' /> |
| | | {t('share.chat.newChat')} |
| | | </Button> |
| | | </div> |
| | | <div className='h-0 grow space-y-2 overflow-y-auto px-3 pt-4'> |
| | | {/* pinned list */} |
| | | {!!pinnedConversationList.length && ( |
| | | <div className='grow px-4 py-2 overflow-y-auto'> |
| | | { |
| | | !!pinnedConversationList.length && ( |
| | | <div className='mb-4'> |
| | | <List |
| | | isPin |
| | |
| | | currentConversationId={currentConversationId} |
| | | /> |
| | | </div> |
| | | )} |
| | | {!!conversationList.length && ( |
| | | ) |
| | | } |
| | | { |
| | | !!conversationList.length && ( |
| | | <List |
| | | title={(pinnedConversationList.length && t('share.chat.unpinnedTitle')) || ''} |
| | | list={conversationList} |
| | |
| | | onOperate={handleOperate} |
| | | currentConversationId={currentConversationId} |
| | | /> |
| | | )} |
| | | ) |
| | | } |
| | | </div> |
| | | <div className='flex shrink-0 items-center justify-between p-3'> |
| | | <MenuDropdown placement='top-start' data={appData?.site} /> |
| | | {/* powered by */} |
| | | <div className='shrink-0'> |
| | | {!appData?.custom_config?.remove_webapp_brand && ( |
| | | <div className={cn( |
| | | 'flex shrink-0 items-center gap-1.5 px-1', |
| | | )}> |
| | | <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> |
| | | {appData?.custom_config?.replace_webapp_logo && ( |
| | | <img src={appData?.custom_config?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' /> |
| | | )} |
| | | {!appData?.custom_config?.replace_webapp_logo && ( |
| | | <DifyLogo size='small' /> |
| | | )} |
| | | {appData?.site.copyright && ( |
| | | <div className='px-4 pb-4 text-xs text-gray-400'> |
| | | © {(new Date()).getFullYear()} {appData?.site.copyright} |
| | | </div> |
| | | )} |
| | | </div> |
| | | </div> |
| | | {!!showConfirm && ( |
| | | <Confirm |
| | | title={t('share.chat.deleteConversation.title')} |
| | |
| | | } from 'react' |
| | | import { useHover } from 'ahooks' |
| | | import type { ConversationItem } from '@/models/share' |
| | | import Operation from '@/app/components/base/chat/chat-with-history/sidebar/operation' |
| | | import cn from '@/utils/classnames' |
| | | import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication' |
| | | import ItemOperation from '@/app/components/explore/item-operation' |
| | | |
| | | type ItemProps = { |
| | | isPin?: boolean |
| | |
| | | }) => { |
| | | const ref = useRef(null) |
| | | const isHovering = useHover(ref) |
| | | const isSelected = currentConversationId === item.id |
| | | |
| | | return ( |
| | | <div |
| | | ref={ref} |
| | | key={item.id} |
| | | className={cn( |
| | | 'system-sm-medium group flex cursor-pointer rounded-lg p-1 pl-3 text-components-menu-item-text hover:bg-state-base-hover', |
| | | isSelected && 'bg-state-accent-active text-text-accent hover:bg-state-accent-active', |
| | | )} |
| | | className={` |
| | | flex mb-0.5 last-of-type:mb-0 py-1.5 pl-3 pr-1.5 text-sm font-medium text-gray-700 |
| | | rounded-lg cursor-pointer hover:bg-gray-50 group |
| | | ${currentConversationId === item.id && 'text-primary-600 bg-primary-50'} |
| | | `} |
| | | onClick={() => onChangeConversation(item.id)} |
| | | > |
| | | <div className='grow truncate p-1 pl-0' title={item.name}>{item.name}</div> |
| | | <MessageDotsCircle className={`shrink-0 mt-1 mr-2 w-4 h-4 text-gray-400 ${currentConversationId === item.id && 'text-primary-600'}`} /> |
| | | <div className='grow py-0.5 break-all' title={item.name}>{item.name}</div> |
| | | {item.id !== '' && ( |
| | | <div className='shrink-0' onClick={e => e.stopPropagation()}> |
| | | <Operation |
| | | isActive={isSelected} |
| | | <div className='shrink-0 h-6' onClick={e => e.stopPropagation()}> |
| | | <ItemOperation |
| | | isPinned={!!isPin} |
| | | isItemHovering={isHovering} |
| | | togglePin={() => onOperate(isPin ? 'unpin' : 'pin', item)} |
| | |
| | | currentConversationId, |
| | | }) => { |
| | | return ( |
| | | <div className='space-y-0.5'> |
| | | {title && ( |
| | | <div className='system-xs-medium-uppercase px-3 pb-1 pt-2 text-text-tertiary'>{title}</div> |
| | | )} |
| | | {list.map(item => ( |
| | | <div> |
| | | { |
| | | title && ( |
| | | <div className='mb-0.5 px-3 h-[26px] text-xs font-medium text-gray-500'> |
| | | {title} |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | list.map(item => ( |
| | | <Item |
| | | key={item.id} |
| | | isPin={isPin} |
| | |
| | | onChangeConversation={onChangeConversation} |
| | | currentConversationId={currentConversationId} |
| | | /> |
| | | ))} |
| | | )) |
| | | } |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | import { useTranslation } from 'react-i18next' |
| | | import Modal from '@/app/components/base/modal' |
| | | import Button from '@/app/components/base/button' |
| | | import Input from '@/app/components/base/input' |
| | | |
| | | export type IRenameModalProps = { |
| | | isShow: boolean |
| | |
| | | isShow={isShow} |
| | | onClose={onClose} |
| | | > |
| | | <div className={'mt-6 text-sm font-medium leading-[21px] text-text-primary'}>{t('common.chat.conversationName')}</div> |
| | | <Input className='mt-2 h-10 w-full' |
| | | <div className={'mt-6 font-medium text-sm leading-[21px] text-gray-900'}>{t('common.chat.conversationName')}</div> |
| | | <input className={'mt-2 w-full rounded-lg h-10 box-border px-3 text-sm leading-10 bg-gray-100'} |
| | | value={tempName} |
| | | onChange={e => setTempName(e.target.value)} |
| | | placeholder={t('common.chat.conversationNamePlaceholder') || ''} |
| | | /> |
| | | |
| | | <div className='mt-10 flex justify-end'> |
| | | <Button className='mr-2 shrink-0' onClick={onClose}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' className='shrink-0' onClick={() => onSave(tempName)} loading={saveLoading}>{t('common.operation.save')}</Button> |
| | | <Button className='mr-2 flex-shrink-0' onClick={onClose}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' className='flex-shrink-0' onClick={() => onSave(tempName)} loading={saveLoading}>{t('common.operation.save')}</Button> |
| | | </div> |
| | | </Modal> |
| | | ) |
| | |
| | | parent_parallel_id: null, |
| | | parent_parallel_start_node_id: null, |
| | | iteration_id: null, |
| | | loop_id: null, |
| | | }, |
| | | { |
| | | extras: {}, |
| | |
| | | parent_parallel_id: null, |
| | | parent_parallel_start_node_id: null, |
| | | iteration_id: null, |
| | | loop_id: null, |
| | | }, |
| | | { |
| | | extras: {}, |
| | |
| | | type AgentContentProps = { |
| | | item: ChatItem |
| | | responding?: boolean |
| | | content?: string |
| | | } |
| | | const AgentContent: FC<AgentContentProps> = ({ |
| | | item, |
| | | responding, |
| | | content, |
| | | }) => { |
| | | const { |
| | | annotation, |
| | |
| | | |
| | | return ( |
| | | <div> |
| | | {content ? <Markdown content={content} /> : agent_thoughts?.map((thought, index) => ( |
| | | {agent_thoughts?.map((thought, index) => ( |
| | | <div key={index} className='px-2 py-1'> |
| | | {thought.thought && ( |
| | | <Markdown content={thought.thought} /> |
| | |
| | | FC, |
| | | ReactNode, |
| | | } from 'react' |
| | | import { memo, useCallback, useEffect, useRef, useState } from 'react' |
| | | import { memo, useEffect, useRef, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import type { |
| | | ChatConfig, |
| | |
| | | import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item' |
| | | import type { AppData } from '@/models/share' |
| | | import AnswerIcon from '@/app/components/base/answer-icon' |
| | | import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' |
| | | import cn from '@/utils/classnames' |
| | | import { FileList } from '@/app/components/base/file-uploader' |
| | | import ContentSwitch from '../content-switch' |
| | | |
| | | type AnswerProps = { |
| | | item: ChatItem |
| | |
| | | } |
| | | }, []) |
| | | |
| | | const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => { |
| | | if (direction === 'prev') |
| | | item.prevSibling && switchSibling?.(item.prevSibling) |
| | | else |
| | | item.nextSibling && switchSibling?.(item.nextSibling) |
| | | }, [switchSibling, item.prevSibling, item.nextSibling]) |
| | | |
| | | return ( |
| | | <div className='mb-2 flex last:mb-0'> |
| | | <div className='relative h-10 w-10 shrink-0'> |
| | | <div className='flex mb-2 last:mb-0'> |
| | | <div className='shrink-0 relative w-10 h-10'> |
| | | {answerIcon || <AnswerIcon />} |
| | | {responding && ( |
| | | <div className='absolute left-[-3px] top-[-3px] flex h-4 w-4 items-center rounded-full border-[0.5px] border-divider-subtle bg-background-section-burn pl-[6px] shadow-xs'> |
| | | <div className='absolute -top-[3px] -left-[3px] pl-[6px] flex items-center w-4 h-4 bg-white rounded-full shadow-xs border-[0.5px] border-gray-50'> |
| | | <LoadingAnim type='avatar' /> |
| | | </div> |
| | | )} |
| | | </div> |
| | | <div className='chat-answer-container group ml-4 w-0 grow pb-4' ref={containerRef}> |
| | | <div className='chat-answer-container group grow w-0 ml-4' ref={containerRef}> |
| | | <div className={cn('group relative pr-10', chatAnswerContainerInner)}> |
| | | <div |
| | | ref={contentRef} |
| | | className={cn('body-lg-regular relative inline-block max-w-full rounded-2xl bg-chat-bubble-bg px-4 py-3 text-text-primary', workflowProcess && 'w-full')} |
| | | className={cn('relative inline-block px-4 py-3 max-w-full bg-chat-bubble-bg rounded-2xl body-lg-regular text-text-primary', workflowProcess && 'w-full')} |
| | | > |
| | | { |
| | | !responding && ( |
| | |
| | | } |
| | | { |
| | | responding && !content && !hasAgentThoughts && ( |
| | | <div className='flex h-5 w-6 items-center justify-center'> |
| | | <div className='flex items-center justify-center w-6 h-5'> |
| | | <LoadingAnim type='text' /> |
| | | </div> |
| | | ) |
| | |
| | | ) |
| | | } |
| | | { |
| | | (hasAgentThoughts) && ( |
| | | hasAgentThoughts && ( |
| | | <AgentContent |
| | | item={item} |
| | | responding={responding} |
| | | content={content} |
| | | /> |
| | | ) |
| | | } |
| | |
| | | <Citation data={citation} showHitInfo={config?.supportCitationHitInfo} /> |
| | | ) |
| | | } |
| | | { |
| | | item.siblingCount && item.siblingCount > 1 && item.siblingIndex !== undefined && ( |
| | | <ContentSwitch |
| | | count={item.siblingCount} |
| | | currentIndex={item.siblingIndex} |
| | | prevDisabled={!item.prevSibling} |
| | | nextDisabled={!item.nextSibling} |
| | | switchSibling={handleSwitchSibling} |
| | | /> |
| | | ) |
| | | } |
| | | {item.siblingCount && item.siblingCount > 1 && item.siblingIndex !== undefined && <div className="pt-3.5 flex justify-center items-center text-sm"> |
| | | <button |
| | | className={`${item.prevSibling ? 'opacity-100' : 'opacity-30'}`} |
| | | disabled={!item.prevSibling} |
| | | onClick={() => item.prevSibling && switchSibling?.(item.prevSibling)} |
| | | > |
| | | <ChevronRight className="w-[14px] h-[14px] rotate-180 text-text-primary" /> |
| | | </button> |
| | | <span className="px-2 text-xs text-text-primary">{item.siblingIndex + 1} / {item.siblingCount}</span> |
| | | <button |
| | | className={`${item.nextSibling ? 'opacity-100' : 'opacity-30'}`} |
| | | disabled={!item.nextSibling} |
| | | onClick={() => item.nextSibling && switchSibling?.(item.nextSibling)} |
| | | > |
| | | <ChevronRight className="w-[14px] h-[14px] text-text-primary" /> |
| | | </button> |
| | | </div>} |
| | | </div> |
| | | </div> |
| | | <More more={more} /> |
| | |
| | | ) |
| | | } |
| | | |
| | | export default memo(Answer, (prevProps, nextProps) => |
| | | prevProps.responding === false && nextProps.responding === false, |
| | | ) |
| | | export default memo(Answer) |
| | |
| | | const { t } = useTranslation() |
| | | |
| | | return ( |
| | | <div className='system-xs-regular mt-1 flex items-center text-text-quaternary opacity-0 group-hover:opacity-100'> |
| | | <div className='flex items-center mt-1 h-[18px] text-xs text-gray-400 opacity-0 group-hover:opacity-100'> |
| | | { |
| | | more && ( |
| | | <> |
| | | <div |
| | | className='mr-2 max-w-[33.3%] shrink-0 truncate' |
| | | className='mr-2 shrink-0 truncate max-w-[33.3%]' |
| | | title={`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`} |
| | | > |
| | | {`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`} |
| | | </div> |
| | | <div |
| | | className='max-w-[33.3%] shrink-0 truncate' |
| | | className='shrink-0 truncate max-w-[33.3%]' |
| | | title={`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`} |
| | | > |
| | | {`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`} |
| | | </div> |
| | | <div className='mx-2 shrink-0'>·</div> |
| | | <div className='shrink-0 mx-2'>·</div> |
| | | <div |
| | | className='max-w-[33.3%] shrink-0 truncate' |
| | | className='shrink-0 truncate max-w-[33.3%]' |
| | | title={more.time} |
| | | > |
| | | {more.time} |
| | |
| | | useState, |
| | | } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiClipboardLine, |
| | | RiResetLeftLine, |
| | | RiThumbDownLine, |
| | | RiThumbUpLine, |
| | | } from '@remixicon/react' |
| | | import type { ChatItem } from '../../types' |
| | | import { useChatContext } from '../context' |
| | | import copy from 'copy-to-clipboard' |
| | | import Toast from '@/app/components/base/toast' |
| | | import AnnotationCtrlButton from '@/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button' |
| | | import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal' |
| | | import Log from '@/app/components/base/chat/chat/log' |
| | | import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' |
| | | import NewAudioButton from '@/app/components/base/new-audio-button' |
| | | import RegenerateBtn from '@/app/components/base/regenerate-btn' |
| | | import cn from '@/utils/classnames' |
| | | import CopyBtn from '@/app/components/base/copy-btn' |
| | | import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' |
| | | import AudioBtn from '@/app/components/base/audio-btn' |
| | | import AnnotationCtrlBtn from '@/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-btn' |
| | | import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal' |
| | | import { |
| | | ThumbsDown, |
| | | ThumbsUp, |
| | | } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import Log from '@/app/components/base/chat/chat/log' |
| | | |
| | | type OperationProps = { |
| | | item: ChatItem |
| | |
| | | adminFeedback, |
| | | agent_thoughts, |
| | | } = item |
| | | const hasAnnotation = !!annotation?.id |
| | | const [localFeedback, setLocalFeedback] = useState(config?.supportAnnotation ? adminFeedback : feedback) |
| | | |
| | | const content = useMemo(() => { |
| | |
| | | const operationWidth = useMemo(() => { |
| | | let width = 0 |
| | | if (!isOpeningStatement) |
| | | width += 26 |
| | | width += 28 |
| | | if (!isOpeningStatement && showPromptLog) |
| | | width += 28 + 8 |
| | | width += 102 + 8 |
| | | if (!isOpeningStatement && config?.text_to_speech?.enabled) |
| | | width += 26 |
| | | width += 33 |
| | | if (!isOpeningStatement && config?.supportAnnotation && config?.annotation_reply?.enabled) |
| | | width += 26 |
| | | width += 56 + 8 |
| | | if (config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement) |
| | | width += 60 + 8 |
| | | if (config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement) |
| | |
| | | <div |
| | | className={cn( |
| | | 'absolute flex justify-end gap-1', |
| | | hasWorkflowProcess && '-bottom-4 right-2', |
| | | !positionRight && '-bottom-4 right-2', |
| | | hasWorkflowProcess && '-top-3.5 -right-3.5', |
| | | !positionRight && '-top-3.5 -right-3.5', |
| | | !hasWorkflowProcess && positionRight && '!top-[9px]', |
| | | )} |
| | | style={(!hasWorkflowProcess && positionRight) ? { left: contentWidth + 8 } : {}} |
| | | > |
| | | {showPromptLog && !isOpeningStatement && ( |
| | | <div className='hidden group-hover:block'> |
| | | <Log logItem={item} /> |
| | | </div> |
| | | )} |
| | | {!isOpeningStatement && ( |
| | | <div className='ml-1 hidden items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex'> |
| | | <CopyBtn |
| | | value={content} |
| | | className='hidden group-hover:block' |
| | | /> |
| | | )} |
| | | |
| | | {!isOpeningStatement && (showPromptLog || config?.text_to_speech?.enabled) && ( |
| | | <div className='hidden group-hover:flex items-center w-max h-[28px] p-0.5 rounded-lg bg-white border-[0.5px] border-gray-100 shadow-md shrink-0'> |
| | | {showPromptLog && ( |
| | | <> |
| | | <Log logItem={item} /> |
| | | <div className='mx-1 w-[1px] h-[14px] bg-gray-200' /> |
| | | </> |
| | | )} |
| | | |
| | | {(config?.text_to_speech?.enabled) && ( |
| | | <NewAudioButton |
| | | <> |
| | | <AudioBtn |
| | | id={id} |
| | | value={content} |
| | | noCache={false} |
| | | voice={config?.text_to_speech?.voice} |
| | | className='hidden group-hover:block' |
| | | /> |
| | | )} |
| | | <ActionButton onClick={() => { |
| | | copy(content) |
| | | Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) |
| | | }}> |
| | | <RiClipboardLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | {!noChatInput && ( |
| | | <ActionButton onClick={() => onRegenerate?.(item)}> |
| | | <RiResetLeftLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | )} |
| | | {(config?.supportAnnotation && config.annotation_reply?.enabled) && ( |
| | | <AnnotationCtrlButton |
| | | appId={config?.appId || ''} |
| | | messageId={id} |
| | | cached={!!annotation?.id} |
| | | query={question} |
| | | answer={content} |
| | | onAdded={(id, authorName) => onAnnotationAdded?.(id, authorName, question, content, index)} |
| | | onEdit={() => setIsShowReplyModal(true)} |
| | | /> |
| | | )} |
| | | </div> |
| | | )} |
| | | {!isOpeningStatement && config?.supportFeedback && !localFeedback?.rating && onFeedback && ( |
| | | <div className='ml-1 hidden items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex'> |
| | | {!localFeedback?.rating && ( |
| | | <> |
| | | <ActionButton onClick={() => handleFeedback('like')}> |
| | | <RiThumbUpLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | <ActionButton onClick={() => handleFeedback('dislike')}> |
| | | <RiThumbDownLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | </> |
| | | )} |
| | | </div> |
| | | )} |
| | | {!isOpeningStatement && config?.supportFeedback && localFeedback?.rating && onFeedback && ( |
| | | <div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'> |
| | | {localFeedback?.rating === 'like' && ( |
| | | <ActionButton state={ActionButtonState.Active} onClick={() => handleFeedback(null)}> |
| | | <RiThumbUpLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | |
| | | {(!isOpeningStatement && config?.supportAnnotation && config.annotation_reply?.enabled) && ( |
| | | <AnnotationCtrlBtn |
| | | appId={config?.appId || ''} |
| | | messageId={id} |
| | | annotationId={annotation?.id || ''} |
| | | className='hidden group-hover:block ml-1 shrink-0' |
| | | cached={hasAnnotation} |
| | | query={question} |
| | | answer={content} |
| | | onAdded={(id, authorName) => onAnnotationAdded?.(id, authorName, question, content, index)} |
| | | onEdit={() => setIsShowReplyModal(true)} |
| | | onRemoved={() => onAnnotationRemoved?.(index)} |
| | | /> |
| | | )} |
| | | {localFeedback?.rating === 'dislike' && ( |
| | | <ActionButton state={ActionButtonState.Destructive} onClick={() => handleFeedback(null)}> |
| | | <RiThumbDownLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | )} |
| | | { |
| | | annotation?.id && ( |
| | | <div |
| | | className='relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-[#444CE7] shadow-md group-hover:hidden' |
| | | > |
| | | <div className='p-1 rounded-lg bg-[#EEF4FF] '> |
| | | <MessageFast className='w-4 h-4' /> |
| | | </div> |
| | | )} |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | !isOpeningStatement && !noChatInput && <RegenerateBtn className='hidden group-hover:block mr-1' onClick={() => onRegenerate?.(item)} /> |
| | | } |
| | | { |
| | | config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement && ( |
| | | <div className='hidden group-hover:flex shrink-0 items-center px-0.5 bg-white border-[0.5px] border-gray-100 shadow-md text-gray-500 rounded-lg'> |
| | | <Tooltip popupContent={t('appDebug.operation.agree')}> |
| | | <div |
| | | className='flex items-center justify-center mr-0.5 w-6 h-6 rounded-md hover:bg-black/5 hover:text-gray-800 cursor-pointer' |
| | | onClick={() => handleFeedback('like')} |
| | | > |
| | | <ThumbsUp className='w-4 h-4' /> |
| | | </div> |
| | | </Tooltip> |
| | | <Tooltip |
| | | popupContent={t('appDebug.operation.disagree')} |
| | | > |
| | | <div |
| | | className='flex items-center justify-center w-6 h-6 rounded-md hover:bg-black/5 hover:text-gray-800 cursor-pointer' |
| | | onClick={() => handleFeedback('dislike')} |
| | | > |
| | | <ThumbsDown className='w-4 h-4' /> |
| | | </div> |
| | | </Tooltip> |
| | | </div> |
| | | ) |
| | | } |
| | | { |
| | | config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement && ( |
| | | <Tooltip |
| | | popupContent={localFeedback.rating === 'like' ? t('appDebug.operation.cancelAgree') : t('appDebug.operation.cancelDisagree')} |
| | | > |
| | | <div |
| | | className={` |
| | | flex items-center justify-center w-7 h-7 rounded-[10px] border-[2px] border-white cursor-pointer |
| | | ${localFeedback.rating === 'like' && 'bg-blue-50 text-blue-600'} |
| | | ${localFeedback.rating === 'dislike' && 'bg-red-100 text-red-600'} |
| | | `} |
| | | onClick={() => handleFeedback(null)} |
| | | > |
| | | { |
| | | localFeedback.rating === 'like' && ( |
| | | <ThumbsUp className='w-4 h-4' /> |
| | | ) |
| | | } |
| | | { |
| | | localFeedback.rating === 'dislike' && ( |
| | | <ThumbsDown className='w-4 h-4' /> |
| | | ) |
| | | } |
| | | </div> |
| | | </Tooltip> |
| | | ) |
| | | } |
| | | </div> |
| | | <EditReplyModal |
| | | isShow={isShowReplyModal} |
| | |
| | | item, |
| | | }) => { |
| | | const { onSend } = useChatContext() |
| | | |
| | | const { |
| | | isOpeningStatement, |
| | | suggestedQuestions, |
| | |
| | | {suggestedQuestions.filter(q => !!q && q.trim()).map((question, index) => ( |
| | | <div |
| | | key={index} |
| | | className='system-sm-medium mr-1 mt-1 inline-flex max-w-full shrink-0 cursor-pointer flex-wrap rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3.5 py-2 text-components-button-secondary-accent-text shadow-xs last:mr-0 hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover' |
| | | className='mt-1 mr-1 max-w-full last:mr-0 shrink-0 py-[5px] leading-[18px] items-center px-4 rounded-lg border border-gray-200 shadow-xs bg-white text-xs font-medium text-primary-600 cursor-pointer' |
| | | onClick={() => onSend?.(question)} |
| | | > |
| | | {question} |
| | |
| | | > |
| | | <div |
| | | className={cn( |
| | | 'system-xs-medium flex cursor-pointer items-center px-2.5 py-2 text-text-tertiary', |
| | | 'flex items-center system-xs-medium text-text-tertiary px-2.5 py-2 cursor-pointer', |
| | | expand && 'pb-1.5', |
| | | )} |
| | | onClick={() => setExpand(!expand)} |
| | | > |
| | | {isFinished && <RiHammerFill className='mr-1 h-3.5 w-3.5' />} |
| | | {!isFinished && <RiLoader2Line className='mr-1 h-3.5 w-3.5 animate-spin' />} |
| | | {isFinished && <RiHammerFill className='mr-1 w-3.5 h-3.5' />} |
| | | {!isFinished && <RiLoader2Line className='mr-1 w-3.5 h-3.5 animate-spin' />} |
| | | {t(`tools.thought.${isFinished ? 'used' : 'using'}`)} |
| | | <div className='mx-1 text-text-secondary'>{toolLabel}</div> |
| | | {!expand && <RiArrowRightSLine className='h-4 w-4' />} |
| | | {expand && <RiArrowDownSLine className='ml-auto h-4 w-4' />} |
| | | {!expand && <RiArrowRightSLine className='w-4 h-4' />} |
| | | {expand && <RiArrowDownSLine className='ml-auto w-4 h-4' />} |
| | | </div> |
| | | { |
| | | expand && ( |
| | | <> |
| | | <div className='mx-1 mb-0.5 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary'> |
| | | <div className='system-xs-semibold-uppercase flex h-7 items-center justify-between px-2 pt-1'> |
| | | <div className='mb-0.5 mx-1 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary'> |
| | | <div className='flex items-center justify-between px-2 pt-1 h-7 system-xs-semibold-uppercase'> |
| | | {t('tools.thought.requestTitle')} |
| | | </div> |
| | | <div className='code-xs-regular break-words px-3 pb-2 pt-1'> |
| | | <div className='pt-1 px-3 pb-2 code-xs-regular break-words'> |
| | | {input} |
| | | </div> |
| | | </div> |
| | | <div className='mx-1 mb-1 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary'> |
| | | <div className='system-xs-semibold-uppercase flex h-7 items-center justify-between px-2 pt-1'> |
| | | <div className='flex items-center justify-between px-2 pt-1 h-7 system-xs-semibold-uppercase'> |
| | | {t('tools.thought.responseTitle')} |
| | | </div> |
| | | <div className='code-xs-regular break-words px-3 pb-2 pt-1'> |
| | | <div className='pt-1 px-3 pb-2 code-xs-regular break-words'> |
| | | {output} |
| | | </div> |
| | | </div> |
| | |
| | | import { |
| | | useCallback, |
| | | useEffect, |
| | | useMemo, |
| | | useState, |
| | | } from 'react' |
| | | import { |
| | |
| | | import cn from '@/utils/classnames' |
| | | import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' |
| | | import { WorkflowRunningStatus } from '@/app/components/workflow/types' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | |
| | | type WorkflowProcessProps = { |
| | | data: WorkflowProcess |
| | |
| | | } |
| | | const WorkflowProcessItem = ({ |
| | | data, |
| | | item, |
| | | expand = false, |
| | | hideInfo = false, |
| | | hideProcessDetail = false, |
| | |
| | | const succeeded = data.status === WorkflowRunningStatus.Succeeded |
| | | const failed = data.status === WorkflowRunningStatus.Failed || data.status === WorkflowRunningStatus.Stopped |
| | | |
| | | const background = useMemo(() => { |
| | | if (collapse) |
| | | return 'linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%)' |
| | | if (running && !collapse) |
| | | return 'linear-gradient(180deg, #E1E4EA 0%, #EAECF0 100%)' |
| | | |
| | | if (succeeded && !collapse) |
| | | return 'linear-gradient(180deg, #ECFDF3 0%, #F6FEF9 100%)' |
| | | |
| | | if (failed && !collapse) |
| | | return 'linear-gradient(180deg, #FEE4E2 0%, #FEF3F2 100%)' |
| | | }, [running, succeeded, failed, collapse]) |
| | | |
| | | useEffect(() => { |
| | | setCollapse(!expand) |
| | | }, [expand]) |
| | | |
| | | const setCurrentLogItem = useAppStore(s => s.setCurrentLogItem) |
| | | const setShowMessageLogModal = useAppStore(s => s.setShowMessageLogModal) |
| | | const setCurrentLogModalActiveTab = useAppStore(s => s.setCurrentLogModalActiveTab) |
| | | |
| | | const showIterationDetail = useCallback(() => { |
| | | setCurrentLogItem(item) |
| | | setCurrentLogModalActiveTab('TRACING') |
| | | setShowMessageLogModal(true) |
| | | }, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal]) |
| | | |
| | | const showRetryDetail = useCallback(() => { |
| | | setCurrentLogItem(item) |
| | | setCurrentLogModalActiveTab('TRACING') |
| | | setShowMessageLogModal(true) |
| | | }, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal]) |
| | | |
| | | return ( |
| | | <div |
| | | className={cn( |
| | | '-mx-1 rounded-xl px-2.5', |
| | | collapse ? 'border-l-[0.25px] border-components-panel-border py-[7px]' : 'border-[0.5px] border-components-panel-border-subtle px-1 pb-1 pt-[7px]', |
| | | running && !collapse && 'bg-background-section-burn', |
| | | succeeded && !collapse && 'bg-state-success-hover', |
| | | failed && !collapse && 'bg-state-destructive-hover', |
| | | collapse && 'bg-workflow-process-bg', |
| | | '-mx-1 px-2.5 rounded-xl border-[0.5px]', |
| | | collapse ? 'py-[7px] border-components-panel-border' : 'pt-[7px] px-1 pb-1 border-components-panel-border-subtle', |
| | | )} |
| | | style={{ |
| | | background, |
| | | }} |
| | | > |
| | | <div |
| | | className={cn('flex cursor-pointer items-center', !collapse && 'px-1.5', readonly && 'cursor-default')} |
| | | className={cn('flex items-center cursor-pointer', !collapse && 'px-1.5', readonly && 'cursor-default')} |
| | | onClick={() => !readonly && setCollapse(!collapse)} |
| | | > |
| | | { |
| | | running && ( |
| | | <RiLoader2Line className='mr-1 h-3.5 w-3.5 shrink-0 animate-spin text-text-tertiary' /> |
| | | <RiLoader2Line className='shrink-0 mr-1 w-3.5 h-3.5 text-text-tertiary' /> |
| | | ) |
| | | } |
| | | { |
| | | succeeded && ( |
| | | <CheckCircle className='mr-1 h-3.5 w-3.5 shrink-0 text-text-success' /> |
| | | <CheckCircle className='shrink-0 mr-1 w-3.5 h-3.5 text-text-success' /> |
| | | ) |
| | | } |
| | | { |
| | | failed && ( |
| | | <RiErrorWarningFill className='mr-1 h-3.5 w-3.5 shrink-0 text-text-destructive' /> |
| | | <RiErrorWarningFill className='shrink-0 mr-1 w-3.5 h-3.5 text-text-destructive' /> |
| | | ) |
| | | } |
| | | <div className={cn('system-xs-medium text-text-secondary', !collapse && 'grow')}> |
| | | {t('workflow.common.workflowProcess')} |
| | | </div> |
| | | {!readonly && <RiArrowRightSLine className={cn('ml-1 h-4 w-4 text-text-tertiary', !collapse && 'rotate-90')} />} |
| | | {!readonly && <RiArrowRightSLine className={`'ml-1 w-4 h-4 text-text-tertiary' ${collapse ? '' : 'rotate-90'}`} />} |
| | | </div> |
| | | { |
| | | !collapse && !readonly && ( |
| | |
| | | { |
| | | <TracingPanel |
| | | list={data.tracing} |
| | | onShowIterationDetail={showIterationDetail} |
| | | onShowRetryDetail={showRetryDetail} |
| | | hideNodeInfo={hideInfo} |
| | | hideNodeProcessDetail={hideProcessDetail} |
| | | /> |
| | |
| | | useRef, |
| | | useState, |
| | | } from 'react' |
| | | import type { TextAreaRef } from 'rc-textarea' |
| | | |
| | | export const useTextAreaHeight = () => { |
| | | const wrapperRef = useRef<HTMLDivElement>(null) |
| | | const textareaRef = useRef<HTMLTextAreaElement | undefined>(undefined) |
| | | const textareaRef = useRef<TextAreaRef>(null) |
| | | const textValueRef = useRef<HTMLDivElement>(null) |
| | | const holdSpaceRef = useRef<HTMLDivElement>(null) |
| | | const [isMultipleLine, setIsMultipleLine] = useState(false) |
| | | |
| | | const handleComputeHeight = useCallback(() => { |
| | | const textareaElement = textareaRef.current |
| | | |
| | | const textareaElement = textareaRef.current?.resizableTextArea.textArea |
| | | if (wrapperRef.current && textareaElement && textValueRef.current && holdSpaceRef.current) { |
| | | const { width: wrapperWidth } = wrapperRef.current.getBoundingClientRect() |
| | | const { height: textareaHeight } = textareaElement.getBoundingClientRect() |
| | | const { width: textValueWidth } = textValueRef.current.getBoundingClientRect() |
| | | const { width: holdSpaceWidth } = holdSpaceRef.current.getBoundingClientRect() |
| | | |
| | | if (textareaHeight > 32) { |
| | | setIsMultipleLine(true) |
| | | } |
| | |
| | | useRef, |
| | | useState, |
| | | } from 'react' |
| | | import Textarea from 'react-textarea-autosize' |
| | | import Textarea from 'rc-textarea' |
| | | import { useTranslation } from 'react-i18next' |
| | | import Recorder from 'js-audio-recorder' |
| | | import type { |
| | |
| | | inputsForm?: InputForm[] |
| | | theme?: Theme | null |
| | | isResponding?: boolean |
| | | disabled?: boolean |
| | | } |
| | | const ChatInputArea = ({ |
| | | showFeatureBar, |
| | |
| | | inputsForm = [], |
| | | theme, |
| | | isResponding, |
| | | disabled, |
| | | }: ChatInputAreaProps) => { |
| | | const { t } = useTranslation() |
| | | const { notify } = useToastContext() |
| | |
| | | const { checkInputsForm } = useCheckInputsForms() |
| | | const historyRef = useRef(['']) |
| | | const [currentIndex, setCurrentIndex] = useState(-1) |
| | | const isComposingRef = useRef(false) |
| | | const handleSend = () => { |
| | | if (isResponding) { |
| | | notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) |
| | |
| | | } |
| | | } |
| | | } |
| | | const handleCompositionStart = () => { |
| | | // e: React.CompositionEvent<HTMLTextAreaElement> |
| | | isComposingRef.current = true |
| | | } |
| | | const handleCompositionEnd = () => { |
| | | // safari or some browsers will trigger compositionend before keydown. |
| | | // delay 50ms for safari. |
| | | setTimeout(() => { |
| | | isComposingRef.current = false |
| | | }, 50) |
| | | } |
| | | const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { |
| | | if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { |
| | | // if isComposing, exit |
| | | if (isComposingRef.current) return |
| | | e.preventDefault() |
| | | setQuery(query.replace(/\n$/, '')) |
| | | historyRef.current.push(query) |
| | |
| | | <> |
| | | <div |
| | | className={cn( |
| | | 'relative z-10 rounded-xl border border-components-chat-input-border bg-components-panel-bg-blur pb-[9px] shadow-md', |
| | | 'relative pb-[9px] bg-components-panel-bg-blur border border-components-chat-input-border rounded-xl shadow-md z-10', |
| | | isDragActive && 'border border-dashed border-components-option-card-option-selected-border', |
| | | disabled && 'pointer-events-none border-components-panel-border opacity-50 shadow-none', |
| | | )} |
| | | > |
| | | <div className='relative max-h-[158px] overflow-y-auto overflow-x-hidden px-[9px] pt-[9px]'> |
| | | <div className='relative px-[9px] pt-[9px] max-h-[158px] overflow-x-hidden overflow-y-auto'> |
| | | <FileListInChatInput fileConfig={visionConfig!} /> |
| | | <div |
| | | ref={wrapperRef} |
| | | className='flex items-center justify-between' |
| | | > |
| | | <div className='relative flex w-full grow items-center'> |
| | | <div className='flex items-center relative grow w-full'> |
| | | <div |
| | | ref={textValueRef} |
| | | className='body-lg-regular pointer-events-none invisible absolute h-auto w-auto whitespace-pre p-1 leading-6' |
| | | className='absolute w-auto h-auto p-1 leading-6 body-lg-regular pointer-events-none whitespace-pre invisible' |
| | | > |
| | | {query} |
| | | </div> |
| | | <Textarea |
| | | ref={ref => textareaRef.current = ref as any} |
| | | ref={textareaRef} |
| | | className={cn( |
| | | 'body-lg-regular w-full resize-none bg-transparent p-1 leading-6 text-text-tertiary outline-none', |
| | | 'p-1 w-full leading-6 body-lg-regular text-text-tertiary outline-none', |
| | | )} |
| | | placeholder={t('common.chat.inputPlaceholder') || ''} |
| | | autoFocus |
| | | minRows={1} |
| | | autoSize={{ minRows: 1 }} |
| | | onResize={handleTextareaResize} |
| | | value={query} |
| | | onChange={(e) => { |
| | | setQuery(e.target.value) |
| | | setTimeout(handleTextareaResize, 0) |
| | | handleTextareaResize() |
| | | }} |
| | | onKeyDown={handleKeyDown} |
| | | onCompositionStart={handleCompositionStart} |
| | | onCompositionEnd={handleCompositionEnd} |
| | | onPaste={handleClipboardPasteFile} |
| | | onDragEnter={handleDragFileEnter} |
| | | onDragLeave={handleDragFileLeave} |
| | |
| | | import { memo } from 'react' |
| | | import { |
| | | forwardRef, |
| | | memo, |
| | | } from 'react' |
| | | import { |
| | | RiMicLine, |
| | | RiSendPlane2Fill, |
| | |
| | | onSend: () => void |
| | | theme?: Theme | null |
| | | } |
| | | const Operation = ( |
| | | { |
| | | ref, |
| | | const Operation = forwardRef<HTMLDivElement, OperationProps>(({ |
| | | fileConfig, |
| | | speechToTextConfig, |
| | | onShowVoiceInput, |
| | | onSend, |
| | | theme, |
| | | }: OperationProps & { |
| | | ref: React.RefObject<HTMLDivElement>; |
| | | }, |
| | | ) => { |
| | | }, ref) => { |
| | | return ( |
| | | <div |
| | | className={cn( |
| | | 'flex shrink-0 items-center justify-end', |
| | | 'shrink-0 flex items-center justify-end', |
| | | )} |
| | | > |
| | | <div |
| | |
| | | size='l' |
| | | onClick={onShowVoiceInput} |
| | | > |
| | | <RiMicLine className='h-5 w-5' /> |
| | | <RiMicLine className='w-5 h-5' /> |
| | | </ActionButton> |
| | | ) |
| | | } |
| | | </div> |
| | | <Button |
| | | className='ml-3 w-8 px-0' |
| | | className='ml-3 px-0 w-8' |
| | | variant='primary' |
| | | onClick={onSend} |
| | | style={ |
| | |
| | | : {} |
| | | } |
| | | > |
| | | <RiSendPlane2Fill className='h-4 w-4' /> |
| | | <RiSendPlane2Fill className='w-4 h-4' /> |
| | | </Button> |
| | | </div> |
| | | </div> |
| | | ) |
| | | } |
| | | }) |
| | | Operation.displayName = 'Operation' |
| | | |
| | | export default memo(Operation) |
| | |
| | | const resourcesLength = resources.length |
| | | |
| | | return ( |
| | | <div className='-mb-1 mt-3'> |
| | | <div className='system-xs-medium mb-2 flex items-center text-text-tertiary'> |
| | | <div className='mt-3 -mb-1'> |
| | | <div className='flex items-center mb-2 text-xs font-medium text-gray-500'> |
| | | {t('common.chat.citation.title')} |
| | | <div className='ml-2 h-[1px] grow bg-divider-regular' /> |
| | | <div className='grow ml-2 h-[1px] bg-black/5' /> |
| | | </div> |
| | | <div className='relative flex flex-wrap'> |
| | | { |
| | | resources.map((res, index) => ( |
| | | <div |
| | | key={index} |
| | | className='absolute left-0 top-0 -z-10 mb-1 mr-1 h-7 w-auto max-w-[240px] whitespace-nowrap pl-7 pr-2 text-xs opacity-0' |
| | | ref={(ele: any) => (elesRef.current[index] = ele!)} |
| | | className='absolute top-0 left-0 w-auto mr-1 mb-1 pl-7 pr-2 max-w-[240px] h-7 text-xs whitespace-nowrap opacity-0 -z-10' |
| | | ref={ele => (elesRef.current[index] = ele!)} |
| | | > |
| | | {res.documentName} |
| | | </div> |
| | |
| | | } |
| | | { |
| | | resources.slice(0, showMore ? resourcesLength : limitNumberInOneLine).map((res, index) => ( |
| | | <div key={index} className='mb-1 mr-1 cursor-pointer'> |
| | | <div key={index} className='mr-1 mb-1 cursor-pointer'> |
| | | <Popup |
| | | data={res} |
| | | showHitInfo={showHitInfo} |
| | |
| | | { |
| | | limitNumberInOneLine < resourcesLength && ( |
| | | <div |
| | | className='system-xs-medium flex h-7 cursor-pointer items-center rounded-lg bg-components-panel-bg px-2 text-text-tertiary' |
| | | className='flex items-center px-2 h-7 bg-white rounded-lg text-xs font-medium text-gray-500 cursor-pointer' |
| | | onClick={() => setShowMore(v => !v)} |
| | | > |
| | | { |
| | | !showMore |
| | | ? `+ ${resourcesLength - limitNumberInOneLine}` |
| | | : <RiArrowDownSLine className='h-4 w-4 rotate-180 text-text-tertiary' /> |
| | | : <RiArrowDownSLine className='w-4 h-4 text-gray-600 rotate-180' /> |
| | | } |
| | | </div> |
| | | ) |
| | |
| | | }} |
| | | > |
| | | <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> |
| | | <div className='flex h-7 max-w-[240px] items-center rounded-lg bg-components-button-secondary-bg px-2'> |
| | | <FileIcon type={fileType} className='mr-1 h-4 w-4 shrink-0' /> |
| | | <div className='truncate text-xs text-text-tertiary'>{data.documentName}</div> |
| | | <div className='flex items-center px-2 max-w-[240px] h-7 bg-white rounded-lg'> |
| | | <FileIcon type={fileType} className='shrink-0 mr-1 w-4 h-4' /> |
| | | <div className='text-xs text-gray-600 truncate'>{data.documentName}</div> |
| | | </div> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent style={{ zIndex: 1000 }}> |
| | | <div className='max-w-[360px] rounded-xl bg-background-section-burn shadow-lg'> |
| | | <div className='px-4 pb-2 pt-3'> |
| | | <div className='flex h-[18px] items-center'> |
| | | <FileIcon type={fileType} className='mr-1 h-4 w-4 shrink-0' /> |
| | | <div className='system-xs-medium truncate text-text-tertiary'>{data.documentName}</div> |
| | | <div className='max-w-[360px] bg-gray-50 rounded-xl shadow-lg'> |
| | | <div className='px-4 pt-3 pb-2'> |
| | | <div className='flex items-center h-[18px]'> |
| | | <FileIcon type={fileType} className='shrink-0 mr-1 w-4 h-4' /> |
| | | <div className='text-xs font-medium text-gray-600 truncate'>{data.documentName}</div> |
| | | </div> |
| | | </div> |
| | | <div className='max-h-[450px] overflow-y-auto rounded-lg bg-components-panel-bg px-4 py-0.5'> |
| | | <div className='px-4 py-0.5 max-h-[450px] bg-white rounded-lg overflow-y-auto'> |
| | | <div className='w-full'> |
| | | { |
| | | data.sources.map((source, index) => ( |
| | | <Fragment key={index}> |
| | | <div className='group py-3'> |
| | | <div className='mb-2 flex items-center justify-between'> |
| | | <div className='flex h-5 items-center rounded-md border border-divider-subtle px-1.5'> |
| | | <Hash02 className='mr-0.5 h-3 w-3 text-text-quaternary' /> |
| | | <div className='text-[11px] font-medium text-text-tertiary'> |
| | | <div className='flex items-center justify-between mb-2'> |
| | | <div className='flex items-center px-1.5 h-5 border border-gray-200 rounded-md'> |
| | | <Hash02 className='mr-0.5 w-3 h-3 text-gray-400' /> |
| | | <div className='text-[11px] font-medium text-gray-500'> |
| | | {source.segment_position || index + 1} |
| | | </div> |
| | | </div> |
| | |
| | | showHitInfo && ( |
| | | <Link |
| | | href={`/datasets/${source.dataset_id}/documents/${source.document_id}`} |
| | | className='hidden h-[18px] items-center text-xs text-text-accent group-hover:flex'> |
| | | className='hidden items-center h-[18px] text-xs text-primary-600 group-hover:flex'> |
| | | {t('common.chat.citation.linkToDataset')} |
| | | <ArrowUpRight className='ml-1 h-3 w-3' /> |
| | | <ArrowUpRight className='ml-1 w-3 h-3' /> |
| | | </Link> |
| | | ) |
| | | } |
| | | </div> |
| | | <div className='break-words text-[13px] text-text-secondary'>{source.content}</div> |
| | | <div className='text-[13px] text-gray-800 break-words'>{source.content}</div> |
| | | { |
| | | showHitInfo && ( |
| | | <div className='system-xs-medium mt-2 flex flex-wrap items-center text-text-quaternary'> |
| | | <div className='flex items-center mt-2 text-xs font-medium text-gray-500 flex-wrap'> |
| | | <Tooltip |
| | | text={t('common.chat.citation.characters')} |
| | | data={source.word_count} |
| | | icon={<TypeSquare className='mr-1 h-3 w-3' />} |
| | | icon={<TypeSquare className='mr-1 w-3 h-3' />} |
| | | /> |
| | | <Tooltip |
| | | text={t('common.chat.citation.hitCount')} |
| | | data={source.hit_count} |
| | | icon={<Target04 className='mr-1 h-3 w-3' />} |
| | | icon={<Target04 className='mr-1 w-3 h-3' />} |
| | | /> |
| | | <Tooltip |
| | | text={t('common.chat.citation.vectorHash')} |
| | | data={source.index_node_hash?.substring(0, 7)} |
| | | icon={<BezierCurve03 className='mr-1 h-3 w-3' />} |
| | | icon={<BezierCurve03 className='mr-1 w-3 h-3' />} |
| | | /> |
| | | { |
| | | source.score && ( |
| | |
| | | </div> |
| | | { |
| | | index !== data.sources.length - 1 && ( |
| | | <div className='my-1 h-[1px] bg-divider-regular' /> |
| | | <div className='my-1 h-[1px] bg-black/5' /> |
| | | ) |
| | | } |
| | | </Fragment> |
| | |
| | | onMouseEnter={() => setOpen(true)} |
| | | onMouseLeave={() => setOpen(false)} |
| | | > |
| | | <div className='flex grow items-center'> |
| | | <div className='mr-1 h-1.5 w-16 overflow-hidden rounded-[3px] border border-components-progress-gray-border'> |
| | | <div className='h-full bg-components-progress-gray-progress' style={{ width: `${data * 100}%` }}></div> |
| | | <div className='grow flex items-center'> |
| | | <div className='mr-1 w-16 h-1.5 rounded-[3px] border border-gray-400 overflow-hidden'> |
| | | <div className='bg-gray-400 h-full' style={{ width: `${data * 100}%` }}></div> |
| | | </div> |
| | | {data} |
| | | </div> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent style={{ zIndex: 1001 }}> |
| | | <div className='system-xs-medium rounded-lg bg-components-tooltip-bg p-3 text-text-quaternary shadow-lg'> |
| | | <div className='p-3 bg-white text-xs font-medium text-gray-500 rounded-lg shadow-lg'> |
| | | {t('common.chat.citation.hitScore')} {data} |
| | | </div> |
| | | </PortalToFollowElemContent> |
| | |
| | | onMouseEnter={() => setOpen(true)} |
| | | onMouseLeave={() => setOpen(false)} |
| | | > |
| | | <div className='mr-6 flex items-center'> |
| | | <div className='flex items-center mr-6'> |
| | | {icon} |
| | | {data} |
| | | </div> |
| | | </PortalToFollowElemTrigger> |
| | | <PortalToFollowElemContent style={{ zIndex: 1001 }}> |
| | | <div className='system-xs-medium rounded-lg bg-components-tooltip-bg p-3 text-text-quaternary shadow-lg'> |
| | | <div className='p-3 bg-white text-xs font-medium text-gray-500 rounded-lg shadow-lg'> |
| | | {text} {data} |
| | | </div> |
| | | </PortalToFollowElemContent> |
| | |
| | | getProcessedFiles, |
| | | getProcessedFilesFromResponse, |
| | | } from '@/app/components/base/file-uploader/utils' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | type GetAbortController = (abortController: AbortController) => void |
| | | type SendCallback = { |
| | |
| | | }, |
| | | prevChatTree?: ChatItemInTree[], |
| | | stopChat?: (taskId: string) => void, |
| | | clearChatList?: boolean, |
| | | clearChatListCallback?: (state: boolean) => void, |
| | | ) => { |
| | | const { t } = useTranslation() |
| | | const { formatTime } = useTimestamp() |
| | |
| | | } |
| | | else { |
| | | ret.unshift({ |
| | | id: 'opening-statement', |
| | | id: `${Date.now()}`, |
| | | content: getIntroduction(config.opening_statement), |
| | | isAnswer: true, |
| | | isOpeningStatement: true, |
| | |
| | | suggestedQuestionsAbortControllerRef.current.abort() |
| | | }, [stopChat, handleResponding]) |
| | | |
| | | const handleRestart = useCallback((cb?: any) => { |
| | | const handleRestart = useCallback(() => { |
| | | conversationId.current = '' |
| | | taskIdRef.current = '' |
| | | handleStop() |
| | | setChatTree([]) |
| | | setSuggestQuestions([]) |
| | | cb?.() |
| | | }, [handleStop]) |
| | | |
| | | const updateCurrentQAOnTree = useCallback(({ |
| | |
| | | else |
| | | ttsUrl = `/apps/${params.appId}/text-to-audio` |
| | | } |
| | | const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', noop) |
| | | const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', (_: any): any => {}) |
| | | ssePost( |
| | | url, |
| | | { |
| | |
| | | ) |
| | | setSuggestQuestions(data) |
| | | } |
| | | // eslint-disable-next-line unused-imports/no-unused-vars |
| | | catch (e) { |
| | | setSuggestQuestions([]) |
| | | } |
| | |
| | | const response = responseItem as any |
| | | if (thought.message_id && !hasSetResponseId) |
| | | response.id = thought.message_id |
| | | if (thought.conversation_id) |
| | | response.conversationId = thought.conversation_id |
| | | |
| | | if (response.agent_thoughts.length === 0) { |
| | | response.agent_thoughts.push(thought) |
| | |
| | | responseItem.workflowProcess!.tracing!.push({ |
| | | ...iterationStartedData, |
| | | status: WorkflowRunningStatus.Running, |
| | | }) |
| | | } as any) |
| | | updateCurrentQAOnTree({ |
| | | placeholderQuestionId, |
| | | questionItem, |
| | |
| | | ...tracing[iterationIndex], |
| | | ...iterationFinishedData, |
| | | status: WorkflowRunningStatus.Succeeded, |
| | | } |
| | | } as any |
| | | |
| | | updateCurrentQAOnTree({ |
| | | placeholderQuestionId, |
| | |
| | | if (nodeStartedData.iteration_id) |
| | | return |
| | | |
| | | if (data.loop_id) |
| | | return |
| | | |
| | | responseItem.workflowProcess!.tracing!.push({ |
| | | ...nodeStartedData, |
| | | status: WorkflowRunningStatus.Running, |
| | | }) |
| | | } as any) |
| | | updateCurrentQAOnTree({ |
| | | placeholderQuestionId, |
| | | questionItem, |
| | |
| | | if (nodeFinishedData.iteration_id) |
| | | return |
| | | |
| | | if (data.loop_id) |
| | | return |
| | | |
| | | const currentIndex = responseItem.workflowProcess!.tracing!.findIndex((item) => { |
| | | if (!item.execution_metadata?.parallel_id) |
| | | return item.node_id === nodeFinishedData.node_id |
| | | |
| | | return item.node_id === nodeFinishedData.node_id && (item.execution_metadata?.parallel_id === nodeFinishedData.execution_metadata?.parallel_id) |
| | | return item.node_id === nodeFinishedData.node_id && (item.execution_metadata?.parallel_id === nodeFinishedData.execution_metadata.parallel_id) |
| | | }) |
| | | responseItem.workflowProcess!.tracing[currentIndex] = nodeFinishedData as any |
| | | |
| | |
| | | }, |
| | | onTTSEnd: (messageId: string, audio: string) => { |
| | | player.playAudioWithAudio(audio, false) |
| | | }, |
| | | onLoopStart: ({ data: loopStartedData }) => { |
| | | responseItem.workflowProcess!.tracing!.push({ |
| | | ...loopStartedData, |
| | | status: WorkflowRunningStatus.Running, |
| | | }) |
| | | updateCurrentQAOnTree({ |
| | | placeholderQuestionId, |
| | | questionItem, |
| | | responseItem, |
| | | parentId: data.parent_message_id, |
| | | }) |
| | | }, |
| | | onLoopFinish: ({ data: loopFinishedData }) => { |
| | | const tracing = responseItem.workflowProcess!.tracing! |
| | | const loopIndex = tracing.findIndex(item => item.node_id === loopFinishedData.node_id |
| | | && (item.execution_metadata?.parallel_id === loopFinishedData.execution_metadata?.parallel_id || item.parallel_id === loopFinishedData.execution_metadata?.parallel_id))! |
| | | tracing[loopIndex] = { |
| | | ...tracing[loopIndex], |
| | | ...loopFinishedData, |
| | | status: WorkflowRunningStatus.Succeeded, |
| | | } |
| | | |
| | | updateCurrentQAOnTree({ |
| | | placeholderQuestionId, |
| | | questionItem, |
| | | responseItem, |
| | | parentId: data.parent_message_id, |
| | | }) |
| | | }, |
| | | }) |
| | | return true |
| | |
| | | } as Annotation, |
| | | }) |
| | | }, [chatList, updateChatTreeNode]) |
| | | |
| | | useEffect(() => { |
| | | if (clearChatList) |
| | | handleRestart(() => clearChatListCallback?.(false)) |
| | | }, [clearChatList, clearChatListCallback, handleRestart]) |
| | | |
| | | return { |
| | | chatList, |
| | |
| | | showFileUpload?: boolean |
| | | onFeatureBarClick?: (state: boolean) => void |
| | | noSpacing?: boolean |
| | | inputDisabled?: boolean |
| | | isMobile?: boolean |
| | | sidebarCollapseState?: boolean |
| | | } |
| | | |
| | | const Chat: FC<ChatProps> = ({ |
| | |
| | | showFileUpload, |
| | | onFeatureBarClick, |
| | | noSpacing, |
| | | inputDisabled, |
| | | isMobile, |
| | | sidebarCollapseState, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({ |
| | |
| | | |
| | | useEffect(() => { |
| | | if (chatFooterRef.current && chatContainerRef.current) { |
| | | // container padding bottom |
| | | const resizeContainerObserver = new ResizeObserver((entries) => { |
| | | const resizeObserver = new ResizeObserver((entries) => { |
| | | for (const entry of entries) { |
| | | const { blockSize } = entry.borderBoxSize[0] |
| | | |
| | | chatContainerRef.current!.style.paddingBottom = `${blockSize}px` |
| | | handleScrollToBottom() |
| | | } |
| | | }) |
| | | resizeContainerObserver.observe(chatFooterRef.current) |
| | | |
| | | // footer width |
| | | const resizeFooterObserver = new ResizeObserver((entries) => { |
| | | for (const entry of entries) { |
| | | const { inlineSize } = entry.borderBoxSize[0] |
| | | chatFooterRef.current!.style.width = `${inlineSize}px` |
| | | } |
| | | }) |
| | | resizeFooterObserver.observe(chatContainerRef.current) |
| | | resizeObserver.observe(chatFooterRef.current) |
| | | |
| | | return () => { |
| | | resizeContainerObserver.disconnect() |
| | | resizeFooterObserver.disconnect() |
| | | resizeObserver.disconnect() |
| | | } |
| | | } |
| | | }, [handleScrollToBottom]) |
| | |
| | | const chatContainer = chatContainerRef.current |
| | | if (chatContainer) { |
| | | const setUserScrolled = () => { |
| | | // eslint-disable-next-line sonarjs/no-gratuitous-expressions |
| | | if (chatContainer) // its in event callback, chatContainer may be null |
| | | userScrolledRef.current = chatContainer.scrollHeight - chatContainer.scrollTop > chatContainer.clientHeight |
| | | if (chatContainer) |
| | | userScrolledRef.current = chatContainer.scrollHeight - chatContainer.scrollTop >= chatContainer.clientHeight + 300 |
| | | } |
| | | chatContainer.addEventListener('scroll', setUserScrolled) |
| | | return () => chatContainer.removeEventListener('scroll', setUserScrolled) |
| | | } |
| | | }, []) |
| | | |
| | | useEffect(() => { |
| | | if (!sidebarCollapseState) |
| | | setTimeout(() => handleWindowResize(), 200) |
| | | }, [handleWindowResize, sidebarCollapseState]) |
| | | |
| | | const hasTryToAsk = config?.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend |
| | | |
| | |
| | | item={item} |
| | | questionIcon={questionIcon} |
| | | theme={themeBuilder?.theme} |
| | | enableEdit={config?.questionEditEnable} |
| | | switchSibling={switchSibling} |
| | | /> |
| | | ) |
| | | }) |
| | |
| | | </div> |
| | | </div> |
| | | <div |
| | | className={`absolute bottom-0 flex justify-center bg-chat-input-mask ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`} |
| | | className={`absolute bottom-0 ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`} |
| | | ref={chatFooterRef} |
| | | style={{ |
| | | background: 'linear-gradient(0deg, #F9FAFB 40%, rgba(255, 255, 255, 0.00) 100%)', |
| | | }} |
| | | > |
| | | <div |
| | | ref={chatFooterInnerRef} |
| | |
| | | > |
| | | { |
| | | !noStopResponding && isResponding && ( |
| | | <div className='mb-2 flex justify-center'> |
| | | <div className='flex justify-center mb-2'> |
| | | <Button onClick={onStopResponding}> |
| | | <StopCircle className='mr-[5px] h-3.5 w-3.5 text-gray-500' /> |
| | | <span className='text-xs font-normal text-gray-500'>{t('appDebug.operation.stopResponding')}</span> |
| | | <StopCircle className='mr-[5px] w-3.5 h-3.5 text-gray-500' /> |
| | | <span className='text-xs text-gray-500 font-normal'>{t('appDebug.operation.stopResponding')}</span> |
| | | </Button> |
| | | </div> |
| | | ) |
| | |
| | | <TryToAsk |
| | | suggestedQuestions={suggestedQuestions} |
| | | onSend={onSend} |
| | | isMobile={isMobile} |
| | | /> |
| | | ) |
| | | } |
| | | { |
| | | !noChatInput && ( |
| | | <ChatInputArea |
| | | disabled={inputDisabled} |
| | | showFeatureBar={showFeatureBar} |
| | | showFileUpload={showFileUpload} |
| | | featureBarDisabled={isResponding} |
| | |
| | | import type { FC } from 'react' |
| | | import { RiFileList3Line } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { File02 } from '@/app/components/base/icons/src/vender/line/files' |
| | | import type { IChatItem } from '@/app/components/base/chat/chat/type' |
| | | import { useStore as useAppStore } from '@/app/components/app/store' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | |
| | | type LogProps = { |
| | | logItem: IChatItem |
| | |
| | | const Log: FC<LogProps> = ({ |
| | | logItem, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const setCurrentLogItem = useAppStore(s => s.setCurrentLogItem) |
| | | const setShowPromptLogModal = useAppStore(s => s.setShowPromptLogModal) |
| | | const setShowAgentLogModal = useAppStore(s => s.setShowAgentLogModal) |
| | |
| | | |
| | | return ( |
| | | <div |
| | | className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm' |
| | | className='shrink-0 p-1 flex items-center justify-center rounded-[6px] font-medium text-gray-500 hover:bg-gray-50 cursor-pointer hover:text-gray-700' |
| | | onClick={(e) => { |
| | | e.stopPropagation() |
| | | e.nativeEvent.stopImmediatePropagation() |
| | |
| | | setShowPromptLogModal(true) |
| | | }} |
| | | > |
| | | <ActionButton> |
| | | <RiFileList3Line className='h-4 w-4' /> |
| | | </ActionButton> |
| | | <File02 className='mr-1 w-4 h-4' /> |
| | | <div className='text-xs leading-4'>{runID ? t('appLog.viewLog') : isAgent ? t('appLog.agentLog') : t('appLog.promptLog')}</div> |
| | | </div> |
| | | ) |
| | | } |
| | |
| | | content: 'You are a helpful assistant.', |
| | | } satisfies ChatItem, |
| | | theme: undefined, |
| | | questionIcon: <div className='h-full w-full rounded-full border-[0.5px] border-black/5'> |
| | | <User className='h-full w-full' /> |
| | | questionIcon: <div className='w-full h-full rounded-full border-[0.5px] border-black/5'> |
| | | <User className='w-full h-full' /> |
| | | </div>, |
| | | }, |
| | | } |
| | |
| | | } from 'react' |
| | | import { |
| | | memo, |
| | | useCallback, |
| | | useEffect, |
| | | useRef, |
| | | useState, |
| | | } from 'react' |
| | | import type { ChatItem } from '../types' |
| | | import type { Theme } from '../embedded-chatbot/theme/theme-context' |
| | | import { CssTransform } from '../embedded-chatbot/theme/utils' |
| | | import ContentSwitch from './content-switch' |
| | | import { User } from '@/app/components/base/icons/src/public/avatar' |
| | | import { Markdown } from '@/app/components/base/markdown' |
| | | import { FileList } from '@/app/components/base/file-uploader' |
| | | import ActionButton from '../../action-button' |
| | | import { RiClipboardLine, RiEditLine } from '@remixicon/react' |
| | | import Toast from '../../toast' |
| | | import copy from 'copy-to-clipboard' |
| | | import { useTranslation } from 'react-i18next' |
| | | import cn from '@/utils/classnames' |
| | | import Textarea from 'react-textarea-autosize' |
| | | import Button from '../../button' |
| | | import { useChatContext } from './context' |
| | | |
| | | type QuestionProps = { |
| | | item: ChatItem |
| | | questionIcon?: ReactNode |
| | | theme: Theme | null | undefined |
| | | enableEdit?: boolean |
| | | switchSibling?: (siblingMessageId: string) => void |
| | | } |
| | | |
| | | const Question: FC<QuestionProps> = ({ |
| | | item, |
| | | questionIcon, |
| | | theme, |
| | | enableEdit = true, |
| | | switchSibling, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | |
| | | const { |
| | | content, |
| | | message_files, |
| | | } = item |
| | | |
| | | const { |
| | | onRegenerate, |
| | | } = useChatContext() |
| | | |
| | | const [isEditing, setIsEditing] = useState(false) |
| | | const [editedContent, setEditedContent] = useState(content) |
| | | const [contentWidth, setContentWidth] = useState(0) |
| | | const contentRef = useRef<HTMLDivElement>(null) |
| | | |
| | | const handleEdit = useCallback(() => { |
| | | setIsEditing(true) |
| | | setEditedContent(content) |
| | | }, [content]) |
| | | |
| | | const handleResend = useCallback(() => { |
| | | setIsEditing(false) |
| | | onRegenerate?.(item, { message: editedContent, files: message_files }) |
| | | }, [editedContent, message_files, item, onRegenerate]) |
| | | |
| | | const handleCancelEditing = useCallback(() => { |
| | | setIsEditing(false) |
| | | setEditedContent(content) |
| | | }, [content]) |
| | | |
| | | const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => { |
| | | if (direction === 'prev') |
| | | item.prevSibling && switchSibling?.(item.prevSibling) |
| | | else |
| | | item.nextSibling && switchSibling?.(item.nextSibling) |
| | | }, [switchSibling, item.prevSibling, item.nextSibling]) |
| | | |
| | | const getContentWidth = () => { |
| | | if (contentRef.current) |
| | | setContentWidth(contentRef.current?.clientWidth) |
| | | } |
| | | |
| | | useEffect(() => { |
| | | if (!contentRef.current) |
| | | return |
| | | const resizeObserver = new ResizeObserver(() => { |
| | | getContentWidth() |
| | | }) |
| | | resizeObserver.observe(contentRef.current) |
| | | return () => { |
| | | resizeObserver.disconnect() |
| | | } |
| | | }, []) |
| | | |
| | | return ( |
| | | <div className='mb-2 flex justify-end last:mb-0'> |
| | | <div className={cn('group relative mr-4 flex max-w-full items-start pl-14', isEditing && 'flex-1')}> |
| | | <div className={cn('mr-2 gap-1', isEditing ? 'hidden' : 'flex')}> |
| | | <div className='flex justify-end mb-2 last:mb-0 pl-14'> |
| | | <div className='group relative mr-4 max-w-full'> |
| | | <div |
| | | className="absolute hidden gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex" |
| | | style={{ right: contentWidth + 8 }} |
| | | > |
| | | <ActionButton onClick={() => { |
| | | copy(content) |
| | | Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) |
| | | }}> |
| | | <RiClipboardLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | {enableEdit && <ActionButton onClick={handleEdit}> |
| | | <RiEditLine className='h-4 w-4' /> |
| | | </ActionButton>} |
| | | </div> |
| | | </div> |
| | | <div |
| | | ref={contentRef} |
| | | className='w-full rounded-2xl bg-[#D1E9FF]/50 px-4 py-3 text-sm text-gray-900' |
| | | className='px-4 py-3 bg-[#D1E9FF]/50 rounded-2xl text-sm text-gray-900' |
| | | style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}} |
| | | > |
| | | { |
| | | !!message_files?.length && ( |
| | | <FileList |
| | | className='mb-2' |
| | | files={message_files} |
| | | showDeleteAction={false} |
| | | showDownloadAction={true} |
| | | /> |
| | | ) |
| | | } |
| | | { !isEditing |
| | | ? <Markdown content={content} /> |
| | | : <div className=" |
| | | flex flex-col gap-2 rounded-xl |
| | | border border-components-chat-input-border bg-components-panel-bg-blur p-[9px] shadow-md |
| | | "> |
| | | <div className="max-h-[158px] overflow-y-auto overflow-x-hidden"> |
| | | <Textarea |
| | | className={cn( |
| | | 'body-lg-regular w-full p-1 leading-6 text-text-tertiary outline-none', |
| | | )} |
| | | autoFocus |
| | | minRows={1} |
| | | value={editedContent} |
| | | onChange={e => setEditedContent(e.target.value)} |
| | | /> |
| | | </div> |
| | | <div className="flex justify-end gap-2"> |
| | | <Button variant='ghost' onClick={handleCancelEditing}>{t('common.operation.cancel')}</Button> |
| | | <Button variant='primary' onClick={handleResend}>{t('common.chat.resend')}</Button> |
| | | </div> |
| | | </div> } |
| | | { !isEditing && <ContentSwitch |
| | | count={item.siblingCount} |
| | | currentIndex={item.siblingIndex} |
| | | prevDisabled={!item.prevSibling} |
| | | nextDisabled={!item.nextSibling} |
| | | switchSibling={handleSwitchSibling} |
| | | />} |
| | | <Markdown content={content} /> |
| | | </div> |
| | | <div className='mt-1 h-[18px]' /> |
| | | </div> |
| | | <div className='h-10 w-10 shrink-0'> |
| | | <div className='shrink-0 w-10 h-10'> |
| | | { |
| | | questionIcon || ( |
| | | <div className='h-full w-full rounded-full border-[0.5px] border-black/5'> |
| | | <User className='h-full w-full' /> |
| | | <div className='w-full h-full rounded-full border-[0.5px] border-black/5'> |
| | | <User className='w-full h-full' /> |
| | | </div> |
| | | ) |
| | | } |
app/components/base/chat/chat/thought/index.tsx
app/components/base/chat/chat/thought/panel.tsx
app/components/base/chat/chat/thought/tool.tsx
app/components/base/chat/chat/try-to-ask.tsx
app/components/base/chat/chat/type.ts
app/components/base/chat/chat/utils.ts
app/components/base/chat/embedded-chatbot/chat-wrapper.tsx
app/components/base/chat/embedded-chatbot/config-panel/form-input.tsx
app/components/base/chat/embedded-chatbot/config-panel/form.tsx
app/components/base/chat/embedded-chatbot/config-panel/index.tsx
app/components/base/chat/embedded-chatbot/context.tsx
app/components/base/chat/embedded-chatbot/header.tsx
app/components/base/chat/embedded-chatbot/hooks.tsx
app/components/base/chat/embedded-chatbot/index.tsx
app/components/base/chat/embedded-chatbot/theme/theme-context.ts
app/components/base/chat/embedded-chatbot/theme/utils.ts
app/components/base/chat/types.ts
app/components/base/chat/utils.ts
app/components/base/checkbox/assets/mixed.svg
app/components/base/checkbox/index.module.css
app/components/base/checkbox/index.tsx
app/components/base/chip/index.tsx
app/components/base/confirm/index.tsx
app/components/base/copy-btn/index.tsx
app/components/base/copy-btn/style.module.css
app/components/base/copy-feedback/index.tsx
app/components/base/copy-icon/index.tsx
app/components/base/corner-label/index.tsx
app/components/base/dialog/index.tsx
app/components/base/divider/with-label.tsx
app/components/base/drawer-plus/index.tsx
app/components/base/drawer/index.tsx
app/components/base/dropdown/index.tsx
app/components/base/emoji-picker/Inner.tsx
app/components/base/emoji-picker/index.tsx
app/components/base/emoji-picker/style.module.css
app/components/base/features/context.tsx
app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-btn/index.tsx
app/components/base/features/new-feature-panel/annotation-reply/config-param-modal.tsx
app/components/base/features/new-feature-panel/annotation-reply/config-param.tsx
app/components/base/features/new-feature-panel/annotation-reply/index.tsx
app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/index.tsx
app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css
app/components/base/features/new-feature-panel/annotation-reply/score-slider/index.tsx
app/components/base/features/new-feature-panel/citation.tsx
app/components/base/features/new-feature-panel/conversation-opener/index.tsx
app/components/base/features/new-feature-panel/conversation-opener/modal.tsx
app/components/base/features/new-feature-panel/dialog-wrapper.tsx
app/components/base/features/new-feature-panel/feature-bar.tsx
app/components/base/features/new-feature-panel/feature-card.tsx
app/components/base/features/new-feature-panel/file-upload/index.tsx
app/components/base/features/new-feature-panel/file-upload/setting-content.tsx
app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx
app/components/base/features/new-feature-panel/follow-up.tsx
app/components/base/features/new-feature-panel/image-upload/index.tsx
app/components/base/features/new-feature-panel/index.tsx
app/components/base/features/new-feature-panel/moderation/form-generation.tsx
app/components/base/features/new-feature-panel/moderation/index.tsx
app/components/base/features/new-feature-panel/moderation/moderation-content.tsx
app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx
app/components/base/features/new-feature-panel/more-like-this.tsx
app/components/base/features/new-feature-panel/speech-to-text.tsx
app/components/base/features/new-feature-panel/text-to-speech/index.tsx
app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx
app/components/base/features/new-feature-panel/text-to-speech/voice-settings.tsx
app/components/base/file-uploader/audio-preview.tsx
app/components/base/file-uploader/file-from-link-or-local/index.tsx
app/components/base/file-uploader/file-image-render.tsx
app/components/base/file-uploader/file-input.tsx
app/components/base/file-uploader/file-list-in-log.tsx
app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx
app/components/base/file-uploader/file-uploader-in-attachment/index.tsx
app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx
app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx
app/components/base/file-uploader/file-uploader-in-chat-input/index.tsx
app/components/base/file-uploader/hooks.ts
app/components/base/file-uploader/pdf-preview.tsx
app/components/base/file-uploader/store.tsx
app/components/base/file-uploader/utils.ts
app/components/base/file-uploader/video-preview.tsx
app/components/base/fullscreen-modal/index.tsx
app/components/base/ga/index.tsx
app/components/base/grid-mask/index.tsx
app/components/base/icons/IconBase.tsx
app/components/base/icons/script.js
app/components/base/icons/src/image/llm/BaichuanTextCn.tsx
app/components/base/icons/src/image/llm/Minimax.tsx
app/components/base/icons/src/image/llm/MinimaxText.tsx
app/components/base/icons/src/image/llm/Tongyi.tsx
app/components/base/icons/src/image/llm/TongyiText.tsx
app/components/base/icons/src/image/llm/TongyiTextCn.tsx
app/components/base/icons/src/image/llm/Wxyy.tsx
app/components/base/icons/src/image/llm/WxyyText.tsx
app/components/base/icons/src/image/llm/WxyyTextCn.tsx
app/components/base/icons/src/public/avatar/Robot.json
app/components/base/icons/src/public/avatar/Robot.tsx
app/components/base/icons/src/public/avatar/User.json
app/components/base/icons/src/public/avatar/User.tsx
app/components/base/icons/src/public/billing/Sparkles.json
app/components/base/icons/src/public/billing/Sparkles.tsx
app/components/base/icons/src/public/billing/index.ts
app/components/base/icons/src/public/common/D.json
app/components/base/icons/src/public/common/D.tsx
app/components/base/icons/src/public/common/DiagonalDividingLine.json
app/components/base/icons/src/public/common/DiagonalDividingLine.tsx
app/components/base/icons/src/public/common/Dify.json
app/components/base/icons/src/public/common/Dify.tsx
app/components/base/icons/src/public/common/Github.json
app/components/base/icons/src/public/common/Github.tsx
app/components/base/icons/src/public/common/Highlight.json
app/components/base/icons/src/public/common/Highlight.tsx
app/components/base/icons/src/public/common/Line3.json
app/components/base/icons/src/public/common/Line3.tsx
app/components/base/icons/src/public/common/Lock.json
app/components/base/icons/src/public/common/Lock.tsx
app/components/base/icons/src/public/common/MessageChatSquare.json
app/components/base/icons/src/public/common/MessageChatSquare.tsx
app/components/base/icons/src/public/common/MultiPathRetrieval.json
app/components/base/icons/src/public/common/MultiPathRetrieval.tsx
app/components/base/icons/src/public/common/NTo1Retrieval.json
app/components/base/icons/src/public/common/NTo1Retrieval.tsx
app/components/base/icons/src/public/common/Notion.json
app/components/base/icons/src/public/common/Notion.tsx
app/components/base/icons/src/public/common/SparklesSoft.json
app/components/base/icons/src/public/common/SparklesSoft.tsx
app/components/base/icons/src/public/common/index.ts
app/components/base/icons/src/public/files/Csv.json
app/components/base/icons/src/public/files/Csv.tsx
app/components/base/icons/src/public/files/Doc.json
app/components/base/icons/src/public/files/Doc.tsx
app/components/base/icons/src/public/files/Docx.json
app/components/base/icons/src/public/files/Docx.tsx
app/components/base/icons/src/public/files/Html.json
app/components/base/icons/src/public/files/Html.tsx
app/components/base/icons/src/public/files/Json.json
app/components/base/icons/src/public/files/Json.tsx
app/components/base/icons/src/public/files/Md.json
app/components/base/icons/src/public/files/Md.tsx
app/components/base/icons/src/public/files/Pdf.json
app/components/base/icons/src/public/files/Pdf.tsx
app/components/base/icons/src/public/files/Txt.json
app/components/base/icons/src/public/files/Txt.tsx
app/components/base/icons/src/public/files/Unknown.json
app/components/base/icons/src/public/files/Unknown.tsx
app/components/base/icons/src/public/files/Xlsx.json
app/components/base/icons/src/public/files/Xlsx.tsx
app/components/base/icons/src/public/files/Yaml.json
app/components/base/icons/src/public/files/Yaml.tsx
app/components/base/icons/src/public/knowledge/Chunk.json
app/components/base/icons/src/public/knowledge/Chunk.tsx
app/components/base/icons/src/public/knowledge/Collapse.json
app/components/base/icons/src/public/knowledge/Collapse.tsx
app/components/base/icons/src/public/knowledge/GeneralType.json
app/components/base/icons/src/public/knowledge/GeneralType.tsx
app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json
app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.tsx
app/components/base/icons/src/public/knowledge/ParentChildType.json
app/components/base/icons/src/public/knowledge/ParentChildType.tsx
app/components/base/icons/src/public/knowledge/SelectionMod.json
app/components/base/icons/src/public/knowledge/SelectionMod.tsx
app/components/base/icons/src/public/llm/Anthropic.json
app/components/base/icons/src/public/llm/Anthropic.tsx
app/components/base/icons/src/public/llm/AnthropicText.json
app/components/base/icons/src/public/llm/AnthropicText.tsx
app/components/base/icons/src/public/llm/AzureOpenaiService.json
app/components/base/icons/src/public/llm/AzureOpenaiService.tsx
app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json
app/components/base/icons/src/public/llm/AzureOpenaiServiceText.tsx
app/components/base/icons/src/public/llm/Azureai.json
app/components/base/icons/src/public/llm/Azureai.tsx
app/components/base/icons/src/public/llm/AzureaiText.json
app/components/base/icons/src/public/llm/AzureaiText.tsx
app/components/base/icons/src/public/llm/Baichuan.json
app/components/base/icons/src/public/llm/Baichuan.tsx
app/components/base/icons/src/public/llm/BaichuanText.json
app/components/base/icons/src/public/llm/BaichuanText.tsx
app/components/base/icons/src/public/llm/Chatglm.json
app/components/base/icons/src/public/llm/Chatglm.tsx
app/components/base/icons/src/public/llm/ChatglmText.json
app/components/base/icons/src/public/llm/ChatglmText.tsx
app/components/base/icons/src/public/llm/Cohere.json
app/components/base/icons/src/public/llm/Cohere.tsx
app/components/base/icons/src/public/llm/CohereText.json
app/components/base/icons/src/public/llm/CohereText.tsx
app/components/base/icons/src/public/llm/Gpt3.json
app/components/base/icons/src/public/llm/Gpt3.tsx
app/components/base/icons/src/public/llm/Gpt4.json
app/components/base/icons/src/public/llm/Gpt4.tsx
app/components/base/icons/src/public/llm/Huggingface.json
app/components/base/icons/src/public/llm/Huggingface.tsx
app/components/base/icons/src/public/llm/HuggingfaceText.json
app/components/base/icons/src/public/llm/HuggingfaceText.tsx
app/components/base/icons/src/public/llm/HuggingfaceTextHub.json
app/components/base/icons/src/public/llm/HuggingfaceTextHub.tsx
app/components/base/icons/src/public/llm/IflytekSpark.json
app/components/base/icons/src/public/llm/IflytekSpark.tsx
app/components/base/icons/src/public/llm/IflytekSparkText.json
app/components/base/icons/src/public/llm/IflytekSparkText.tsx
app/components/base/icons/src/public/llm/IflytekSparkTextCn.json
app/components/base/icons/src/public/llm/IflytekSparkTextCn.tsx
app/components/base/icons/src/public/llm/Jina.json
app/components/base/icons/src/public/llm/Jina.tsx
app/components/base/icons/src/public/llm/JinaText.json
app/components/base/icons/src/public/llm/JinaText.tsx
app/components/base/icons/src/public/llm/Localai.json
app/components/base/icons/src/public/llm/Localai.tsx
app/components/base/icons/src/public/llm/LocalaiText.json
app/components/base/icons/src/public/llm/LocalaiText.tsx
app/components/base/icons/src/public/llm/Microsoft.json
app/components/base/icons/src/public/llm/Microsoft.tsx
app/components/base/icons/src/public/llm/OpenaiBlack.json
app/components/base/icons/src/public/llm/OpenaiBlack.tsx
app/components/base/icons/src/public/llm/OpenaiBlue.json
app/components/base/icons/src/public/llm/OpenaiBlue.tsx
app/components/base/icons/src/public/llm/OpenaiGreen.json
app/components/base/icons/src/public/llm/OpenaiGreen.tsx
app/components/base/icons/src/public/llm/OpenaiText.json
app/components/base/icons/src/public/llm/OpenaiText.tsx
app/components/base/icons/src/public/llm/OpenaiTransparent.json
app/components/base/icons/src/public/llm/OpenaiTransparent.tsx
app/components/base/icons/src/public/llm/OpenaiViolet.json
app/components/base/icons/src/public/llm/OpenaiViolet.tsx
app/components/base/icons/src/public/llm/Openllm.json
app/components/base/icons/src/public/llm/Openllm.tsx
app/components/base/icons/src/public/llm/OpenllmText.json
app/components/base/icons/src/public/llm/OpenllmText.tsx
app/components/base/icons/src/public/llm/Replicate.json
app/components/base/icons/src/public/llm/Replicate.tsx
app/components/base/icons/src/public/llm/ReplicateText.json
app/components/base/icons/src/public/llm/ReplicateText.tsx
app/components/base/icons/src/public/llm/XorbitsInference.json
app/components/base/icons/src/public/llm/XorbitsInference.tsx
app/components/base/icons/src/public/llm/XorbitsInferenceText.json
app/components/base/icons/src/public/llm/XorbitsInferenceText.tsx
app/components/base/icons/src/public/llm/Zhipuai.json
app/components/base/icons/src/public/llm/Zhipuai.tsx
app/components/base/icons/src/public/llm/ZhipuaiText.json
app/components/base/icons/src/public/llm/ZhipuaiText.tsx
app/components/base/icons/src/public/llm/ZhipuaiTextCn.json
app/components/base/icons/src/public/llm/ZhipuaiTextCn.tsx
app/components/base/icons/src/public/llm/index.ts
app/components/base/icons/src/public/model/Checked.json
app/components/base/icons/src/public/model/Checked.tsx
app/components/base/icons/src/public/other/DefaultToolIcon.json
app/components/base/icons/src/public/other/DefaultToolIcon.tsx
app/components/base/icons/src/public/other/Icon3Dots.json
app/components/base/icons/src/public/other/Icon3Dots.tsx
app/components/base/icons/src/public/other/RowStruct.json
app/components/base/icons/src/public/other/RowStruct.tsx
app/components/base/icons/src/public/other/index.ts
app/components/base/icons/src/public/plugins/Google.json
app/components/base/icons/src/public/plugins/Google.tsx
app/components/base/icons/src/public/plugins/WebReader.json
app/components/base/icons/src/public/plugins/WebReader.tsx
app/components/base/icons/src/public/plugins/Wikipedia.json
app/components/base/icons/src/public/plugins/Wikipedia.tsx
app/components/base/icons/src/public/plugins/index.ts
app/components/base/icons/src/public/thought/DataSet.json
app/components/base/icons/src/public/thought/DataSet.tsx
app/components/base/icons/src/public/thought/Loading.json
app/components/base/icons/src/public/thought/Loading.tsx
app/components/base/icons/src/public/thought/Search.json
app/components/base/icons/src/public/thought/Search.tsx
app/components/base/icons/src/public/thought/ThoughtList.json
app/components/base/icons/src/public/thought/ThoughtList.tsx
app/components/base/icons/src/public/thought/WebReader.json
app/components/base/icons/src/public/thought/WebReader.tsx
app/components/base/icons/src/public/tracing/LangfuseIcon.json
app/components/base/icons/src/public/tracing/LangfuseIcon.tsx
app/components/base/icons/src/public/tracing/LangfuseIconBig.json
app/components/base/icons/src/public/tracing/LangfuseIconBig.tsx
app/components/base/icons/src/public/tracing/LangsmithIcon.json
app/components/base/icons/src/public/tracing/LangsmithIcon.tsx
app/components/base/icons/src/public/tracing/LangsmithIconBig.json
app/components/base/icons/src/public/tracing/LangsmithIconBig.tsx
app/components/base/icons/src/public/tracing/OpikIcon.json
app/components/base/icons/src/public/tracing/OpikIcon.tsx
app/components/base/icons/src/public/tracing/OpikIconBig.json
app/components/base/icons/src/public/tracing/OpikIconBig.tsx
app/components/base/icons/src/public/tracing/TracingIcon.json
app/components/base/icons/src/public/tracing/TracingIcon.tsx
app/components/base/icons/src/public/tracing/index.ts
app/components/base/icons/src/vender/features/Citations.tsx
app/components/base/icons/src/vender/features/ContentModeration.tsx
app/components/base/icons/src/vender/features/Document.tsx
app/components/base/icons/src/vender/features/FolderUpload.tsx
app/components/base/icons/src/vender/features/LoveMessage.tsx
app/components/base/icons/src/vender/features/MessageFast.tsx
app/components/base/icons/src/vender/features/Microphone01.tsx
app/components/base/icons/src/vender/features/TextToAudio.tsx
app/components/base/icons/src/vender/features/VirtualAssistant.tsx
app/components/base/icons/src/vender/features/Vision.tsx
app/components/base/icons/src/vender/line/alertsAndFeedback/AlertTriangle.tsx
app/components/base/icons/src/vender/line/alertsAndFeedback/ThumbsDown.tsx
app/components/base/icons/src/vender/line/alertsAndFeedback/ThumbsUp.tsx
app/components/base/icons/src/vender/line/arrows/ArrowNarrowLeft.tsx
app/components/base/icons/src/vender/line/arrows/ArrowUpRight.tsx
app/components/base/icons/src/vender/line/arrows/ChevronDownDouble.tsx
app/components/base/icons/src/vender/line/arrows/ChevronRight.tsx
app/components/base/icons/src/vender/line/arrows/ChevronSelectorVertical.tsx
app/components/base/icons/src/vender/line/arrows/RefreshCcw01.tsx
app/components/base/icons/src/vender/line/arrows/RefreshCw05.tsx
app/components/base/icons/src/vender/line/arrows/ReverseLeft.tsx
app/components/base/icons/src/vender/line/communication/AiText.tsx
app/components/base/icons/src/vender/line/communication/ChatBot.tsx
app/components/base/icons/src/vender/line/communication/ChatBotSlim.tsx
app/components/base/icons/src/vender/line/communication/CuteRobot.tsx
app/components/base/icons/src/vender/line/communication/MessageCheckRemove.tsx
app/components/base/icons/src/vender/line/communication/MessageFastPlus.tsx
app/components/base/icons/src/vender/line/development/ArtificialBrain.tsx
app/components/base/icons/src/vender/line/development/BarChartSquare02.tsx
app/components/base/icons/src/vender/line/development/BracketsX.tsx
app/components/base/icons/src/vender/line/development/CodeBrowser.tsx
app/components/base/icons/src/vender/line/development/Container.tsx
app/components/base/icons/src/vender/line/development/Database01.tsx
app/components/base/icons/src/vender/line/development/Database03.tsx
app/components/base/icons/src/vender/line/development/FileHeart02.tsx
app/components/base/icons/src/vender/line/development/GitBranch01.tsx
app/components/base/icons/src/vender/line/development/PromptEngineering.tsx
app/components/base/icons/src/vender/line/development/PuzzlePiece01.tsx
app/components/base/icons/src/vender/line/development/TerminalSquare.tsx
app/components/base/icons/src/vender/line/development/Variable.tsx
app/components/base/icons/src/vender/line/development/Webhooks.tsx
app/components/base/icons/src/vender/line/editor/AlignLeft.tsx
app/components/base/icons/src/vender/line/editor/BezierCurve03.tsx
app/components/base/icons/src/vender/line/editor/Colors.tsx
app/components/base/icons/src/vender/line/editor/ImageIndentLeft.tsx
app/components/base/icons/src/vender/line/editor/LeftIndent02.tsx
app/components/base/icons/src/vender/line/editor/LetterSpacing01.tsx
app/components/base/icons/src/vender/line/editor/TypeSquare.tsx
app/components/base/icons/src/vender/line/editor/index.ts
app/components/base/icons/src/vender/line/education/BookOpen01.tsx
app/components/base/icons/src/vender/line/files/Clipboard.tsx
app/components/base/icons/src/vender/line/files/ClipboardCheck.tsx
app/components/base/icons/src/vender/line/files/File02.tsx
app/components/base/icons/src/vender/line/files/FileArrow01.tsx
app/components/base/icons/src/vender/line/files/FileCheck02.tsx
app/components/base/icons/src/vender/line/files/FileDownload02.tsx
app/components/base/icons/src/vender/line/files/FilePlus01.tsx
app/components/base/icons/src/vender/line/files/FilePlus02.tsx
app/components/base/icons/src/vender/line/files/FileText.tsx
app/components/base/icons/src/vender/line/files/FileUpload.tsx
app/components/base/icons/src/vender/line/files/Folder.tsx
app/components/base/icons/src/vender/line/financeAndECommerce/Balance.tsx
app/components/base/icons/src/vender/line/financeAndECommerce/CoinsStacked01.tsx
app/components/base/icons/src/vender/line/financeAndECommerce/GoldCoin.tsx
app/components/base/icons/src/vender/line/financeAndECommerce/ReceiptList.tsx
app/components/base/icons/src/vender/line/financeAndECommerce/Tag01.tsx
app/components/base/icons/src/vender/line/financeAndECommerce/Tag03.tsx
app/components/base/icons/src/vender/line/general/AtSign.tsx
app/components/base/icons/src/vender/line/general/Bookmark.tsx
app/components/base/icons/src/vender/line/general/Check.tsx
app/components/base/icons/src/vender/line/general/CheckDone01.tsx
app/components/base/icons/src/vender/line/general/ChecklistSquare.tsx
app/components/base/icons/src/vender/line/general/DotsGrid.tsx
app/components/base/icons/src/vender/line/general/Edit02.tsx
app/components/base/icons/src/vender/line/general/Edit04.tsx
app/components/base/icons/src/vender/line/general/Edit05.tsx
app/components/base/icons/src/vender/line/general/Hash02.tsx
app/components/base/icons/src/vender/line/general/InfoCircle.tsx
app/components/base/icons/src/vender/line/general/Link03.tsx
app/components/base/icons/src/vender/line/general/LinkExternal02.tsx
app/components/base/icons/src/vender/line/general/LogIn04.tsx
app/components/base/icons/src/vender/line/general/LogOut01.tsx
app/components/base/icons/src/vender/line/general/LogOut04.tsx
app/components/base/icons/src/vender/line/general/Menu01.tsx
app/components/base/icons/src/vender/line/general/Pin01.tsx
app/components/base/icons/src/vender/line/general/Pin02.tsx
app/components/base/icons/src/vender/line/general/Plus02.tsx
app/components/base/icons/src/vender/line/general/Refresh.tsx
app/components/base/icons/src/vender/line/general/Settings01.tsx
app/components/base/icons/src/vender/line/general/Settings04.tsx
app/components/base/icons/src/vender/line/general/Target04.tsx
app/components/base/icons/src/vender/line/general/Upload03.tsx
app/components/base/icons/src/vender/line/general/UploadCloud01.tsx
app/components/base/icons/src/vender/line/general/X.tsx
app/components/base/icons/src/vender/line/images/ImagePlus.tsx
app/components/base/icons/src/vender/line/layout/AlignLeft01.tsx
app/components/base/icons/src/vender/line/layout/AlignRight01.tsx
app/components/base/icons/src/vender/line/layout/Grid01.tsx
app/components/base/icons/src/vender/line/layout/LayoutGrid02.tsx
app/components/base/icons/src/vender/line/mapsAndTravel/Globe01.tsx
app/components/base/icons/src/vender/line/mapsAndTravel/Route.tsx
app/components/base/icons/src/vender/line/mediaAndDevices/Microphone01.tsx
app/components/base/icons/src/vender/line/mediaAndDevices/PlayCircle.tsx
app/components/base/icons/src/vender/line/mediaAndDevices/SlidersH.tsx
app/components/base/icons/src/vender/line/mediaAndDevices/Speaker.tsx
app/components/base/icons/src/vender/line/mediaAndDevices/Stop.tsx
app/components/base/icons/src/vender/line/mediaAndDevices/StopCircle.tsx
app/components/base/icons/src/vender/line/others/Apps02.tsx
app/components/base/icons/src/vender/line/others/BubbleX.tsx
app/components/base/icons/src/vender/line/others/Colors.tsx
app/components/base/icons/src/vender/line/others/DragHandle.tsx
app/components/base/icons/src/vender/line/others/Env.tsx
app/components/base/icons/src/vender/line/others/Exchange02.tsx
app/components/base/icons/src/vender/line/others/FileCode.tsx
app/components/base/icons/src/vender/line/others/GlobalVariable.tsx
app/components/base/icons/src/vender/line/others/Icon3Dots.tsx
app/components/base/icons/src/vender/line/others/LongArrowLeft.tsx
app/components/base/icons/src/vender/line/others/LongArrowRight.tsx
app/components/base/icons/src/vender/line/others/Tools.tsx
app/components/base/icons/src/vender/line/shapes/CubeOutline.tsx
app/components/base/icons/src/vender/line/time/ClockFastForward.tsx
app/components/base/icons/src/vender/line/time/ClockPlay.tsx
app/components/base/icons/src/vender/line/time/ClockPlaySlim.tsx
app/components/base/icons/src/vender/line/time/ClockRefresh.tsx
app/components/base/icons/src/vender/line/users/User01.tsx
app/components/base/icons/src/vender/line/users/Users01.tsx
app/components/base/icons/src/vender/line/weather/Stars02.tsx
app/components/base/icons/src/vender/other/Generator.tsx
app/components/base/icons/src/vender/other/ReplayLine.tsx
app/components/base/icons/src/vender/other/index.ts
app/components/base/icons/src/vender/solid/FinanceAndECommerce/GoldCoin.tsx
app/components/base/icons/src/vender/solid/FinanceAndECommerce/Scales02.tsx
app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle.tsx
app/components/base/icons/src/vender/solid/arrows/ChevronDown.tsx
app/components/base/icons/src/vender/solid/arrows/HighPriority.tsx
app/components/base/icons/src/vender/solid/communication/AiText.tsx
app/components/base/icons/src/vender/solid/communication/BubbleTextMod.tsx
app/components/base/icons/src/vender/solid/communication/ChatBot.tsx
app/components/base/icons/src/vender/solid/communication/CuteRobot.tsx
app/components/base/icons/src/vender/solid/communication/EditList.tsx
app/components/base/icons/src/vender/solid/communication/ListSparkle.tsx
app/components/base/icons/src/vender/solid/communication/Logic.tsx
app/components/base/icons/src/vender/solid/communication/MessageDotsCircle.tsx
app/components/base/icons/src/vender/solid/communication/MessageFast.tsx
app/components/base/icons/src/vender/solid/communication/MessageHeartCircle.tsx
app/components/base/icons/src/vender/solid/communication/MessageSmileSquare.tsx
app/components/base/icons/src/vender/solid/communication/Send03.tsx
app/components/base/icons/src/vender/solid/development/ApiConnection.tsx
app/components/base/icons/src/vender/solid/development/ApiConnectionMod.tsx
app/components/base/icons/src/vender/solid/development/BarChartSquare02.tsx
app/components/base/icons/src/vender/solid/development/Container.tsx
app/components/base/icons/src/vender/solid/development/Database02.tsx
app/components/base/icons/src/vender/solid/development/Database03.tsx
app/components/base/icons/src/vender/solid/development/FileHeart02.tsx
app/components/base/icons/src/vender/solid/development/PatternRecognition.tsx
app/components/base/icons/src/vender/solid/development/PromptEngineering.tsx
app/components/base/icons/src/vender/solid/development/PuzzlePiece01.tsx
app/components/base/icons/src/vender/solid/development/Semantic.tsx
app/components/base/icons/src/vender/solid/development/TerminalSquare.tsx
app/components/base/icons/src/vender/solid/development/Variable02.tsx
app/components/base/icons/src/vender/solid/editor/Brush01.tsx
app/components/base/icons/src/vender/solid/editor/Citations.tsx
app/components/base/icons/src/vender/solid/editor/Colors.tsx
app/components/base/icons/src/vender/solid/editor/Paragraph.tsx
app/components/base/icons/src/vender/solid/editor/TypeSquare.tsx
app/components/base/icons/src/vender/solid/education/Beaker02.tsx
app/components/base/icons/src/vender/solid/education/BubbleText.tsx
app/components/base/icons/src/vender/solid/education/Heart02.tsx
app/components/base/icons/src/vender/solid/education/Unblur.tsx
app/components/base/icons/src/vender/solid/files/File05.tsx
app/components/base/icons/src/vender/solid/files/FileSearch02.tsx
app/components/base/icons/src/vender/solid/files/Folder.tsx
app/components/base/icons/src/vender/solid/files/index.ts
app/components/base/icons/src/vender/solid/general/AnswerTriangle.tsx
app/components/base/icons/src/vender/solid/general/CheckCircle.tsx
app/components/base/icons/src/vender/solid/general/CheckDone01.tsx
app/components/base/icons/src/vender/solid/general/Download02.tsx
app/components/base/icons/src/vender/solid/general/Edit03.tsx
app/components/base/icons/src/vender/solid/general/Edit04.tsx
app/components/base/icons/src/vender/solid/general/Eye.tsx
app/components/base/icons/src/vender/solid/general/MessageClockCircle.tsx
app/components/base/icons/src/vender/solid/general/PlusCircle.tsx
app/components/base/icons/src/vender/solid/general/QuestionTriangle.tsx
app/components/base/icons/src/vender/solid/general/SearchMd.tsx
app/components/base/icons/src/vender/solid/general/Target04.tsx
app/components/base/icons/src/vender/solid/general/Tool03.tsx
app/components/base/icons/src/vender/solid/general/XCircle.tsx
app/components/base/icons/src/vender/solid/general/ZapFast.tsx
app/components/base/icons/src/vender/solid/general/ZapNarrow.tsx
app/components/base/icons/src/vender/solid/general/index.ts
app/components/base/icons/src/vender/solid/layout/Grid01.tsx
app/components/base/icons/src/vender/solid/mapsAndTravel/Globe06.tsx
app/components/base/icons/src/vender/solid/mapsAndTravel/Route.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/MagicBox.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/MagicEyes.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/MagicWand.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/Microphone01.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/Play.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/Robot.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/Sliders02.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/Speaker.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/StopCircle.tsx
app/components/base/icons/src/vender/solid/mediaAndDevices/index.ts
app/components/base/icons/src/vender/solid/security/Lock01.tsx
app/components/base/icons/src/vender/solid/shapes/Corner.tsx
app/components/base/icons/src/vender/solid/shapes/Star04.tsx
app/components/base/icons/src/vender/solid/shapes/Star06.tsx
app/components/base/icons/src/vender/solid/users/User01.tsx
app/components/base/icons/src/vender/solid/users/UserEdit02.tsx
app/components/base/icons/src/vender/solid/users/Users01.tsx
app/components/base/icons/src/vender/solid/users/UsersPlus.tsx
app/components/base/icons/src/vender/workflow/Answer.tsx
app/components/base/icons/src/vender/workflow/Assigner.tsx
app/components/base/icons/src/vender/workflow/Code.tsx
app/components/base/icons/src/vender/workflow/DocsExtractor.tsx
app/components/base/icons/src/vender/workflow/End.tsx
app/components/base/icons/src/vender/workflow/Home.tsx
app/components/base/icons/src/vender/workflow/Http.tsx
app/components/base/icons/src/vender/workflow/IfElse.tsx
app/components/base/icons/src/vender/workflow/Iteration.tsx
app/components/base/icons/src/vender/workflow/IterationStart.tsx
app/components/base/icons/src/vender/workflow/Jinja.tsx
app/components/base/icons/src/vender/workflow/KnowledgeRetrieval.tsx
app/components/base/icons/src/vender/workflow/ListFilter.tsx
app/components/base/icons/src/vender/workflow/Llm.tsx
app/components/base/icons/src/vender/workflow/ParameterExtractor.tsx
app/components/base/icons/src/vender/workflow/QuestionClassifier.tsx
app/components/base/icons/src/vender/workflow/TemplatingTransform.tsx
app/components/base/icons/src/vender/workflow/VariableX.tsx
app/components/base/icons/src/vender/workflow/index.ts
app/components/base/image-gallery/index.tsx
app/components/base/image-gallery/style.module.css
app/components/base/image-uploader/audio-preview.tsx
app/components/base/image-uploader/chat-image-uploader.tsx
app/components/base/image-uploader/image-link-input.tsx
app/components/base/image-uploader/image-list.tsx
app/components/base/image-uploader/image-preview.tsx
app/components/base/image-uploader/text-generation-image-uploader.tsx
app/components/base/image-uploader/uploader.tsx
app/components/base/image-uploader/video-preview.tsx
app/components/base/input-number/index.tsx
app/components/base/input/index.tsx
app/components/base/linked-apps-panel/index.tsx
app/components/base/list-empty/index.tsx
app/components/base/loading/index.tsx
app/components/base/logo/logo-embedded-chat-avatar.tsx
app/components/base/logo/logo-embedded-chat-header.tsx
app/components/base/logo/logo-site.tsx
app/components/base/markdown-blocks/button.tsx
app/components/base/markdown-blocks/form.tsx
app/components/base/markdown.tsx
app/components/base/mermaid/index.tsx
app/components/base/message-log-modal/index.tsx
app/components/base/modal/index.tsx
app/components/base/notion-icon/index.module.css
app/components/base/notion-icon/index.tsx
app/components/base/notion-page-selector/base.module.css
app/components/base/notion-page-selector/base.tsx
app/components/base/notion-page-selector/notion-page-selector-modal/index.module.css
app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx
app/components/base/notion-page-selector/page-selector/index.module.css
app/components/base/notion-page-selector/page-selector/index.tsx
app/components/base/notion-page-selector/search-input/index.module.css
app/components/base/notion-page-selector/search-input/index.tsx
app/components/base/notion-page-selector/workspace-selector/index.module.css
app/components/base/notion-page-selector/workspace-selector/index.tsx
app/components/base/pagination/hook.ts
app/components/base/pagination/index.tsx
app/components/base/pagination/pagination.tsx
app/components/base/pagination/type.ts
app/components/base/param-item/index.tsx
app/components/base/param-item/score-threshold-item.tsx
app/components/base/param-item/top-k-item.tsx
app/components/base/popover/index.tsx
app/components/base/portal-to-follow-elem/index.tsx
app/components/base/premium-badge/index.css
app/components/base/premium-badge/index.tsx
app/components/base/progress-bar/index.tsx
app/components/base/prompt-editor/constants.tsx
app/components/base/prompt-editor/index.tsx
app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx
app/components/base/prompt-editor/plugins/component-picker-block/index.tsx
app/components/base/prompt-editor/plugins/component-picker-block/menu.tsx
app/components/base/prompt-editor/plugins/component-picker-block/prompt-option.tsx
app/components/base/prompt-editor/plugins/component-picker-block/variable-option.tsx
app/components/base/prompt-editor/plugins/context-block/component.tsx
app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx
app/components/base/prompt-editor/plugins/context-block/index.tsx
app/components/base/prompt-editor/plugins/context-block/node.tsx
app/components/base/prompt-editor/plugins/custom-text/node.tsx
app/components/base/prompt-editor/plugins/history-block/component.tsx
app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx
app/components/base/prompt-editor/plugins/history-block/index.tsx
app/components/base/prompt-editor/plugins/history-block/node.tsx
app/components/base/prompt-editor/plugins/on-blur-or-focus-block.tsx
app/components/base/prompt-editor/plugins/placeholder.tsx
app/components/base/prompt-editor/plugins/query-block/component.tsx
app/components/base/prompt-editor/plugins/query-block/node.tsx
app/components/base/prompt-editor/plugins/variable-value-block/node.tsx
app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx
app/components/base/prompt-editor/plugins/workflow-variable-block/index.tsx
app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx
app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx
app/components/base/prompt-editor/types.ts
app/components/base/prompt-editor/utils.ts
app/components/base/prompt-log-modal/card.tsx
app/components/base/prompt-log-modal/index.tsx
app/components/base/qrcode/index.tsx
app/components/base/qrcode/style.module.css
app/components/base/radio-card/index.tsx
app/components/base/radio-card/simple/index.tsx
app/components/base/radio-card/simple/style.module.css
app/components/base/radio/component/group/index.tsx
app/components/base/radio/component/radio/index.tsx
app/components/base/radio/ui.tsx
app/components/base/regenerate-btn/index.tsx
app/components/base/search-input/index.tsx
app/components/base/select/index.tsx
app/components/base/select/locale.tsx
app/components/base/skeleton/index.tsx
app/components/base/slider/index.tsx
app/components/base/slider/style.css
app/components/base/sort/index.tsx
app/components/base/spinner/index.tsx
app/components/base/svg-gallery/index.tsx
app/components/base/svg/index.tsx
app/components/base/switch/index.tsx
app/components/base/tab-header/index.tsx
app/components/base/tab-header/style.module.css
app/components/base/tab-slider-new/index.tsx
app/components/base/tab-slider-plain/index.tsx
app/components/base/tab-slider/index.tsx
app/components/base/tag-input/index.tsx
app/components/base/tag-management/filter.tsx
app/components/base/tag-management/index.tsx
app/components/base/tag-management/selector.tsx
app/components/base/tag-management/style.module.css
app/components/base/tag-management/tag-item-editor.tsx
app/components/base/tag-management/tag-remove-modal.tsx
app/components/base/text-generation/types.ts
app/components/base/textarea/index.tsx
app/components/base/toast/index.tsx
app/components/base/toast/style.module.css
app/components/base/tooltip/index.tsx
app/components/base/video-gallery/index.tsx
app/components/base/voice-input/index.module.css
app/components/base/voice-input/index.tsx
app/components/billing/annotation-full/index.tsx
app/components/billing/annotation-full/modal.tsx
app/components/billing/annotation-full/style.module.css
app/components/billing/apps-full-in-dialog/index.tsx
app/components/billing/apps-full-in-dialog/style.module.css
app/components/billing/apps-full/index.tsx
app/components/billing/apps-full/style.module.css
app/components/billing/billing-page/index.tsx
app/components/billing/config.ts
app/components/billing/header-billing-btn/index.tsx
app/components/billing/plan/index.tsx
app/components/billing/pricing/index.tsx
app/components/billing/pricing/plan-item.tsx
app/components/billing/pricing/select-plan-range.tsx
app/components/billing/priority-label/index.tsx
app/components/billing/progress-bar/index.tsx
app/components/billing/type.ts
app/components/billing/upgrade-btn/index.tsx
app/components/billing/upgrade-btn/style.module.css
app/components/billing/usage-info/apps-info.tsx
app/components/billing/usage-info/index.tsx
app/components/billing/usage-info/vector-space-info.tsx
app/components/billing/vector-space-full/index.tsx
app/components/billing/vector-space-full/style.module.css
app/components/browser-initor.tsx
app/components/custom/custom-page/index.tsx
app/components/custom/custom-web-app-brand/index.tsx
app/components/custom/custom-web-app-brand/style.module.css
app/components/custom/style.module.css
app/components/datasets/api/index.tsx
app/components/datasets/chunk.tsx
app/components/datasets/common/chunking-mode-label.tsx
app/components/datasets/common/document-picker/document-list.tsx
app/components/datasets/common/document-picker/index.tsx
app/components/datasets/common/document-picker/preview-document-picker.tsx
app/components/datasets/common/document-status-with-action/index-failed.tsx
app/components/datasets/common/document-status-with-action/status-with-action.tsx
app/components/datasets/common/economical-retrieval-method-config/index.tsx
app/components/datasets/common/retrieval-method-config/index.tsx
app/components/datasets/common/retrieval-method-info/index.tsx
app/components/datasets/common/retrieval-param-config/index.tsx
app/components/datasets/create/embedding-process/index.tsx
app/components/datasets/create/empty-dataset-creation-modal/index.module.css
app/components/datasets/create/empty-dataset-creation-modal/index.tsx
app/components/datasets/create/file-preview/index.module.css
app/components/datasets/create/file-preview/index.tsx
app/components/datasets/create/file-uploader/index.tsx
app/components/datasets/create/index.tsx
app/components/datasets/create/notion-page-preview/index.module.css
app/components/datasets/create/notion-page-preview/index.tsx
app/components/datasets/create/step-one/index.module.css
app/components/datasets/create/step-one/index.tsx
app/components/datasets/create/step-three/index.tsx
app/components/datasets/create/step-two/index.module.css
app/components/datasets/create/step-two/index.tsx
app/components/datasets/create/step-two/inputs.tsx
app/components/datasets/create/step-two/language-select/index.tsx
app/components/datasets/create/step-two/option-card.tsx
app/components/datasets/create/step-two/preview-item/index.tsx
app/components/datasets/create/step-two/unescape.ts
app/components/datasets/create/stepper/index.tsx
app/components/datasets/create/stepper/step.tsx
app/components/datasets/create/steps-nav-bar/index.module.css
app/components/datasets/create/steps-nav-bar/index.tsx
app/components/datasets/create/stop-embedding-modal/index.tsx
app/components/datasets/create/top-bar/index.tsx
app/components/datasets/create/website/base/checkbox-with-label.tsx
app/components/datasets/create/website/base/crawled-result-item.tsx
app/components/datasets/create/website/base/crawled-result.tsx
app/components/datasets/create/website/base/crawling.tsx
app/components/datasets/create/website/base/error-message.tsx
app/components/datasets/create/website/base/field.tsx
app/components/datasets/create/website/base/input.tsx
app/components/datasets/create/website/base/options-wrap.tsx
app/components/datasets/create/website/base/url-input.tsx
app/components/datasets/create/website/firecrawl/header.tsx
app/components/datasets/create/website/firecrawl/index.tsx
app/components/datasets/create/website/firecrawl/options.tsx
app/components/datasets/create/website/index.module.css
app/components/datasets/create/website/index.tsx
app/components/datasets/create/website/jina-reader/base/checkbox-with-label.tsx
app/components/datasets/create/website/jina-reader/base/error-message.tsx
app/components/datasets/create/website/jina-reader/base/field.tsx
app/components/datasets/create/website/jina-reader/base/input.tsx
app/components/datasets/create/website/jina-reader/base/options-wrap.tsx
app/components/datasets/create/website/jina-reader/crawled-result-item.tsx
app/components/datasets/create/website/jina-reader/crawled-result.tsx
app/components/datasets/create/website/jina-reader/crawling.tsx
app/components/datasets/create/website/jina-reader/header.tsx
app/components/datasets/create/website/jina-reader/index.tsx
app/components/datasets/create/website/jina-reader/mock-crawl-result.ts
app/components/datasets/create/website/jina-reader/options.tsx
app/components/datasets/create/website/no-data.tsx
app/components/datasets/create/website/preview.tsx
app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx
app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx
app/components/datasets/documents/detail/batch-modal/index.tsx
app/components/datasets/documents/detail/completed/SegmentCard.tsx
app/components/datasets/documents/detail/completed/child-segment-detail.tsx
app/components/datasets/documents/detail/completed/child-segment-list.tsx
app/components/datasets/documents/detail/completed/common/action-buttons.tsx
app/components/datasets/documents/detail/completed/common/add-another.tsx
app/components/datasets/documents/detail/completed/common/batch-action.tsx
app/components/datasets/documents/detail/completed/common/chunk-content.tsx
app/components/datasets/documents/detail/completed/common/dot.tsx
app/components/datasets/documents/detail/completed/common/empty.tsx
app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx
app/components/datasets/documents/detail/completed/common/keywords.tsx
app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx
app/components/datasets/documents/detail/completed/common/segment-index-tag.tsx
app/components/datasets/documents/detail/completed/common/tag.tsx
app/components/datasets/documents/detail/completed/display-toggle.tsx
app/components/datasets/documents/detail/completed/index.tsx
app/components/datasets/documents/detail/completed/new-child-segment.tsx
app/components/datasets/documents/detail/completed/segment-card.tsx
app/components/datasets/documents/detail/completed/segment-detail.tsx
app/components/datasets/documents/detail/completed/segment-list.tsx
app/components/datasets/documents/detail/completed/skeleton/full-doc-list-skeleton.tsx
app/components/datasets/documents/detail/completed/skeleton/general-list-skeleton.tsx
app/components/datasets/documents/detail/completed/skeleton/paragraph-list-skeleton.tsx
app/components/datasets/documents/detail/completed/skeleton/parent-chunk-card-skeleton.tsx
app/components/datasets/documents/detail/completed/status-item.tsx
app/components/datasets/documents/detail/embedding/index.tsx
app/components/datasets/documents/detail/embedding/skeleton/index.tsx
app/components/datasets/documents/detail/index.tsx
app/components/datasets/documents/detail/metadata/index.tsx
app/components/datasets/documents/detail/new-segment.tsx
app/components/datasets/documents/detail/segment-add/index.tsx
app/components/datasets/documents/detail/settings/index.tsx
app/components/datasets/documents/index.tsx
app/components/datasets/documents/list.tsx
app/components/datasets/documents/rename-modal.tsx
app/components/datasets/documents/style.module.css
app/components/datasets/external-api/external-api-modal/Form.tsx
app/components/datasets/external-api/external-api-modal/index.tsx
app/components/datasets/external-api/external-api-panel/index.tsx
app/components/datasets/external-api/external-knowledge-api-card/index.tsx
app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx
app/components/datasets/external-knowledge-base/create/ExternalApiSelection.tsx
app/components/datasets/external-knowledge-base/create/InfoPanel.tsx
app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx
app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx
app/components/datasets/external-knowledge-base/create/index.tsx
app/components/datasets/formatted-text/flavours/edit-slice.tsx
app/components/datasets/formatted-text/flavours/preview-slice.tsx
app/components/datasets/formatted-text/flavours/shared.tsx
app/components/datasets/hit-testing/components/child-chunks-item.tsx
app/components/datasets/hit-testing/components/chunk-detail-modal.tsx
app/components/datasets/hit-testing/components/result-item-external.tsx
app/components/datasets/hit-testing/components/result-item-footer.tsx
app/components/datasets/hit-testing/components/result-item-meta.tsx
app/components/datasets/hit-testing/components/result-item.tsx
app/components/datasets/hit-testing/components/score.tsx
app/components/datasets/hit-testing/index.tsx
app/components/datasets/hit-testing/modify-external-retrieval-modal.tsx
app/components/datasets/hit-testing/modify-retrieval-modal.tsx
app/components/datasets/hit-testing/style.module.css
app/components/datasets/hit-testing/textarea.tsx
app/components/datasets/preview/container.tsx
app/components/datasets/preview/header.tsx
app/components/datasets/rename-modal/index.tsx
app/components/datasets/settings/form/index.tsx
app/components/datasets/settings/index-method-radio/index.tsx
app/components/datasets/settings/permission-selector/index.tsx
app/components/develop/code.tsx
app/components/develop/doc.tsx
app/components/develop/index.tsx
app/components/develop/md.tsx
app/components/develop/secret-key/input-copy.tsx
app/components/develop/secret-key/secret-key-button.tsx
app/components/develop/secret-key/secret-key-generate.tsx
app/components/develop/secret-key/secret-key-modal.tsx
app/components/develop/secret-key/style.module.css
app/components/develop/template/template.en.mdx
app/components/develop/template/template.ja.mdx
app/components/develop/template/template.zh.mdx
app/components/develop/template/template_advanced_chat.en.mdx
app/components/develop/template/template_advanced_chat.ja.mdx
app/components/develop/template/template_advanced_chat.zh.mdx
app/components/develop/template/template_chat.en.mdx
app/components/develop/template/template_chat.ja.mdx
app/components/develop/template/template_chat.zh.mdx
app/components/develop/template/template_workflow.en.mdx
app/components/develop/template/template_workflow.ja.mdx
app/components/develop/template/template_workflow.zh.mdx
app/components/explore/app-card/index.tsx
app/components/explore/app-list/index.tsx
app/components/explore/app-list/style.module.css
app/components/explore/category.tsx
app/components/explore/create-app-modal/index.tsx
app/components/explore/index.tsx
app/components/explore/installed-app/index.tsx
app/components/explore/item-operation/index.tsx
app/components/explore/item-operation/style.module.css
app/components/explore/sidebar/app-nav-item/index.tsx
app/components/explore/sidebar/app-nav-item/style.module.css
app/components/explore/sidebar/index.tsx
app/components/header/account-about/index.module.css
app/components/header/account-about/index.tsx
app/components/header/account-dropdown/index.tsx
app/components/header/account-dropdown/workplace-selector/index.module.css
app/components/header/account-dropdown/workplace-selector/index.tsx
app/components/header/account-setting/Integrations-page/index.module.css
app/components/header/account-setting/Integrations-page/index.tsx
app/components/header/account-setting/api-based-extension-page/empty.tsx
app/components/header/account-setting/api-based-extension-page/index.tsx
app/components/header/account-setting/api-based-extension-page/item.tsx
app/components/header/account-setting/api-based-extension-page/modal.tsx
app/components/header/account-setting/api-based-extension-page/selector.tsx
app/components/header/account-setting/collapse/index.tsx
app/components/header/account-setting/data-source-page/data-source-notion/index.tsx
app/components/header/account-setting/data-source-page/data-source-notion/operate/index.tsx
app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx
app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx
app/components/header/account-setting/data-source-page/data-source-website/index.tsx
app/components/header/account-setting/data-source-page/index.tsx
app/components/header/account-setting/data-source-page/panel/config-item.tsx
app/components/header/account-setting/data-source-page/panel/index.tsx
app/components/header/account-setting/data-source-page/panel/style.module.css
app/components/header/account-setting/index.module.css
app/components/header/account-setting/index.tsx
app/components/header/account-setting/key-validator/KeyInput.tsx
app/components/header/account-setting/key-validator/Operate.tsx
app/components/header/account-setting/key-validator/ValidateStatus.tsx
app/components/header/account-setting/key-validator/index.tsx
app/components/header/account-setting/language-page/index.module.css
app/components/header/account-setting/members-page/index.tsx
app/components/header/account-setting/members-page/invite-modal/index.tsx
app/components/header/account-setting/members-page/invite-modal/role-selector.tsx
app/components/header/account-setting/members-page/invited-modal/index.module.css
app/components/header/account-setting/members-page/invited-modal/index.tsx
app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx
app/components/header/account-setting/members-page/operation/index.module.css
app/components/header/account-setting/members-page/operation/index.tsx
app/components/header/account-setting/model-provider-page/declarations.ts
app/components/header/account-setting/model-provider-page/hooks.ts
app/components/header/account-setting/model-provider-page/index.tsx
app/components/header/account-setting/model-provider-page/model-icon/index.tsx
app/components/header/account-setting/model-provider-page/model-modal/Form.tsx
app/components/header/account-setting/model-provider-page/model-modal/Input.tsx
app/components/header/account-setting/model-provider-page/model-modal/index.tsx
app/components/header/account-setting/model-provider-page/model-modal/model-load-balancing-entry-modal.tsx
app/components/header/account-setting/model-provider-page/model-name/index.tsx
app/components/header/account-setting/model-provider-page/model-parameter-modal/index.tsx
app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx
app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx
app/components/header/account-setting/model-provider-page/model-parameter-modal/stop-sequence.tsx
app/components/header/account-setting/model-provider-page/model-parameter-modal/trigger.tsx
app/components/header/account-setting/model-provider-page/model-selector/deprecated-model-trigger.tsx
app/components/header/account-setting/model-provider-page/model-selector/empty-trigger.tsx
app/components/header/account-setting/model-provider-page/model-selector/feature-icon.tsx
app/components/header/account-setting/model-provider-page/model-selector/index.tsx
app/components/header/account-setting/model-provider-page/model-selector/model-trigger.tsx
app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx
app/components/header/account-setting/model-provider-page/model-selector/popup.tsx
app/components/header/account-setting/model-provider-page/model-selector/rerank-trigger.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/add-model-button.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/cooldown-timer.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/priority-selector.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/priority-use-tip.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx
app/components/header/account-setting/model-provider-page/provider-added-card/tab.tsx
app/components/header/account-setting/model-provider-page/provider-card/index.module.css
app/components/header/account-setting/model-provider-page/provider-card/index.tsx
app/components/header/account-setting/model-provider-page/provider-icon/index.tsx
app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx
app/components/header/account-setting/model-provider-page/utils.ts
app/components/header/account-setting/plugin-page/index.tsx
app/components/header/app-back/index.tsx
app/components/header/app-nav/index.tsx
app/components/header/app-selector/index.tsx
app/components/header/dataset-nav/index.tsx
app/components/header/env-nav/index.tsx
app/components/header/explore-nav/index.tsx
app/components/header/github-star/index.tsx
app/components/header/index.module.css
app/components/header/index.tsx
app/components/header/license-env/index.tsx
app/components/header/maintenance-notice.tsx
app/components/header/nav/index.tsx
app/components/header/nav/nav-selector/index.tsx
app/components/header/tools-nav/index.tsx
app/components/i18n-server.tsx
app/components/i18n.tsx
app/components/sentry-initor.tsx
app/components/share/text-generation/index.tsx
app/components/share/text-generation/no-data/index.tsx
app/components/share/text-generation/result/content.tsx
app/components/share/text-generation/result/header.tsx
app/components/share/text-generation/result/index.tsx
app/components/share/text-generation/run-batch/csv-download/index.tsx
app/components/share/text-generation/run-batch/csv-reader/index.tsx
app/components/share/text-generation/run-batch/csv-reader/style.module.css
app/components/share/text-generation/run-batch/index.tsx
app/components/share/text-generation/run-batch/res-download/index.tsx
app/components/share/text-generation/run-once/index.tsx
app/components/share/text-generation/style.module.css
app/components/share/utils.ts
app/components/signin/countdown.tsx
app/components/swr-initor.tsx
app/components/tools/add-tool-modal/category.tsx
app/components/tools/add-tool-modal/empty.tsx
app/components/tools/add-tool-modal/index.tsx
app/components/tools/add-tool-modal/tools.tsx
app/components/tools/add-tool-modal/type.tsx
app/components/tools/edit-custom-collection-modal/config-credentials.tsx
app/components/tools/edit-custom-collection-modal/examples.ts
app/components/tools/edit-custom-collection-modal/get-schema.tsx
app/components/tools/edit-custom-collection-modal/index.tsx
app/components/tools/edit-custom-collection-modal/test-api.tsx
app/components/tools/labels/constant.ts
app/components/tools/labels/filter.tsx
app/components/tools/labels/selector.tsx
app/components/tools/labels/store.ts
app/components/tools/provider-list.tsx
app/components/tools/provider/card.tsx
app/components/tools/provider/contribute.tsx
app/components/tools/provider/custom-create-card.tsx
app/components/tools/provider/detail.tsx
app/components/tools/provider/grid_bg.svg
app/components/tools/provider/tool-item.tsx
app/components/tools/setting/build-in/config-credentials.tsx
app/components/tools/types.ts
app/components/tools/utils/to-form-schema.ts
app/components/tools/workflow-tool/configure-button.tsx
app/components/tools/workflow-tool/confirm-modal/index.tsx
app/components/tools/workflow-tool/confirm-modal/style.module.css
app/components/tools/workflow-tool/index.tsx
app/components/tools/workflow-tool/method-selector.tsx
app/components/workflow/block-icon.tsx
app/components/workflow/block-selector/all-tools.tsx
app/components/workflow/block-selector/blocks.tsx
app/components/workflow/block-selector/constants.tsx
app/components/workflow/block-selector/hooks.ts
app/components/workflow/block-selector/index-bar.tsx
app/components/workflow/block-selector/index.tsx
app/components/workflow/block-selector/tabs.tsx
app/components/workflow/block-selector/tools.tsx
app/components/workflow/block-selector/types.ts
app/components/workflow/candidate-node.tsx
app/components/workflow/constants.ts
app/components/workflow/context.tsx
app/components/workflow/custom-edge.tsx
app/components/workflow/dsl-export-confirm-modal.tsx
app/components/workflow/header/chat-variable-button.tsx
app/components/workflow/header/checklist.tsx
app/components/workflow/header/editing-title.tsx
app/components/workflow/header/env-button.tsx
app/components/workflow/header/global-variable-button.tsx
app/components/workflow/header/index.tsx
app/components/workflow/header/restoring-title.tsx
app/components/workflow/header/run-and-history.tsx
app/components/workflow/header/running-title.tsx
app/components/workflow/header/undo-redo.tsx
app/components/workflow/header/version-history-item.tsx
app/components/workflow/header/version-history-modal.tsx
app/components/workflow/header/view-history.tsx
app/components/workflow/header/view-workflow-history.tsx
app/components/workflow/help-line/index.tsx
app/components/workflow/hooks/index.ts
app/components/workflow/hooks/use-checklist.ts
app/components/workflow/hooks/use-edges-interactions.ts
app/components/workflow/hooks/use-helpline.ts
app/components/workflow/hooks/use-nodes-data.ts
app/components/workflow/hooks/use-nodes-interactions.ts
app/components/workflow/hooks/use-nodes-sync-draft.ts
app/components/workflow/hooks/use-workflow-interactions.ts
app/components/workflow/hooks/use-workflow-run.ts
app/components/workflow/hooks/use-workflow-start-run.tsx
app/components/workflow/hooks/use-workflow-template.ts
app/components/workflow/hooks/use-workflow-variables.ts
app/components/workflow/hooks/use-workflow.ts
app/components/workflow/index.tsx
app/components/workflow/limit-tips.tsx
app/components/workflow/nodes/_base/components/add-button.tsx
app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx
app/components/workflow/nodes/_base/components/add-variable-popup.tsx
app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx
app/components/workflow/nodes/_base/components/before-run-form/form.tsx
app/components/workflow/nodes/_base/components/before-run-form/index.tsx
app/components/workflow/nodes/_base/components/code-generator-button.tsx
app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx
app/components/workflow/nodes/_base/components/collapse/index.tsx
app/components/workflow/nodes/_base/components/editor/base.tsx
app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx
app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx
app/components/workflow/nodes/_base/components/editor/code-editor/style.css
app/components/workflow/nodes/_base/components/editor/text-editor.tsx
app/components/workflow/nodes/_base/components/error-handle/default-value.tsx
app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx
app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx
app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx
app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx
app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx
app/components/workflow/nodes/_base/components/field.tsx
app/components/workflow/nodes/_base/components/file-type-item.tsx
app/components/workflow/nodes/_base/components/file-upload-setting.tsx
app/components/workflow/nodes/_base/components/help-link.tsx
app/components/workflow/nodes/_base/components/info-panel.tsx
app/components/workflow/nodes/_base/components/input-number-with-slider.tsx
app/components/workflow/nodes/_base/components/input-support-select-var.tsx
app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx
app/components/workflow/nodes/_base/components/memory-config.tsx
app/components/workflow/nodes/_base/components/next-step/add.tsx
app/components/workflow/nodes/_base/components/next-step/container.tsx
app/components/workflow/nodes/_base/components/next-step/index.tsx
app/components/workflow/nodes/_base/components/next-step/item.tsx
app/components/workflow/nodes/_base/components/next-step/line.tsx
app/components/workflow/nodes/_base/components/next-step/operator.tsx
app/components/workflow/nodes/_base/components/node-control.tsx
app/components/workflow/nodes/_base/components/node-handle.tsx
app/components/workflow/nodes/_base/components/node-resizer.tsx
app/components/workflow/nodes/_base/components/option-card.tsx
app/components/workflow/nodes/_base/components/output-vars.tsx
app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx
app/components/workflow/nodes/_base/components/panel-operator/index.tsx
app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx
app/components/workflow/nodes/_base/components/prompt/editor.tsx
app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx
app/components/workflow/nodes/_base/components/remove-button.tsx
app/components/workflow/nodes/_base/components/retry/retry-on-node.tsx
app/components/workflow/nodes/_base/components/retry/retry-on-panel.tsx
app/components/workflow/nodes/_base/components/retry/style.module.css
app/components/workflow/nodes/_base/components/selector.tsx
app/components/workflow/nodes/_base/components/support-var-input/index.tsx
app/components/workflow/nodes/_base/components/title-description-input.tsx
app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx
app/components/workflow/nodes/_base/components/variable-tag.tsx
app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx
app/components/workflow/nodes/_base/components/variable/constant-field.tsx
app/components/workflow/nodes/_base/components/variable/output-var-list.tsx
app/components/workflow/nodes/_base/components/variable/utils.ts
app/components/workflow/nodes/_base/components/variable/var-list.tsx
app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx
app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx
app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx
app/components/workflow/nodes/_base/hooks/use-available-var-list.ts
app/components/workflow/nodes/_base/hooks/use-node-help-link.ts
app/components/workflow/nodes/_base/hooks/use-node-info.ts
app/components/workflow/nodes/_base/hooks/use-one-step-run.ts
app/components/workflow/nodes/_base/hooks/use-toggle-expend.ts
app/components/workflow/nodes/_base/node.tsx
app/components/workflow/nodes/_base/panel.tsx
app/components/workflow/nodes/answer/panel.tsx
app/components/workflow/nodes/assigner/components/operation-selector.tsx
app/components/workflow/nodes/assigner/components/var-list/index.tsx
app/components/workflow/nodes/assigner/default.ts
app/components/workflow/nodes/assigner/node.tsx
app/components/workflow/nodes/assigner/panel.tsx
app/components/workflow/nodes/assigner/types.ts
app/components/workflow/nodes/assigner/use-config.ts
app/components/workflow/nodes/code/code-parser.ts
app/components/workflow/nodes/code/dependency-picker.tsx
app/components/workflow/nodes/code/panel.tsx
app/components/workflow/nodes/code/use-config.ts
app/components/workflow/nodes/code/utils.ts
app/components/workflow/nodes/constants.ts
app/components/workflow/nodes/document-extractor/default.ts
app/components/workflow/nodes/document-extractor/node.tsx
app/components/workflow/nodes/document-extractor/panel.tsx
app/components/workflow/nodes/document-extractor/use-config.ts
app/components/workflow/nodes/end/default.ts
app/components/workflow/nodes/end/node.tsx
app/components/workflow/nodes/end/panel.tsx
app/components/workflow/nodes/http/components/api-input.tsx
app/components/workflow/nodes/http/components/authorization/index.tsx
app/components/workflow/nodes/http/components/authorization/radio-group.tsx
app/components/workflow/nodes/http/components/curl-panel.tsx
app/components/workflow/nodes/http/components/edit-body/index.tsx
app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx
app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx
app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx
app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx
app/components/workflow/nodes/http/components/timeout/index.tsx
app/components/workflow/nodes/http/hooks/use-key-value-list.ts
app/components/workflow/nodes/http/node.tsx
app/components/workflow/nodes/http/panel.tsx
app/components/workflow/nodes/http/use-config.ts
app/components/workflow/nodes/if-else/components/condition-add.tsx
app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx
app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx
app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx
app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx
app/components/workflow/nodes/if-else/components/condition-list/index.tsx
app/components/workflow/nodes/if-else/components/condition-number-input.tsx
app/components/workflow/nodes/if-else/components/condition-value.tsx
app/components/workflow/nodes/if-else/components/condition-wrap.tsx
app/components/workflow/nodes/if-else/default.ts
app/components/workflow/nodes/if-else/node.tsx
app/components/workflow/nodes/if-else/panel.tsx
app/components/workflow/nodes/if-else/types.ts
app/components/workflow/nodes/if-else/use-config.ts
app/components/workflow/nodes/if-else/use-is-var-file-attribute.ts
app/components/workflow/nodes/iteration-start/index.tsx
app/components/workflow/nodes/iteration/add-block.tsx
app/components/workflow/nodes/iteration/node.tsx
app/components/workflow/nodes/iteration/panel.tsx
app/components/workflow/nodes/iteration/use-interactions.ts
app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx
app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx
app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx
app/components/workflow/nodes/knowledge-retrieval/node.tsx
app/components/workflow/nodes/knowledge-retrieval/panel.tsx
app/components/workflow/nodes/knowledge-retrieval/types.ts
app/components/workflow/nodes/knowledge-retrieval/use-config.ts
app/components/workflow/nodes/knowledge-retrieval/utils.ts
app/components/workflow/nodes/list-operator/components/extract-input.tsx
app/components/workflow/nodes/list-operator/components/filter-condition.tsx
app/components/workflow/nodes/list-operator/components/limit-config.tsx
app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx
app/components/workflow/nodes/list-operator/node.tsx
app/components/workflow/nodes/list-operator/panel.tsx
app/components/workflow/nodes/list-operator/use-config.ts
app/components/workflow/nodes/llm/components/config-prompt-item.tsx
app/components/workflow/nodes/llm/components/config-prompt.tsx
app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx
app/components/workflow/nodes/llm/components/resolution-picker.tsx
app/components/workflow/nodes/llm/node.tsx
app/components/workflow/nodes/llm/panel.tsx
app/components/workflow/nodes/llm/types.ts
app/components/workflow/nodes/llm/use-config.ts
app/components/workflow/nodes/llm/utils.ts
app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx
app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx
app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx
app/components/workflow/nodes/parameter-extractor/node.tsx
app/components/workflow/nodes/parameter-extractor/panel.tsx
app/components/workflow/nodes/parameter-extractor/use-config.ts
app/components/workflow/nodes/question-classifier/components/class-item.tsx
app/components/workflow/nodes/question-classifier/components/class-list.tsx
app/components/workflow/nodes/question-classifier/node.tsx
app/components/workflow/nodes/question-classifier/panel.tsx
app/components/workflow/nodes/question-classifier/use-config.ts
app/components/workflow/nodes/question-classifier/utils.ts
app/components/workflow/nodes/start/components/var-item.tsx
app/components/workflow/nodes/start/components/var-list.tsx
app/components/workflow/nodes/start/node.tsx
app/components/workflow/nodes/start/panel.tsx
app/components/workflow/nodes/template-transform/panel.tsx
app/components/workflow/nodes/tool/components/input-var-list.tsx
app/components/workflow/nodes/tool/node.tsx
app/components/workflow/nodes/tool/panel.tsx
app/components/workflow/nodes/tool/types.ts
app/components/workflow/nodes/tool/use-config.ts
app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx
app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx
app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx
app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx
app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx
app/components/workflow/nodes/variable-assigner/default.ts
app/components/workflow/nodes/variable-assigner/node.tsx
app/components/workflow/nodes/variable-assigner/panel.tsx
app/components/workflow/nodes/variable-assigner/use-config.ts
app/components/workflow/note-node/index.tsx
app/components/workflow/note-node/note-editor/context.tsx
app/components/workflow/note-node/note-editor/editor.tsx
app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx
app/components/workflow/note-node/note-editor/theme/theme.css
app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx
app/components/workflow/note-node/note-editor/toolbar/command.tsx
app/components/workflow/note-node/note-editor/toolbar/divider.tsx
app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx
app/components/workflow/note-node/note-editor/toolbar/index.tsx
app/components/workflow/note-node/note-editor/toolbar/operator.tsx
app/components/workflow/note-node/note-editor/utils.ts
app/components/workflow/operator/add-block.tsx
app/components/workflow/operator/control.tsx
app/components/workflow/operator/index.tsx
app/components/workflow/operator/tip-popup.tsx
app/components/workflow/operator/zoom-in-out.tsx
app/components/workflow/panel-contextmenu.tsx
app/components/workflow/panel/chat-record/index.tsx
app/components/workflow/panel/chat-record/user-input.tsx
app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx
app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx
app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx
app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx
app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx
app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx
app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx
app/components/workflow/panel/chat-variable-panel/index.tsx
app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx
app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx
app/components/workflow/panel/debug-and-preview/empty.tsx
app/components/workflow/panel/debug-and-preview/hooks.ts
app/components/workflow/panel/debug-and-preview/index.tsx
app/components/workflow/panel/debug-and-preview/user-input.tsx
app/components/workflow/panel/env-panel/env-item.tsx
app/components/workflow/panel/env-panel/index.tsx
app/components/workflow/panel/env-panel/variable-modal.tsx
app/components/workflow/panel/env-panel/variable-trigger.tsx
app/components/workflow/panel/global-variable-panel/index.tsx
app/components/workflow/panel/global-variable-panel/item.tsx
app/components/workflow/panel/index.tsx
app/components/workflow/panel/inputs-panel.tsx
app/components/workflow/panel/record.tsx
app/components/workflow/panel/workflow-preview.tsx
app/components/workflow/run/index.tsx
app/components/workflow/run/iteration-result-panel.tsx
app/components/workflow/run/meta.tsx
app/components/workflow/run/node.tsx
app/components/workflow/run/output-panel.tsx
app/components/workflow/run/result-panel.tsx
app/components/workflow/run/result-text.tsx
app/components/workflow/run/retry-result-panel.tsx
app/components/workflow/run/status-container.tsx
app/components/workflow/run/status.tsx
app/components/workflow/run/tracing-panel.tsx
app/components/workflow/shortcuts-name.tsx
app/components/workflow/store.ts
app/components/workflow/style.css
app/components/workflow/types.ts
app/components/workflow/update-dsl-modal.tsx
app/components/workflow/utils.ts
app/components/workflow/workflow-history-store.tsx
app/forgot-password/ChangePasswordForm.tsx
app/forgot-password/ForgotPasswordForm.tsx
app/forgot-password/page.tsx
app/init/InitPasswordPopup.tsx
app/init/page.tsx
app/install/installForm.tsx
app/install/page.tsx
app/layout.tsx
app/page.module.css
app/page.tsx
app/reset-password/check-code/page.tsx
app/reset-password/layout.tsx
app/reset-password/page.tsx
app/reset-password/set-password/page.tsx
app/signin/_header.tsx
app/signin/assets/background.png
app/signin/check-code/page.tsx
app/signin/components/mail-and-code-auth.tsx
app/signin/components/mail-and-password-auth.tsx
app/signin/components/sso-auth.tsx
app/signin/invite-settings/page.tsx
app/signin/layout.tsx
app/signin/normalForm.tsx
app/signin/oneMoreStep.tsx
app/signin/page.module.css
app/styles/globals.css
app/styles/markdown.scss
app/styles/preflight.css
config/index.ts
context/app-context.tsx
context/datasets-context.tsx
context/debug-configuration.ts
context/explore-context.ts
context/i18n.ts
context/modal-context.tsx
context/provider-context.tsx
docker/entrypoint.sh
hooks/use-metadata.ts
hooks/use-pay.tsx
hooks/use-tab-searchparams.ts
hooks/use-timestamp.ts
i18n/auto-gen-i18n.js
i18n/check-i18n.js
i18n/de-DE/app-overview.ts
i18n/de-DE/app.ts
i18n/de-DE/billing.ts
i18n/de-DE/common.ts
i18n/de-DE/custom.ts
i18n/de-DE/dataset-creation.ts
i18n/de-DE/dataset-documents.ts
i18n/de-DE/dataset-hit-testing.ts
i18n/de-DE/dataset-settings.ts
i18n/de-DE/dataset.ts
i18n/de-DE/explore.ts
i18n/de-DE/run-log.ts
i18n/de-DE/share-app.ts
i18n/de-DE/tools.ts
i18n/de-DE/workflow.ts
i18n/en-US/app-debug.ts
i18n/en-US/app-overview.ts
i18n/en-US/app.ts
i18n/en-US/billing.ts
i18n/en-US/common.ts
i18n/en-US/custom.ts
i18n/en-US/dataset-creation.ts
i18n/en-US/dataset-documents.ts
i18n/en-US/dataset-settings.ts
i18n/en-US/dataset.ts
i18n/en-US/explore.ts
i18n/en-US/login.ts
i18n/en-US/run-log.ts
i18n/en-US/share-app.ts
i18n/en-US/tools.ts
i18n/en-US/workflow.ts
i18n/es-ES/app-overview.ts
i18n/es-ES/app.ts
i18n/es-ES/billing.ts
i18n/es-ES/common.ts
i18n/es-ES/custom.ts
i18n/es-ES/dataset-creation.ts
i18n/es-ES/dataset-documents.ts
i18n/es-ES/dataset-settings.ts
i18n/es-ES/dataset.ts
i18n/es-ES/explore.ts
i18n/es-ES/run-log.ts
i18n/es-ES/share-app.ts
i18n/es-ES/tools.ts
i18n/es-ES/workflow.ts
i18n/fa-IR/app-overview.ts
i18n/fa-IR/app.ts
i18n/fa-IR/billing.ts
i18n/fa-IR/common.ts
i18n/fa-IR/custom.ts
i18n/fa-IR/dataset-creation.ts
i18n/fa-IR/dataset-documents.ts
i18n/fa-IR/dataset-settings.ts
i18n/fa-IR/dataset.ts
i18n/fa-IR/explore.ts
i18n/fa-IR/run-log.ts
i18n/fa-IR/share-app.ts
i18n/fa-IR/tools.ts
i18n/fa-IR/workflow.ts
i18n/fr-FR/app-overview.ts
i18n/fr-FR/app.ts
i18n/fr-FR/billing.ts
i18n/fr-FR/common.ts
i18n/fr-FR/custom.ts
i18n/fr-FR/dataset-creation.ts
i18n/fr-FR/dataset-documents.ts
i18n/fr-FR/dataset-settings.ts
i18n/fr-FR/dataset.ts
i18n/fr-FR/explore.ts
i18n/fr-FR/run-log.ts
i18n/fr-FR/share-app.ts
i18n/fr-FR/tools.ts
i18n/fr-FR/workflow.ts
i18n/hi-IN/app-overview.ts
i18n/hi-IN/app.ts
i18n/hi-IN/billing.ts
i18n/hi-IN/common.ts
i18n/hi-IN/custom.ts
i18n/hi-IN/dataset-creation.ts
i18n/hi-IN/dataset-documents.ts
i18n/hi-IN/dataset-settings.ts
i18n/hi-IN/dataset.ts
i18n/hi-IN/explore.ts
i18n/hi-IN/run-log.ts
i18n/hi-IN/share-app.ts
i18n/hi-IN/tools.ts
i18n/hi-IN/workflow.ts
i18n/i18next-config.ts
i18n/index.ts
i18n/it-IT/app-overview.ts
i18n/it-IT/app.ts
i18n/it-IT/billing.ts
i18n/it-IT/common.ts
i18n/it-IT/custom.ts
i18n/it-IT/dataset-creation.ts
i18n/it-IT/dataset-documents.ts
i18n/it-IT/dataset-settings.ts
i18n/it-IT/dataset.ts
i18n/it-IT/explore.ts
i18n/it-IT/run-log.ts
i18n/it-IT/share-app.ts
i18n/it-IT/tools.ts
i18n/it-IT/workflow.ts
i18n/ja-JP/app-annotation.ts
i18n/ja-JP/app-overview.ts
i18n/ja-JP/app.ts
i18n/ja-JP/billing.ts
i18n/ja-JP/common.ts
i18n/ja-JP/custom.ts
i18n/ja-JP/dataset-creation.ts
i18n/ja-JP/dataset-documents.ts
i18n/ja-JP/dataset-settings.ts
i18n/ja-JP/dataset.ts
i18n/ja-JP/explore.ts
i18n/ja-JP/run-log.ts
i18n/ja-JP/share-app.ts
i18n/ja-JP/tools.ts
i18n/ja-JP/workflow.ts
i18n/ko-KR/app-overview.ts
i18n/ko-KR/app.ts
i18n/ko-KR/billing.ts
i18n/ko-KR/common.ts
i18n/ko-KR/custom.ts
i18n/ko-KR/dataset-creation.ts
i18n/ko-KR/dataset-documents.ts
i18n/ko-KR/dataset-settings.ts
i18n/ko-KR/dataset.ts
i18n/ko-KR/explore.ts
i18n/ko-KR/run-log.ts
i18n/ko-KR/share-app.ts
i18n/ko-KR/tools.ts
i18n/ko-KR/workflow.ts
i18n/language.ts
i18n/pl-PL/app-overview.ts
i18n/pl-PL/app.ts
i18n/pl-PL/billing.ts
i18n/pl-PL/common.ts
i18n/pl-PL/custom.ts
i18n/pl-PL/dataset-creation.ts
i18n/pl-PL/dataset-documents.ts
i18n/pl-PL/dataset-settings.ts
i18n/pl-PL/dataset.ts
i18n/pl-PL/explore.ts
i18n/pl-PL/run-log.ts
i18n/pl-PL/share-app.ts
i18n/pl-PL/tools.ts
i18n/pl-PL/workflow.ts
i18n/pt-BR/app-overview.ts
i18n/pt-BR/app.ts
i18n/pt-BR/billing.ts
i18n/pt-BR/common.ts
i18n/pt-BR/custom.ts
i18n/pt-BR/dataset-creation.ts
i18n/pt-BR/dataset-documents.ts
i18n/pt-BR/dataset-settings.ts
i18n/pt-BR/dataset.ts
i18n/pt-BR/explore.ts
i18n/pt-BR/run-log.ts
i18n/pt-BR/share-app.ts
i18n/pt-BR/tools.ts
i18n/pt-BR/workflow.ts
i18n/ro-RO/app-overview.ts
i18n/ro-RO/app.ts
i18n/ro-RO/billing.ts
i18n/ro-RO/common.ts
i18n/ro-RO/custom.ts
i18n/ro-RO/dataset-creation.ts
i18n/ro-RO/dataset-documents.ts
i18n/ro-RO/dataset-settings.ts
i18n/ro-RO/dataset.ts
i18n/ro-RO/explore.ts
i18n/ro-RO/run-log.ts
i18n/ro-RO/share-app.ts
i18n/ro-RO/tools.ts
i18n/ro-RO/workflow.ts
i18n/ru-RU/app-overview.ts
i18n/ru-RU/app.ts
i18n/ru-RU/billing.ts
i18n/ru-RU/common.ts
i18n/ru-RU/custom.ts
i18n/ru-RU/dataset-creation.ts
i18n/ru-RU/dataset-documents.ts
i18n/ru-RU/dataset-settings.ts
i18n/ru-RU/dataset.ts
i18n/ru-RU/explore.ts
i18n/ru-RU/run-log.ts
i18n/ru-RU/share-app.ts
i18n/ru-RU/tools.ts
i18n/ru-RU/workflow.ts
i18n/server.ts
i18n/sl-SI/app-overview.ts
i18n/sl-SI/app.ts
i18n/sl-SI/billing.ts
i18n/sl-SI/common.ts
i18n/sl-SI/custom.ts
i18n/sl-SI/dataset-creation.ts
i18n/sl-SI/dataset-documents.ts
i18n/sl-SI/dataset-settings.ts
i18n/sl-SI/dataset.ts
i18n/sl-SI/explore.ts
i18n/sl-SI/run-log.ts
i18n/sl-SI/share-app.ts
i18n/sl-SI/tools.ts
i18n/sl-SI/workflow.ts
i18n/th-TH/app-debug.ts
i18n/th-TH/app-overview.ts
i18n/th-TH/app.ts
i18n/th-TH/billing.ts
i18n/th-TH/common.ts
i18n/th-TH/custom.ts
i18n/th-TH/dataset-creation.ts
i18n/th-TH/dataset-documents.ts
i18n/th-TH/dataset-settings.ts
i18n/th-TH/dataset.ts
i18n/th-TH/explore.ts
i18n/th-TH/run-log.ts
i18n/th-TH/share-app.ts
i18n/th-TH/tools.ts
i18n/th-TH/workflow.ts
i18n/tr-TR/app-overview.ts
i18n/tr-TR/app.ts
i18n/tr-TR/billing.ts
i18n/tr-TR/common.ts
i18n/tr-TR/custom.ts
i18n/tr-TR/dataset-creation.ts
i18n/tr-TR/dataset-documents.ts
i18n/tr-TR/dataset-settings.ts
i18n/tr-TR/dataset.ts
i18n/tr-TR/explore.ts
i18n/tr-TR/run-log.ts
i18n/tr-TR/share-app.ts
i18n/tr-TR/tools.ts
i18n/tr-TR/workflow.ts
i18n/uk-UA/app-overview.ts
i18n/uk-UA/app.ts
i18n/uk-UA/billing.ts
i18n/uk-UA/common.ts
i18n/uk-UA/custom.ts
i18n/uk-UA/dataset-creation.ts
i18n/uk-UA/dataset-documents.ts
i18n/uk-UA/dataset-settings.ts
i18n/uk-UA/dataset.ts
i18n/uk-UA/explore.ts
i18n/uk-UA/run-log.ts
i18n/uk-UA/share-app.ts
i18n/uk-UA/tools.ts
i18n/uk-UA/workflow.ts
i18n/vi-VN/app-overview.ts
i18n/vi-VN/app.ts
i18n/vi-VN/billing.ts
i18n/vi-VN/common.ts
i18n/vi-VN/custom.ts
i18n/vi-VN/dataset-creation.ts
i18n/vi-VN/dataset-documents.ts
i18n/vi-VN/dataset-settings.ts
i18n/vi-VN/dataset.ts
i18n/vi-VN/explore.ts
i18n/vi-VN/run-log.ts
i18n/vi-VN/share-app.ts
i18n/vi-VN/tools.ts
i18n/vi-VN/workflow.ts
i18n/zh-Hans/app-debug.ts
i18n/zh-Hans/app-overview.ts
i18n/zh-Hans/app.ts
i18n/zh-Hans/billing.ts
i18n/zh-Hans/common.ts
i18n/zh-Hans/custom.ts
i18n/zh-Hans/dataset-creation.ts
i18n/zh-Hans/dataset-documents.ts
i18n/zh-Hans/dataset-settings.ts
i18n/zh-Hans/dataset.ts
i18n/zh-Hans/explore.ts
i18n/zh-Hans/run-log.ts
i18n/zh-Hans/share-app.ts
i18n/zh-Hans/tools.ts
i18n/zh-Hans/workflow.ts
i18n/zh-Hant/app-overview.ts
i18n/zh-Hant/app.ts
i18n/zh-Hant/billing.ts
i18n/zh-Hant/common.ts
i18n/zh-Hant/custom.ts
i18n/zh-Hant/dataset-creation.ts
i18n/zh-Hant/dataset-documents.ts
i18n/zh-Hant/dataset-settings.ts
i18n/zh-Hant/dataset.ts
i18n/zh-Hant/explore.ts
i18n/zh-Hant/run-log.ts
i18n/zh-Hant/share-app.ts
i18n/zh-Hant/tools.ts
i18n/zh-Hant/workflow.ts
jest.config.ts
middleware.ts
models/app.ts
models/common.ts
models/datasets.ts
models/debug.ts
next.config.js
package.json
public/embed.js
public/embed.min.js
public/favicon.ico
public/logo/logo-embedded-chat-header.png
public/logo/logo-site-dark.png
public/logo/logo-site.png
public/vs/language/typescript/tsWorker.js
service/base.ts
service/common.ts
service/datasets.ts
service/knowledge/use-create-dataset.ts
service/knowledge/use-dateset.ts
service/knowledge/use-document.ts
service/log.ts
service/refresh-token.ts
service/share.ts
service/tools.ts
service/use-base.ts
service/use-workflow.ts
service/workflow.ts
tailwind.config.js
themes/dark.css
themes/light.css
themes/manual-dark.css
themes/manual-light.css
themes/tailwind-theme-var-define.ts
types/app.ts
types/feature.ts
types/workflow.ts
typography.js
utils/app-redirection.ts
utils/classnames.spec.ts
utils/format.spec.ts
utils/format.ts
utils/index.ts
utils/model-config.ts
utils/timezone.json
utils/var.ts
yarn.lock |