mirror of
https://github.com/langgenius/webapp-conversation.git
synced 2025-12-08 17:32:27 +08:00
feat: migrate ESLint to v9 flat config
- Replace .eslintrc.json with eslint.config.mjs - Simplify configuration using @antfu/eslint-config - Add necessary ESLint plugin dependencies - Disable overly strict style rules - Set package.json type to module for ESM support - Fix ESLint disable comment format 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"@antfu",
|
|
||||||
"plugin:react-hooks/recommended"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/consistent-type-definitions": [
|
|
||||||
"error",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -49,3 +49,6 @@ yarn.lock
|
|||||||
|
|
||||||
# pmpm
|
# pmpm
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
# mcp
|
||||||
|
.serena
|
||||||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@ -0,0 +1 @@
|
|||||||
|
pnpm eslint-fix
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { client, getInfo } from '@/app/api/utils/common'
|
import { client, getInfo } from '@/app/api/utils/common'
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { client, getInfo } from '@/app/api/utils/common'
|
import { client, getInfo } from '@/app/api/utils/common'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { client, getInfo, setSession } from '@/app/api/utils/common'
|
import { client, getInfo, setSession } from '@/app/api/utils/common'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { client, getInfo } from '@/app/api/utils/common'
|
import { client, getInfo } from '@/app/api/utils/common'
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { client, getInfo } from '@/app/api/utils/common'
|
import { client, getInfo } from '@/app/api/utils/common'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { client, getInfo, setSession } from '@/app/api/utils/common'
|
import { client, getInfo, setSession } from '@/app/api/utils/common'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { client, getInfo, setSession } from '@/app/api/utils/common'
|
import { client, getInfo, setSession } from '@/app/api/utils/common'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { ChatClient } from 'dify-client'
|
import { ChatClient } from 'dify-client'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { API_KEY, API_URL, APP_ID } from '@/config'
|
import { API_KEY, API_URL, APP_ID } from '@/config'
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { FC } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type IAppUnavailableProps = {
|
interface IAppUnavailableProps {
|
||||||
isUnknownReason: boolean
|
isUnknownReason: boolean
|
||||||
errMessage?: string
|
errMessage?: string
|
||||||
}
|
}
|
||||||
@ -14,8 +14,7 @@ const AppUnavailable: FC<IAppUnavailableProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
let message = errMessage
|
let message = errMessage
|
||||||
if (!errMessage)
|
if (!errMessage) { message = (isUnknownReason ? t('app.common.appUnkonwError') : t('app.common.appUnavailable')) as string }
|
||||||
message = (isUnknownReason ? t('app.common.appUnkonwError') : t('app.common.appUnavailable')) as string
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center justify-center w-screen h-screen'>
|
<div className='flex items-center justify-center w-screen h-screen'>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { FC } from 'react'
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import style from './style.module.css'
|
import style from './style.module.css'
|
||||||
|
|
||||||
export type AppIconProps = {
|
export interface AppIconProps {
|
||||||
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'
|
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'
|
||||||
rounded?: boolean
|
rounded?: boolean
|
||||||
icon?: string
|
icon?: string
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { forwardRef, useEffect, useRef } from 'react'
|
import { forwardRef, useEffect, useRef } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
|
|
||||||
type IProps = {
|
interface IProps {
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
value: string
|
value: string
|
||||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
|
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
@ -36,19 +36,16 @@ const AutoHeightTextarea = forwardRef(
|
|||||||
let hasFocus = false
|
let hasFocus = false
|
||||||
const runId = setInterval(() => {
|
const runId = setInterval(() => {
|
||||||
hasFocus = doFocus()
|
hasFocus = doFocus()
|
||||||
if (hasFocus)
|
if (hasFocus) { clearInterval(runId) }
|
||||||
clearInterval(runId)
|
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (autoFocus)
|
if (autoFocus) { focus() }
|
||||||
focus()
|
|
||||||
}, [])
|
}, [])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (controlFocus)
|
if (controlFocus) { focus() }
|
||||||
focus()
|
|
||||||
}, [controlFocus])
|
}, [controlFocus])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { FC, MouseEventHandler } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Spinner from '@/app/components/base/spinner'
|
import Spinner from '@/app/components/base/spinner'
|
||||||
|
|
||||||
export type IButtonProps = {
|
export interface IButtonProps {
|
||||||
type?: string
|
type?: string
|
||||||
className?: string
|
className?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type FileFromLinkOrLocalProps = {
|
interface FileFromLinkOrLocalProps {
|
||||||
showFromLink?: boolean
|
showFromLink?: boolean
|
||||||
showFromLocal?: boolean
|
showFromLocal?: boolean
|
||||||
trigger: (open: boolean) => React.ReactNode
|
trigger: (open: boolean) => React.ReactNode
|
||||||
@ -38,8 +38,7 @@ const FileFromLinkOrLocal = ({
|
|||||||
const disabled = !!fileConfig.number_limits && files.length >= fileConfig.number_limits
|
const disabled = !!fileConfig.number_limits && files.length >= fileConfig.number_limits
|
||||||
|
|
||||||
const handleSaveUrl = () => {
|
const handleSaveUrl = () => {
|
||||||
if (!url)
|
if (!url) { return }
|
||||||
return
|
|
||||||
|
|
||||||
if (!FILE_URL_REGEX.test(url)) {
|
if (!FILE_URL_REGEX.test(url)) {
|
||||||
setShowError(true)
|
setShowError(true)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type FileImageRenderProps = {
|
interface FileImageRenderProps {
|
||||||
imageUrl: string
|
imageUrl: string
|
||||||
className?: string
|
className?: string
|
||||||
alt?: string
|
alt?: string
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { FileUpload } from './types'
|
|||||||
import { FILE_EXTS } from './constants'
|
import { FILE_EXTS } from './constants'
|
||||||
import { SupportUploadFileTypes } from './types'
|
import { SupportUploadFileTypes } from './types'
|
||||||
|
|
||||||
type FileInputProps = {
|
interface FileInputProps {
|
||||||
fileConfig: FileUpload
|
fileConfig: FileUpload
|
||||||
}
|
}
|
||||||
const FileInput = ({
|
const FileInput = ({
|
||||||
@ -18,8 +18,7 @@ const FileInput = ({
|
|||||||
if (targetFiles) {
|
if (targetFiles) {
|
||||||
if (fileConfig.number_limits) {
|
if (fileConfig.number_limits) {
|
||||||
for (let i = 0; i < targetFiles.length; i++) {
|
for (let i = 0; i < targetFiles.length; i++) {
|
||||||
if (i + 1 + files.length <= fileConfig.number_limits)
|
if (i + 1 + files.length <= fileConfig.number_limits) { handleLocalFileUpload(targetFiles[i]) }
|
||||||
handleLocalFileUpload(targetFiles[i])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import cn from '@/utils/classnames'
|
|||||||
import ReplayLine from '@/app/components/base/icons/other/ReplayLine'
|
import ReplayLine from '@/app/components/base/icons/other/ReplayLine'
|
||||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||||
|
|
||||||
type FileInAttachmentItemProps = {
|
interface FileInAttachmentItemProps {
|
||||||
file: FileEntity
|
file: FileEntity
|
||||||
showDeleteAction?: boolean
|
showDeleteAction?: boolean
|
||||||
showDownloadAction?: boolean
|
showDownloadAction?: boolean
|
||||||
|
|||||||
@ -67,7 +67,7 @@ const FILE_TYPE_ICON_MAP = {
|
|||||||
color: 'text-[#00B2EA]',
|
color: 'text-[#00B2EA]',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
type FileTypeIconProps = {
|
interface FileTypeIconProps {
|
||||||
type: FileAppearanceType
|
type: FileAppearanceType
|
||||||
size?: 'sm' | 'lg' | 'md'
|
size?: 'sm' | 'lg' | 'md'
|
||||||
className?: string
|
className?: string
|
||||||
|
|||||||
@ -148,8 +148,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
|||||||
const newFiles = produce(files, (draft) => {
|
const newFiles = produce(files, (draft) => {
|
||||||
const index = draft.findIndex(file => file.id === newFile.id)
|
const index = draft.findIndex(file => file.id === newFile.id)
|
||||||
|
|
||||||
if (index > -1)
|
if (index > -1) { draft[index] = newFile }
|
||||||
draft[index] = newFile
|
|
||||||
})
|
})
|
||||||
setFiles(newFiles)
|
setFiles(newFiles)
|
||||||
}, [fileStore])
|
}, [fileStore])
|
||||||
@ -198,10 +197,8 @@ export const useFile = (fileConfig: FileUpload) => {
|
|||||||
const files = fileStore.getState().files
|
const files = fileStore.getState().files
|
||||||
const file = files.find(file => file.id === fileId)
|
const file = files.find(file => file.id === fileId)
|
||||||
|
|
||||||
if (file && file.progress < 80 && file.progress >= 0)
|
if (file && file.progress < 80 && file.progress >= 0) { handleUpdateFile({ ...file, progress: file.progress + 20 }) }
|
||||||
handleUpdateFile({ ...file, progress: file.progress + 20 })
|
else { clearTimeout(timer) }
|
||||||
else
|
|
||||||
clearTimeout(timer)
|
|
||||||
}, 200)
|
}, 200)
|
||||||
}, [fileStore, handleUpdateFile])
|
}, [fileStore, handleUpdateFile])
|
||||||
const handleLoadFileFromLink = useCallback((url: string) => {
|
const handleLoadFileFromLink = useCallback((url: string) => {
|
||||||
@ -235,10 +232,8 @@ export const useFile = (fileConfig: FileUpload) => {
|
|||||||
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
|
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
|
||||||
handleRemoveFile(uploadingFile.id)
|
handleRemoveFile(uploadingFile.id)
|
||||||
}
|
}
|
||||||
if (!checkSizeLimit(newFile.supportFileType, newFile.size))
|
if (!checkSizeLimit(newFile.supportFileType, newFile.size)) { handleRemoveFile(uploadingFile.id) }
|
||||||
handleRemoveFile(uploadingFile.id)
|
else { handleUpdateFile(newFile) }
|
||||||
else
|
|
||||||
handleUpdateFile(newFile)
|
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') })
|
notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') })
|
||||||
handleRemoveFile(uploadingFile.id)
|
handleRemoveFile(uploadingFile.id)
|
||||||
@ -263,8 +258,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
|||||||
}
|
}
|
||||||
const allowedFileTypes = fileConfig.allowed_file_types
|
const allowedFileTypes = fileConfig.allowed_file_types
|
||||||
const fileType = getSupportFileType(file.name, file.type, allowedFileTypes?.includes(SupportUploadFileTypes.custom))
|
const fileType = getSupportFileType(file.name, file.type, allowedFileTypes?.includes(SupportUploadFileTypes.custom))
|
||||||
if (!checkSizeLimit(fileType, file.size))
|
if (!checkSizeLimit(fileType, file.size)) { return }
|
||||||
return
|
|
||||||
|
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
const isImage = file.type.startsWith('image')
|
const isImage = file.type.startsWith('image')
|
||||||
@ -344,8 +338,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
|||||||
|
|
||||||
const file = e.dataTransfer.files[0]
|
const file = e.dataTransfer.files[0]
|
||||||
|
|
||||||
if (file)
|
if (file) { handleLocalFileUpload(file) }
|
||||||
handleLocalFileUpload(file)
|
|
||||||
}, [handleLocalFileUpload])
|
}, [handleLocalFileUpload])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -19,12 +19,12 @@ import Button from '@/app/components/base/button'
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
|
|
||||||
type Option = {
|
interface Option {
|
||||||
value: string
|
value: string
|
||||||
label: string
|
label: string
|
||||||
icon: JSX.Element
|
icon: JSX.Element
|
||||||
}
|
}
|
||||||
type FileUploaderInAttachmentProps = {
|
interface FileUploaderInAttachmentProps {
|
||||||
fileConfig: FileUpload
|
fileConfig: FileUpload
|
||||||
}
|
}
|
||||||
const FileUploaderInAttachment = ({
|
const FileUploaderInAttachment = ({
|
||||||
@ -71,8 +71,7 @@ const FileUploaderInAttachment = ({
|
|||||||
return (open: boolean) => renderButton(option, open)
|
return (open: boolean) => renderButton(option, open)
|
||||||
}, [renderButton])
|
}, [renderButton])
|
||||||
const renderOption = useCallback((option: Option) => {
|
const renderOption = useCallback((option: Option) => {
|
||||||
if (option.value === TransferMethod.local_file && fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.local_file))
|
if (option.value === TransferMethod.local_file && fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.local_file)) { return renderButton(option) }
|
||||||
return renderButton(option)
|
|
||||||
|
|
||||||
if (option.value === TransferMethod.remote_url && fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.remote_url)) {
|
if (option.value === TransferMethod.remote_url && fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.remote_url)) {
|
||||||
return (
|
return (
|
||||||
@ -109,7 +108,7 @@ const FileUploaderInAttachment = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileUploaderInAttachmentWrapperProps = {
|
interface FileUploaderInAttachmentWrapperProps {
|
||||||
value?: FileEntity[]
|
value?: FileEntity[]
|
||||||
onChange: (files: FileEntity[]) => void
|
onChange: (files: FileEntity[]) => void
|
||||||
fileConfig: FileUpload
|
fileConfig: FileUpload
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import type {
|
|||||||
FileEntity,
|
FileEntity,
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
type Shape = {
|
interface Shape {
|
||||||
files: FileEntity[]
|
files: FileEntity[]
|
||||||
setFiles: (files: FileEntity[]) => void
|
setFiles: (files: FileEntity[]) => void
|
||||||
}
|
}
|
||||||
@ -34,8 +34,7 @@ export const FileContext = createContext<FileStore | null>(null)
|
|||||||
|
|
||||||
export function useStore<T>(selector: (state: Shape) => T): T {
|
export function useStore<T>(selector: (state: Shape) => T): T {
|
||||||
const store = useContext(FileContext)
|
const store = useContext(FileContext)
|
||||||
if (!store)
|
if (!store) { throw new Error('Missing FileContext.Provider in the tree') }
|
||||||
throw new Error('Missing FileContext.Provider in the tree')
|
|
||||||
|
|
||||||
return useZustandStore(store, selector)
|
return useZustandStore(store, selector)
|
||||||
}
|
}
|
||||||
@ -44,7 +43,7 @@ export const useFileStore = () => {
|
|||||||
return useContext(FileContext)!
|
return useContext(FileContext)!
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileProviderProps = {
|
interface FileProviderProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
value?: FileEntity[]
|
value?: FileEntity[]
|
||||||
onChange?: (files: FileEntity[]) => void
|
onChange?: (files: FileEntity[]) => void
|
||||||
@ -56,8 +55,7 @@ export const FileContextProvider = ({
|
|||||||
}: FileProviderProps) => {
|
}: FileProviderProps) => {
|
||||||
const storeRef = useRef<FileStore | undefined>(undefined)
|
const storeRef = useRef<FileStore | undefined>(undefined)
|
||||||
|
|
||||||
if (!storeRef.current)
|
if (!storeRef.current) { storeRef.current = createFileStore(value, onChange) }
|
||||||
storeRef.current = createFileStore(value, onChange)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileContext.Provider value={storeRef.current}>
|
<FileContext.Provider value={storeRef.current}>
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export enum FileAppearanceTypeEnum {
|
|||||||
|
|
||||||
export type FileAppearanceType = keyof typeof FileAppearanceTypeEnum
|
export type FileAppearanceType = keyof typeof FileAppearanceTypeEnum
|
||||||
|
|
||||||
export type FileEntity = {
|
export interface FileEntity {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
size: number
|
size: number
|
||||||
@ -32,7 +32,7 @@ export type FileEntity = {
|
|||||||
isRemote?: boolean
|
isRemote?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EnabledOrDisabled = {
|
export interface EnabledOrDisabled {
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ export enum Resolution {
|
|||||||
high = 'high',
|
high = 'high',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FileUploadConfigResponse = {
|
export interface FileUploadConfigResponse {
|
||||||
batch_count_limit: number
|
batch_count_limit: number
|
||||||
image_file_size_limit?: number | string // default is 10MB
|
image_file_size_limit?: number | string // default is 10MB
|
||||||
file_size_limit: number // default is 15MB
|
file_size_limit: number // default is 15MB
|
||||||
@ -71,7 +71,7 @@ export enum SupportUploadFileTypes {
|
|||||||
custom = 'custom',
|
custom = 'custom',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FileResponse = {
|
export interface FileResponse {
|
||||||
related_id: string
|
related_id: string
|
||||||
extension: string
|
extension: string
|
||||||
filename: string
|
filename: string
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { FILE_EXTS } from './constants'
|
|||||||
import { upload } from '@/service/base'
|
import { upload } from '@/service/base'
|
||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
|
|
||||||
type FileUploadParams = {
|
interface FileUploadParams {
|
||||||
file: File
|
file: File
|
||||||
onProgressCallback: (progress: number) => void
|
onProgressCallback: (progress: number) => void
|
||||||
onSuccessCallback: (res: { id: string }) => void
|
onSuccessCallback: (res: { id: string }) => void
|
||||||
@ -42,21 +42,17 @@ export const fileUpload: FileUpload = ({
|
|||||||
|
|
||||||
export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
|
export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
|
||||||
let extension = ''
|
let extension = ''
|
||||||
if (fileMimetype)
|
if (fileMimetype) { extension = mime.getExtension(fileMimetype) || '' }
|
||||||
extension = mime.getExtension(fileMimetype) || ''
|
|
||||||
|
|
||||||
if (fileName && !extension) {
|
if (fileName && !extension) {
|
||||||
const fileNamePair = fileName.split('.')
|
const fileNamePair = fileName.split('.')
|
||||||
const fileNamePairLength = fileNamePair.length
|
const fileNamePairLength = fileNamePair.length
|
||||||
|
|
||||||
if (fileNamePairLength > 1)
|
if (fileNamePairLength > 1) { extension = fileNamePair[fileNamePairLength - 1] }
|
||||||
extension = fileNamePair[fileNamePairLength - 1]
|
else { extension = '' }
|
||||||
else
|
|
||||||
extension = ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRemote)
|
if (isRemote) { extension = '' }
|
||||||
extension = ''
|
|
||||||
|
|
||||||
return extension
|
return extension
|
||||||
}
|
}
|
||||||
@ -64,50 +60,37 @@ export const getFileExtension = (fileName: string, fileMimetype: string, isRemot
|
|||||||
export const getFileAppearanceType = (fileName: string, fileMimetype: string) => {
|
export const getFileAppearanceType = (fileName: string, fileMimetype: string) => {
|
||||||
const extension = getFileExtension(fileName, fileMimetype)
|
const extension = getFileExtension(fileName, fileMimetype)
|
||||||
|
|
||||||
if (extension === 'gif')
|
if (extension === 'gif') { return FileAppearanceTypeEnum.gif }
|
||||||
return FileAppearanceTypeEnum.gif
|
|
||||||
|
|
||||||
if (FILE_EXTS.image.includes(extension.toUpperCase()))
|
if (FILE_EXTS.image.includes(extension.toUpperCase())) { return FileAppearanceTypeEnum.image }
|
||||||
return FileAppearanceTypeEnum.image
|
|
||||||
|
|
||||||
if (FILE_EXTS.video.includes(extension.toUpperCase()))
|
if (FILE_EXTS.video.includes(extension.toUpperCase())) { return FileAppearanceTypeEnum.video }
|
||||||
return FileAppearanceTypeEnum.video
|
|
||||||
|
|
||||||
if (FILE_EXTS.audio.includes(extension.toUpperCase()))
|
if (FILE_EXTS.audio.includes(extension.toUpperCase())) { return FileAppearanceTypeEnum.audio }
|
||||||
return FileAppearanceTypeEnum.audio
|
|
||||||
|
|
||||||
if (extension === 'html')
|
if (extension === 'html') { return FileAppearanceTypeEnum.code }
|
||||||
return FileAppearanceTypeEnum.code
|
|
||||||
|
|
||||||
if (extension === 'pdf')
|
if (extension === 'pdf') { return FileAppearanceTypeEnum.pdf }
|
||||||
return FileAppearanceTypeEnum.pdf
|
|
||||||
|
|
||||||
if (extension === 'md' || extension === 'markdown' || extension === 'mdx')
|
if (extension === 'md' || extension === 'markdown' || extension === 'mdx') { return FileAppearanceTypeEnum.markdown }
|
||||||
return FileAppearanceTypeEnum.markdown
|
|
||||||
|
|
||||||
if (extension === 'xlsx' || extension === 'xls')
|
if (extension === 'xlsx' || extension === 'xls') { return FileAppearanceTypeEnum.excel }
|
||||||
return FileAppearanceTypeEnum.excel
|
|
||||||
|
|
||||||
if (extension === 'docx' || extension === 'doc')
|
if (extension === 'docx' || extension === 'doc') { return FileAppearanceTypeEnum.word }
|
||||||
return FileAppearanceTypeEnum.word
|
|
||||||
|
|
||||||
if (extension === 'pptx' || extension === 'ppt')
|
if (extension === 'pptx' || extension === 'ppt') { return FileAppearanceTypeEnum.ppt }
|
||||||
return FileAppearanceTypeEnum.ppt
|
|
||||||
|
|
||||||
if (FILE_EXTS.document.includes(extension.toUpperCase()))
|
if (FILE_EXTS.document.includes(extension.toUpperCase())) { return FileAppearanceTypeEnum.document }
|
||||||
return FileAppearanceTypeEnum.document
|
|
||||||
|
|
||||||
return FileAppearanceTypeEnum.custom
|
return FileAppearanceTypeEnum.custom
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSupportFileType = (fileName: string, fileMimetype: string, isCustom?: boolean) => {
|
export const getSupportFileType = (fileName: string, fileMimetype: string, isCustom?: boolean) => {
|
||||||
if (isCustom)
|
if (isCustom) { return SupportUploadFileTypes.custom }
|
||||||
return SupportUploadFileTypes.custom
|
|
||||||
|
|
||||||
const extension = getFileExtension(fileName, fileMimetype)
|
const extension = getFileExtension(fileName, fileMimetype)
|
||||||
for (const key in FILE_EXTS) {
|
for (const key in FILE_EXTS) {
|
||||||
if ((FILE_EXTS[key]).includes(extension.toUpperCase()))
|
if ((FILE_EXTS[key]).includes(extension.toUpperCase())) { return key }
|
||||||
return key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
@ -144,8 +127,7 @@ export const getFileNameFromUrl = (url: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getSupportFileExtensionList = (allowFileTypes: string[], allowFileExtensions: string[]) => {
|
export const getSupportFileExtensionList = (allowFileTypes: string[], allowFileExtensions: string[]) => {
|
||||||
if (allowFileTypes.includes(SupportUploadFileTypes.custom))
|
if (allowFileTypes.includes(SupportUploadFileTypes.custom)) { return allowFileExtensions.map(item => item.slice(1).toUpperCase()) }
|
||||||
return allowFileExtensions.map(item => item.slice(1).toUpperCase())
|
|
||||||
|
|
||||||
return allowFileTypes.map(type => FILE_EXTS[type]).flat()
|
return allowFileTypes.map(type => FILE_EXTS[type]).flat()
|
||||||
}
|
}
|
||||||
@ -174,11 +156,9 @@ export const getFilesInLogs = (rawData: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fileIsUploaded = (file: FileEntity) => {
|
export const fileIsUploaded = (file: FileEntity) => {
|
||||||
if (file.uploadedId)
|
if (file.uploadedId) { return true }
|
||||||
return true
|
|
||||||
|
|
||||||
if (file.transferMethod === TransferMethod.remote_url && file.progress === 100)
|
if (file.transferMethod === TransferMethod.remote_url && file.progress === 100) { return true }
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const downloadFile = (url: string, filename: string) => {
|
export const downloadFile = (url: string, filename: string) => {
|
||||||
|
|||||||
@ -2,12 +2,12 @@ import { forwardRef } from 'react'
|
|||||||
import { generate } from './utils'
|
import { generate } from './utils'
|
||||||
import type { AbstractNode } from './utils'
|
import type { AbstractNode } from './utils'
|
||||||
|
|
||||||
export type IconData = {
|
export interface IconData {
|
||||||
name: string
|
name: string
|
||||||
icon: AbstractNode
|
icon: AbstractNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IconBaseProps = {
|
export interface IconBaseProps {
|
||||||
data: IconData
|
data: IconData
|
||||||
className?: string
|
className?: string
|
||||||
onClick?: React.MouseEventHandler<SVGElement>
|
onClick?: React.MouseEventHandler<SVGElement>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const Icon = (
|
|||||||
ref,
|
ref,
|
||||||
...props
|
...props
|
||||||
}: React.SVGProps<SVGSVGElement> & {
|
}: React.SVGProps<SVGSVGElement> & {
|
||||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
|
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
|
||||||
},
|
},
|
||||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export type AbstractNode = {
|
export interface AbstractNode {
|
||||||
name: string
|
name: string
|
||||||
attributes: {
|
attributes: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
@ -8,7 +8,7 @@ export type AbstractNode = {
|
|||||||
children?: AbstractNode[]
|
children?: AbstractNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Attrs = {
|
export interface Attrs {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import cn from 'classnames'
|
|||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
srcs: string[]
|
srcs: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +65,9 @@ export const ImageGalleryTest = () => {
|
|||||||
const imgGallerySrcs = (() => {
|
const imgGallerySrcs = (() => {
|
||||||
const srcs = []
|
const srcs = []
|
||||||
for (let i = 0; i < 6; i++)
|
for (let i = 0; i < 6; i++)
|
||||||
// srcs.push('https://placekitten.com/640/360')
|
// srcs.push('https://placekitten.com/640/360')
|
||||||
// srcs.push('https://placekitten.com/360/640')
|
// srcs.push('https://placekitten.com/360/640')
|
||||||
srcs.push('https://placekitten.com/360/360')
|
{ srcs.push('https://placekitten.com/360/360') }
|
||||||
|
|
||||||
return srcs
|
return srcs
|
||||||
})()
|
})()
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
import Upload03 from '@/app/components/base/icons/line/upload-03'
|
import Upload03 from '@/app/components/base/icons/line/upload-03'
|
||||||
import type { ImageFile, VisionSettings } from '@/types/app'
|
import type { ImageFile, VisionSettings } from '@/types/app'
|
||||||
|
|
||||||
type UploadOnlyFromLocalProps = {
|
interface UploadOnlyFromLocalProps {
|
||||||
onUpload: (imageFile: ImageFile) => void
|
onUpload: (imageFile: ImageFile) => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
limit?: number
|
limit?: number
|
||||||
@ -39,7 +39,7 @@ const UploadOnlyFromLocal: FC<UploadOnlyFromLocalProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploaderButtonProps = {
|
interface UploaderButtonProps {
|
||||||
methods: VisionSettings['transfer_methods']
|
methods: VisionSettings['transfer_methods']
|
||||||
onUpload: (imageFile: ImageFile) => void
|
onUpload: (imageFile: ImageFile) => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
@ -62,8 +62,7 @@ const UploaderButton: FC<UploaderButtonProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
if (disabled)
|
if (disabled) { return }
|
||||||
return
|
|
||||||
|
|
||||||
setOpen(v => !v)
|
setOpen(v => !v)
|
||||||
}
|
}
|
||||||
@ -115,7 +114,7 @@ const UploaderButton: FC<UploaderButtonProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatImageUploaderProps = {
|
interface ChatImageUploaderProps {
|
||||||
settings: VisionSettings
|
settings: VisionSettings
|
||||||
onUpload: (imageFile: ImageFile) => void
|
onUpload: (imageFile: ImageFile) => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import Button from '@/app/components/base/button'
|
|||||||
import type { ImageFile } from '@/types/app'
|
import type { ImageFile } from '@/types/app'
|
||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
|
|
||||||
type ImageLinkInputProps = {
|
interface ImageLinkInputProps {
|
||||||
onUpload: (imageFile: ImageFile) => void
|
onUpload: (imageFile: ImageFile) => void
|
||||||
}
|
}
|
||||||
const regex = /^(https?|ftp):\/\//
|
const regex = /^(https?|ftp):\/\//
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import type { ImageFile } from '@/types/app'
|
|||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||||
|
|
||||||
type ImageListProps = {
|
interface ImageListProps {
|
||||||
list: ImageFile[]
|
list: ImageFile[]
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
onRemove?: (imageFileId: string) => void
|
onRemove?: (imageFileId: string) => void
|
||||||
@ -31,12 +31,10 @@ const ImageList: FC<ImageListProps> = ({
|
|||||||
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
|
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
|
||||||
|
|
||||||
const handleImageLinkLoadSuccess = (item: ImageFile) => {
|
const handleImageLinkLoadSuccess = (item: ImageFile) => {
|
||||||
if (item.type === TransferMethod.remote_url && onImageLinkLoadSuccess && item.progress !== -1)
|
if (item.type === TransferMethod.remote_url && onImageLinkLoadSuccess && item.progress !== -1) { onImageLinkLoadSuccess(item._id) }
|
||||||
onImageLinkLoadSuccess(item._id)
|
|
||||||
}
|
}
|
||||||
const handleImageLinkLoadError = (item: ImageFile) => {
|
const handleImageLinkLoadError = (item: ImageFile) => {
|
||||||
if (item.type === TransferMethod.remote_url && onImageLinkLoadError)
|
if (item.type === TransferMethod.remote_url && onImageLinkLoadError) { onImageLinkLoadError(item._id) }
|
||||||
onImageLinkLoadError(item._id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { FC } from 'react'
|
|||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import XClose from '@/app/components/base/icons/line/x-close'
|
import XClose from '@/app/components/base/icons/line/x-close'
|
||||||
|
|
||||||
type ImagePreviewProps = {
|
interface ImagePreviewProps {
|
||||||
url: string
|
url: string
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import type { ImageFile } from '@/types/app'
|
|||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
|
|
||||||
type UploaderProps = {
|
interface UploaderProps {
|
||||||
children: (hovering: boolean) => JSX.Element
|
children: (hovering: boolean) => JSX.Element
|
||||||
onUpload: (imageFile: ImageFile) => void
|
onUpload: (imageFile: ImageFile) => void
|
||||||
limit?: number
|
limit?: number
|
||||||
@ -28,8 +28,7 @@ const Uploader: FC<UploaderProps> = ({
|
|||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0]
|
const file = e.target.files?.[0]
|
||||||
|
|
||||||
if (!file)
|
if (!file) { return }
|
||||||
return
|
|
||||||
|
|
||||||
if (limit && file.size > limit * 1024 * 1024) {
|
if (limit && file.size > limit * 1024 * 1024) {
|
||||||
notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerLimit', { size: limit }) })
|
notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerLimit', { size: limit }) })
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { upload } from '@/service/base'
|
import { upload } from '@/service/base'
|
||||||
|
|
||||||
type ImageUploadParams = {
|
interface ImageUploadParams {
|
||||||
file: File
|
file: File
|
||||||
onProgressCallback: (progress: number) => void
|
onProgressCallback: (progress: number) => void
|
||||||
onSuccessCallback: (res: { id: string }) => void
|
onSuccessCallback: (res: { id: string }) => void
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
|
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
|
||||||
type ILoadingProps = {
|
interface ILoadingProps {
|
||||||
type?: 'area' | 'app'
|
type?: 'area' | 'app'
|
||||||
}
|
}
|
||||||
const Loading = (
|
const Loading = (
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
|
|
||||||
import type { OffsetOptions, Placement } from '@floating-ui/react'
|
import type { OffsetOptions, Placement } from '@floating-ui/react'
|
||||||
|
|
||||||
type PortalToFollowElemOptions = {
|
interface PortalToFollowElemOptions {
|
||||||
/*
|
/*
|
||||||
* top, bottom, left, right
|
* top, bottom, left, right
|
||||||
* start, end. Default is middle
|
* start, end. Default is middle
|
||||||
@ -85,8 +85,7 @@ const PortalToFollowElemContext = React.createContext<ContextType>(null)
|
|||||||
export function usePortalToFollowElemContext() {
|
export function usePortalToFollowElemContext() {
|
||||||
const context = React.useContext(PortalToFollowElemContext)
|
const context = React.useContext(PortalToFollowElemContext)
|
||||||
|
|
||||||
if (context == null)
|
if (context == null) { throw new Error('PortalToFollowElem components must be wrapped in <PortalToFollowElem />') }
|
||||||
throw new Error('PortalToFollowElem components must be wrapped in <PortalToFollowElem />')
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
@ -106,7 +105,7 @@ export function PortalToFollowElem({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const PortalToFollowElemTrigger = React.forwardRef<
|
export const PortalToFollowElemTrigger = React.forwardRef<
|
||||||
HTMLElement,
|
HTMLElement,
|
||||||
React.HTMLProps<HTMLElement> & { asChild?: boolean }
|
React.HTMLProps<HTMLElement> & { asChild?: boolean }
|
||||||
>(({ children, asChild = false, ...props }, propRef) => {
|
>(({ children, asChild = false, ...props }, propRef) => {
|
||||||
const context = usePortalToFollowElemContext()
|
const context = usePortalToFollowElemContext()
|
||||||
@ -141,14 +140,13 @@ React.HTMLProps<HTMLElement> & { asChild?: boolean }
|
|||||||
PortalToFollowElemTrigger.displayName = 'PortalToFollowElemTrigger'
|
PortalToFollowElemTrigger.displayName = 'PortalToFollowElemTrigger'
|
||||||
|
|
||||||
export const PortalToFollowElemContent = React.forwardRef<
|
export const PortalToFollowElemContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLProps<HTMLDivElement>
|
React.HTMLProps<HTMLDivElement>
|
||||||
>(({ style, ...props }, propRef) => {
|
>(({ style, ...props }, propRef) => {
|
||||||
const context = usePortalToFollowElemContext()
|
const context = usePortalToFollowElemContext()
|
||||||
const ref = useMergeRefs([context.refs.setFloating, propRef])
|
const ref = useMergeRefs([context.refs.setFloating, propRef])
|
||||||
|
|
||||||
if (!context.open)
|
if (!context.open) { return null }
|
||||||
return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FloatingPortal>
|
<FloatingPortal>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
type ProgressBarProps = {
|
interface ProgressBarProps {
|
||||||
percent: number
|
percent: number
|
||||||
}
|
}
|
||||||
const ProgressBar = ({
|
const ProgressBar = ({
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type ProgressCircleProps = {
|
interface ProgressCircleProps {
|
||||||
className?: string
|
className?: string
|
||||||
percentage?: number
|
percentage?: number
|
||||||
size?: number
|
size?: number
|
||||||
|
|||||||
@ -15,12 +15,12 @@ const defaultItems = [
|
|||||||
{ value: 7, name: 'option7' },
|
{ value: 7, name: 'option7' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export type Item = {
|
export interface Item {
|
||||||
value: number | string
|
value: number | string
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ISelectProps = {
|
export interface ISelectProps {
|
||||||
className?: string
|
className?: string
|
||||||
items?: Item[]
|
items?: Item[]
|
||||||
defaultValue?: number | string
|
defaultValue?: number | string
|
||||||
@ -45,8 +45,7 @@ const Select: FC<ISelectProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let defaultSelect = null
|
let defaultSelect = null
|
||||||
const existed = items.find((item: Item) => item.value === defaultValue)
|
const existed = items.find((item: Item) => item.value === defaultValue)
|
||||||
if (existed)
|
if (existed) { defaultSelect = existed }
|
||||||
defaultSelect = existed
|
|
||||||
|
|
||||||
setSelectedItem(defaultSelect)
|
setSelectedItem(defaultSelect)
|
||||||
}, [defaultValue])
|
}, [defaultValue])
|
||||||
@ -77,23 +76,20 @@ const Select: FC<ISelectProps> = ({
|
|||||||
? <Combobox.Input
|
? <Combobox.Input
|
||||||
className={`w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-not-allowed`}
|
className={`w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-not-allowed`}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!disabled)
|
if (!disabled) { setQuery(event.target.value) }
|
||||||
setQuery(event.target.value)
|
|
||||||
}}
|
}}
|
||||||
displayValue={(item: Item) => item?.name}
|
displayValue={(item: Item) => item?.name}
|
||||||
/>
|
/>
|
||||||
: <Combobox.Button onClick={
|
: <Combobox.Button onClick={
|
||||||
() => {
|
() => {
|
||||||
if (!disabled)
|
if (!disabled) { setOpen(!open) }
|
||||||
setOpen(!open)
|
|
||||||
}
|
}
|
||||||
} className={`flex items-center h-9 w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200`}>
|
} className={`flex items-center h-9 w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200`}>
|
||||||
{selectedItem?.name}
|
{selectedItem?.name}
|
||||||
</Combobox.Button>}
|
</Combobox.Button>}
|
||||||
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none group-hover:bg-gray-200" onClick={
|
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none group-hover:bg-gray-200" onClick={
|
||||||
() => {
|
() => {
|
||||||
if (!disabled)
|
if (!disabled) { setOpen(!open) }
|
||||||
setOpen(!open)
|
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
{open ? <ChevronUpIcon className="h-5 w-5" /> : <ChevronDownIcon className="h-5 w-5" />}
|
{open ? <ChevronUpIcon className="h-5 w-5" /> : <ChevronDownIcon className="h-5 w-5" />}
|
||||||
@ -147,8 +143,7 @@ const SimpleSelect: FC<ISelectProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let defaultSelect = null
|
let defaultSelect = null
|
||||||
const existed = items.find((item: Item) => item.value === defaultValue)
|
const existed = items.find((item: Item) => item.value === defaultValue)
|
||||||
if (existed)
|
if (existed) { defaultSelect = existed }
|
||||||
defaultSelect = existed
|
|
||||||
|
|
||||||
setSelectedItem(defaultSelect)
|
setSelectedItem(defaultSelect)
|
||||||
}, [defaultValue])
|
}, [defaultValue])
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
children?: React.ReactNode | string
|
children?: React.ReactNode | string
|
||||||
|
|||||||
18
app/components/base/streamdown-markdown.tsx
Normal file
18
app/components/base/streamdown-markdown.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
'use client'
|
||||||
|
import { Streamdown } from 'streamdown'
|
||||||
|
import 'katex/dist/katex.min.css'
|
||||||
|
|
||||||
|
interface StreamdownMarkdownProps {
|
||||||
|
content: string
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StreamdownMarkdown({ content, className = '' }: StreamdownMarkdownProps) {
|
||||||
|
return (
|
||||||
|
<div className={`markdown-body streamdown-markdown ${className}`}>
|
||||||
|
<Streamdown>{content}</Streamdown>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StreamdownMarkdown
|
||||||
@ -11,14 +11,14 @@ import {
|
|||||||
} from '@heroicons/react/20/solid'
|
} from '@heroicons/react/20/solid'
|
||||||
import { createContext, useContext } from 'use-context-selector'
|
import { createContext, useContext } from 'use-context-selector'
|
||||||
|
|
||||||
export type IToastProps = {
|
export interface IToastProps {
|
||||||
type?: 'success' | 'error' | 'warning' | 'info'
|
type?: 'success' | 'error' | 'warning' | 'info'
|
||||||
duration?: number
|
duration?: number
|
||||||
message: string
|
message: string
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
type IToastContext = {
|
interface IToastContext {
|
||||||
notify: (props: IToastProps) => void
|
notify: (props: IToastProps) => void
|
||||||
}
|
}
|
||||||
const defaultDuring = 3000
|
const defaultDuring = 3000
|
||||||
@ -33,8 +33,7 @@ const Toast = ({
|
|||||||
children,
|
children,
|
||||||
}: IToastProps) => {
|
}: IToastProps) => {
|
||||||
// sometimes message is react node array. Not handle it.
|
// sometimes message is react node array. Not handle it.
|
||||||
if (typeof message !== 'string')
|
if (typeof message !== 'string') { return null }
|
||||||
return null
|
|
||||||
|
|
||||||
return <div className={classNames(
|
return <div className={classNames(
|
||||||
'fixed rounded-md p-4 my-4 mx-8 z-50',
|
'fixed rounded-md p-4 my-4 mx-8 z-50',
|
||||||
@ -124,8 +123,7 @@ Toast.notify = ({
|
|||||||
root.render(<Toast type={type} message={message} duration={duration} />)
|
root.render(<Toast type={type} message={message} duration={duration} />)
|
||||||
document.body.appendChild(holder)
|
document.body.appendChild(holder)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (holder)
|
if (holder) { holder.remove() }
|
||||||
holder.remove()
|
|
||||||
}, duration || defaultDuring)
|
}, duration || defaultDuring)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||||
export type TooltipProps = {
|
export interface TooltipProps {
|
||||||
position?: 'top' | 'right' | 'bottom' | 'left'
|
position?: 'top' | 'right' | 'bottom' | 'left'
|
||||||
triggerMethod?: 'hover' | 'click'
|
triggerMethod?: 'hover' | 'click'
|
||||||
popupContent: React.ReactNode
|
popupContent: React.ReactNode
|
||||||
@ -13,7 +13,7 @@ const arrow = (
|
|||||||
<svg className="absolute text-white h-2 w-full left-0 top-full" x="0px" y="0px" viewBox="0 0 255 255"><polygon className="fill-current" points="0,0 127.5,127.5 255,0"></polygon></svg>
|
<svg className="absolute text-white h-2 w-full left-0 top-full" x="0px" y="0px" viewBox="0 0 255 255"><polygon className="fill-current" points="0,0 127.5,127.5 255,0"></polygon></svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
const Tooltip: FC< TooltipProps> = ({
|
const Tooltip: FC<TooltipProps> = ({
|
||||||
position = 'top',
|
position = 'top',
|
||||||
triggerMethod = 'hover',
|
triggerMethod = 'hover',
|
||||||
popupContent,
|
popupContent,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import React from 'react'
|
|||||||
import { Tooltip as ReactTooltip } from 'react-tooltip' // fixed version to 5.8.3 https://github.com/ReactTooltip/react-tooltip/issues/972
|
import { Tooltip as ReactTooltip } from 'react-tooltip' // fixed version to 5.8.3 https://github.com/ReactTooltip/react-tooltip/issues/972
|
||||||
import 'react-tooltip/dist/react-tooltip.css'
|
import 'react-tooltip/dist/react-tooltip.css'
|
||||||
|
|
||||||
type TooltipProps = {
|
interface TooltipProps {
|
||||||
selector: string
|
selector: string
|
||||||
content?: string
|
content?: string
|
||||||
htmlContent?: React.ReactNode
|
htmlContent?: React.ReactNode
|
||||||
|
|||||||
@ -1,30 +1,32 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
|
||||||
import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import LoadingAnim from '../loading-anim'
|
|
||||||
import type { FeedbackFunc } from '../type'
|
import type { FeedbackFunc } from '../type'
|
||||||
import s from '../style.module.css'
|
|
||||||
import ImageGallery from '../../base/image-gallery'
|
|
||||||
import Thought from '../thought'
|
|
||||||
import { randomString } from '@/utils/string'
|
|
||||||
import type { ChatItem, MessageRating, VisionFile } from '@/types/app'
|
import type { ChatItem, MessageRating, VisionFile } from '@/types/app'
|
||||||
|
import type { Emoji } from '@/types/tools'
|
||||||
|
import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import StreamdownMarkdown from '@/app/components/base/streamdown-markdown'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import WorkflowProcess from '@/app/components/workflow/workflow-process'
|
import WorkflowProcess from '@/app/components/workflow/workflow-process'
|
||||||
import { Markdown } from '@/app/components/base/markdown'
|
import { randomString } from '@/utils/string'
|
||||||
import Button from '@/app/components/base/button'
|
import ImageGallery from '../../base/image-gallery'
|
||||||
import type { Emoji } from '@/types/tools'
|
import LoadingAnim from '../loading-anim'
|
||||||
|
import s from '../style.module.css'
|
||||||
|
import Thought from '../thought'
|
||||||
|
|
||||||
const OperationBtn = ({ innerContent, onClick, className }: { innerContent: React.ReactNode; onClick?: () => void; className?: string }) => (
|
function OperationBtn({ innerContent, onClick, className }: { innerContent: React.ReactNode, onClick?: () => void, className?: string }) {
|
||||||
<div
|
return (
|
||||||
className={`relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800 ${className ?? ''}`}
|
<div
|
||||||
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
|
className={`relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800 ${className ?? ''}`}
|
||||||
onClick={onClick && onClick}
|
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
|
||||||
>
|
onClick={onClick && onClick}
|
||||||
{innerContent}
|
>
|
||||||
</div>
|
{innerContent}
|
||||||
)
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => (
|
const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => (
|
||||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@ -33,29 +35,35 @@ const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const RatingIcon: FC<{ isLike: boolean }> = ({ isLike }) => {
|
const RatingIcon: FC<{ isLike: boolean }> = ({ isLike }) => {
|
||||||
return isLike ? <HandThumbUpIcon className='w-4 h-4' /> : <HandThumbDownIcon className='w-4 h-4' />
|
return isLike ? <HandThumbUpIcon className="w-4 h-4" /> : <HandThumbDownIcon className="w-4 h-4" />
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditIcon: FC<{ className?: string }> = ({ className }) => {
|
const EditIcon: FC<{ className?: string }> = ({ className }) => {
|
||||||
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
|
return (
|
||||||
<path d="M14 11.9998L13.3332 12.7292C12.9796 13.1159 12.5001 13.3332 12.0001 13.3332C11.5001 13.3332 11.0205 13.1159 10.6669 12.7292C10.3128 12.3432 9.83332 12.1265 9.33345 12.1265C8.83359 12.1265 8.35409 12.3432 7.99998 12.7292M2 13.3332H3.11636C3.44248 13.3332 3.60554 13.3332 3.75899 13.2963C3.89504 13.2637 4.0251 13.2098 4.1444 13.1367C4.27895 13.0542 4.39425 12.9389 4.62486 12.7083L13 4.33316C13.5523 3.78087 13.5523 2.88544 13 2.33316C12.4477 1.78087 11.5523 1.78087 11 2.33316L2.62484 10.7083C2.39424 10.9389 2.27894 11.0542 2.19648 11.1888C2.12338 11.3081 2.0695 11.4381 2.03684 11.5742C2 11.7276 2 11.8907 2 12.2168V13.3332Z" stroke="#6B7280" strokeLinecap="round" strokeLinejoin="round" />
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
|
||||||
</svg>
|
<path d="M14 11.9998L13.3332 12.7292C12.9796 13.1159 12.5001 13.3332 12.0001 13.3332C11.5001 13.3332 11.0205 13.1159 10.6669 12.7292C10.3128 12.3432 9.83332 12.1265 9.33345 12.1265C8.83359 12.1265 8.35409 12.3432 7.99998 12.7292M2 13.3332H3.11636C3.44248 13.3332 3.60554 13.3332 3.75899 13.2963C3.89504 13.2637 4.0251 13.2098 4.1444 13.1367C4.27895 13.0542 4.39425 12.9389 4.62486 12.7083L13 4.33316C13.5523 3.78087 13.5523 2.88544 13 2.33316C12.4477 1.78087 11.5523 1.78087 11 2.33316L2.62484 10.7083C2.39424 10.9389 2.27894 11.0542 2.19648 11.1888C2.12338 11.3081 2.0695 11.4381 2.03684 11.5742C2 11.7276 2 11.8907 2 12.2168V13.3332Z" stroke="#6B7280" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditIconSolid: FC<{ className?: string }> = ({ className }) => {
|
export const EditIconSolid: FC<{ className?: string }> = ({ className }) => {
|
||||||
return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
|
return (
|
||||||
<path fillRule="evenodd" clip-rule="evenodd" d="M10.8374 8.63108C11.0412 8.81739 11.0554 9.13366 10.8691 9.33747L10.369 9.88449C10.0142 10.2725 9.52293 10.5001 9.00011 10.5001C8.47746 10.5001 7.98634 10.2727 7.63157 9.8849C7.45561 9.69325 7.22747 9.59515 7.00014 9.59515C6.77271 9.59515 6.54446 9.69335 6.36846 9.88517C6.18177 10.0886 5.86548 10.1023 5.66201 9.91556C5.45853 9.72888 5.44493 9.41259 5.63161 9.20911C5.98678 8.82201 6.47777 8.59515 7.00014 8.59515C7.52251 8.59515 8.0135 8.82201 8.36867 9.20911L8.36924 9.20974C8.54486 9.4018 8.77291 9.50012 9.00011 9.50012C9.2273 9.50012 9.45533 9.40182 9.63095 9.20979L10.131 8.66276C10.3173 8.45895 10.6336 8.44476 10.8374 8.63108Z" fill="#6B7280" />
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
|
||||||
<path fillRule="evenodd" clip-rule="evenodd" d="M7.89651 1.39656C8.50599 0.787085 9.49414 0.787084 10.1036 1.39656C10.7131 2.00604 10.7131 2.99419 10.1036 3.60367L3.82225 9.88504C3.81235 9.89494 3.80254 9.90476 3.79281 9.91451C3.64909 10.0585 3.52237 10.1855 3.3696 10.2791C3.23539 10.3613 3.08907 10.4219 2.93602 10.4587C2.7618 10.5005 2.58242 10.5003 2.37897 10.5001C2.3652 10.5001 2.35132 10.5001 2.33732 10.5001H1.50005C1.22391 10.5001 1.00005 10.2763 1.00005 10.0001V9.16286C1.00005 9.14886 1.00004 9.13497 1.00003 9.1212C0.999836 8.91776 0.999669 8.73838 1.0415 8.56416C1.07824 8.4111 1.13885 8.26479 1.22109 8.13058C1.31471 7.97781 1.44166 7.85109 1.58566 7.70736C1.5954 7.69764 1.60523 7.68783 1.61513 7.67793L7.89651 1.39656Z" fill="#6B7280" />
|
<path fillRule="evenodd" clip-rule="evenodd" d="M10.8374 8.63108C11.0412 8.81739 11.0554 9.13366 10.8691 9.33747L10.369 9.88449C10.0142 10.2725 9.52293 10.5001 9.00011 10.5001C8.47746 10.5001 7.98634 10.2727 7.63157 9.8849C7.45561 9.69325 7.22747 9.59515 7.00014 9.59515C6.77271 9.59515 6.54446 9.69335 6.36846 9.88517C6.18177 10.0886 5.86548 10.1023 5.66201 9.91556C5.45853 9.72888 5.44493 9.41259 5.63161 9.20911C5.98678 8.82201 6.47777 8.59515 7.00014 8.59515C7.52251 8.59515 8.0135 8.82201 8.36867 9.20911L8.36924 9.20974C8.54486 9.4018 8.77291 9.50012 9.00011 9.50012C9.2273 9.50012 9.45533 9.40182 9.63095 9.20979L10.131 8.66276C10.3173 8.45895 10.6336 8.44476 10.8374 8.63108Z" fill="#6B7280" />
|
||||||
</svg>
|
<path fillRule="evenodd" clip-rule="evenodd" d="M7.89651 1.39656C8.50599 0.787085 9.49414 0.787084 10.1036 1.39656C10.7131 2.00604 10.7131 2.99419 10.1036 3.60367L3.82225 9.88504C3.81235 9.89494 3.80254 9.90476 3.79281 9.91451C3.64909 10.0585 3.52237 10.1855 3.3696 10.2791C3.23539 10.3613 3.08907 10.4219 2.93602 10.4587C2.7618 10.5005 2.58242 10.5003 2.37897 10.5001C2.3652 10.5001 2.35132 10.5001 2.33732 10.5001H1.50005C1.22391 10.5001 1.00005 10.2763 1.00005 10.0001V9.16286C1.00005 9.14886 1.00004 9.13497 1.00003 9.1212C0.999836 8.91776 0.999669 8.73838 1.0415 8.56416C1.07824 8.4111 1.13885 8.26479 1.22109 8.13058C1.31471 7.97781 1.44166 7.85109 1.58566 7.70736C1.5954 7.69764 1.60523 7.68783 1.61513 7.67793L7.89651 1.39656Z" fill="#6B7280" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => {
|
const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => {
|
||||||
return <div className={'rounded-lg h-6 w-6 flex items-center justify-center hover:bg-gray-100'}>
|
return (
|
||||||
{children}
|
<div className="rounded-lg h-6 w-6 flex items-center justify-center hover:bg-gray-100">
|
||||||
</div>
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type IAnswerProps = {
|
interface IAnswerProps {
|
||||||
item: ChatItem
|
item: ChatItem
|
||||||
feedbackDisabled: boolean
|
feedbackDisabled: boolean
|
||||||
onFeedback?: FeedbackFunc
|
onFeedback?: FeedbackFunc
|
||||||
@ -79,15 +87,14 @@ const Answer: FC<IAnswerProps> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render feedback results (distinguish between users and administrators)
|
* Render feedback results (distinguish between users and administrators)
|
||||||
* User reviews cannot be cancelled in Console
|
* User reviews cannot be cancelled in Console
|
||||||
* @param rating feedback result
|
* @param rating feedback result
|
||||||
* @param isUserFeedback Whether it is user's feedback
|
* @param isUserFeedback Whether it is user's feedback
|
||||||
* @returns comp
|
* @returns comp
|
||||||
*/
|
*/
|
||||||
const renderFeedbackRating = (rating: MessageRating | undefined) => {
|
const renderFeedbackRating = (rating: MessageRating | undefined) => {
|
||||||
if (!rating)
|
if (!rating) { return null }
|
||||||
return null
|
|
||||||
|
|
||||||
const isLike = rating === 'like'
|
const isLike = rating === 'like'
|
||||||
const ratingIconClassname = isLike ? 'text-primary-600 bg-primary-100 hover:bg-primary-200' : 'text-red-600 bg-red-100 hover:bg-red-200'
|
const ratingIconClassname = isLike ? 'text-primary-600 bg-primary-100 hover:bg-primary-200' : 'text-red-600 bg-red-100 hover:bg-red-200'
|
||||||
@ -98,7 +105,7 @@ const Answer: FC<IAnswerProps> = ({
|
|||||||
content={isLike ? '取消赞同' : '取消反对'}
|
content={isLike ? '取消赞同' : '取消反对'}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={'relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800'}
|
className="relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800"
|
||||||
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
|
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await onFeedback?.(id, { rating: null })
|
await onFeedback?.(id, { rating: null })
|
||||||
@ -120,14 +127,16 @@ const Answer: FC<IAnswerProps> = ({
|
|||||||
const userOperation = () => {
|
const userOperation = () => {
|
||||||
return feedback?.rating
|
return feedback?.rating
|
||||||
? null
|
? null
|
||||||
: <div className='flex gap-1'>
|
: (
|
||||||
<Tooltip selector={`user-feedback-${randomString(16)}`} content={t('common.operation.like') as string}>
|
<div className="flex gap-1">
|
||||||
{OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={true} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'like' }) })}
|
<Tooltip selector={`user-feedback-${randomString(16)}`} content={t('common.operation.like') as string}>
|
||||||
</Tooltip>
|
{OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={true} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'like' }) })}
|
||||||
<Tooltip selector={`user-feedback-${randomString(16)}`} content={t('common.operation.dislike') as string}>
|
</Tooltip>
|
||||||
{OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={false} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'dislike' }) })}
|
<Tooltip selector={`user-feedback-${randomString(16)}`} content={t('common.operation.dislike') as string}>
|
||||||
</Tooltip>
|
{OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={false} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'dislike' }) })}
|
||||||
</div>
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -138,8 +147,7 @@ const Answer: FC<IAnswerProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getImgs = (list?: VisionFile[]) => {
|
const getImgs = (list?: VisionFile[]) => {
|
||||||
if (!list)
|
if (!list) { return [] }
|
||||||
return []
|
|
||||||
return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant')
|
return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +156,7 @@ const Answer: FC<IAnswerProps> = ({
|
|||||||
{agent_thoughts?.map((item, index) => (
|
{agent_thoughts?.map((item, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
{item.thought && (
|
{item.thought && (
|
||||||
<Markdown content={item.thought} />
|
<StreamdownMarkdown content={item.thought} />
|
||||||
)}
|
)}
|
||||||
{/* {item.tool} */}
|
{/* {item.tool} */}
|
||||||
{/* perhaps not use tool */}
|
{/* perhaps not use tool */}
|
||||||
@ -170,13 +178,14 @@ const Answer: FC<IAnswerProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={id}>
|
<div key={id}>
|
||||||
<div className='flex items-start'>
|
<div className="flex items-start">
|
||||||
<div className={`${s.answerIcon} w-10 h-10 shrink-0`}>
|
<div className={`${s.answerIcon} w-10 h-10 shrink-0`}>
|
||||||
{isResponding
|
{isResponding
|
||||||
&& <div className={s.typeingIcon}>
|
&& (
|
||||||
<LoadingAnim type='avatar' />
|
<div className={s.typeingIcon}>
|
||||||
</div>
|
<LoadingAnim type="avatar" />
|
||||||
}
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={`${s.answerWrap}`}>
|
<div className={`${s.answerWrap}`}>
|
||||||
<div className={`${s.answer} relative text-sm text-gray-900`}>
|
<div className={`${s.answer} relative text-sm text-gray-900`}>
|
||||||
@ -186,28 +195,28 @@ const Answer: FC<IAnswerProps> = ({
|
|||||||
)}
|
)}
|
||||||
{(isResponding && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content))
|
{(isResponding && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content))
|
||||||
? (
|
? (
|
||||||
<div className='flex items-center justify-center w-6 h-5'>
|
<div className="flex items-center justify-center w-6 h-5">
|
||||||
<LoadingAnim type='text' />
|
<LoadingAnim type="text" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: (isAgentMode
|
: (isAgentMode
|
||||||
? agentModeAnswer
|
? agentModeAnswer
|
||||||
: (
|
: (
|
||||||
<Markdown content={content} />
|
<StreamdownMarkdown content={content} />
|
||||||
))}
|
))}
|
||||||
{suggestedQuestions.length > 0 && (
|
{suggestedQuestions.length > 0 && (
|
||||||
<div className='mt-3'>
|
<div className="mt-3">
|
||||||
<div className='flex gap-1 mt-1 flex-wrap'>
|
<div className="flex gap-1 mt-1 flex-wrap">
|
||||||
{suggestedQuestions.map((suggestion, index) => (
|
{suggestedQuestions.map((suggestion, index) => (
|
||||||
<div key={index} className='flex items-center gap-1'>
|
<div key={index} className="flex items-center gap-1">
|
||||||
<Button className='text-sm' type='link' onClick={() => suggestionClick(suggestion)}>{suggestion}</Button>
|
<Button className="text-sm" type="link" onClick={() => suggestionClick(suggestion)}>{suggestion}</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'>
|
<div className="absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1">
|
||||||
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()}
|
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()}
|
||||||
{/* User feedback must be displayed */}
|
{/* User feedback must be displayed */}
|
||||||
{!feedbackDisabled && renderFeedbackRating(feedback?.rating)}
|
{!feedbackDisabled && renderFeedbackRating(feedback?.rating)}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import FileUploaderInAttachmentWrapper from '@/app/components/base/file-uploader
|
|||||||
import type { FileEntity, FileUpload } from '@/app/components/base/file-uploader-in-attachment/types'
|
import type { FileEntity, FileUpload } from '@/app/components/base/file-uploader-in-attachment/types'
|
||||||
import { getProcessedFiles } from '@/app/components/base/file-uploader-in-attachment/utils'
|
import { getProcessedFiles } from '@/app/components/base/file-uploader-in-attachment/utils'
|
||||||
|
|
||||||
export type IChatProps = {
|
export interface IChatProps {
|
||||||
chatList: ChatItem[]
|
chatList: ChatItem[]
|
||||||
/**
|
/**
|
||||||
* Whether to display the editing area and rating status
|
* Whether to display the editing area and rating status
|
||||||
@ -97,8 +97,7 @@ const Chat: FC<IChatProps> = ({
|
|||||||
const [attachmentFiles, setAttachmentFiles] = React.useState<FileEntity[]>([])
|
const [attachmentFiles, setAttachmentFiles] = React.useState<FileEntity[]>([])
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (!valid() || (checkCanSend && !checkCanSend()))
|
if (!valid() || (checkCanSend && !checkCanSend())) { return }
|
||||||
return
|
|
||||||
const imageFiles: VisionFile[] = files.filter(file => file.progress !== -1).map(fileItem => ({
|
const imageFiles: VisionFile[] = files.filter(file => file.progress !== -1).map(fileItem => ({
|
||||||
type: 'image',
|
type: 'image',
|
||||||
transfer_method: fileItem.type,
|
transfer_method: fileItem.type,
|
||||||
@ -109,23 +108,20 @@ const Chat: FC<IChatProps> = ({
|
|||||||
const combinedFiles: VisionFile[] = [...imageFiles, ...docAndOtherFiles]
|
const combinedFiles: VisionFile[] = [...imageFiles, ...docAndOtherFiles]
|
||||||
onSend(queryRef.current, combinedFiles)
|
onSend(queryRef.current, combinedFiles)
|
||||||
if (!files.find(item => item.type === TransferMethod.local_file && !item.fileId)) {
|
if (!files.find(item => item.type === TransferMethod.local_file && !item.fileId)) {
|
||||||
if (files.length)
|
if (files.length) { onClear() }
|
||||||
onClear()
|
|
||||||
if (!isResponding) {
|
if (!isResponding) {
|
||||||
setQuery('')
|
setQuery('')
|
||||||
queryRef.current = ''
|
queryRef.current = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!attachmentFiles.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId))
|
if (!attachmentFiles.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId)) { setAttachmentFiles([]) }
|
||||||
setAttachmentFiles([])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyUp = (e: any) => {
|
const handleKeyUp = (e: any) => {
|
||||||
if (e.code === 'Enter') {
|
if (e.code === 'Enter') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
// prevent send message when using input method enter
|
// prevent send message when using input method enter
|
||||||
if (!e.shiftKey && !isUseInputMethod.current)
|
if (!e.shiftKey && !isUseInputMethod.current) { handleSend() }
|
||||||
handleSend()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { FC } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
|
|
||||||
export type ILoaidingAnimProps = {
|
export interface ILoaidingAnimProps {
|
||||||
type: 'text' | 'avatar'
|
type: 'text' | 'avatar'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { ThoughtItem, ToolInfoInThought } from '../type'
|
|||||||
import Tool from './tool'
|
import Tool from './tool'
|
||||||
import type { Emoji } from '@/types/tools'
|
import type { Emoji } from '@/types/tools'
|
||||||
|
|
||||||
export type IThoughtProps = {
|
export interface IThoughtProps {
|
||||||
thought: ThoughtItem
|
thought: ThoughtItem
|
||||||
allToolIcons: Record<string, string | Emoji>
|
allToolIcons: Record<string, string | Emoji>
|
||||||
isFinished: boolean
|
isFinished: boolean
|
||||||
@ -29,8 +29,7 @@ const Thought: FC<IThoughtProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [toolNames, isValueArray]: [string[], boolean] = (() => {
|
const [toolNames, isValueArray]: [string[], boolean] = (() => {
|
||||||
try {
|
try {
|
||||||
if (Array.isArray(JSON.parse(thought.tool)))
|
if (Array.isArray(JSON.parse(thought.tool))) { return [JSON.parse(thought.tool), true] }
|
||||||
return [JSON.parse(thought.tool), true]
|
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { FC } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
isRequest: boolean
|
isRequest: boolean
|
||||||
toolName: string
|
toolName: string
|
||||||
content: string
|
content: string
|
||||||
|
|||||||
@ -13,17 +13,15 @@ import DataSetIcon from '@/app/components/base/icons/public/data-set'
|
|||||||
import type { Emoji } from '@/types/tools'
|
import type { Emoji } from '@/types/tools'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
payload: ToolInfoInThought
|
payload: ToolInfoInThought
|
||||||
allToolIcons?: Record<string, string | Emoji>
|
allToolIcons?: Record<string, string | Emoji>
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
|
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
|
||||||
if (toolName.startsWith('dataset-'))
|
if (toolName.startsWith('dataset-')) { return <DataSetIcon className='shrink-0'></DataSetIcon> }
|
||||||
return <DataSetIcon className='shrink-0'></DataSetIcon>
|
|
||||||
const icon = allToolIcons[toolName]
|
const icon = allToolIcons[toolName]
|
||||||
if (!icon)
|
if (!icon) { return null }
|
||||||
return null
|
|
||||||
return (
|
return (
|
||||||
typeof icon === 'string'
|
typeof icon === 'string'
|
||||||
? (
|
? (
|
||||||
@ -87,12 +85,14 @@ const Tool: FC<Props> = ({
|
|||||||
<Panel
|
<Panel
|
||||||
isRequest={true}
|
isRequest={true}
|
||||||
toolName={toolName}
|
toolName={toolName}
|
||||||
content={input} />
|
content={input}
|
||||||
|
/>
|
||||||
{output && (
|
{output && (
|
||||||
<Panel
|
<Panel
|
||||||
isRequest={false}
|
isRequest={false}
|
||||||
toolName={toolName}
|
toolName={toolName}
|
||||||
content={output as string} />
|
content={output as string}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { VisionFile } from '@/types/app'
|
import type { VisionFile } from '@/types/app'
|
||||||
|
|
||||||
export type LogAnnotation = {
|
export interface LogAnnotation {
|
||||||
content: string
|
content: string
|
||||||
account: {
|
account: {
|
||||||
id: string
|
id: string
|
||||||
@ -10,7 +10,7 @@ export type LogAnnotation = {
|
|||||||
created_at: number
|
created_at: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Annotation = {
|
export interface Annotation {
|
||||||
id: string
|
id: string
|
||||||
authorName: string
|
authorName: string
|
||||||
logAnnotation?: LogAnnotation
|
logAnnotation?: LogAnnotation
|
||||||
@ -20,13 +20,13 @@ export type Annotation = {
|
|||||||
export const MessageRatings = ['like', 'dislike', null] as const
|
export const MessageRatings = ['like', 'dislike', null] as const
|
||||||
export type MessageRating = typeof MessageRatings[number]
|
export type MessageRating = typeof MessageRatings[number]
|
||||||
|
|
||||||
export type MessageMore = {
|
export interface MessageMore {
|
||||||
time: string
|
time: string
|
||||||
tokens: number
|
tokens: number
|
||||||
latency: number | string
|
latency: number | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Feedbacktype = {
|
export interface Feedbacktype {
|
||||||
rating: MessageRating
|
rating: MessageRating
|
||||||
content?: string | null
|
content?: string | null
|
||||||
}
|
}
|
||||||
@ -36,14 +36,14 @@ export type SubmitAnnotationFunc = (messageId: string, content: string) => Promi
|
|||||||
|
|
||||||
export type DisplayScene = 'web' | 'console'
|
export type DisplayScene = 'web' | 'console'
|
||||||
|
|
||||||
export type ToolInfoInThought = {
|
export interface ToolInfoInThought {
|
||||||
name: string
|
name: string
|
||||||
input: string
|
input: string
|
||||||
output: string
|
output: string
|
||||||
isFinished: boolean
|
isFinished: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ThoughtItem = {
|
export interface ThoughtItem {
|
||||||
id: string
|
id: string
|
||||||
tool: string // plugin or dataset. May has multi.
|
tool: string // plugin or dataset. May has multi.
|
||||||
thought: string
|
thought: string
|
||||||
@ -55,7 +55,7 @@ export type ThoughtItem = {
|
|||||||
message_files?: VisionFile[]
|
message_files?: VisionFile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CitationItem = {
|
export interface CitationItem {
|
||||||
content: string
|
content: string
|
||||||
data_source_type: string
|
data_source_type: string
|
||||||
dataset_name: string
|
dataset_name: string
|
||||||
@ -70,7 +70,7 @@ export type CitationItem = {
|
|||||||
word_count: number
|
word_count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IChatItem = {
|
export interface IChatItem {
|
||||||
id: string
|
id: string
|
||||||
content: string
|
content: string
|
||||||
citation?: CitationItem[]
|
citation?: CitationItem[]
|
||||||
@ -98,12 +98,12 @@ export type IChatItem = {
|
|||||||
useCurrentUserAvatar?: boolean
|
useCurrentUserAvatar?: boolean
|
||||||
isOpeningStatement?: boolean
|
isOpeningStatement?: boolean
|
||||||
suggestedQuestions?: string[]
|
suggestedQuestions?: string[]
|
||||||
log?: { role: string; text: string }[]
|
log?: { role: string, text: string }[]
|
||||||
agent_thoughts?: ThoughtItem[]
|
agent_thoughts?: ThoughtItem[]
|
||||||
message_files?: VisionFile[]
|
message_files?: VisionFile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageEnd = {
|
export interface MessageEnd {
|
||||||
id: string
|
id: string
|
||||||
metadata: {
|
metadata: {
|
||||||
retriever_resources?: CitationItem[]
|
retriever_resources?: CitationItem[]
|
||||||
@ -117,14 +117,14 @@ export type MessageEnd = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageReplace = {
|
export interface MessageReplace {
|
||||||
id: string
|
id: string
|
||||||
task_id: string
|
task_id: string
|
||||||
answer: string
|
answer: string
|
||||||
conversation_id: string
|
conversation_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnnotationReply = {
|
export interface AnnotationReply {
|
||||||
id: string
|
id: string
|
||||||
task_id: string
|
task_id: string
|
||||||
answer: string
|
answer: string
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
PencilSquareIcon,
|
PencilSquareIcon,
|
||||||
} from '@heroicons/react/24/solid'
|
} from '@heroicons/react/24/solid'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
export type IHeaderProps = {
|
export interface IHeaderProps {
|
||||||
title: string
|
title: string
|
||||||
isMobile?: boolean
|
isMobile?: boolean
|
||||||
onShowSideBar?: () => void
|
onShowSideBar?: () => void
|
||||||
@ -35,9 +35,7 @@ const Header: FC<IHeaderProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
{isMobile
|
{isMobile
|
||||||
? (
|
? (
|
||||||
<div className='flex items-center justify-center h-8 w-8 cursor-pointer'
|
<div className='flex items-center justify-center h-8 w-8 cursor-pointer' onClick={() => onCreateNewChat?.()} >
|
||||||
onClick={() => onCreateNewChat?.()}
|
|
||||||
>
|
|
||||||
<PencilSquareIcon className="h-4 w-4 text-gray-500" />
|
<PencilSquareIcon className="h-4 w-4 text-gray-500" />
|
||||||
</div>)
|
</div>)
|
||||||
: <div></div>}
|
: <div></div>}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
@ -24,7 +23,7 @@ import { API_KEY, APP_ID, APP_INFO, isShowPrompt, promptTemplate } from '@/confi
|
|||||||
import type { Annotation as AnnotationType } from '@/types/log'
|
import type { Annotation as AnnotationType } from '@/types/log'
|
||||||
import { addFileInfos, sortAgentSorts } from '@/utils/tools'
|
import { addFileInfos, sortAgentSorts } from '@/utils/tools'
|
||||||
|
|
||||||
export type IMainProps = {
|
export interface IMainProps {
|
||||||
params: any
|
params: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +51,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
const [fileConfig, setFileConfig] = useState<FileUpload | undefined>()
|
const [fileConfig, setFileConfig] = useState<FileUpload | undefined>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (APP_INFO?.title)
|
if (APP_INFO?.title) { document.title = `${APP_INFO.title} - Powered by Dify` }
|
||||||
document.title = `${APP_INFO.title} - Powered by Dify`
|
|
||||||
}, [APP_INFO?.title])
|
}, [APP_INFO?.title])
|
||||||
|
|
||||||
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
|
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
|
||||||
@ -95,8 +93,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
setChatList(generateNewChatListWithOpenStatement('', inputs))
|
setChatList(generateNewChatListWithOpenStatement('', inputs))
|
||||||
}
|
}
|
||||||
const hasSetInputs = (() => {
|
const hasSetInputs = (() => {
|
||||||
if (!isNewConversation)
|
if (!isNewConversation) { return true }
|
||||||
return true
|
|
||||||
|
|
||||||
return isChatStarted
|
return isChatStarted
|
||||||
})()
|
})()
|
||||||
@ -106,8 +103,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
const suggestedQuestions = currConversationInfo?.suggested_questions || []
|
const suggestedQuestions = currConversationInfo?.suggested_questions || []
|
||||||
|
|
||||||
const handleConversationSwitch = () => {
|
const handleConversationSwitch = () => {
|
||||||
if (!inited)
|
if (!inited) { return }
|
||||||
return
|
|
||||||
|
|
||||||
// update inputs of current conversation
|
// update inputs of current conversation
|
||||||
let notSyncToStateIntroduction = ''
|
let notSyncToStateIntroduction = ''
|
||||||
@ -155,8 +151,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNewConversation && isChatStarted)
|
if (isNewConversation && isChatStarted) { setChatList(generateNewChatListWithOpenStatement()) }
|
||||||
setChatList(generateNewChatListWithOpenStatement())
|
|
||||||
}
|
}
|
||||||
useEffect(handleConversationSwitch, [currConversationId, inited])
|
useEffect(handleConversationSwitch, [currConversationId, inited])
|
||||||
|
|
||||||
@ -180,15 +175,13 @@ const Main: FC<IMainProps> = () => {
|
|||||||
const chatListDomRef = useRef<HTMLDivElement>(null)
|
const chatListDomRef = useRef<HTMLDivElement>(null)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// scroll to bottom
|
// scroll to bottom
|
||||||
if (chatListDomRef.current)
|
if (chatListDomRef.current) { chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight }
|
||||||
chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight
|
|
||||||
}, [chatList, currConversationId])
|
}, [chatList, currConversationId])
|
||||||
// user can not edit inputs if user had send message
|
// user can not edit inputs if user had send message
|
||||||
const canEditInputs = !chatList.some(item => item.isAnswer === false) && isNewConversation
|
const canEditInputs = !chatList.some(item => item.isAnswer === false) && isNewConversation
|
||||||
const createNewChat = () => {
|
const createNewChat = () => {
|
||||||
// if new chat is already exist, do not create new chat
|
// if new chat is already exist, do not create new chat
|
||||||
if (conversationList.some(item => item.id === '-1'))
|
if (conversationList.some(item => item.id === '-1')) { return }
|
||||||
return
|
|
||||||
|
|
||||||
setConversationList(produce(conversationList, (draft) => {
|
setConversationList(produce(conversationList, (draft) => {
|
||||||
draft.unshift({
|
draft.unshift({
|
||||||
@ -205,8 +198,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
const generateNewChatListWithOpenStatement = (introduction?: string, inputs?: Record<string, any> | null) => {
|
const generateNewChatListWithOpenStatement = (introduction?: string, inputs?: Record<string, any> | null) => {
|
||||||
let calculatedIntroduction = introduction || conversationIntroduction || ''
|
let calculatedIntroduction = introduction || conversationIntroduction || ''
|
||||||
const calculatedPromptVariables = inputs || currInputs || null
|
const calculatedPromptVariables = inputs || currInputs || null
|
||||||
if (calculatedIntroduction && calculatedPromptVariables)
|
if (calculatedIntroduction && calculatedPromptVariables) { calculatedIntroduction = replaceVarWithValues(calculatedIntroduction, promptConfig?.prompt_variables || [], calculatedPromptVariables) }
|
||||||
calculatedIntroduction = replaceVarWithValues(calculatedIntroduction, promptConfig?.prompt_variables || [], calculatedPromptVariables)
|
|
||||||
|
|
||||||
const openStatement = {
|
const openStatement = {
|
||||||
id: `${Date.now()}`,
|
id: `${Date.now()}`,
|
||||||
@ -214,10 +206,9 @@ const Main: FC<IMainProps> = () => {
|
|||||||
isAnswer: true,
|
isAnswer: true,
|
||||||
feedbackDisabled: true,
|
feedbackDisabled: true,
|
||||||
isOpeningStatement: isShowPrompt,
|
isOpeningStatement: isShowPrompt,
|
||||||
suggestedQuestions: suggestedQuestions,
|
suggestedQuestions,
|
||||||
}
|
}
|
||||||
if (calculatedIntroduction)
|
if (calculatedIntroduction) { return [openStatement] }
|
||||||
return [openStatement]
|
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -232,7 +223,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
try {
|
try {
|
||||||
const [conversationData, appParams] = await Promise.all([fetchConversations(), fetchAppParams()])
|
const [conversationData, appParams] = await Promise.all([fetchConversations(), fetchAppParams()])
|
||||||
// handle current conversation id
|
// handle current conversation id
|
||||||
const { data: conversations, error } = conversationData as { data: ConversationItem[]; error: string }
|
const { data: conversations, error } = conversationData as { data: ConversationItem[], error: string }
|
||||||
if (error) {
|
if (error) {
|
||||||
Toast.notify({ type: 'error', message: error })
|
Toast.notify({ type: 'error', message: error })
|
||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
@ -248,13 +239,13 @@ const Main: FC<IMainProps> = () => {
|
|||||||
setNewConversationInfo({
|
setNewConversationInfo({
|
||||||
name: t('app.chat.newChatDefaultName'),
|
name: t('app.chat.newChatDefaultName'),
|
||||||
introduction,
|
introduction,
|
||||||
suggested_questions
|
suggested_questions,
|
||||||
})
|
})
|
||||||
if (isNotNewConversation) {
|
if (isNotNewConversation) {
|
||||||
setExistConversationInfo({
|
setExistConversationInfo({
|
||||||
name: currentConversation.name || t('app.chat.newChatDefaultName'),
|
name: currentConversation.name || t('app.chat.newChatDefaultName'),
|
||||||
introduction,
|
introduction,
|
||||||
suggested_questions
|
suggested_questions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const prompt_variables = userInputsFormToPromptVariables(user_input_form)
|
const prompt_variables = userInputsFormToPromptVariables(user_input_form)
|
||||||
@ -278,8 +269,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
})
|
})
|
||||||
setConversationList(conversations as ConversationItem[])
|
setConversationList(conversations as ConversationItem[])
|
||||||
|
|
||||||
if (isNotNewConversation)
|
if (isNotNewConversation) { setCurrConversationId(_conversationId, APP_ID, false) }
|
||||||
setCurrConversationId(_conversationId, APP_ID, false)
|
|
||||||
|
|
||||||
setInited(true)
|
setInited(true)
|
||||||
}
|
}
|
||||||
@ -303,11 +293,9 @@ const Main: FC<IMainProps> = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checkCanSend = () => {
|
const checkCanSend = () => {
|
||||||
if (currConversationId !== '-1')
|
if (currConversationId !== '-1') { return true }
|
||||||
return true
|
|
||||||
|
|
||||||
if (!currInputs || !promptConfig?.prompt_variables)
|
if (!currInputs || !promptConfig?.prompt_variables) { return true }
|
||||||
return true
|
|
||||||
|
|
||||||
const inputLens = Object.values(currInputs).length
|
const inputLens = Object.values(currInputs).length
|
||||||
const promptVariablesLens = promptConfig.prompt_variables.length
|
const promptVariablesLens = promptConfig.prompt_variables.length
|
||||||
@ -342,11 +330,11 @@ const Main: FC<IMainProps> = () => {
|
|||||||
const newListWithAnswer = produce(
|
const newListWithAnswer = produce(
|
||||||
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
||||||
(draft) => {
|
(draft) => {
|
||||||
if (!draft.find(item => item.id === questionId))
|
if (!draft.find(item => item.id === questionId)) { draft.push({ ...questionItem }) }
|
||||||
draft.push({ ...questionItem })
|
|
||||||
|
|
||||||
draft.push({ ...responseItem })
|
draft.push({ ...responseItem })
|
||||||
})
|
},
|
||||||
|
)
|
||||||
setChatList(newListWithAnswer)
|
setChatList(newListWithAnswer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,14 +356,11 @@ const Main: FC<IMainProps> = () => {
|
|||||||
if (currInputs) {
|
if (currInputs) {
|
||||||
Object.keys(currInputs).forEach((key) => {
|
Object.keys(currInputs).forEach((key) => {
|
||||||
const value = currInputs[key]
|
const value = currInputs[key]
|
||||||
if (value.supportFileType)
|
if (value.supportFileType) { toServerInputs[key] = transformToServerFile(value) }
|
||||||
toServerInputs[key] = transformToServerFile(value)
|
|
||||||
|
|
||||||
else if (value[0]?.supportFileType)
|
else if (value[0]?.supportFileType) { toServerInputs[key] = value.map((item: any) => transformToServerFile(item)) }
|
||||||
toServerInputs[key] = value.map((item: any) => transformToServerFile(item))
|
|
||||||
|
|
||||||
else
|
else { toServerInputs[key] = value }
|
||||||
toServerInputs[key] = value
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,16 +427,14 @@ const Main: FC<IMainProps> = () => {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
|
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
|
||||||
if (lastThought)
|
if (lastThought) { lastThought.thought = lastThought.thought + message } // need immer setAutoFreeze
|
||||||
lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
|
|
||||||
}
|
}
|
||||||
if (messageId && !hasSetResponseId) {
|
if (messageId && !hasSetResponseId) {
|
||||||
responseItem.id = messageId
|
responseItem.id = messageId
|
||||||
hasSetResponseId = true
|
hasSetResponseId = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirstMessage && newConversationId)
|
if (isFirstMessage && newConversationId) { tempNewConversationId = newConversationId }
|
||||||
tempNewConversationId = newConversationId
|
|
||||||
|
|
||||||
setMessageTaskId(taskId)
|
setMessageTaskId(taskId)
|
||||||
// has switched to other conversation
|
// has switched to other conversation
|
||||||
@ -467,8 +450,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async onCompleted(hasError?: boolean) {
|
async onCompleted(hasError?: boolean) {
|
||||||
if (hasError)
|
if (hasError) { return }
|
||||||
return
|
|
||||||
|
|
||||||
if (getConversationIdChangeBecauseOfNew()) {
|
if (getConversationIdChangeBecauseOfNew()) {
|
||||||
const { data: allConversations }: any = await fetchConversations()
|
const { data: allConversations }: any = await fetchConversations()
|
||||||
@ -487,8 +469,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
},
|
},
|
||||||
onFile(file) {
|
onFile(file) {
|
||||||
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
|
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
|
||||||
if (lastThought)
|
if (lastThought) { lastThought.message_files = [...(lastThought as any).message_files, { ...file }] }
|
||||||
lastThought.message_files = [...(lastThought as any).message_files, { ...file }]
|
|
||||||
|
|
||||||
updateCurrentQA({
|
updateCurrentQA({
|
||||||
responseItem,
|
responseItem,
|
||||||
@ -543,13 +524,13 @@ const Main: FC<IMainProps> = () => {
|
|||||||
const newListWithAnswer = produce(
|
const newListWithAnswer = produce(
|
||||||
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
||||||
(draft) => {
|
(draft) => {
|
||||||
if (!draft.find(item => item.id === questionId))
|
if (!draft.find(item => item.id === questionId)) { draft.push({ ...questionItem }) }
|
||||||
draft.push({ ...questionItem })
|
|
||||||
|
|
||||||
draft.push({
|
draft.push({
|
||||||
...responseItem,
|
...responseItem,
|
||||||
})
|
})
|
||||||
})
|
},
|
||||||
|
)
|
||||||
setChatList(newListWithAnswer)
|
setChatList(newListWithAnswer)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -558,11 +539,11 @@ const Main: FC<IMainProps> = () => {
|
|||||||
const newListWithAnswer = produce(
|
const newListWithAnswer = produce(
|
||||||
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
||||||
(draft) => {
|
(draft) => {
|
||||||
if (!draft.find(item => item.id === questionId))
|
if (!draft.find(item => item.id === questionId)) { draft.push({ ...questionItem }) }
|
||||||
draft.push({ ...questionItem })
|
|
||||||
|
|
||||||
draft.push({ ...responseItem })
|
draft.push({ ...responseItem })
|
||||||
})
|
},
|
||||||
|
)
|
||||||
setChatList(newListWithAnswer)
|
setChatList(newListWithAnswer)
|
||||||
},
|
},
|
||||||
onMessageReplace: (messageReplace) => {
|
onMessageReplace: (messageReplace) => {
|
||||||
@ -571,8 +552,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
(draft) => {
|
(draft) => {
|
||||||
const current = draft.find(item => item.id === messageReplace.id)
|
const current = draft.find(item => item.id === messageReplace.id)
|
||||||
|
|
||||||
if (current)
|
if (current) { current.content = messageReplace.answer }
|
||||||
current.content = messageReplace.answer
|
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
@ -648,8 +628,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderSidebar = () => {
|
const renderSidebar = () => {
|
||||||
if (!APP_ID || !APP_INFO || !promptConfig)
|
if (!APP_ID || !APP_INFO || !promptConfig) { return null }
|
||||||
return null
|
|
||||||
return (
|
return (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
list={conversationList}
|
list={conversationList}
|
||||||
@ -660,11 +639,9 @@ const Main: FC<IMainProps> = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appUnavailable)
|
if (appUnavailable) { return <AppUnavailable isUnknownReason={isUnknownReason} errMessage={!hasSetAppConfig ? 'Please set APP_ID and API_KEY in config/index.tsx' : ''} /> }
|
||||||
return <AppUnavailable isUnknownReason={isUnknownReason} errMessage={!hasSetAppConfig ? 'Please set APP_ID and API_KEY in config/index.tsx' : ''} />
|
|
||||||
|
|
||||||
if (!APP_ID || !APP_INFO || !promptConfig)
|
if (!APP_ID || !APP_INFO || !promptConfig) { return <Loading type='app' /> }
|
||||||
return <Loading type='app' />
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='bg-gray-100'>
|
<div className='bg-gray-100'>
|
||||||
@ -678,10 +655,7 @@ const Main: FC<IMainProps> = () => {
|
|||||||
{/* sidebar */}
|
{/* sidebar */}
|
||||||
{!isMobile && renderSidebar()}
|
{!isMobile && renderSidebar()}
|
||||||
{isMobile && isShowSidebar && (
|
{isMobile && isShowSidebar && (
|
||||||
<div className='fixed inset-0 z-50'
|
<div className='fixed inset-0 z-50' style={{ backgroundColor: 'rgba(35, 56, 118, 0.2)' }} onClick={hideSidebar} >
|
||||||
style={{ backgroundColor: 'rgba(35, 56, 118, 0.2)' }}
|
|
||||||
onClick={hideSidebar}
|
|
||||||
>
|
|
||||||
<div className='inline-block' onClick={e => e.stopPropagation()}>
|
<div className='inline-block' onClick={e => e.stopPropagation()}>
|
||||||
{renderSidebar()}
|
{renderSidebar()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import s from './card.module.css'
|
import s from './card.module.css'
|
||||||
|
|
||||||
type PropType = {
|
interface PropType {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
text?: string
|
text?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ function classNames(...classes: any[]) {
|
|||||||
|
|
||||||
const MAX_CONVERSATION_LENTH = 20
|
const MAX_CONVERSATION_LENTH = 20
|
||||||
|
|
||||||
export type ISidebarProps = {
|
export interface ISidebarProps {
|
||||||
copyRight: string
|
copyRight: string
|
||||||
currentId: string
|
currentId: string
|
||||||
onCurrentIdChange: (id: string) => void
|
onCurrentIdChange: (id: string) => void
|
||||||
@ -38,7 +38,8 @@ const Sidebar: FC<ISidebarProps> = ({
|
|||||||
<div className="flex flex-shrink-0 p-4 !pb-0">
|
<div className="flex flex-shrink-0 p-4 !pb-0">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => { onCurrentIdChange('-1') }}
|
onClick={() => { onCurrentIdChange('-1') }}
|
||||||
className="group block w-full flex-shrink-0 !justify-start !h-9 text-primary-600 items-center text-sm">
|
className="group block w-full flex-shrink-0 !justify-start !h-9 text-primary-600 items-center text-sm"
|
||||||
|
>
|
||||||
<PencilSquareIcon className="mr-2 h-4 w-4" /> {t('app.chat.newChat')}
|
<PencilSquareIcon className="mr-2 h-4 w-4" /> {t('app.chat.newChat')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import s from './style.module.css'
|
|||||||
import { StarIcon } from '@/app/components//welcome/massive-component'
|
import { StarIcon } from '@/app/components//welcome/massive-component'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
|
|
||||||
export type ITemplateVarPanelProps = {
|
export interface ITemplateVarPanelProps {
|
||||||
className?: string
|
className?: string
|
||||||
header: ReactNode
|
header: ReactNode
|
||||||
children?: ReactNode | null
|
children?: ReactNode | null
|
||||||
@ -38,7 +38,7 @@ const TemplateVarPanel: FC<ITemplateVarPanelProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelTitle: FC<{ title: string; className?: string }> = ({
|
export const PanelTitle: FC<{ title: string, className?: string }> = ({
|
||||||
title,
|
title,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
@ -50,7 +50,7 @@ export const PanelTitle: FC<{ title: string; className?: string }> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VarOpBtnGroup: FC<{ className?: string; onConfirm: () => void; onCancel: () => void }> = ({
|
export const VarOpBtnGroup: FC<{ className?: string, onConfirm: () => void, onCancel: () => void }> = ({
|
||||||
className,
|
className,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { DEFAULT_VALUE_MAX_LEN } from '@/config'
|
|||||||
// regex to match the {{}} and replace it with a span
|
// regex to match the {{}} and replace it with a span
|
||||||
const regex = /\{\{([^}]+)\}\}/g
|
const regex = /\{\{([^}]+)\}\}/g
|
||||||
|
|
||||||
export type IWelcomeProps = {
|
export interface IWelcomeProps {
|
||||||
conversationName: string
|
conversationName: string
|
||||||
hasSetInputs: boolean
|
hasSetInputs: boolean
|
||||||
isPublicVersion: boolean
|
isPublicVersion: boolean
|
||||||
@ -42,8 +42,7 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
const hasVar = promptConfig.prompt_variables.length > 0
|
const hasVar = promptConfig.prompt_variables.length > 0
|
||||||
const [isFold, setIsFold] = useState<boolean>(true)
|
const [isFold, setIsFold] = useState<boolean>(true)
|
||||||
const [inputs, setInputs] = useState<Record<string, any>>((() => {
|
const [inputs, setInputs] = useState<Record<string, any>>((() => {
|
||||||
if (hasSetInputs)
|
if (hasSetInputs) { return savedInputs }
|
||||||
return savedInputs
|
|
||||||
|
|
||||||
const res: Record<string, any> = {}
|
const res: Record<string, any> = {}
|
||||||
if (promptConfig) {
|
if (promptConfig) {
|
||||||
@ -69,8 +68,7 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
}, [savedInputs])
|
}, [savedInputs])
|
||||||
|
|
||||||
const highLightPromoptTemplate = (() => {
|
const highLightPromoptTemplate = (() => {
|
||||||
if (!promptConfig)
|
if (!promptConfig) { return '' }
|
||||||
return ''
|
|
||||||
const res = promptConfig.prompt_template.replace(regex, (match, p1) => {
|
const res = promptConfig.prompt_template.replace(regex, (match, p1) => {
|
||||||
return `<span class='text-gray-800 font-bold'>${inputs?.[p1] ? inputs?.[p1] : match}</span>`
|
return `<span class='text-gray-800 font-bold'>${inputs?.[p1] ? inputs?.[p1] : match}</span>`
|
||||||
})
|
})
|
||||||
@ -189,8 +187,7 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleChat = () => {
|
const handleChat = () => {
|
||||||
if (!canChat())
|
if (!canChat()) { return }
|
||||||
return
|
|
||||||
|
|
||||||
onStartChat(inputs)
|
onStartChat(inputs)
|
||||||
}
|
}
|
||||||
@ -251,8 +248,7 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
return (
|
return (
|
||||||
<VarOpBtnGroup
|
<VarOpBtnGroup
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
if (!canChat())
|
if (!canChat()) { return }
|
||||||
return
|
|
||||||
|
|
||||||
onInputsChange(inputs)
|
onInputsChange(inputs)
|
||||||
setIsFold(true)
|
setIsFold(true)
|
||||||
@ -309,8 +305,7 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderHasSetInputsPrivate = () => {
|
const renderHasSetInputsPrivate = () => {
|
||||||
if (!canEditInputs || !hasVar)
|
if (!canEditInputs || !hasVar) { return null }
|
||||||
return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TemplateVarPanel
|
<TemplateVarPanel
|
||||||
@ -333,8 +328,7 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderHasSetInputs = () => {
|
const renderHasSetInputs = () => {
|
||||||
if ((!isPublicVersion && !canEditInputs) || !hasVar)
|
if ((!isPublicVersion && !canEditInputs) || !hasVar) { return null }
|
||||||
return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -375,7 +369,8 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
<a
|
<a
|
||||||
className='text-gray-500'
|
className='text-gray-500'
|
||||||
href={siteInfo.privacy_policy}
|
href={siteInfo.privacy_policy}
|
||||||
target='_blank'>{t('app.chat.privacyPolicyMiddle')}</a>
|
target='_blank'
|
||||||
|
>{t('app.chat.privacyPolicyMiddle')}</a>
|
||||||
{t('app.chat.privacyPolicyRight')}
|
{t('app.chat.privacyPolicyRight')}
|
||||||
</div>
|
</div>
|
||||||
: <div>
|
: <div>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export const StarIcon = () => (
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const ChatBtn: FC<{ onClick: () => void; className?: string }> = ({
|
export const ChatBtn: FC<{ onClick: () => void, className?: string }> = ({
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
@ -46,7 +46,8 @@ export const ChatBtn: FC<{ onClick: () => void; className?: string }> = ({
|
|||||||
<Button
|
<Button
|
||||||
type='primary'
|
type='primary'
|
||||||
className={cn(className, `space-x-2 flex items-center ${s.customBtn}`)}
|
className={cn(className, `space-x-2 flex items-center ${s.customBtn}`)}
|
||||||
onClick={onClick}>
|
onClick={onClick}
|
||||||
|
>
|
||||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M18 10.5C18 14.366 14.418 17.5 10 17.5C8.58005 17.506 7.17955 17.1698 5.917 16.52L2 17.5L3.338 14.377C2.493 13.267 2 11.934 2 10.5C2 6.634 5.582 3.5 10 3.5C14.418 3.5 18 6.634 18 10.5ZM7 9.5H5V11.5H7V9.5ZM15 9.5H13V11.5H15V9.5ZM9 9.5H11V11.5H9V9.5Z" fill="white" />
|
<path fillRule="evenodd" clipRule="evenodd" d="M18 10.5C18 14.366 14.418 17.5 10 17.5C8.58005 17.506 7.17955 17.1698 5.917 16.52L2 17.5L3.338 14.377C2.493 13.267 2 11.934 2 10.5C2 6.634 5.582 3.5 10 3.5C14.418 3.5 18 6.634 18 10.5ZM7 9.5H5V11.5H7V9.5ZM15 9.5H13V11.5H15V9.5ZM9 9.5H11V11.5H9V9.5Z" fill="white" />
|
||||||
</svg>
|
</svg>
|
||||||
@ -55,7 +56,7 @@ export const ChatBtn: FC<{ onClick: () => void; className?: string }> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditBtn = ({ className, onClick }: { className?: string; onClick: () => void }) => {
|
export const EditBtn = ({ className, onClick }: { className?: string, onClick: () => void }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -16,11 +16,11 @@ import {
|
|||||||
} from '@/app/components/base/icons/workflow'
|
} from '@/app/components/base/icons/workflow'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
|
|
||||||
type BlockIconProps = {
|
interface BlockIconProps {
|
||||||
type: BlockEnum
|
type: BlockEnum
|
||||||
size?: string
|
size?: string
|
||||||
className?: string
|
className?: string
|
||||||
toolIcon?: string | { content: string; background: string }
|
toolIcon?: string | { content: string, background: string }
|
||||||
}
|
}
|
||||||
const ICON_CONTAINER_CLASSNAME_SIZE_MAP: Record<string, string> = {
|
const ICON_CONTAINER_CLASSNAME_SIZE_MAP: Record<string, string> = {
|
||||||
xs: 'w-4 h-4 rounded-[5px] shadow-xs',
|
xs: 'w-4 h-4 rounded-[5px] shadow-xs',
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import './style.css'
|
|||||||
// load file from local instead of cdn https://github.com/suren-atoyan/monaco-react/issues/482
|
// load file from local instead of cdn https://github.com/suren-atoyan/monaco-react/issues/482
|
||||||
loader.config({ paths: { vs: '/vs' } })
|
loader.config({ paths: { vs: '/vs' } })
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
value?: string | object
|
value?: string | object
|
||||||
onChange?: (value: string) => void
|
onChange?: (value: string) => void
|
||||||
title: JSX.Element
|
title: JSX.Element
|
||||||
@ -72,8 +72,7 @@ const CodeEditor: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const outPutValue = (() => {
|
const outPutValue = (() => {
|
||||||
if (!isJSONStringifyBeauty)
|
if (!isJSONStringifyBeauty) { return value as string }
|
||||||
return value as string
|
|
||||||
try {
|
try {
|
||||||
return JSON.stringify(value as object, null, 2)
|
return JSON.stringify(value as object, null, 2)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import ToggleExpandBtn from './toggle-expand-btn'
|
|||||||
import useToggleExpend from './use-toggle-expend'
|
import useToggleExpend from './use-toggle-expend'
|
||||||
import { Clipboard, ClipboardCheck } from '@/app/components/base/icons/line/files'
|
import { Clipboard, ClipboardCheck } from '@/app/components/base/icons/line/files'
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
title: JSX.Element | string
|
title: JSX.Element | string
|
||||||
headerRight?: JSX.Element
|
headerRight?: JSX.Element
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { FC } from 'react'
|
|||||||
import { useDebounceFn } from 'ahooks'
|
import { useDebounceFn } from 'ahooks'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
height: number
|
height: number
|
||||||
minHeight: number
|
minHeight: number
|
||||||
@ -40,14 +40,12 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
|||||||
}, [prevUserSelectStyle])
|
}, [prevUserSelectStyle])
|
||||||
|
|
||||||
const { run: didHandleResize } = useDebounceFn((e) => {
|
const { run: didHandleResize } = useDebounceFn((e) => {
|
||||||
if (!isResizing)
|
if (!isResizing) { return }
|
||||||
return
|
|
||||||
|
|
||||||
const offset = e.clientY - clientY
|
const offset = e.clientY - clientY
|
||||||
let newHeight = height + offset
|
let newHeight = height + offset
|
||||||
setClientY(e.clientY)
|
setClientY(e.clientY)
|
||||||
if (newHeight < minHeight)
|
if (newHeight < minHeight) { newHeight = minHeight }
|
||||||
newHeight = minHeight
|
|
||||||
onHeightChange(newHeight)
|
onHeightChange(newHeight)
|
||||||
}, {
|
}, {
|
||||||
wait: 0,
|
wait: 0,
|
||||||
@ -85,7 +83,8 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
|||||||
{!hideResize && (
|
{!hideResize && (
|
||||||
<div
|
<div
|
||||||
className='absolute bottom-0 left-0 w-full flex justify-center h-2 cursor-row-resize'
|
className='absolute bottom-0 left-0 w-full flex justify-center h-2 cursor-row-resize'
|
||||||
onMouseDown={handleStartResize}>
|
onMouseDown={handleStartResize}
|
||||||
|
>
|
||||||
<div className='w-5 h-[3px] rounded-sm bg-gray-300'></div>
|
<div className='w-5 h-[3px] rounded-sm bg-gray-300'></div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, { useCallback } from 'react'
|
|||||||
import Expand04 from '@/app/components/base/icons/solid/expand-04'
|
import Expand04 from '@/app/components/base/icons/solid/expand-04'
|
||||||
import Collapse04 from '@/app/components/base/icons/line/arrows/collapse-04'
|
import Collapse04 from '@/app/components/base/icons/line/arrows/collapse-04'
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
isExpand: boolean
|
isExpand: boolean
|
||||||
onExpandChange: (isExpand: boolean) => void
|
onExpandChange: (isExpand: boolean) => void
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
type Params = {
|
interface Params {
|
||||||
ref: React.RefObject<HTMLDivElement>
|
ref: React.RefObject<HTMLDivElement>
|
||||||
hasFooter?: boolean
|
hasFooter?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import Loading02 from '@/app/components/base/icons/line/loading-02'
|
|||||||
import CheckCircle from '@/app/components/base/icons/line/check-circle'
|
import CheckCircle from '@/app/components/base/icons/line/check-circle'
|
||||||
import type { NodeTracing } from '@/types/app'
|
import type { NodeTracing } from '@/types/app'
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
nodeInfo: NodeTracing
|
nodeInfo: NodeTracing
|
||||||
hideInfo?: boolean
|
hideInfo?: boolean
|
||||||
}
|
}
|
||||||
@ -18,20 +18,15 @@ const NodePanel: FC<Props> = ({ nodeInfo, hideInfo = false }) => {
|
|||||||
const [collapseState, setCollapseState] = useState<boolean>(true)
|
const [collapseState, setCollapseState] = useState<boolean>(true)
|
||||||
|
|
||||||
const getTime = (time: number) => {
|
const getTime = (time: number) => {
|
||||||
if (time < 1)
|
if (time < 1) { return `${(time * 1000).toFixed(3)} ms` }
|
||||||
return `${(time * 1000).toFixed(3)} ms`
|
if (time > 60) { return `${parseInt(Math.round(time / 60).toString())} m ${(time % 60).toFixed(3)} s` }
|
||||||
if (time > 60)
|
|
||||||
return `${parseInt(Math.round(time / 60).toString())} m ${(time % 60).toFixed(3)} s`
|
|
||||||
return `${time.toFixed(3)} s`
|
return `${time.toFixed(3)} s`
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTokenCount = (tokens: number) => {
|
const getTokenCount = (tokens: number) => {
|
||||||
if (tokens < 1000)
|
if (tokens < 1000) { return tokens }
|
||||||
return tokens
|
if (tokens >= 1000 && tokens < 1000000) { return `${parseFloat((tokens / 1000).toFixed(3))}K` }
|
||||||
if (tokens >= 1000 && tokens < 1000000)
|
if (tokens >= 1000000) { return `${parseFloat((tokens / 1000000).toFixed(3))}M` }
|
||||||
return `${parseFloat((tokens / 1000).toFixed(3))}K`
|
|
||||||
if (tokens >= 1000000)
|
|
||||||
return `${parseFloat((tokens / 1000000).toFixed(3))}M`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import Loading02 from '@/app/components/base/icons/line/loading-02'
|
|||||||
import ChevronRight from '@/app/components/base/icons/line/chevron-right'
|
import ChevronRight from '@/app/components/base/icons/line/chevron-right'
|
||||||
import { WorkflowRunningStatus } from '@/types/app'
|
import { WorkflowRunningStatus } from '@/types/app'
|
||||||
|
|
||||||
type WorkflowProcessProps = {
|
interface WorkflowProcessProps {
|
||||||
data: WorkflowProcess
|
data: WorkflowProcess
|
||||||
grayBg?: boolean
|
grayBg?: boolean
|
||||||
expand?: boolean
|
expand?: boolean
|
||||||
@ -30,14 +30,11 @@ const WorkflowProcessItem = ({
|
|||||||
const failed = data.status === WorkflowRunningStatus.Failed || data.status === WorkflowRunningStatus.Stopped
|
const failed = data.status === WorkflowRunningStatus.Failed || data.status === WorkflowRunningStatus.Stopped
|
||||||
|
|
||||||
const background = useMemo(() => {
|
const background = useMemo(() => {
|
||||||
if (running && !collapse)
|
if (running && !collapse) { return 'linear-gradient(180deg, #E1E4EA 0%, #EAECF0 100%)' }
|
||||||
return 'linear-gradient(180deg, #E1E4EA 0%, #EAECF0 100%)'
|
|
||||||
|
|
||||||
if (succeeded && !collapse)
|
if (succeeded && !collapse) { return 'linear-gradient(180deg, #ECFDF3 0%, #F6FEF9 100%)' }
|
||||||
return 'linear-gradient(180deg, #ECFDF3 0%, #F6FEF9 100%)'
|
|
||||||
|
|
||||||
if (failed && !collapse)
|
if (failed && !collapse) { return 'linear-gradient(180deg, #FEE4E2 0%, #FEF3F2 100%)' }
|
||||||
return 'linear-gradient(180deg, #FEE4E2 0%, #FEF3F2 100%)'
|
|
||||||
}, [running, succeeded, failed, collapse])
|
}, [running, succeeded, failed, collapse])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@source "../../node_modules/streamdown/dist/index.js";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--max-width: 1100px;
|
--max-width: 1100px;
|
||||||
--border-radius: 12px;
|
--border-radius: 12px;
|
||||||
|
|||||||
67
eslint.config.mjs
Normal file
67
eslint.config.mjs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { combine, javascript, typescript, stylistic } from '@antfu/eslint-config'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
|
||||||
|
export default combine(
|
||||||
|
javascript({
|
||||||
|
overrides: {
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'no-console': 'off',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
typescript(),
|
||||||
|
|
||||||
|
stylistic({
|
||||||
|
lessOpinionated: true,
|
||||||
|
jsx: false,
|
||||||
|
semi: false,
|
||||||
|
quotes: 'single',
|
||||||
|
overrides: {
|
||||||
|
'style/indent': ['error', 2],
|
||||||
|
'style/quotes': ['error', 'single'],
|
||||||
|
'style/max-statements-per-line': 'off',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
'unused-imports/no-unused-vars': 'warn',
|
||||||
|
'unused-imports/no-unused-imports': 'warn',
|
||||||
|
'@typescript-eslint/no-use-before-define': 'off',
|
||||||
|
'ts/no-use-before-define': 'off',
|
||||||
|
'style/brace-style': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'**/node_modules/**',
|
||||||
|
'**/dist/**',
|
||||||
|
'**/build/**',
|
||||||
|
'**/out/**',
|
||||||
|
'**/.next/**',
|
||||||
|
'**/public/**',
|
||||||
|
'**/*.json',
|
||||||
|
'tailwind.config.js',
|
||||||
|
'next.config.js',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.es2025,
|
||||||
|
...globals.node,
|
||||||
|
React: 'readable',
|
||||||
|
JSX: 'readable',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
@ -10,10 +10,8 @@ export enum MediaType {
|
|||||||
const useBreakpoints = () => {
|
const useBreakpoints = () => {
|
||||||
const [width, setWidth] = React.useState(globalThis.innerWidth)
|
const [width, setWidth] = React.useState(globalThis.innerWidth)
|
||||||
const media = (() => {
|
const media = (() => {
|
||||||
if (width <= 640)
|
if (width <= 640) { return MediaType.mobile }
|
||||||
return MediaType.mobile
|
if (width <= 768) { return MediaType.tablet }
|
||||||
if (width <= 768)
|
|
||||||
return MediaType.tablet
|
|
||||||
return MediaType.pc
|
return MediaType.pc
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
|||||||
@ -30,8 +30,7 @@ function useConversation() {
|
|||||||
// input can be updated by user
|
// input can be updated by user
|
||||||
const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any> | null>(null)
|
const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any> | null>(null)
|
||||||
const resetNewConversationInputs = () => {
|
const resetNewConversationInputs = () => {
|
||||||
if (!newConversationInputs)
|
if (!newConversationInputs) { return }
|
||||||
return
|
|
||||||
setNewConversationInputs(produce(newConversationInputs, (draft) => {
|
setNewConversationInputs(produce(newConversationInputs, (draft) => {
|
||||||
Object.keys(draft).forEach((key) => {
|
Object.keys(draft).forEach((key) => {
|
||||||
draft[key] = ''
|
draft[key] = ''
|
||||||
|
|||||||
@ -12,6 +12,5 @@ export const getLocaleOnClient = (): Locale => {
|
|||||||
export const setLocaleOnClient = (locale: Locale, notReload?: boolean) => {
|
export const setLocaleOnClient = (locale: Locale, notReload?: boolean) => {
|
||||||
Cookies.set(LOCALE_COOKIE_NAME, locale)
|
Cookies.set(LOCALE_COOKIE_NAME, locale)
|
||||||
changeLanguage(locale)
|
changeLanguage(locale)
|
||||||
if (!notReload)
|
if (!notReload) { location.reload() }
|
||||||
location.reload()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,4 +33,4 @@ const translation = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation;
|
export default translation
|
||||||
|
|||||||
@ -100,4 +100,4 @@ const translation = {
|
|||||||
notAuthorized: 'Công cụ chưa được ủy quyền',
|
notAuthorized: 'Công cụ chưa được ủy quyền',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation;
|
export default translation
|
||||||
|
|||||||
19
package.json
19
package.json
@ -7,9 +7,9 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"fix": "next lint --fix",
|
"fix": "eslint . --fix",
|
||||||
"eslint-fix": "eslint . --fix",
|
"eslint-fix": "eslint . --fix",
|
||||||
"prepare": "husky install ./.husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react": "^0.26.25",
|
"@floating-ui/react": "^0.26.25",
|
||||||
@ -41,10 +41,10 @@
|
|||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"negotiator": "^0.6.3",
|
"negotiator": "^0.6.3",
|
||||||
"next": "^14.2.32",
|
"next": "^15.5.2",
|
||||||
"rc-textarea": "^1.5.3",
|
"rc-textarea": "^1.5.3",
|
||||||
"react": "~18.3.0",
|
"react": "~19.1.1",
|
||||||
"react-dom": "~18.3.0",
|
"react-dom": "~19.1.1",
|
||||||
"react-error-boundary": "^4.0.2",
|
"react-error-boundary": "^4.0.2",
|
||||||
"react-headless-pagination": "^1.1.4",
|
"react-headless-pagination": "^1.1.4",
|
||||||
"react-i18next": "^12.2.0",
|
"react-i18next": "^12.2.0",
|
||||||
@ -58,15 +58,16 @@
|
|||||||
"sass": "^1.61.0",
|
"sass": "^1.61.0",
|
||||||
"scheduler": "^0.23.0",
|
"scheduler": "^0.23.0",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
|
"streamdown": "^1.2.0",
|
||||||
"swr": "^2.3.0",
|
"swr": "^2.3.0",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"typescript": "4.9.5",
|
|
||||||
"use-context-selector": "^2.0.0",
|
"use-context-selector": "^2.0.0",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "~5.2.2",
|
"@antfu/eslint-config": "~5.2.2",
|
||||||
|
"@eslint-react/eslint-plugin": "^1.53.0",
|
||||||
"@faker-js/faker": "^9.0.3",
|
"@faker-js/faker": "^9.0.3",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
@ -74,10 +75,14 @@
|
|||||||
"@types/negotiator": "^0.6.3",
|
"@types/negotiator": "^0.6.3",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "~9.35.0",
|
"eslint": "~9.35.0",
|
||||||
|
"eslint-plugin-format": "^1.0.1",
|
||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
|
"globals": "^16.4.0",
|
||||||
"lint-staged": "^15.2.10",
|
"lint-staged": "^15.2.10",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.4.14"
|
"tailwindcss": "^3.4.14",
|
||||||
|
"typescript": "5.9.2"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"**/*.js?(x)": [
|
"**/*.js?(x)": [
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const baseOptions = {
|
|||||||
redirect: 'follow',
|
redirect: 'follow',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkflowStartedResponse = {
|
export interface WorkflowStartedResponse {
|
||||||
task_id: string
|
task_id: string
|
||||||
workflow_run_id: string
|
workflow_run_id: string
|
||||||
event: string
|
event: string
|
||||||
@ -34,7 +34,7 @@ export type WorkflowStartedResponse = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkflowFinishedResponse = {
|
export interface WorkflowFinishedResponse {
|
||||||
task_id: string
|
task_id: string
|
||||||
workflow_run_id: string
|
workflow_run_id: string
|
||||||
event: string
|
event: string
|
||||||
@ -52,7 +52,7 @@ export type WorkflowFinishedResponse = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeStartedResponse = {
|
export interface NodeStartedResponse {
|
||||||
task_id: string
|
task_id: string
|
||||||
workflow_run_id: string
|
workflow_run_id: string
|
||||||
event: string
|
event: string
|
||||||
@ -68,7 +68,7 @@ export type NodeStartedResponse = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeFinishedResponse = {
|
export interface NodeFinishedResponse {
|
||||||
task_id: string
|
task_id: string
|
||||||
workflow_run_id: string
|
workflow_run_id: string
|
||||||
event: string
|
event: string
|
||||||
@ -93,7 +93,7 @@ export type NodeFinishedResponse = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IOnDataMoreInfo = {
|
export interface IOnDataMoreInfo {
|
||||||
conversationId?: string
|
conversationId?: string
|
||||||
taskId?: string
|
taskId?: string
|
||||||
messageId: string
|
messageId: string
|
||||||
@ -114,7 +114,7 @@ export type IOnWorkflowFinished = (workflowFinished: WorkflowFinishedResponse) =
|
|||||||
export type IOnNodeStarted = (nodeStarted: NodeStartedResponse) => void
|
export type IOnNodeStarted = (nodeStarted: NodeStartedResponse) => void
|
||||||
export type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => void
|
export type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => void
|
||||||
|
|
||||||
type IOtherOptions = {
|
interface IOtherOptions {
|
||||||
isPublicAPI?: boolean
|
isPublicAPI?: boolean
|
||||||
bodyStringify?: boolean
|
bodyStringify?: boolean
|
||||||
needAllResponseContent?: boolean
|
needAllResponseContent?: boolean
|
||||||
@ -152,8 +152,7 @@ const handleStream = (
|
|||||||
onNodeStarted?: IOnNodeStarted,
|
onNodeStarted?: IOnNodeStarted,
|
||||||
onNodeFinished?: IOnNodeFinished,
|
onNodeFinished?: IOnNodeFinished,
|
||||||
) => {
|
) => {
|
||||||
if (!response.ok)
|
if (!response.ok) { throw new Error('Network response was not ok') }
|
||||||
throw new Error('Network response was not ok')
|
|
||||||
|
|
||||||
const reader = response.body?.getReader()
|
const reader = response.body?.getReader()
|
||||||
const decoder = new TextDecoder('utf-8')
|
const decoder = new TextDecoder('utf-8')
|
||||||
@ -241,8 +240,7 @@ const handleStream = (
|
|||||||
onCompleted?.(true)
|
onCompleted?.(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!hasError)
|
if (!hasError) { read() }
|
||||||
read()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
read()
|
read()
|
||||||
@ -262,17 +260,14 @@ const baseFetch = (url: string, fetchOptions: any, { needAllResponseContent }: I
|
|||||||
Object.keys(params).forEach(key =>
|
Object.keys(params).forEach(key =>
|
||||||
paramsArray.push(`${key}=${encodeURIComponent(params[key])}`),
|
paramsArray.push(`${key}=${encodeURIComponent(params[key])}`),
|
||||||
)
|
)
|
||||||
if (urlWithPrefix.search(/\?/) === -1)
|
if (urlWithPrefix.search(/\?/) === -1) { urlWithPrefix += `?${paramsArray.join('&')}` }
|
||||||
urlWithPrefix += `?${paramsArray.join('&')}`
|
|
||||||
|
|
||||||
else
|
else { urlWithPrefix += `&${paramsArray.join('&')}` }
|
||||||
urlWithPrefix += `&${paramsArray.join('&')}`
|
|
||||||
|
|
||||||
delete options.params
|
delete options.params
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body)
|
if (body) { options.body = JSON.stringify(body) }
|
||||||
options.body = JSON.stringify(body)
|
|
||||||
|
|
||||||
// Handle timeout
|
// Handle timeout
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
@ -344,16 +339,13 @@ export const upload = (fetchOptions: any): Promise<any> => {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = options.xhr
|
const xhr = options.xhr
|
||||||
xhr.open(options.method, options.url)
|
xhr.open(options.method, options.url)
|
||||||
for (const key in options.headers)
|
for (const key in options.headers) { xhr.setRequestHeader(key, options.headers[key]) }
|
||||||
xhr.setRequestHeader(key, options.headers[key])
|
|
||||||
|
|
||||||
xhr.withCredentials = true
|
xhr.withCredentials = true
|
||||||
xhr.onreadystatechange = function () {
|
xhr.onreadystatechange = function () {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
if (xhr.status === 200)
|
if (xhr.status === 200) { resolve({ id: xhr.response }) }
|
||||||
resolve({ id: xhr.response })
|
else { reject(xhr) }
|
||||||
else
|
|
||||||
reject(xhr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
xhr.upload.onprogress = options.onprogress
|
xhr.upload.onprogress = options.onprogress
|
||||||
@ -386,8 +378,7 @@ export const ssePost = (
|
|||||||
const urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
|
const urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
|
||||||
|
|
||||||
const { body } = options
|
const { body } = options
|
||||||
if (body)
|
if (body) { options.body = JSON.stringify(body) }
|
||||||
options.body = JSON.stringify(body)
|
|
||||||
|
|
||||||
globalThis.fetch(urlWithPrefix, options)
|
globalThis.fetch(urlWithPrefix, options)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
@ -410,7 +401,8 @@ export const ssePost = (
|
|||||||
}, () => {
|
}, () => {
|
||||||
onCompleted?.()
|
onCompleted?.()
|
||||||
}, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished)
|
}, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished)
|
||||||
}).catch((e) => {
|
})
|
||||||
|
.catch((e) => {
|
||||||
Toast.notify({ type: 'error', message: e })
|
Toast.notify({ type: 'error', message: e })
|
||||||
onError?.(e)
|
onError?.(e)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export const fetchAppParams = async () => {
|
|||||||
return get('parameters')
|
return get('parameters')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => {
|
export const updateFeedback = async ({ url, body }: { url: string, body: Feedbacktype }) => {
|
||||||
return post(url, { body })
|
return post(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
38
types/app.ts
38
types/app.ts
@ -2,7 +2,7 @@ import type { Annotation } from './log'
|
|||||||
import type { Locale } from '@/i18n'
|
import type { Locale } from '@/i18n'
|
||||||
import type { ThoughtItem } from '@/app/components/chat/type'
|
import type { ThoughtItem } from '@/app/components/chat/type'
|
||||||
|
|
||||||
export type PromptVariable = {
|
export interface PromptVariable {
|
||||||
key: string
|
key: string
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
@ -15,19 +15,19 @@ export type PromptVariable = {
|
|||||||
allowed_file_upload_methods?: TransferMethod[]
|
allowed_file_upload_methods?: TransferMethod[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptConfig = {
|
export interface PromptConfig {
|
||||||
prompt_template: string
|
prompt_template: string
|
||||||
prompt_variables: PromptVariable[]
|
prompt_variables: PromptVariable[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TextTypeFormItem = {
|
export interface TextTypeFormItem {
|
||||||
label: string
|
label: string
|
||||||
variable: string
|
variable: string
|
||||||
required: boolean
|
required: boolean
|
||||||
max_length: number
|
max_length: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SelectTypeFormItem = {
|
export interface SelectTypeFormItem {
|
||||||
label: string
|
label: string
|
||||||
variable: string
|
variable: string
|
||||||
required: boolean
|
required: boolean
|
||||||
@ -39,26 +39,26 @@ export type SelectTypeFormItem = {
|
|||||||
export type UserInputFormItem = {
|
export type UserInputFormItem = {
|
||||||
'text-input': TextTypeFormItem
|
'text-input': TextTypeFormItem
|
||||||
} | {
|
} | {
|
||||||
'select': SelectTypeFormItem
|
select: SelectTypeFormItem
|
||||||
} | {
|
} | {
|
||||||
'paragraph': TextTypeFormItem
|
paragraph: TextTypeFormItem
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageRatings = ['like', 'dislike', null] as const
|
export const MessageRatings = ['like', 'dislike', null] as const
|
||||||
export type MessageRating = typeof MessageRatings[number]
|
export type MessageRating = typeof MessageRatings[number]
|
||||||
|
|
||||||
export type Feedbacktype = {
|
export interface Feedbacktype {
|
||||||
rating: MessageRating
|
rating: MessageRating
|
||||||
content?: string | null
|
content?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageMore = {
|
export interface MessageMore {
|
||||||
time: string
|
time: string
|
||||||
tokens: number
|
tokens: number
|
||||||
latency: number | string
|
latency: number | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IChatItem = {
|
export interface IChatItem {
|
||||||
id: string
|
id: string
|
||||||
content: string
|
content: string
|
||||||
/**
|
/**
|
||||||
@ -85,7 +85,7 @@ export type IChatItem = {
|
|||||||
useCurrentUserAvatar?: boolean
|
useCurrentUserAvatar?: boolean
|
||||||
isOpeningStatement?: boolean
|
isOpeningStatement?: boolean
|
||||||
suggestedQuestions?: string[]
|
suggestedQuestions?: string[]
|
||||||
log?: { role: string; text: string }[]
|
log?: { role: string, text: string }[]
|
||||||
agent_thoughts?: ThoughtItem[]
|
agent_thoughts?: ThoughtItem[]
|
||||||
message_files?: VisionFile[]
|
message_files?: VisionFile[]
|
||||||
}
|
}
|
||||||
@ -96,17 +96,17 @@ export type ChatItem = IChatItem & {
|
|||||||
workflowProcess?: WorkflowProcess
|
workflowProcess?: WorkflowProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResponseHolder = {}
|
export interface ResponseHolder {}
|
||||||
|
|
||||||
export type ConversationItem = {
|
export interface ConversationItem {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
inputs: Record<string, any> | null
|
inputs: Record<string, any> | null
|
||||||
introduction: string,
|
introduction: string
|
||||||
suggested_questions?: string[]
|
suggested_questions?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppInfo = {
|
export interface AppInfo {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
default_language: Locale
|
default_language: Locale
|
||||||
@ -125,7 +125,7 @@ export enum TransferMethod {
|
|||||||
remote_url = 'remote_url',
|
remote_url = 'remote_url',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VisionSettings = {
|
export interface VisionSettings {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
number_limits: number
|
number_limits: number
|
||||||
detail: Resolution
|
detail: Resolution
|
||||||
@ -133,7 +133,7 @@ export type VisionSettings = {
|
|||||||
image_file_size_limit?: number | string
|
image_file_size_limit?: number | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImageFile = {
|
export interface ImageFile {
|
||||||
type: TransferMethod
|
type: TransferMethod
|
||||||
_id: string
|
_id: string
|
||||||
fileId: string
|
fileId: string
|
||||||
@ -144,7 +144,7 @@ export type ImageFile = {
|
|||||||
deleted?: boolean
|
deleted?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VisionFile = {
|
export interface VisionFile {
|
||||||
id?: string
|
id?: string
|
||||||
type: string
|
type: string
|
||||||
transfer_method: TransferMethod
|
transfer_method: TransferMethod
|
||||||
@ -168,7 +168,7 @@ export enum BlockEnum {
|
|||||||
Tool = 'tool',
|
Tool = 'tool',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeTracing = {
|
export interface NodeTracing {
|
||||||
id: string
|
id: string
|
||||||
index: number
|
index: number
|
||||||
predecessor_node_id: string
|
predecessor_node_id: string
|
||||||
@ -213,7 +213,7 @@ export enum WorkflowRunningStatus {
|
|||||||
Stopped = 'stopped',
|
Stopped = 'stopped',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkflowProcess = {
|
export interface WorkflowProcess {
|
||||||
status: WorkflowRunningStatus
|
status: WorkflowRunningStatus
|
||||||
tracing: NodeTracing[]
|
tracing: NodeTracing[]
|
||||||
expand?: boolean // for UI
|
expand?: boolean // for UI
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export type TypeWithI18N<T = string> = {
|
export interface TypeWithI18N<T = string> {
|
||||||
'en_US': T
|
en_US: T
|
||||||
'zh_Hans': T
|
zh_Hans: T
|
||||||
[key: string]: T
|
[key: string]: T
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export type LogAnnotation = {
|
export interface LogAnnotation {
|
||||||
content: string
|
content: string
|
||||||
account: {
|
account: {
|
||||||
id: string
|
id: string
|
||||||
@ -8,7 +8,7 @@ export type LogAnnotation = {
|
|||||||
created_at: number
|
created_at: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Annotation = {
|
export interface Annotation {
|
||||||
id: string
|
id: string
|
||||||
authorName: string
|
authorName: string
|
||||||
logAnnotation?: LogAnnotation
|
logAnnotation?: LogAnnotation
|
||||||
|
|||||||
@ -9,10 +9,10 @@ export enum AuthType {
|
|||||||
apiKey = 'api_key',
|
apiKey = 'api_key',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Credential = {
|
export interface Credential {
|
||||||
'auth_type': AuthType
|
auth_type: AuthType
|
||||||
'api_key_header'?: string
|
api_key_header?: string
|
||||||
'api_key_value'?: string
|
api_key_value?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CollectionType {
|
export enum CollectionType {
|
||||||
@ -21,12 +21,12 @@ export enum CollectionType {
|
|||||||
custom = 'api',
|
custom = 'api',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Emoji = {
|
export interface Emoji {
|
||||||
background: string
|
background: string
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Collection = {
|
export interface Collection {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
author: string
|
author: string
|
||||||
@ -39,7 +39,7 @@ export type Collection = {
|
|||||||
allow_delete: boolean
|
allow_delete: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolParameter = {
|
export interface ToolParameter {
|
||||||
name: string
|
name: string
|
||||||
label: TypeWithI18N
|
label: TypeWithI18N
|
||||||
human_description: TypeWithI18N
|
human_description: TypeWithI18N
|
||||||
@ -52,14 +52,14 @@ export type ToolParameter = {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Tool = {
|
export interface Tool {
|
||||||
name: string
|
name: string
|
||||||
label: TypeWithI18N
|
label: TypeWithI18N
|
||||||
description: any
|
description: any
|
||||||
parameters: ToolParameter[]
|
parameters: ToolParameter[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolCredential = {
|
export interface ToolCredential {
|
||||||
name: string
|
name: string
|
||||||
label: TypeWithI18N
|
label: TypeWithI18N
|
||||||
help: TypeWithI18N
|
help: TypeWithI18N
|
||||||
@ -73,7 +73,7 @@ export type ToolCredential = {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomCollectionBackend = {
|
export interface CustomCollectionBackend {
|
||||||
provider: string
|
provider: string
|
||||||
original_provider?: string
|
original_provider?: string
|
||||||
credentials: Credential
|
credentials: Credential
|
||||||
@ -84,7 +84,7 @@ export type CustomCollectionBackend = {
|
|||||||
tools?: ParamItem[]
|
tools?: ParamItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ParamItem = {
|
export interface ParamItem {
|
||||||
name: string
|
name: string
|
||||||
label: TypeWithI18N
|
label: TypeWithI18N
|
||||||
human_description: TypeWithI18N
|
human_description: TypeWithI18N
|
||||||
@ -99,7 +99,7 @@ export type ParamItem = {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomParamSchema = {
|
export interface CustomParamSchema {
|
||||||
operation_id: string // name
|
operation_id: string // name
|
||||||
summary: string
|
summary: string
|
||||||
server_url: string
|
server_url: string
|
||||||
|
|||||||
@ -4,8 +4,7 @@
|
|||||||
* @example formatNumber(1234567.89) will return '1,234,567.89'
|
* @example formatNumber(1234567.89) will return '1,234,567.89'
|
||||||
*/
|
*/
|
||||||
export const formatNumber = (num: number | string) => {
|
export const formatNumber = (num: number | string) => {
|
||||||
if (!num)
|
if (!num) { return num }
|
||||||
return num
|
|
||||||
const parts = num.toString().split('.')
|
const parts = num.toString().split('.')
|
||||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
return parts.join('.')
|
return parts.join('.')
|
||||||
@ -18,8 +17,7 @@ export const formatNumber = (num: number | string) => {
|
|||||||
* @example formatFileSize(1024 * 1024) will return '1.00MB'
|
* @example formatFileSize(1024 * 1024) will return '1.00MB'
|
||||||
*/
|
*/
|
||||||
export const formatFileSize = (fileSize: number) => {
|
export const formatFileSize = (fileSize: number) => {
|
||||||
if (!fileSize)
|
if (!fileSize) { return fileSize }
|
||||||
return fileSize
|
|
||||||
const units = ['', 'K', 'M', 'G', 'T', 'P']
|
const units = ['', 'K', 'M', 'G', 'T', 'P']
|
||||||
let index = 0
|
let index = 0
|
||||||
while (fileSize >= 1024 && index < units.length) {
|
while (fileSize >= 1024 && index < units.length) {
|
||||||
@ -35,8 +33,7 @@ export const formatFileSize = (fileSize: number) => {
|
|||||||
* @example formatTime(60 * 60) will return '1.00 h'
|
* @example formatTime(60 * 60) will return '1.00 h'
|
||||||
*/
|
*/
|
||||||
export const formatTime = (seconds: number) => {
|
export const formatTime = (seconds: number) => {
|
||||||
if (!seconds)
|
if (!seconds) { return seconds }
|
||||||
return seconds
|
|
||||||
const units = ['sec', 'min', 'h']
|
const units = ['sec', 'min', 'h']
|
||||||
let index = 0
|
let index = 0
|
||||||
while (seconds >= 60 && index < units.length) {
|
while (seconds >= 60 && index < units.length) {
|
||||||
@ -46,7 +43,7 @@ export const formatTime = (seconds: number) => {
|
|||||||
return `${seconds.toFixed(2)} ${units[index]}`
|
return `${seconds.toFixed(2)} ${units[index]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const downloadFile = ({ data, fileName }: { data: Blob; fileName: string }) => {
|
export const downloadFile = ({ data, fileName }: { data: Blob, fileName: string }) => {
|
||||||
const url = window.URL.createObjectURL(data)
|
const url = window.URL.createObjectURL(data)
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
a.href = url
|
a.href = url
|
||||||
|
|||||||
@ -3,8 +3,7 @@ import type { PromptVariable, UserInputFormItem } from '@/types/app'
|
|||||||
export function replaceVarWithValues(str: string, promptVariables: PromptVariable[], inputs: Record<string, any>) {
|
export function replaceVarWithValues(str: string, promptVariables: PromptVariable[], inputs: Record<string, any>) {
|
||||||
return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
||||||
const name = inputs[key]
|
const name = inputs[key]
|
||||||
if (name)
|
if (name) { return name }
|
||||||
return name
|
|
||||||
|
|
||||||
const valueObj: PromptVariable | undefined = promptVariables.find(v => v.key === key)
|
const valueObj: PromptVariable | undefined = promptVariables.find(v => v.key === key)
|
||||||
return valueObj ? `{{${valueObj.key}}}` : match
|
return valueObj ? `{{${valueObj.key}}}` : match
|
||||||
@ -12,8 +11,7 @@ export function replaceVarWithValues(str: string, promptVariables: PromptVariabl
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | null) => {
|
export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | null) => {
|
||||||
if (!useInputs)
|
if (!useInputs) { return [] }
|
||||||
return []
|
|
||||||
const promptVariables: PromptVariable[] = []
|
const promptVariables: PromptVariable[] = []
|
||||||
useInputs.forEach((item: any) => {
|
useInputs.forEach((item: any) => {
|
||||||
const [type, content] = (() => {
|
const [type, content] = (() => {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
|
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
|
||||||
export function randomString(length: number) {
|
export function randomString(length: number) {
|
||||||
let result = ''
|
let result = ''
|
||||||
for (let i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]
|
for (let i = length; i > 0; --i) { result += chars[Math.floor(Math.random() * chars.length)] }
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,18 +2,15 @@ import type { ThoughtItem } from '@/app/components/chat/type'
|
|||||||
import type { VisionFile } from '@/types/app'
|
import type { VisionFile } from '@/types/app'
|
||||||
|
|
||||||
export const sortAgentSorts = (list: ThoughtItem[]) => {
|
export const sortAgentSorts = (list: ThoughtItem[]) => {
|
||||||
if (!list)
|
if (!list) { return list }
|
||||||
return list
|
if (list.some(item => item.position === undefined)) { return list }
|
||||||
if (list.some(item => item.position === undefined))
|
|
||||||
return list
|
|
||||||
const temp = [...list]
|
const temp = [...list]
|
||||||
temp.sort((a, b) => a.position - b.position)
|
temp.sort((a, b) => a.position - b.position)
|
||||||
return temp
|
return temp
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addFileInfos = (list: ThoughtItem[], messageFiles: VisionFile[]) => {
|
export const addFileInfos = (list: ThoughtItem[], messageFiles: VisionFile[]) => {
|
||||||
if (!list || !messageFiles)
|
if (!list || !messageFiles) { return list }
|
||||||
return list
|
|
||||||
return list.map((item) => {
|
return list.map((item) => {
|
||||||
if (item.files && item.files?.length > 0) {
|
if (item.files && item.files?.length > 0) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user