feat: support assistant frontend (#2139)

Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
This commit is contained in:
Joel 2024-01-23 19:31:56 +08:00 committed by GitHub
parent e65a2a400d
commit 7bbe12b2bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
194 changed files with 8726 additions and 1586 deletions

4
web/.gitignore vendored
View File

@ -47,4 +47,6 @@ package-lock.json
.yarnrc.yml .yarnrc.yml
# pmpm # pmpm
pnpm-lock.yaml pnpm-lock.yaml
.favorites.json

View File

@ -38,15 +38,24 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const navigation = useMemo(() => { const navigation = useMemo(() => {
const navs = [ const navs = [
{ name: t('common.appMenus.overview'), href: `/app/${appId}/overview`, icon: ChartBarSquareIcon, selectedIcon: ChartBarSquareSolidIcon },
...(isCurrentWorkspaceManager ? [{ name: t('common.appMenus.promptEng'), href: `/app/${appId}/configuration`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon }] : []), ...(isCurrentWorkspaceManager ? [{ name: t('common.appMenus.promptEng'), href: `/app/${appId}/configuration`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon }] : []),
{ name: t('common.appMenus.overview'), href: `/app/${appId}/overview`, icon: ChartBarSquareIcon, selectedIcon: ChartBarSquareSolidIcon },
{ name: t('common.appMenus.apiAccess'), href: `/app/${appId}/develop`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon }, { name: t('common.appMenus.apiAccess'), href: `/app/${appId}/develop`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
{ name: t('common.appMenus.logAndAnn'), href: `/app/${appId}/logs`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon }, { name: t('common.appMenus.logAndAnn'), href: `/app/${appId}/logs`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
] ]
return navs return navs
}, [appId, isCurrentWorkspaceManager, t]) }, [appId, isCurrentWorkspaceManager, t])
const appModeName = response?.mode?.toUpperCase() === 'COMPLETION' ? t('common.appModes.completionApp') : t('common.appModes.chatApp') const appModeName = (() => {
if (response?.mode?.toUpperCase() === 'COMPLETION')
return t('app.newApp.completeApp')
const isAgent = !!response?.is_agent
if (isAgent)
return t('appDebug.assistantType.agentAssistant.name')
return t('appDebug.assistantType.chatAssistant.name')
})()
useEffect(() => { useEffect(() => {
if (response?.name) if (response?.name)
document.title = `${(response.name || 'App')} - Dify` document.title = `${(response.name || 'App')} - Dify`

View File

@ -55,9 +55,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
catch (e: any) { catch (e: any) {
notify({ notify({
type: 'error', type: 'error',
message: `${t('app.appDeleteFailed')}${ message: `${t('app.appDeleteFailed')}${'message' in e ? `: ${e.message}` : ''}`,
'message' in e ? `: ${e.message}` : ''
}`,
}) })
} }
setShowConfirmDelete(false) setShowConfirmDelete(false)
@ -141,7 +139,8 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
if (showSettingsModal) if (showSettingsModal)
return return
e.preventDefault() e.preventDefault()
push(`/app/${app.id}/overview`)
push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`)
}} }}
className={style.listItem} className={style.listItem}
> >
@ -173,7 +172,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
{app.model_config?.pre_prompt} {app.model_config?.pre_prompt}
</div> </div>
<div className={style.listItemFooter}> <div className={style.listItemFooter}>
<AppModeLabel mode={app.mode} /> <AppModeLabel mode={app.mode} isAgent={app.is_agent} />
</div> </div>
{showConfirmDelete && ( {showConfirmDelete && (

View File

@ -1,25 +1,53 @@
'use client' 'use client'
import classNames from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import style from '../list.module.css'
import { type AppMode } from '@/types/app' import { type AppMode } from '@/types/app'
import {
AiText,
CuteRobote,
} from '@/app/components/base/icons/src/vender/solid/communication'
import { BubbleText } from '@/app/components/base/icons/src/vender/solid/education'
export type AppModeLabelProps = { export type AppModeLabelProps = {
mode: AppMode mode: AppMode
isAgent?: boolean
className?: string className?: string
} }
const AppModeLabel = ({ const AppModeLabel = ({
mode, mode,
isAgent,
className, className,
}: AppModeLabelProps) => { }: AppModeLabelProps) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<span className={classNames('flex items-center w-fit h-6 gap-1 px-2 text-gray-500 text-xs border border-gray-100 rounded', className)}> <div className={`inline-flex items-center px-2 h-6 rounded-md border border-gray-100 text-xs text-gray-500 ${className}`}>
<span className={classNames(style.listItemFooterIcon, mode === 'chat' && style.solidChatIcon, mode === 'completion' && style.solidCompletionIcon)} /> {
{t(`app.modes.${mode}`)} mode === 'completion' && (
</span> <>
<AiText className='mr-1 w-3 h-3 text-gray-400' />
{t('app.newApp.completeApp')}
</>
)
}
{
mode === 'chat' && !isAgent && (
<>
<BubbleText className='mr-1 w-3 h-3 text-gray-400' />
{t('appDebug.assistantType.chatAssistant.name')}
</>
)
}
{
mode === 'chat' && isAgent && (
<>
<CuteRobote className='mr-1 w-3 h-3 text-gray-400' />
{t('appDebug.assistantType.agentAssistant.name')}
</>
)
}
</div>
) )
} }

View File

@ -1,8 +1,9 @@
'use client' 'use client'
import { useEffect, useRef } from 'react' import { useEffect, useRef, useState } from 'react'
import useSWRInfinite from 'swr/infinite' import useSWRInfinite from 'swr/infinite'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks'
import AppCard from './AppCard' import AppCard from './AppCard'
import NewAppCard from './NewAppCard' import NewAppCard from './NewAppCard'
import type { AppListResponse } from '@/models/app' import type { AppListResponse } from '@/models/app'
@ -10,17 +11,46 @@ import { fetchAppList } from '@/service/apps'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { CheckModal } from '@/hooks/use-pay' import { CheckModal } from '@/hooks/use-pay'
const getKey = (pageIndex: number, previousPageData: AppListResponse) => { import TabSlider from '@/app/components/base/tab-slider'
if (!pageIndex || previousPageData.has_more) import { SearchLg } from '@/app/components/base/icons/src/vender/line/general'
return { url: 'apps', params: { page: pageIndex + 1, limit: 30 } } import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
const getKey = (
pageIndex: number,
previousPageData: AppListResponse,
activeTab: string,
keywords: string,
) => {
if (!pageIndex || previousPageData.has_more) {
const params: any = { url: 'apps', params: { page: pageIndex + 1, limit: 30, name: keywords } }
if (activeTab !== 'all')
params.params.mode = activeTab
return params
}
return null return null
} }
const Apps = () => { const Apps = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
const { data, isLoading, setSize, mutate } = useSWRInfinite(getKey, fetchAppList, { revalidateFirstPage: false }) const [activeTab, setActiveTab] = useState('all')
const [keywords, setKeywords] = useState('')
const [searchKeywords, setSearchKeywords] = useState('')
const { data, isLoading, setSize, mutate } = useSWRInfinite(
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, searchKeywords),
fetchAppList,
{ revalidateFirstPage: false },
)
const anchorRef = useRef<HTMLDivElement>(null) const anchorRef = useRef<HTMLDivElement>(null)
const options = [
{ value: 'all', text: t('app.types.all') },
{ value: 'chat', text: t('app.types.assistant') },
{ value: 'completion', text: t('app.types.completion') },
]
useEffect(() => { useEffect(() => {
document.title = `${t('app.title')} - Dify` document.title = `${t('app.title')} - Dify`
@ -34,24 +64,72 @@ const Apps = () => {
let observer: IntersectionObserver | undefined let observer: IntersectionObserver | undefined
if (anchorRef.current) { if (anchorRef.current) {
observer = new IntersectionObserver((entries) => { observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) if (entries[0].isIntersecting && !isLoading)
setSize(size => size + 1) setSize((size: number) => size + 1)
}, { rootMargin: '100px' }) }, { rootMargin: '100px' })
observer.observe(anchorRef.current) observer.observe(anchorRef.current)
} }
return () => observer?.disconnect() return () => observer?.disconnect()
}, [isLoading, setSize, anchorRef, mutate]) }, [isLoading, setSize, anchorRef, mutate])
const { run: handleSearch } = useDebounceFn(() => {
setSearchKeywords(keywords)
}, { wait: 500 })
const handleKeywordsChange = (value: string) => {
setKeywords(value)
handleSearch()
}
const handleClear = () => {
handleKeywordsChange('')
}
return ( return (
<><nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'> <>
{ isCurrentWorkspaceManager <div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-gray-100 z-10 flex-wrap gap-y-2'>
&& <NewAppCard onSuccess={mutate} />} <div className="flex items-center px-2 w-[200px] h-8 rounded-lg bg-gray-200">
{data?.map(({ data: apps }) => apps.map(app => ( <div className="pointer-events-none shrink-0 flex items-center mr-1.5 justify-center w-4 h-4">
<AppCard key={app.id} app={app} onRefresh={mutate} /> <SearchLg className="h-3.5 w-3.5 text-gray-500" aria-hidden="true" />
)))} </div>
<CheckModal /> <input
</nav> type="text"
<div ref={anchorRef} className='h-0'> </div> name="query"
className="grow block h-[18px] bg-gray-200 rounded-md border-0 text-gray-600 text-[13px] placeholder:text-gray-500 appearance-none outline-none"
placeholder={t('common.operation.search')!}
value={keywords}
onChange={(e) => {
handleKeywordsChange(e.target.value)
}}
autoComplete="off"
/>
{
keywords && (
<div
className='shrink-0 flex items-center justify-center w-4 h-4 cursor-pointer'
onClick={handleClear}
>
<XCircle className='w-3.5 h-3.5 text-gray-400' />
</div>
)
}
</div>
<TabSlider
value={activeTab}
onChange={setActiveTab}
options={options}
/>
</div>
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
{isCurrentWorkspaceManager
&& <NewAppCard onSuccess={mutate} />}
{data?.map(({ data: apps }: any) => apps.map((app: any) => (
<AppCard key={app.id} app={app} onRefresh={mutate} />
)))}
<CheckModal />
</nav>
<div ref={anchorRef} className='h-0'> </div>
</> </>
) )
} }

View File

@ -15,10 +15,11 @@ import type { AppMode } from '@/types/app'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import { createApp, fetchAppTemplates } from '@/service/apps' import { createApp, fetchAppTemplates } from '@/service/apps'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import AppsContext from '@/context/app-context' import AppsContext, { useAppContext } from '@/context/app-context'
import EmojiPicker from '@/app/components/base/emoji-picker' import EmojiPicker from '@/app/components/base/emoji-picker'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import AppsFull from '@/app/components/billing/apps-full-in-dialog' import AppsFull from '@/app/components/billing/apps-full-in-dialog'
import { AiText } from '@/app/components/base/icons/src/vender/solid/communication'
type NewAppDialogProps = { type NewAppDialogProps = {
show: boolean show: boolean
@ -29,6 +30,8 @@ type NewAppDialogProps = {
const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => { const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
const router = useRouter() const router = useRouter()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const { isCurrentWorkspaceManager } = useAppContext()
const { t } = useTranslation() const { t } = useTranslation()
const nameInputRef = useRef<HTMLInputElement>(null) const nameInputRef = useRef<HTMLInputElement>(null)
@ -90,7 +93,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
onClose() onClose()
notify({ type: 'success', message: t('app.newApp.appCreated') }) notify({ type: 'success', message: t('app.newApp.appCreated') })
mutateApps() mutateApps()
router.push(`/app/${app.id}/overview`) router.push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`)
} }
catch (e) { catch (e) {
notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
@ -119,13 +122,6 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
</> </>
} }
> >
<h3 className={style.newItemCaption}>{t('app.newApp.captionName')}</h3>
<div className='flex items-center justify-between gap-3 mb-8'>
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} />
<input ref={nameInputRef} className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' placeholder={t('app.appNamePlaceholder') || ''}/>
</div>
<div className='overflow-y-auto'> <div className='overflow-y-auto'>
<div className={style.newItemCaption}> <div className={style.newItemCaption}>
<h3 className='inline'>{t('app.newApp.captionAppType')}</h3> <h3 className='inline'>{t('app.newApp.captionAppType')}</h3>
@ -141,29 +137,9 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
</> </>
)} )}
</div> </div>
{isWithTemplate
? ( {!isWithTemplate && (
<ul className='grid grid-cols-1 md:grid-cols-2 gap-4'> (
{templates?.data?.map((template, index) => (
<li
key={index}
className={classNames(style.listItem, style.selectable, selectedTemplateIndex === index && style.selected)}
onClick={() => setSelectedTemplateIndex(index)}
>
<div className={style.listItemTitle}>
<AppIcon size='small' />
<div className={style.listItemHeading}>
<div className={style.listItemHeadingContent}>{template.name}</div>
</div>
</div>
<div className={style.listItemDescription}>{template.model_config?.pre_prompt}</div>
<AppModeLabel mode={template.mode} className='mt-2' />
{/* <AppModeLabel mode='chat' className='mt-2' /> */}
</li>
))}
</ul>
)
: (
<> <>
<ul className='grid grid-cols-1 md:grid-cols-2 gap-4'> <ul className='grid grid-cols-1 md:grid-cols-2 gap-4'>
<li <li
@ -178,10 +154,10 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
<div className={style.listItemHeadingContent}>{t('app.newApp.chatApp')}</div> <div className={style.listItemHeadingContent}>{t('app.newApp.chatApp')}</div>
</div> </div>
</div> </div>
<div className={style.listItemDescription}>{t('app.newApp.chatAppIntro')}</div> <div className={`${style.listItemDescription} ${style.noClip}`}>{t('app.newApp.chatAppIntro')}</div>
<div className={classNames(style.listItemFooter, 'justify-end')}> {/* <div className={classNames(style.listItemFooter, 'justify-end')}>
<a className={style.listItemLink} href='https://udify.app/chat/7CQBa5yyvYLSkZtx' target='_blank'>{t('app.newApp.previewDemo')}<span className={classNames(style.linkIcon, style.grayLinkIcon)} /></a> <a className={style.listItemLink} href='https://udify.app/chat/7CQBa5yyvYLSkZtx' target='_blank'>{t('app.newApp.previewDemo')}<span className={classNames(style.linkIcon, style.grayLinkIcon)} /></a>
</div> </div> */}
</li> </li>
<li <li
className={classNames(style.listItem, style.selectable, newAppMode === 'completion' && style.selected)} className={classNames(style.listItem, style.selectable, newAppMode === 'completion' && style.selected)}
@ -189,28 +165,65 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
> >
<div className={style.listItemTitle}> <div className={style.listItemTitle}>
<span className={style.newItemIcon}> <span className={style.newItemIcon}>
<span className={classNames(style.newItemIconImage, style.newItemIconComplete)} /> {/* <span className={classNames(style.newItemIconImage, style.newItemIconComplete)} /> */}
<AiText className={classNames('w-5 h-5', newAppMode === 'completion' ? 'text-[#155EEF]' : 'text-gray-700')} />
</span> </span>
<div className={style.listItemHeading}> <div className={style.listItemHeading}>
<div className={style.listItemHeadingContent}>{t('app.newApp.completeApp')}</div> <div className={style.listItemHeadingContent}>{t('app.newApp.completeApp')}</div>
</div> </div>
</div> </div>
<div className={style.listItemDescription}>{t('app.newApp.completeAppIntro')}</div> <div className={`${style.listItemDescription} ${style.noClip}`}>{t('app.newApp.completeAppIntro')}</div>
<div className={classNames(style.listItemFooter, 'justify-end')}>
<a className={style.listItemLink} href='https://udify.app/completion/aeFTj0VCb3Ok3TUE' target='_blank'>{t('app.newApp.previewDemo')}<span className={classNames(style.linkIcon, style.grayLinkIcon)} /></a>
</div>
</li> </li>
</ul> </ul>
<div className='flex items-center h-[34px] mt-2'>
<span
className='inline-flex items-center gap-1 text-xs font-medium cursor-pointer text-primary-600'
onClick={() => setIsWithTemplate(true)}
>
{t('app.newApp.showTemplates')}<span className={style.rightIcon} />
</span>
</div>
</> </>
)} )
)}
{isWithTemplate && (
<ul className='grid grid-cols-1 md:grid-cols-2 gap-4'>
{templates?.data?.map((template, index) => (
<li
key={index}
className={classNames(style.listItem, style.selectable, selectedTemplateIndex === index && style.selected)}
onClick={() => setSelectedTemplateIndex(index)}
>
<div className={style.listItemTitle}>
<AppIcon size='small' />
<div className={style.listItemHeading}>
<div className={style.listItemHeadingContent}>{template.name}</div>
</div>
</div>
<div className={style.listItemDescription}>{template.model_config?.pre_prompt}</div>
<div className='inline-block pl-3.5'>
<AppModeLabel mode={template.mode} isAgent={template.model_config.agent_mode.enabled} className='mt-2' />
</div>
</li>
))}
</ul>
)}
<div className='mt-8'>
<h3 className={style.newItemCaption}>{t('app.newApp.captionName')}</h3>
<div className='flex items-center justify-between gap-3'>
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} />
<input ref={nameInputRef} className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow' placeholder={t('app.appNamePlaceholder') || ''} />
</div>
</div>
{
!isWithTemplate && (
<div className='flex items-center h-[34px] mt-2'>
<span
className='inline-flex items-center gap-1 text-xs font-medium cursor-pointer text-primary-600'
onClick={() => setIsWithTemplate(true)}
>
{t('app.newApp.showTemplates')}<span className={style.rightIcon} />
</span>
</div>
)
}
</div> </div>
{isAppsFull && <AppsFull loc='app-create' />} {isAppsFull && <AppsFull loc='app-create' />}
</Dialog> </Dialog>

View File

@ -9,7 +9,7 @@ const AppList = async () => {
const { t } = await translate(locale, 'app') const { t } = await translate(locale, 'app')
return ( return (
<div className='flex flex-col overflow-auto bg-gray-100 shrink-0 grow'> <div className='relative flex flex-col overflow-y-auto bg-gray-100 shrink-0 h-0 grow'>
<Apps /> <Apps />
<footer className='px-12 py-6 grow-0 shrink-0'> <footer className='px-12 py-6 grow-0 shrink-0'>
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('join')}</h3> <h3 className='text-xl font-semibold leading-tight text-gradient'>{t('join')}</h3>

View File

@ -10,19 +10,20 @@
.listItem.selectable { .listItem.selectable {
@apply relative bg-gray-50 outline outline-1 outline-gray-200 -outline-offset-1 shadow-none hover:bg-none hover:shadow-none hover:outline-primary-200 transition-colors; @apply relative bg-gray-50 outline outline-1 outline-gray-200 -outline-offset-1 shadow-none hover:bg-none hover:shadow-none hover:outline-primary-200 transition-colors;
} }
.listItem.selectable * { .listItem.selectable * {
@apply relative; @apply relative;
} }
.listItem.selectable::before { .listItem.selectable::before {
content: ""; content: "";
@apply absolute top-0 left-0 block w-full h-full rounded-lg pointer-events-none opacity-0 transition-opacity duration-200 ease-in-out hover:opacity-100; @apply absolute top-0 left-0 block w-full h-full rounded-lg pointer-events-none opacity-0 transition-opacity duration-200 ease-in-out hover:opacity-100;
background: linear-gradient( background: linear-gradient(0deg,
0deg,
rgba(235, 245, 255, 0.5), rgba(235, 245, 255, 0.5),
rgba(235, 245, 255, 0.5) rgba(235, 245, 255, 0.5)),
),
#ffffff; #ffffff;
} }
.listItem.selectable:hover::before { .listItem.selectable:hover::before {
@apply opacity-100; @apply opacity-100;
} }
@ -30,6 +31,7 @@
.listItem.selected { .listItem.selected {
@apply border-primary-600 hover:border-primary-600 border-2; @apply border-primary-600 hover:border-primary-600 border-2;
} }
.listItem.selected::before { .listItem.selected::before {
@apply opacity-100; @apply opacity-100;
} }
@ -37,9 +39,11 @@
.appIcon { .appIcon {
@apply flex items-center justify-center w-8 h-8 bg-pink-100 rounded-lg grow-0 shrink-0; @apply flex items-center justify-center w-8 h-8 bg-pink-100 rounded-lg grow-0 shrink-0;
} }
.appIcon.medium { .appIcon.medium {
@apply w-9 h-9; @apply w-9 h-9;
} }
.appIcon.large { .appIcon.large {
@apply w-10 h-10; @apply w-10 h-10;
} }
@ -47,34 +51,48 @@
.newItemIcon { .newItemIcon {
@apply flex items-center justify-center w-8 h-8 transition-colors duration-200 ease-in-out border border-gray-200 rounded-lg hover:bg-white grow-0 shrink-0; @apply flex items-center justify-center w-8 h-8 transition-colors duration-200 ease-in-out border border-gray-200 rounded-lg hover:bg-white grow-0 shrink-0;
} }
.listItem:hover .newItemIcon { .listItem:hover .newItemIcon {
@apply bg-gray-50 border-primary-100; @apply bg-gray-50 border-primary-100;
} }
.newItemCard .newItemIcon { .newItemCard .newItemIcon {
@apply bg-gray-100; @apply bg-gray-100;
} }
.newItemCard:hover .newItemIcon { .newItemCard:hover .newItemIcon {
@apply bg-white; @apply bg-white;
} }
.selectable .newItemIcon { .selectable .newItemIcon {
@apply bg-gray-50; @apply bg-gray-50;
} }
.selectable:hover .newItemIcon { .selectable:hover .newItemIcon {
@apply bg-primary-50; @apply bg-primary-50;
} }
.newItemIconImage { .newItemIconImage {
@apply grow-0 shrink-0 block w-4 h-4 bg-center bg-contain transition-colors duration-200 ease-in-out; @apply grow-0 shrink-0 block w-4 h-4 bg-center bg-contain transition-colors duration-200 ease-in-out;
color: #1f2a37; color: #1f2a37;
} }
.listItem:hover .newIconImage { .listItem:hover .newIconImage {
@apply text-primary-600; @apply text-primary-600;
} }
.newItemIconAdd { .newItemIconAdd {
background-image: url("./apps/assets/add.svg"); background-image: url("./apps/assets/add.svg");
} }
.newItemIconChat { .newItemIconChat {
background-image: url("./apps/assets/chat.svg"); background-image: url("~@/app/components/base/icons/assets/public/header-nav/studio/Robot.svg");
} }
.selected .newItemIconChat {
background-image: url("~@/app/components/base/icons/assets/public/header-nav/studio/Robot-Active.svg");
}
.newItemIconComplete { .newItemIconComplete {
background-image: url("./apps/assets/completion.svg"); background-image: url("./apps/assets/completion.svg");
} }
@ -94,14 +112,17 @@
.actionIconWrapper { .actionIconWrapper {
@apply hidden h-8 w-8 p-2 rounded-md border-none hover:bg-gray-100 !important; @apply hidden h-8 w-8 p-2 rounded-md border-none hover:bg-gray-100 !important;
} }
.listItem:hover .actionIconWrapper { .listItem:hover .actionIconWrapper {
@apply !inline-flex; @apply !inline-flex;
} }
.deleteDatasetIcon { .deleteDatasetIcon {
@apply hidden grow-0 shrink-0 basis-8 w-8 h-8 rounded-lg transition-colors duration-200 ease-in-out bg-white border border-gray-200 hover:bg-gray-100 bg-center bg-no-repeat; @apply hidden grow-0 shrink-0 basis-8 w-8 h-8 rounded-lg transition-colors duration-200 ease-in-out bg-white border border-gray-200 hover:bg-gray-100 bg-center bg-no-repeat;
background-size: 16px; background-size: 16px;
background-image: url('~@/assets/delete.svg'); background-image: url('~@/assets/delete.svg');
} }
.listItem:hover .deleteDatasetIcon { .listItem:hover .deleteDatasetIcon {
@apply block; @apply block;
} }
@ -110,9 +131,14 @@
@apply mb-3 px-[14px] h-9 text-xs leading-normal text-gray-500 line-clamp-2; @apply mb-3 px-[14px] h-9 text-xs leading-normal text-gray-500 line-clamp-2;
} }
.listItemDescription.noClip {
@apply line-clamp-none;
}
.listItemFooter { .listItemFooter {
@apply flex items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px]; @apply flex items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px];
} }
.listItemFooter.datasetCardFooter { .listItemFooter.datasetCardFooter {
@apply flex items-center gap-4 text-xs text-gray-500; @apply flex items-center gap-4 text-xs text-gray-500;
} }
@ -124,18 +150,23 @@
.listItemFooterIcon { .listItemFooterIcon {
@apply block w-3 h-3 bg-center bg-contain; @apply block w-3 h-3 bg-center bg-contain;
} }
.solidChatIcon { .solidChatIcon {
background-image: url("./apps/assets/chat-solid.svg"); background-image: url("./apps/assets/chat-solid.svg");
} }
.solidCompletionIcon { .solidCompletionIcon {
background-image: url("./apps/assets/completion-solid.svg"); background-image: url("./apps/assets/completion-solid.svg");
} }
.docIcon { .docIcon {
background-image: url("./datasets/assets/doc.svg"); background-image: url("./datasets/assets/doc.svg");
} }
.textIcon { .textIcon {
background-image: url("./datasets/assets/text.svg"); background-image: url("./datasets/assets/text.svg");
} }
.applicationIcon { .applicationIcon {
background-image: url("./datasets/assets/application.svg"); background-image: url("./datasets/assets/application.svg");
} }
@ -143,6 +174,7 @@
.newItemCardHeading { .newItemCardHeading {
@apply transition-colors duration-200 ease-in-out; @apply transition-colors duration-200 ease-in-out;
} }
.listItem:hover .newItemCardHeading { .listItem:hover .newItemCardHeading {
@apply text-primary-600; @apply text-primary-600;
} }
@ -150,6 +182,7 @@
.listItemLink { .listItemLink {
@apply inline-flex items-center gap-1 text-xs text-gray-400 transition-colors duration-200 ease-in-out; @apply inline-flex items-center gap-1 text-xs text-gray-400 transition-colors duration-200 ease-in-out;
} }
.listItem:hover .listItemLink { .listItem:hover .listItemLink {
@apply text-primary-600; @apply text-primary-600;
} }
@ -162,6 +195,7 @@
.linkIcon.grayLinkIcon { .linkIcon.grayLinkIcon {
background-image: url("./apps/assets/link-gray.svg"); background-image: url("./apps/assets/link-gray.svg");
} }
.listItem:hover .grayLinkIcon { .listItem:hover .grayLinkIcon {
background-image: url("./apps/assets/link.svg"); background-image: url("./apps/assets/link.svg");
} }
@ -191,6 +225,7 @@
.newItemCaption { .newItemCaption {
@apply inline-flex items-center mb-2 text-sm font-medium; @apply inline-flex items-center mb-2 text-sm font-medium;
} }
/* #endregion new app dialog */ /* #endregion new app dialog */
.unavailable { .unavailable {
@ -199,4 +234,4 @@
.listItem:hover .unavailable { .listItem:hover .unavailable {
@apply opacity-100; @apply opacity-100;
} }

View File

@ -0,0 +1,10 @@
import React from 'react'
const Custom = () => {
return (
<div>
Custom
</div>
)
}
export default Custom

View File

@ -0,0 +1,23 @@
'use client'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import React, { useEffect } from 'react'
import Tools from '@/app/components/tools'
import { LOC } from '@/app/components/tools/types'
const Layout: FC = () => {
const { t } = useTranslation()
useEffect(() => {
document.title = `${t('tools.title')} - Dify`
}, [])
return (
<div className='overflow-hidden' style={{
height: 'calc(100vh - 56px)',
}}>
<Tools loc={LOC.tools} />
</div>
)
}
export default React.memo(Layout)

View File

@ -0,0 +1,10 @@
import React from 'react'
const ThirdPart = () => {
return (
<div>
Third part
</div>
)
}
export default ThirdPart

View File

@ -4,10 +4,10 @@ import React, { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { UserCircleIcon } from '@heroicons/react/24/solid' import { UserCircleIcon } from '@heroicons/react/24/solid'
import cn from 'classnames' import cn from 'classnames'
import type { CitationItem, DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, ThoughtItem } from '../type' import type { CitationItem, DisplayScene, FeedbackFunc, Feedbacktype, IChatItem } from '../type'
import OperationBtn from '../operation' import OperationBtn from '../operation'
import LoadingAnim from '../loading-anim' import LoadingAnim from '../loading-anim'
import { EditIconSolid, OpeningStatementIcon, RatingIcon } from '../icon-component' import { EditIconSolid, RatingIcon } from '../icon-component'
import s from '../style.module.css' import s from '../style.module.css'
import MoreInfo from '../more-info' import MoreInfo from '../more-info'
import CopyBtn from '../copy-btn' import CopyBtn from '../copy-btn'
@ -22,6 +22,9 @@ import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annota
import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal' import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal'
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item' import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
import type { Emoji } from '@/app/components/tools/types'
import type { VisionFile } from '@/types/app'
import ImageGallery from '@/app/components/base/image-gallery'
const Divider: FC<{ name: string }> = ({ name }) => { const Divider: FC<{ name: string }> = ({ name }) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -41,13 +44,12 @@ export type IAnswerProps = {
item: IChatItem item: IChatItem
feedbackDisabled: boolean feedbackDisabled: boolean
isHideFeedbackEdit: boolean isHideFeedbackEdit: boolean
onQueryChange: (query: string) => void
onFeedback?: FeedbackFunc onFeedback?: FeedbackFunc
displayScene: DisplayScene displayScene: DisplayScene
isResponsing?: boolean isResponsing?: boolean
answerIcon?: ReactNode answerIcon?: ReactNode
thoughts?: ThoughtItem[]
citation?: CitationItem[] citation?: CitationItem[]
isThinking?: boolean
dataSets?: DataSet[] dataSets?: DataSet[]
isShowCitation?: boolean isShowCitation?: boolean
isShowCitationHitInfo?: boolean isShowCitationHitInfo?: boolean
@ -58,20 +60,19 @@ export type IAnswerProps = {
onAnnotationEdited?: (question: string, answer: string) => void onAnnotationEdited?: (question: string, answer: string) => void
onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string) => void onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string) => void
onAnnotationRemoved?: () => void onAnnotationRemoved?: () => void
allToolIcons?: Record<string, string | Emoji>
} }
// The component needs to maintain its own state to control whether to display input component // The component needs to maintain its own state to control whether to display input component
const Answer: FC<IAnswerProps> = ({ const Answer: FC<IAnswerProps> = ({
item, item,
onQueryChange,
feedbackDisabled = false, feedbackDisabled = false,
isHideFeedbackEdit = false, isHideFeedbackEdit = false,
onFeedback, onFeedback,
displayScene = 'web', displayScene = 'web',
isResponsing, isResponsing,
answerIcon, answerIcon,
thoughts,
citation, citation,
isThinking,
dataSets,
isShowCitation, isShowCitation,
isShowCitationHitInfo = false, isShowCitationHitInfo = false,
supportAnnotation, supportAnnotation,
@ -80,8 +81,10 @@ const Answer: FC<IAnswerProps> = ({
onAnnotationEdited, onAnnotationEdited,
onAnnotationAdded, onAnnotationAdded,
onAnnotationRemoved, onAnnotationRemoved,
allToolIcons,
}) => { }) => {
const { id, content, more, feedback, adminFeedback, annotation } = item const { id, content, more, feedback, adminFeedback, annotation, agent_thoughts } = item
const isAgentMode = !!agent_thoughts && agent_thoughts.length > 0
const hasAnnotation = !!annotation?.id const hasAnnotation = !!annotation?.id
const [showEdit, setShowEdit] = useState(false) const [showEdit, setShowEdit] = useState(false)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@ -202,8 +205,40 @@ const Answer: FC<IAnswerProps> = ({
) )
} }
const getImgs = (list?: VisionFile[]) => {
if (!list)
return []
return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant')
}
const agentModeAnswer = (
<div>
{agent_thoughts?.map((item, index) => (
<div key={index}>
{item.thought && (
<Markdown content={item.thought} />
)}
{/* {item.tool} */}
{/* perhaps not use tool */}
{!!item.tool && (
<Thought
thought={item}
allToolIcons={allToolIcons || {}}
isFinished={!!item.observation}
/>
)}
{getImgs(item.message_files).length > 0 && (
<ImageGallery srcs={getImgs(item.message_files).map(item => item.url)} />
)}
</div>
))}
</div>
)
return ( return (
<div key={id}> // data-id for debug the item message is right
<div key={id} data-id={id}>
<div className='flex items-start'> <div className='flex items-start'>
{ {
answerIcon || ( answerIcon || (
@ -220,20 +255,7 @@ const Answer: FC<IAnswerProps> = ({
<div className={`${s.answerWrap} ${showEdit ? 'w-full' : ''}`}> <div className={`${s.answerWrap} ${showEdit ? 'w-full' : ''}`}>
<div className={`${s.answer} relative text-sm text-gray-900`}> <div className={`${s.answer} relative text-sm text-gray-900`}>
<div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}> <div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
{item.isOpeningStatement && ( {(isResponsing && (isAgentMode ? (!content && (agent_thoughts || []).length === 0) : !content))
<div className='flex items-center mb-1 gap-1'>
<OpeningStatementIcon />
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.title')}</div>
</div>
)}
{(thoughts && thoughts.length > 0) && (
<Thought
list={thoughts || []}
isThinking={isThinking}
dataSets={dataSets}
/>
)}
{(isResponsing && !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' />
@ -241,29 +263,54 @@ const Answer: FC<IAnswerProps> = ({
) )
: ( : (
<div> <div>
{annotation?.logAnnotation && ( {annotation?.logAnnotation && (
<div className='mb-1'> <div className='mb-1'>
<div className='mb-3'> <div className='mb-3'>
<Markdown className='line-through !text-gray-400' content={content} /> {isAgentMode
? (<div className='line-through !text-gray-400'>{agentModeAnswer}</div>)
: (
<Markdown className='line-through !text-gray-400' content={content} />
)}
</div> </div>
<EditTitle title={t('appAnnotation.editBy', { <EditTitle title={t('appAnnotation.editBy', {
author: annotation?.logAnnotation.account?.name, author: annotation?.logAnnotation.account?.name,
})} /> })} />
</div> </div>
)} )}
<div> <div>
<Markdown content={annotation?.logAnnotation ? annotation?.logAnnotation.content : content} /> {annotation?.logAnnotation
? (
<Markdown content={annotation?.logAnnotation.content || ''} />
)
: (isAgentMode
? agentModeAnswer
: (
<Markdown content={content} />
))}
</div> </div>
{(hasAnnotation && !annotation?.logAnnotation) && ( {(hasAnnotation && !annotation?.logAnnotation) && (
<EditTitle className='mt-1' title={t('appAnnotation.editBy', { <EditTitle className='mt-1' title={t('appAnnotation.editBy', {
author: annotation.authorName, author: annotation.authorName,
})} /> })} />
)} )}
{item.isOpeningStatement && item.suggestedQuestions && item.suggestedQuestions.length > 0 && (
<div className='flex flex-wrap'>
{item.suggestedQuestions.map((question, index) => (
<div
key={index}
className='mt-1 mr-1 max-w-full last:mr-0 shrink-0 py-[5px] leading-[18px] items-center px-4 rounded-lg border border-gray-200 shadow-xs bg-white text-xs font-medium text-primary-600 cursor-pointer'
onClick={() => onQueryChange(question)}
>
{question}
</div>),
)}
</div>
)}
</div> </div>
)} )}
{ {
!!citation?.length && !isThinking && isShowCitation && !isResponsing && ( !!citation?.length && isShowCitation && !isResponsing && (
<Citation data={citation} showHitInfo={isShowCitationHitInfo} /> <Citation data={citation} showHitInfo={isShowCitationHitInfo} />
) )
} }

View File

@ -25,6 +25,7 @@ import ImageList from '@/app/components/base/image-uploader/image-list'
import { TransferMethod, type VisionFile, type VisionSettings } from '@/types/app' import { TransferMethod, type VisionFile, type VisionSettings } from '@/types/app'
import { useClipboardUploader, useDraggableUploader, useImageFiles } from '@/app/components/base/image-uploader/hooks' import { useClipboardUploader, useDraggableUploader, useImageFiles } from '@/app/components/base/image-uploader/hooks'
import type { Annotation } from '@/models/log' import type { Annotation } from '@/models/log'
import type { Emoji } from '@/app/components/tools/types'
export type IChatProps = { export type IChatProps = {
appId?: string appId?: string
@ -43,6 +44,8 @@ export type IChatProps = {
isHideSendInput?: boolean isHideSendInput?: boolean
onFeedback?: FeedbackFunc onFeedback?: FeedbackFunc
checkCanSend?: () => boolean checkCanSend?: () => boolean
query?: string
onQueryChange?: (query: string) => void
onSend?: (message: string, files: VisionFile[]) => void onSend?: (message: string, files: VisionFile[]) => void
displayScene?: DisplayScene displayScene?: DisplayScene
useCurrentUserAvatar?: boolean useCurrentUserAvatar?: boolean
@ -62,12 +65,14 @@ export type IChatProps = {
isShowPromptLog?: boolean isShowPromptLog?: boolean
visionConfig?: VisionSettings visionConfig?: VisionSettings
supportAnnotation?: boolean supportAnnotation?: boolean
allToolIcons?: Record<string, string | Emoji>
} }
const Chat: FC<IChatProps> = ({ const Chat: FC<IChatProps> = ({
configElem, configElem,
chatList, chatList,
query = '',
onQueryChange = () => { },
feedbackDisabled = false, feedbackDisabled = false,
isHideFeedbackEdit = false, isHideFeedbackEdit = false,
isHideSendInput = false, isHideSendInput = false,
@ -94,6 +99,7 @@ const Chat: FC<IChatProps> = ({
appId, appId,
supportAnnotation, supportAnnotation,
onChatListChange, onChatListChange,
allToolIcons,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
@ -110,18 +116,18 @@ const Chat: FC<IChatProps> = ({
const { onDragEnter, onDragLeave, onDragOver, onDrop, isDragActive } = useDraggableUploader<HTMLTextAreaElement>({ onUpload, files, visionConfig }) const { onDragEnter, onDragLeave, onDragOver, onDrop, isDragActive } = useDraggableUploader<HTMLTextAreaElement>({ onUpload, files, visionConfig })
const isUseInputMethod = useRef(false) const isUseInputMethod = useRef(false)
const [query, setQuery] = React.useState('')
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value const value = e.target.value
setQuery(value) onQueryChange(value)
} }
const logError = (message: string) => { const logError = (message: string) => {
notify({ type: 'error', message, duration: 3000 }) notify({ type: 'error', message, duration: 3000 })
} }
const valid = () => { const valid = (q?: string) => {
if (!query || query.trim() === '') { const sendQuery = q || query
if (!sendQuery || sendQuery.trim() === '') {
logError('Message cannot be empty') logError('Message cannot be empty')
return false return false
} }
@ -130,13 +136,13 @@ const Chat: FC<IChatProps> = ({
useEffect(() => { useEffect(() => {
if (controlClearQuery) if (controlClearQuery)
setQuery('') onQueryChange('')
}, [controlClearQuery]) }, [controlClearQuery])
const handleSend = () => { const handleSend = (q?: string) => {
if (!valid() || (checkCanSend && !checkCanSend())) if (!valid(q) || (checkCanSend && !checkCanSend()))
return return
onSend(query, files.filter(file => file.progress !== -1).map(fileItem => ({ onSend(q || query, files.filter(file => file.progress !== -1).map(fileItem => ({
type: 'image', type: 'image',
transfer_method: fileItem.type, transfer_method: fileItem.type,
url: fileItem.url, url: fileItem.url,
@ -146,7 +152,7 @@ const Chat: FC<IChatProps> = ({
if (files.length) if (files.length)
onClear() onClear()
if (!isResponsing) if (!isResponsing)
setQuery('') onQueryChange('')
} }
} }
@ -162,14 +168,14 @@ const Chat: FC<IChatProps> = ({
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
isUseInputMethod.current = e.nativeEvent.isComposing isUseInputMethod.current = e.nativeEvent.isComposing
if (e.code === 'Enter' && !e.shiftKey) { if (e.code === 'Enter' && !e.shiftKey) {
setQuery(query.replace(/\n$/, '')) onQueryChange(query.replace(/\n$/, ''))
e.preventDefault() e.preventDefault()
} }
} }
const media = useBreakpoints() const media = useBreakpoints()
const isMobile = media === MediaType.mobile const isMobile = media === MediaType.mobile
const sendBtn = <div className={cn(!(!query || query.trim() === '') && s.sendBtnActive, `${s.sendBtn} w-8 h-8 cursor-pointer rounded-md`)} onClick={handleSend}></div> const sendBtn = <div className={cn(!(!query || query.trim() === '') && s.sendBtnActive, `${s.sendBtn} w-8 h-8 cursor-pointer rounded-md`)} onClick={() => handleSend()}></div>
const suggestionListRef = useRef<HTMLDivElement>(null) const suggestionListRef = useRef<HTMLDivElement>(null)
const [hasScrollbar, setHasScrollbar] = useState(false) const [hasScrollbar, setHasScrollbar] = useState(false)
@ -198,21 +204,21 @@ const Chat: FC<IChatProps> = ({
{chatList.map((item, index) => { {chatList.map((item, index) => {
if (item.isAnswer) { if (item.isAnswer) {
const isLast = item.id === chatList[chatList.length - 1].id const isLast = item.id === chatList[chatList.length - 1].id
const thoughts = item.agent_thoughts?.filter(item => item.thought !== '[DONE]')
const citation = item.citation const citation = item.citation
const isThinking = !item.content && item.agent_thoughts && item.agent_thoughts?.length > 0 && !item.agent_thoughts.some(item => item.thought === '[DONE]')
return <Answer return <Answer
key={item.id} key={item.id}
item={item} item={item}
onQueryChange={(val) => {
onQueryChange(val)
handleSend(val)
}}
feedbackDisabled={feedbackDisabled} feedbackDisabled={feedbackDisabled}
isHideFeedbackEdit={isHideFeedbackEdit} isHideFeedbackEdit={isHideFeedbackEdit}
onFeedback={onFeedback} onFeedback={onFeedback}
displayScene={displayScene ?? 'web'} displayScene={displayScene ?? 'web'}
isResponsing={isResponsing && isLast} isResponsing={isResponsing && isLast}
answerIcon={answerIcon} answerIcon={answerIcon}
thoughts={thoughts}
citation={citation} citation={citation}
isThinking={isThinking}
dataSets={dataSets} dataSets={dataSets}
isShowCitation={isShowCitation} isShowCitation={isShowCitation}
isShowCitationHitInfo={isShowCitationHitInfo} isShowCitationHitInfo={isShowCitationHitInfo}
@ -285,7 +291,7 @@ const Chat: FC<IChatProps> = ({
return item return item
})) }))
}} }}
allToolIcons={allToolIcons}
/> />
} }
return ( return (
@ -308,7 +314,7 @@ const Chat: FC<IChatProps> = ({
!isHideSendInput && ( !isHideSendInput && (
<div className={cn(!feedbackDisabled && '!left-3.5 !right-3.5', 'absolute z-10 bottom-0 left-0 right-0')}> <div className={cn(!feedbackDisabled && '!left-3.5 !right-3.5', 'absolute z-10 bottom-0 left-0 right-0')}>
{/* Thinking is sync and can not be stopped */} {/* Thinking is sync and can not be stopped */}
{(isResponsing && canStopResponsing && !!chatList[chatList.length - 1]?.content) && ( {(isResponsing && canStopResponsing && ((!!chatList[chatList.length - 1]?.content) || (chatList[chatList.length - 1]?.agent_thoughts && chatList[chatList.length - 1].agent_thoughts!.length > 0))) && (
<div className='flex justify-center mb-4'> <div className='flex justify-center mb-4'>
<Button className='flex items-center space-x-1 bg-white' onClick={() => abortResponsing?.()}> <Button className='flex items-center space-x-1 bg-white' onClick={() => abortResponsing?.()}>
{stopIcon} {stopIcon}
@ -339,7 +345,7 @@ const Chat: FC<IChatProps> = ({
<div key={item} className='shrink-0 flex justify-center mr-2'> <div key={item} className='shrink-0 flex justify-center mr-2'>
<Button <Button
key={index} key={index}
onClick={() => setQuery(item)} onClick={() => onQueryChange(item)}
> >
<span className='text-primary-600 text-xs font-medium'>{item}</span> <span className='text-primary-600 text-xs font-medium'>{item}</span>
</Button> </Button>
@ -393,7 +399,7 @@ const Chat: FC<IChatProps> = ({
{ {
query query
? ( ? (
<div className='flex justify-center items-center w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => setQuery('')}> <div className='flex justify-center items-center w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => onQueryChange('')}>
<XCircle className='w-4 h-4 text-[#98A2B3]' /> <XCircle className='w-4 h-4 text-[#98A2B3]' />
</div> </div>
) )
@ -429,7 +435,7 @@ const Chat: FC<IChatProps> = ({
voiceInputShow && ( voiceInputShow && (
<VoiceInput <VoiceInput
onCancel={() => setVoiceInputShow(false)} onCancel={() => setVoiceInputShow(false)}
onConverted={text => setQuery(text)} onConverted={text => onQueryChange(text)}
/> />
) )
} }

View File

@ -1,92 +1,60 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import cn from 'classnames' import type { ThoughtItem, ToolInfoInThought } from '../type'
import { useTranslation } from 'react-i18next' import Tool from '@/app/components/app/chat/thought/tool'
import type { ThoughtItem } from '../type' import type { Emoji } from '@/app/components/tools/types'
import s from './style.module.css'
import { DataSet as DataSetIcon, Loading as LodingIcon, Search, ThoughtList, WebReader } from '@/app/components/base/icons/src/public/thought'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import type { DataSet } from '@/models/datasets'
export type IThoughtProps = { export type IThoughtProps = {
list: ThoughtItem[] thought: ThoughtItem
isThinking?: boolean allToolIcons: Record<string, string | Emoji>
dataSets?: DataSet[] isFinished: boolean
} }
const getIcon = (toolId: string) => { function getValue(value: string, isValueArray: boolean, index: number) {
switch (toolId) { if (isValueArray) {
case 'dataset': try {
return <DataSetIcon /> return JSON.parse(value)[index]
case 'web_reader': }
return <WebReader /> catch (e) {
default: }
return <Search />
} }
return value
} }
const Thought: FC<IThoughtProps> = ({ const Thought: FC<IThoughtProps> = ({
list, thought,
isThinking, allToolIcons,
dataSets, isFinished,
}) => { }) => {
const { t } = useTranslation() const [toolNames, isValueArray]: [string[], boolean] = (() => {
const [isShowDetail, setIsShowDetail] = React.useState(false)
const getThoughtText = (item: ThoughtItem) => {
try { try {
const input = JSON.parse(item.tool_input) if (Array.isArray(JSON.parse(thought.tool)))
// dataset return [JSON.parse(thought.tool), true]
if (item.tool.startsWith('dataset-')) {
const dataSetId = item.tool.replace('dataset-', '')
const datasetName = dataSets?.find(item => item.id === dataSetId)?.name || 'unknown dataset'
return t('explore.universalChat.thought.res.dataset').replace('{datasetName}', `<span class="text-gray-700">${datasetName}</span>`)
}
switch (item.tool) {
case 'web_reader':
return t(`explore.universalChat.thought.res.webReader.${!input.cursor ? 'normal' : 'hasPageInfo'}`).replace('{url}', `<a href="${input.url}" class="text-[#155EEF]">${input.url}</a>`)
case 'google_search':
return t('explore.universalChat.thought.res.google', { query: input.query })
case 'wikipedia':
return t('explore.universalChat.thought.res.wikipedia', { query: input.query })
case 'current_datetime':
return t('explore.universalChat.thought.res.date')
default:
return `Unknown tool: ${item.tool}`
}
} }
catch (error) { catch (e) {
console.error(error)
return item
} }
} return [[thought.tool], false]
const renderItem = (item: ThoughtItem) => ( })()
<div className='flex space-x-1 py-[3px] leading-[18px]' key={item.id}>
<div className='flex items-center h-[18px] shrink-0'>{getIcon(item.tool)}</div> const toolThoughtList = toolNames.map((toolName, index) => {
<div dangerouslySetInnerHTML={{ return {
__html: getThoughtText(item), name: toolName,
// item.thought.replace(urlRegex, (url) => { input: getValue(thought.tool_input, isValueArray, index),
// return `<a href="${url}" class="text-[#155EEF]">${url}</a>` output: getValue(thought.observation, isValueArray, index),
// }), isFinished,
}}></div> }
</div> })
)
return ( return (
<div className={cn(s.wrap, !isShowDetail && s.wrapHoverEffect, 'inline-block mb-2 px-2 py-0.5 rounded-md text-xs text-gray-500 font-medium')} > <div className='my-2 space-y-2'>
<div className='flex items-center h-6 space-x-1 cursor-pointer' onClick={() => setIsShowDetail(!isShowDetail)} > {toolThoughtList.map((item: ToolInfoInThought, index) => (
{!isThinking ? <ThoughtList /> : <div className='animate-spin'><LodingIcon /></div>} <Tool
<div dangerouslySetInnerHTML= {{ key={index}
__html: isThinking ? getThoughtText(list[list.length - 1]) : (t(`explore.universalChat.thought.${isShowDetail ? 'hide' : 'show'}`) + t('explore.universalChat.thought.processOfThought')), payload={item}
}} allToolIcons={allToolIcons}
></div> />
<ChevronDown className={isShowDetail ? 'rotate-180' : '' } /> ))}
</div>
{isShowDetail && (
<div>
{list.map(item => renderItem(item))}
</div>
)}
</div> </div>
) )
} }

View File

@ -0,0 +1,28 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
type Props = {
isRequest: boolean
toolName: string
content: string
}
const Panel: FC<Props> = ({
isRequest,
toolName,
content,
}) => {
const { t } = useTranslation()
return (
<div className='rounded-md bg-gray-100 overflow-hidden border border-black/5'>
<div className='flex items-center px-2 py-1 leading-[18px] bg-gray-50 uppercase text-xs font-medium text-gray-500'>
{t(`tools.thought.${isRequest ? 'requestTitle' : 'responseTitle'}`)} {toolName}
</div>
<div className='p-2 border-t border-black/5 leading-4 text-xs text-gray-700'>{content}</div>
</div>
)
}
export default React.memo(Panel)

View File

@ -0,0 +1,103 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import type { ToolInfoInThought } from '../type'
import Panel from './panel'
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { DataSet as DataSetIcon } from '@/app/components/base/icons/src/public/thought'
import type { Emoji } from '@/app/components/tools/types'
import AppIcon from '@/app/components/base/app-icon'
type Props = {
payload: ToolInfoInThought
allToolIcons?: Record<string, string | Emoji>
}
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
if (toolName.startsWith('dataset-'))
return <DataSetIcon className='shrink-0'></DataSetIcon>
const icon = allToolIcons[toolName]
if (!icon)
return null
return (
typeof icon === 'string'
? (
<div
className='w-3 h-3 bg-cover bg-center rounded-[3px] shrink-0'
style={{
backgroundImage: `url(${icon})`,
}}
></div>
)
: (
<AppIcon
className='rounded-[3px] shrink-0'
size='xs'
icon={icon?.content}
background={icon?.background}
/>
))
}
const Tool: FC<Props> = ({
payload,
allToolIcons = {},
}) => {
const { t } = useTranslation()
const { name, input, isFinished, output } = payload
const toolName = name.startsWith('dataset-') ? t('dataset.knowledge') : name
const [isShowDetail, setIsShowDetail] = useState(false)
const icon = getIcon(toolName, allToolIcons) as any
return (
<div>
<div className={cn(!isShowDetail && 'shadow-sm', !isShowDetail && 'inline-block', 'max-w-full overflow-x-auto bg-white rounded-md')}>
<div
className={cn('flex items-center h-7 px-2 cursor-pointer')}
onClick={() => setIsShowDetail(!isShowDetail)}
>
{!isFinished && (
<Loading02 className='w-3 h-3 text-gray-500 animate-spin shrink-0' />
)}
{isFinished && !isShowDetail && (
<CheckCircle className='w-3 h-3 text-[#12B76A] shrink-0' />
)}
{isFinished && isShowDetail && (
icon
)}
<span className='mx-1 text-xs font-medium text-gray-500 shrink-0'>
{t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
</span>
<span
className='text-xs font-medium text-gray-700 truncate'
title={toolName}
>
{toolName}
</span>
<ChevronDown
className={cn(isShowDetail && 'rotate-180', 'ml-1 w-3 h-3 text-gray-500 select-none cursor-pointer shrink-0')}
/>
</div>
{isShowDetail && (
<div className='border-t border-black/5 p-2 space-y-2 '>
<Panel
isRequest={true}
toolName={toolName}
content={input} />
{output && (
<Panel
isRequest={false}
toolName={toolName}
content={output as string} />
)}
</div>
)}
</div>
</div>
)
}
export default React.memo(Tool)

View File

@ -1,5 +1,6 @@
import type { Annotation, MessageRating } from '@/models/log' import type { Annotation, MessageRating } from '@/models/log'
import type { VisionFile } from '@/types/app' import type { VisionFile } from '@/types/app'
export type MessageMore = { export type MessageMore = {
time: string time: string
tokens: number tokens: number
@ -16,13 +17,25 @@ export type SubmitAnnotationFunc = (messageId: string, content: string) => Promi
export type DisplayScene = 'web' | 'console' export type DisplayScene = 'web' | 'console'
export type ToolInfoInThought = {
name: string
input: string
output: string
isFinished: boolean
}
export type ThoughtItem = { export type ThoughtItem = {
id: string id: string
tool: string // plugin or dataset tool: string // plugin or dataset. May has multi.
thought: string thought: string
tool_input: string tool_input: string
message_id: string message_id: string
observation: string
position: number
files?: string[]
message_files?: VisionFile[]
} }
export type CitationItem = { export type CitationItem = {
content: string content: string
data_source_type: string data_source_type: string
@ -41,7 +54,6 @@ export type CitationItem = {
export type IChatItem = { export type IChatItem = {
id: string id: string
content: string content: string
agent_thoughts?: ThoughtItem[]
citation?: CitationItem[] citation?: CitationItem[]
/** /**
* Specific message type * Specific message type
@ -66,7 +78,9 @@ export type IChatItem = {
annotation?: Annotation annotation?: Annotation
useCurrentUserAvatar?: boolean useCurrentUserAvatar?: boolean
isOpeningStatement?: boolean isOpeningStatement?: boolean
suggestedQuestions?: string[]
log?: { role: string; text: string }[] log?: { role: string; text: string }[]
agent_thoughts?: ThoughtItem[]
message_files?: VisionFile[] message_files?: VisionFile[]
} }

View File

@ -3,11 +3,13 @@ import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { PlusIcon } from '@heroicons/react/20/solid' import { PlusIcon } from '@heroicons/react/20/solid'
import cn from 'classnames'
export type IOperationBtnProps = { export type IOperationBtnProps = {
className?: string
type: 'add' | 'edit' type: 'add' | 'edit'
actionName?: string actionName?: string
onClick: () => void onClick?: () => void
} }
const iconMap = { const iconMap = {
@ -19,14 +21,15 @@ const iconMap = {
} }
const OperationBtn: FC<IOperationBtnProps> = ({ const OperationBtn: FC<IOperationBtnProps> = ({
className,
type, type,
actionName, actionName,
onClick, onClick = () => { },
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<div <div
className='flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200' className={cn(className, 'flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200 select-none')}
onClick={onClick}> onClick={onClick}>
<div> <div>
{iconMap[type]} {iconMap[type]}

View File

@ -22,6 +22,9 @@ import { AlertCircle } from '@/app/components/base/icons/src/vender/solid/alerts
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import type { ExternalDataTool } from '@/models/common' import type { ExternalDataTool } from '@/models/common'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
type Props = { type Props = {
type: PromptRole type: PromptRole
@ -49,6 +52,7 @@ const AdvancedPromptInput: FC<Props> = ({
onHideContextMissingTip, onHideContextMissingTip,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { eventEmitter } = useEventEmitterContextContext()
const { const {
mode, mode,
@ -60,7 +64,6 @@ const AdvancedPromptInput: FC<Props> = ({
dataSets, dataSets,
showSelectDataSet, showSelectDataSet,
externalDataToolsConfig, externalDataToolsConfig,
setExternalDataToolsConfig,
} = useContext(ConfigContext) } = useContext(ConfigContext)
const { notify } = useToastContext() const { notify } = useToastContext()
const { setShowExternalDataToolModal } = useModalContext() const { setShowExternalDataToolModal } = useModalContext()
@ -68,7 +71,14 @@ const AdvancedPromptInput: FC<Props> = ({
setShowExternalDataToolModal({ setShowExternalDataToolModal({
payload: {}, payload: {},
onSaveCallback: (newExternalDataTool: ExternalDataTool) => { onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
setExternalDataToolsConfig([...externalDataToolsConfig, newExternalDataTool]) eventEmitter?.emit({
type: ADD_EXTERNAL_DATA_TOOL,
payload: newExternalDataTool,
} as any)
eventEmitter?.emit({
type: INSERT_VARIABLE_VALUE_BLOCK_COMMAND,
payload: newExternalDataTool.variable,
} as any)
}, },
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => { onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
for (let i = 0; i < promptVariables.length; i++) { for (let i = 0; i < promptVariables.length; i++) {
@ -78,13 +88,6 @@ const AdvancedPromptInput: FC<Props> = ({
} }
} }
for (let i = 0; i < externalDataToolsConfig.length; i++) {
if (externalDataToolsConfig[i].variable === newExternalDataTool.variable) {
notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: externalDataToolsConfig[i].variable }) })
return false
}
}
return true return true
}, },
}) })
@ -108,7 +111,7 @@ const AdvancedPromptInput: FC<Props> = ({
} }
const handleBlur = () => { const handleBlur = () => {
const keys = getVars(value) const keys = getVars(value)
const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key)) const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key, ''))
if (newPromptVariables.length > 0) { if (newPromptVariables.length > 0) {
setNewPromptVariables(newPromptVariables) setNewPromptVariables(newPromptVariables)
showConfirmAddVar() showConfirmAddVar()
@ -202,13 +205,13 @@ const AdvancedPromptInput: FC<Props> = ({
onAddContext: showSelectDataSet, onAddContext: showSelectDataSet,
}} }}
variableBlock={{ variableBlock={{
variables: modelConfig.configs.prompt_variables.map(item => ({ variables: modelConfig.configs.prompt_variables.filter(item => item.type !== 'api').map(item => ({
name: item.name, name: item.name,
value: item.key, value: item.key,
})), })),
externalTools: externalDataToolsConfig.map(item => ({ externalTools: modelConfig.configs.prompt_variables.filter(item => item.type === 'api').map(item => ({
name: item.label!, name: item.name,
variableName: item.variable!, variableName: item.key,
icon: item.icon, icon: item.icon,
icon_background: item.icon_background, icon_background: item.icon_background,
})), })),

View File

@ -8,7 +8,7 @@ import produce from 'immer'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import ConfirmAddVar from './confirm-add-var' import ConfirmAddVar from './confirm-add-var'
import s from './style.module.css' import s from './style.module.css'
import type { PromptVariable } from '@/models/debug' import { PromptMode, type PromptVariable } from '@/models/debug'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import { AppType } from '@/types/app' import { AppType } from '@/types/app'
import { getNewVar, getVars } from '@/utils/var' import { getNewVar, getVars } from '@/utils/var'
@ -21,6 +21,10 @@ import ConfigContext from '@/context/debug-configuration'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import type { ExternalDataTool } from '@/models/common' import type { ExternalDataTool } from '@/models/common'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import { ArrowNarrowRight } from '@/app/components/base/icons/src/vender/line/arrows'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
export type ISimplePromptInput = { export type ISimplePromptInput = {
mode: AppType mode: AppType
@ -38,6 +42,7 @@ const Prompt: FC<ISimplePromptInput> = ({
onChange, onChange,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { eventEmitter } = useEventEmitterContextContext()
const { const {
modelConfig, modelConfig,
dataSets, dataSets,
@ -47,7 +52,9 @@ const Prompt: FC<ISimplePromptInput> = ({
hasSetBlockStatus, hasSetBlockStatus,
showSelectDataSet, showSelectDataSet,
externalDataToolsConfig, externalDataToolsConfig,
setExternalDataToolsConfig, isAdvancedMode,
isAgent,
setPromptMode,
} = useContext(ConfigContext) } = useContext(ConfigContext)
const { notify } = useToastContext() const { notify } = useToastContext()
const { setShowExternalDataToolModal } = useModalContext() const { setShowExternalDataToolModal } = useModalContext()
@ -55,7 +62,14 @@ const Prompt: FC<ISimplePromptInput> = ({
setShowExternalDataToolModal({ setShowExternalDataToolModal({
payload: {}, payload: {},
onSaveCallback: (newExternalDataTool: ExternalDataTool) => { onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
setExternalDataToolsConfig([...externalDataToolsConfig, newExternalDataTool]) eventEmitter?.emit({
type: ADD_EXTERNAL_DATA_TOOL,
payload: newExternalDataTool,
} as any)
eventEmitter?.emit({
type: INSERT_VARIABLE_VALUE_BLOCK_COMMAND,
payload: newExternalDataTool.variable,
} as any)
}, },
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => { onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
for (let i = 0; i < promptVariables.length; i++) { for (let i = 0; i < promptVariables.length; i++) {
@ -65,13 +79,6 @@ const Prompt: FC<ISimplePromptInput> = ({
} }
} }
for (let i = 0; i < externalDataToolsConfig.length; i++) {
if (externalDataToolsConfig[i].variable === newExternalDataTool.variable) {
notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: externalDataToolsConfig[i].variable }) })
return false
}
}
return true return true
}, },
}) })
@ -89,7 +96,7 @@ const Prompt: FC<ISimplePromptInput> = ({
const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false) const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false)
const handleChange = (newTemplates: string, keys: string[]) => { const handleChange = (newTemplates: string, keys: string[]) => {
const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key)) const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key, ''))
if (newPromptVariables.length > 0) { if (newPromptVariables.length > 0) {
setNewPromptVariables(newPromptVariables) setNewPromptVariables(newPromptVariables)
setNewTemplates(newTemplates) setNewTemplates(newTemplates)
@ -138,7 +145,21 @@ const Prompt: FC<ISimplePromptInput> = ({
</Tooltip> </Tooltip>
)} )}
</div> </div>
<AutomaticBtn onClick={showAutomaticTrue}/> <div className='flex items-center'>
<AutomaticBtn onClick={showAutomaticTrue} />
{!isAgent && !isAdvancedMode && (
<>
<div className='mx-1 w-px h-3.5 bg-black/5'></div>
<div
className='flex items-center px-2 space-x-1 leading-[18px] text-xs font-semibold text-[#444CE7] cursor-pointer'
onClick={() => setPromptMode(PromptMode.advanced)}
>
<div>{t('appDebug.promptMode.advanced')}</div>
<ArrowNarrowRight className='w-3 h-3'></ArrowNarrowRight>
</div>
</>
)}
</div>
</div> </div>
<div className='px-4 py-2 min-h-[228px] max-h-[156px] overflow-y-auto bg-white rounded-xl text-sm text-gray-700'> <div className='px-4 py-2 min-h-[228px] max-h-[156px] overflow-y-auto bg-white rounded-xl text-sm text-gray-700'>
<PromptEditor <PromptEditor
@ -155,13 +176,13 @@ const Prompt: FC<ISimplePromptInput> = ({
onAddContext: showSelectDataSet, onAddContext: showSelectDataSet,
}} }}
variableBlock={{ variableBlock={{
variables: modelConfig.configs.prompt_variables.map(item => ({ variables: modelConfig.configs.prompt_variables.filter(item => item.type !== 'api').map(item => ({
name: item.name, name: item.name,
value: item.key, value: item.key,
})), })),
externalTools: externalDataToolsConfig.map(item => ({ externalTools: modelConfig.configs.prompt_variables.filter(item => item.type === 'api').map(item => ({
name: item.label!, name: item.name,
variableName: item.variable!, variableName: item.key,
icon: item.icon, icon: item.icon,
icon_background: item.icon_background, icon_background: item.icon_background,
})), })),
@ -174,7 +195,7 @@ const Prompt: FC<ISimplePromptInput> = ({
user: '', user: '',
assistant: '', assistant: '',
}, },
onEditRole: () => {}, onEditRole: () => { },
}} }}
queryBlock={{ queryBlock={{
show: false, show: false,

View File

@ -13,8 +13,6 @@
} }
.inputWrap:hover { .inputWrap:hover {
border-color: #FEE4E2;
background-color: #FFFBFA;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
} }

View File

@ -2,16 +2,15 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Cog8ToothIcon, TrashIcon } from '@heroicons/react/24/outline'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import type { Timeout } from 'ahooks/lib/useRequest/src/types' import type { Timeout } from 'ahooks/lib/useRequest/src/types'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import Panel from '../base/feature-panel' import Panel from '../base/feature-panel'
import OperationBtn from '../base/operation-btn'
import EditModal from './config-modal' import EditModal from './config-modal'
import IconTypeIcon from './input-type-icon' import IconTypeIcon from './input-type-icon'
import type { IInputTypeIconProps } from './input-type-icon' import type { IInputTypeIconProps } from './input-type-icon'
import s from './style.module.css' import s from './style.module.css'
import SelectVarType from './select-var-type'
import { BracketsX as VarIcon } from '@/app/components/base/icons/src/vender/line/development' import { BracketsX as VarIcon } from '@/app/components/base/icons/src/vender/line/development'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import type { PromptVariable } from '@/models/debug' import type { PromptVariable } from '@/models/debug'
@ -19,10 +18,25 @@ import { DEFAULT_VALUE_MAX_LEN, getMaxVarNameLength } from '@/config'
import { checkKeys, getNewVar } from '@/utils/var' import { checkKeys, getNewVar } from '@/utils/var'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general' import { HelpCircle, Settings01, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
import ConfirmModal from '@/app/components/base/confirm/common' import ConfirmModal from '@/app/components/base/confirm/common'
import ConfigContext from '@/context/debug-configuration' import ConfigContext from '@/context/debug-configuration'
import { AppType } from '@/types/app' import { AppType } from '@/types/app'
import type { ExternalDataTool } from '@/models/common'
import { useModalContext } from '@/context/modal-context'
import { useEventEmitterContextContext } from '@/context/event-emitter'
export const ADD_EXTERNAL_DATA_TOOL = 'ADD_EXTERNAL_DATA_TOOL'
type ExternalDataToolParams = {
key: string
type: string
index: number
name: string
config?: Record<string, any>
icon?: string
icon_background?: string
}
export type IConfigVarProps = { export type IConfigVarProps = {
promptVariables: PromptVariable[] promptVariables: PromptVariable[]
@ -37,17 +51,11 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
const { const {
mode, mode,
dataSets, dataSets,
externalDataToolsConfig,
} = useContext(ConfigContext) } = useContext(ConfigContext)
const { eventEmitter } = useEventEmitterContextContext()
const hasVar = promptVariables.length > 0 const hasVar = promptVariables.length > 0
const promptVariableObj = (() => {
const obj: Record<string, boolean> = {}
promptVariables.forEach((item) => {
obj[item.key] = true
})
return obj
})()
const updatePromptVariable = (key: string, updateKey: string, newValue: string | boolean) => { const updatePromptVariable = (key: string, updateKey: string, newValue: string | boolean) => {
const newPromptVariables = promptVariables.map((item) => { const newPromptVariables = promptVariables.map((item) => {
if (item.key === key) { if (item.key === key) {
@ -131,11 +139,89 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
onPromptVariablesChange?.(newPromptVariables) onPromptVariablesChange?.(newPromptVariables)
} }
const handleAddVar = () => { const { setShowExternalDataToolModal } = useModalContext()
const newVar = getNewVar('')
onPromptVariablesChange?.([...promptVariables, newVar]) const handleOpenExternalDataToolModal = (
{ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams,
oldPromptVariables: PromptVariable[],
) => {
setShowExternalDataToolModal({
payload: {
variable: key,
label: name,
config,
icon,
icon_background,
},
onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
const newPromptVariables = oldPromptVariables.map((item, i) => {
if (i === index) {
return {
key: newExternalDataTool.variable as string,
name: newExternalDataTool.label as string,
enabled: newExternalDataTool.enabled,
type: newExternalDataTool.type as string,
config: newExternalDataTool.config,
required: item.required,
icon: newExternalDataTool.icon,
icon_background: newExternalDataTool.icon_background,
}
}
return item
})
onPromptVariablesChange?.(newPromptVariables)
},
onCancelCallback: () => {
if (!key)
onPromptVariablesChange?.(promptVariables.filter((_, i) => i !== index))
},
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
for (let i = 0; i < promptVariables.length; i++) {
if (promptVariables[i].key === newExternalDataTool.variable && i !== index) {
Toast.notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: promptVariables[i].key }) })
return false
}
}
return true
},
})
} }
const handleAddVar = (type: string) => {
const newVar = getNewVar('', type)
const newPromptVariables = [...promptVariables, newVar]
onPromptVariablesChange?.(newPromptVariables)
if (type === 'api') {
handleOpenExternalDataToolModal({
type,
key: newVar.key,
name: newVar.name,
index: promptVariables.length,
}, newPromptVariables)
}
}
eventEmitter?.useSubscription((v: any) => {
if (v.type === ADD_EXTERNAL_DATA_TOOL) {
const payload = v.payload
onPromptVariablesChange?.([
...promptVariables,
{
key: payload.variable as string,
name: payload.label as string,
enabled: payload.enabled,
type: payload.type as string,
config: payload.config,
required: true,
icon: payload.icon,
icon_background: payload.icon_background,
},
])
}
})
const [isShowDeleteContextVarModal, { setTrue: showDeleteContextVarModal, setFalse: hideDeleteContextVarModal }] = useBoolean(false) const [isShowDeleteContextVarModal, { setTrue: showDeleteContextVarModal, setFalse: hideDeleteContextVarModal }] = useBoolean(false)
const [removeIndex, setRemoveIndex] = useState<number | null>(null) const [removeIndex, setRemoveIndex] = useState<number | null>(null)
const didRemoveVar = (index: number) => { const didRemoveVar = (index: number) => {
@ -156,16 +242,21 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
const [currKey, setCurrKey] = useState<string | null>(null) const [currKey, setCurrKey] = useState<string | null>(null)
const currItem = currKey ? promptVariables.find(item => item.key === currKey) : null const currItem = currKey ? promptVariables.find(item => item.key === currKey) : null
const [isShowEditModal, { setTrue: showEditModal, setFalse: hideEditModal }] = useBoolean(false) const [isShowEditModal, { setTrue: showEditModal, setFalse: hideEditModal }] = useBoolean(false)
const handleConfig = (key: string) => {
const handleConfig = ({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => {
setCurrKey(key) setCurrKey(key)
if (type === 'api') {
handleOpenExternalDataToolModal({ key, type, index, name, config, icon, icon_background }, promptVariables)
return
}
showEditModal() showEditModal()
} }
return ( return (
<Panel <Panel
className="mt-4" className="mt-4"
headerIcon={ headerIcon={
<VarIcon className='w-4 h-4 text-primary-500'/> <VarIcon className='w-4 h-4 text-primary-500' />
} }
title={ title={
<div className='flex items-center'> <div className='flex items-center'>
@ -179,7 +270,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
)} )}
</div> </div>
} }
headerRight={!readonly ? <OperationBtn type="add" onClick={handleAddVar} /> : null} headerRight={!readonly ? <SelectVarType onChange={handleAddVar} /> : null}
> >
{!hasVar && ( {!hasVar && (
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.notSetVar')}</div> <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.notSetVar')}</div>
@ -201,7 +292,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
</tr> </tr>
</thead> </thead>
<tbody className="text-gray-700"> <tbody className="text-gray-700">
{promptVariables.map(({ key, name, type, required }, index) => ( {promptVariables.map(({ key, name, type, required, config, icon, icon_background }, index) => (
<tr key={index} className="h-9 leading-9"> <tr key={index} className="h-9 leading-9">
<td className="w-[160px] border-b border-gray-100 pl-3"> <td className="w-[160px] border-b border-gray-100 pl-3">
<div className='flex items-center space-x-1'> <div className='flex items-center space-x-1'>
@ -247,11 +338,11 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
</td> </td>
<td className='w-20 border-b border-gray-100'> <td className='w-20 border-b border-gray-100'>
<div className='flex h-full items-center space-x-1'> <div className='flex h-full items-center space-x-1'>
<div className='flex items-center justify-items-center w-6 h-6 text-gray-500 cursor-pointer' onClick={() => handleConfig(key)}> <div className=' p-1 rounded-md hover:bg-black/5 w-6 h-6 cursor-pointer' onClick={() => handleConfig({ type, key, index, name, config, icon, icon_background })}>
<Cog8ToothIcon width={16} height={16} /> <Settings01 className='w-4 h-4 text-gray-500' />
</div> </div>
<div className='flex items-center justify-items-center w-6 h-6 text-gray-500 cursor-pointer' onClick={() => handleRemoveVar(index)} > <div className=' p-1 rounded-md hover:bg-black/5 w-6 h-6 cursor-pointer' onClick={() => handleRemoveVar(index)} >
<TrashIcon width={16} height={16} /> <Trash03 className='w-4 h-4 text-gray-500' />
</div> </div>
</div> </div>
</td> </td>

View File

@ -3,6 +3,7 @@ import React from 'react'
import type { FC } from 'react' import type { FC } from 'react'
import { Paragraph, TypeSquare } from '@/app/components/base/icons/src/vender/solid/editor' import { Paragraph, TypeSquare } from '@/app/components/base/icons/src/vender/solid/editor'
import { CheckDone01 } from '@/app/components/base/icons/src/vender/solid/general' import { CheckDone01 } from '@/app/components/base/icons/src/vender/solid/general'
import { ApiConnection } from '@/app/components/base/icons/src/vender/solid/development'
export type IInputTypeIconProps = { export type IInputTypeIconProps = {
type: 'string' | 'select' type: 'string' | 'select'
@ -21,6 +22,9 @@ const IconMap = (type: IInputTypeIconProps['type'], className: string) => {
select: ( select: (
<CheckDone01 className={classNames} /> <CheckDone01 className={classNames} />
), ),
api: (
<ApiConnection className={classNames} />
),
} }
return icons[type] return icons[type]

View File

@ -0,0 +1,76 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { Paragraph, TypeSquare } from '@/app/components/base/icons/src/vender/solid/editor'
import { CheckDone01 } from '@/app/components/base/icons/src/vender/solid/general'
import { ApiConnection } from '@/app/components/base/icons/src/vender/solid/development'
type Props = {
onChange: (value: string) => void
}
type ItemProps = {
text: string
value: string
Icon: any
onClick: (value: string) => void
}
const SelectItem: FC<ItemProps> = ({ text, value, Icon, onClick }) => {
return (
<div
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
onClick={() => onClick(value)}
>
<Icon className='w-4 h-4 text-gray-500' />
<div className='ml-2 text-xs text-gray-600 truncate'>{text}</div>
</div>
)
}
const SelectVarType: FC<Props> = ({
onChange,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const handleChange = (value: string) => {
onChange(value)
setOpen(false)
}
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={{
mainAxis: 8,
crossAxis: -2,
}}
>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<OperationBtn type='add' className={cn(open && 'bg-gray-200')} />
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{ zIndex: 1000 }}>
<div className='bg-white border border-gray-200 shadow-lg rounded-lg min-w-[192px]'>
<div className='p-1'>
<SelectItem Icon={TypeSquare} value='string' text={t('appDebug.variableConig.string')} onClick={handleChange}></SelectItem>
<SelectItem Icon={Paragraph} value='paragraph' text={t('appDebug.variableConig.paragraph')} onClick={handleChange}></SelectItem>
<SelectItem Icon={CheckDone01} value='select' text={t('appDebug.variableConig.select')} onClick={handleChange}></SelectItem>
</div>
<div className='h-[1px] bg-gray-100'></div>
<div className='p-1'>
<SelectItem Icon={ApiConnection} value='api' text={t('appDebug.variableConig.apiBasedVar')} onClick={handleChange}></SelectItem>
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default React.memo(SelectVarType)

View File

@ -0,0 +1,156 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ItemPanel from './item-panel'
import Button from '@/app/components/base/button'
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
import { CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
import { Unblur } from '@/app/components/base/icons/src/vender/solid/education'
import Slider from '@/app/components/base/slider'
import type { AgentConfig } from '@/models/debug'
import { DEFAULT_AGENT_PROMPT } from '@/config'
type Props = {
isChatModel: boolean
payload: AgentConfig
isFunctionCall: boolean
onCancel: () => void
onSave: (payload: any) => void
}
const maxIterationsMin = 1
const maxIterationsMax = 5
const AgentSetting: FC<Props> = ({
isChatModel,
payload,
isFunctionCall,
onCancel,
onSave,
}) => {
const { t } = useTranslation()
const [tempPayload, setTempPayload] = useState(payload)
const handleSave = () => {
onSave(tempPayload)
}
return (
<div className='fixed z-[100] inset-0 overflow-hidden flex justify-end p-2'
style={{
backgroundColor: 'rgba(16, 24, 40, 0.20)',
}}
>
<div
className='w-[640px] flex flex-col h-full overflow-hidden bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl'
>
<div className='shrink-0 flex justify-between items-center pl-6 pr-5 h-14 border-b border-b-gray-100'>
<div className='flex flex-col text-base font-semibold text-gray-900'>
<div className='leading-6'>{t('appDebug.agent.setting.name')}</div>
</div>
<div className='flex items-center'>
<div
onClick={onCancel}
className='flex justify-center items-center w-6 h-6 cursor-pointer'
>
<XClose className='w-4 h-4 text-gray-500' />
</div>
</div>
</div>
{/* Body */}
<div className='grow p-6 pt-5 border-b overflow-y-auto pb-[68px]' style={{
borderBottom: 'rgba(0, 0, 0, 0.05)',
}}>
{/* Agent Mode */}
<ItemPanel
className='mb-4'
icon={
<CuteRobote className='w-4 h-4 text-indigo-600' />
}
name={t('appDebug.agent.agentMode')}
description={t('appDebug.agent.agentModeDes')}
>
<div className='leading-[18px] text-[13px] font-medium text-gray-900'>{isFunctionCall ? t('appDebug.agent.agentModeType.functionCall') : t('appDebug.agent.agentModeType.ReACT')}</div>
</ItemPanel>
<ItemPanel
className='mb-4'
icon={
<Unblur className='w-4 h-4 text-[#FB6514]' />
}
name={t('appDebug.agent.setting.maximumIterations.name')}
description={t('appDebug.agent.setting.maximumIterations.description')}
>
<div className='flex items-center'>
<Slider
className='mr-3 w-[156px]'
min={maxIterationsMin}
max={maxIterationsMax}
value={tempPayload.max_iteration}
onChange={(value) => {
setTempPayload({
...tempPayload,
max_iteration: value,
})
}}
/>
<input
type="number"
min={maxIterationsMin}
max={maxIterationsMax} step={1}
className="block w-11 h-7 leading-7 rounded-lg border-0 pl-1 px-1.5 bg-gray-100 text-gray-900 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-primary-600"
value={tempPayload.max_iteration}
onChange={(e) => {
let value = parseInt(e.target.value, 10)
if (value < maxIterationsMin)
value = maxIterationsMin
if (value > maxIterationsMax)
value = maxIterationsMax
setTempPayload({
...tempPayload,
max_iteration: value,
})
}} />
</div>
</ItemPanel>
{!isFunctionCall && (
<div className='py-2 bg-gray-50 rounded-xl shadow-xs'>
<div className='flex items-center h-8 px-4 leading-6 text-sm font-semibold text-gray-700'>{t('tools.builtInPromptTitle')}</div>
<div className='h-[396px] px-4 overflow-y-auto leading-5 text-sm font-normal text-gray-700 whitespace-pre-line'>
{isChatModel ? DEFAULT_AGENT_PROMPT.chat : DEFAULT_AGENT_PROMPT.completion}
</div>
<div className='px-4'>
<div className='inline-flex items-center h-5 px-1 rounded-md bg-gray-100 leading-[18px] text-xs font-medium text-gray-500'>{(isChatModel ? DEFAULT_AGENT_PROMPT.chat : DEFAULT_AGENT_PROMPT.completion).length}</div>
</div>
</div>
)}
</div>
<div
className='sticky z-[5] bottom-0 w-full flex justify-end py-4 px-6 border-t bg-white '
style={{
borderColor: 'rgba(0, 0, 0, 0.05)',
}}
>
<Button
onClick={onCancel}
className='mr-2 text-sm font-medium'
>
{t('common.operation.cancel')}
</Button>
<Button
type='primary'
className='text-sm font-medium'
onClick={handleSave}
>
{t('common.operation.save')}
</Button>
</div>
</div>
</div>
)
}
export default React.memo(AgentSetting)

View File

@ -0,0 +1,44 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import cn from 'classnames'
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
import Tooltip from '@/app/components/base/tooltip'
type Props = {
className?: string
icon: JSX.Element
name: string
description: string
children: JSX.Element
}
const ItemPanel: FC<Props> = ({
className,
icon,
name,
description,
children,
}) => {
return (
<div className={cn(className, 'flex justify-between items-center h-12 px-3 rounded-lg bg-gray-50')}>
<div className='flex items-center'>
{icon}
<div className='ml-3 mr-1 leading-6 text-sm font-semibold text-gray-800'>{name}</div>
<Tooltip
htmlContent={
<div className='w-[180px]'>
{description}
</div>
}
selector={`agent-setting-tooltip-${name}`}
>
<HelpCircle className='w-[14px] h-[14px] text-gray-400' />
</Tooltip>
</div>
<div>
{children}
</div>
</div>
)
}
export default React.memo(ItemPanel)

View File

@ -0,0 +1,77 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import produce from 'immer'
import Tools from '@/app/components/tools'
import { LOC } from '@/app/components/tools/types'
import Drawer from '@/app/components/base/drawer-plus'
import ConfigContext from '@/context/debug-configuration'
import type { ModelConfig } from '@/models/debug'
import I18n from '@/context/i18n'
type Props = {
show: boolean
onHide: () => void
selectedProviderId?: string
}
const ChooseTool: FC<Props> = ({
show,
onHide,
selectedProviderId,
}) => {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const {
modelConfig,
setModelConfig,
} = useContext(ConfigContext)
if (!show)
return null
return (
<Drawer
isShow
onHide={onHide}
title={t('tools.addTool') as string}
panelClassName='mt-2 !w-[760px]'
maxWidthClassName='!max-w-[760px]'
height='calc(100vh - 16px)'
contentClassName='!bg-gray-100'
headerClassName='!border-b-black/5'
body={
<Tools
loc={LOC.app}
selectedProviderId={selectedProviderId}
onAddTool={(collection, tool) => {
const parameters: Record<string, string> = {}
if (tool.parameters) {
tool.parameters.forEach((item) => {
parameters[item.name] = ''
})
}
const nexModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.agentConfig.tools.push({
provider_id: collection.id || collection.name,
provider_type: collection.type,
provider_name: collection.name,
tool_name: tool.name,
tool_label: tool.label[locale === 'en' ? 'en_US' : 'zh_Hans'],
tool_parameters: parameters,
enabled: true,
})
})
setModelConfig(nexModelConfig)
}}
addedTools={(modelConfig?.agentConfig?.tools as any) || []}
/>
}
isShowMask={true}
clickOutsideNotOpen={false}
/>
)
}
export default React.memo(ChooseTool)

View File

@ -0,0 +1,216 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { useContext } from 'use-context-selector'
import produce from 'immer'
import ChooseTool from './choose-tool'
import SettingBuiltInTool from './setting-built-in-tool'
import Panel from '@/app/components/app/configuration/base/feature-panel'
import Tooltip from '@/app/components/base/tooltip'
import { HelpCircle, InfoCircle, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
import { ToolsActive } from '@/app/components/base/icons/src/public/header-nav/tools'
import AppIcon from '@/app/components/base/app-icon'
import Switch from '@/app/components/base/switch'
import ConfigContext from '@/context/debug-configuration'
import type { AgentTool } from '@/types/app'
import { type Collection, CollectionType } from '@/app/components/tools/types'
import { MAX_TOOLS_NUM } from '@/config'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null
const AgentTools: FC = () => {
const { t } = useTranslation()
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext)
const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
const [selectedProviderId, setSelectedProviderId] = useState<string | undefined>(undefined)
const [isShowSettingTool, setIsShowSettingTool] = useState(false)
const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => {
const collection = collectionList.find(collection => collection.id === item.provider_id)
const icon = collection?.icon
return {
...item,
icon,
collection,
}
})
const handleToolSettingChange = (value: Record<string, any>) => {
const newModelConfig = produce(modelConfig, (draft) => {
const tool = (draft.agentConfig.tools).find((item: any) => item.provider_id === currentTool?.collection?.id && item.tool_name === currentTool?.tool_name)
if (tool)
(tool as AgentTool).tool_parameters = value
})
setModelConfig(newModelConfig)
setIsShowSettingTool(false)
}
return (
<>
<Panel
className="mt-4"
noBodySpacing={tools.length === 0}
headerIcon={
<ToolsActive className='w-4 h-4 text-primary-500' />
}
title={
<div className='flex items-center'>
<div className='mr-1'>{t('appDebug.agent.tools.name')}</div>
<Tooltip htmlContent={<div className='w-[180px]'>
{t('appDebug.agent.tools.description')}
</div>} selector='config-tools-tooltip'>
<HelpCircle className='w-[14px] h-[14px] text-gray-400' />
</Tooltip>
</div>
}
headerRight={
<div className='flex items-center'>
<div className='leading-[18px] text-xs font-normal text-gray-500'>{tools.filter((item: any) => !!item.enabled).length}/{tools.length}&nbsp;{t('appDebug.agent.tools.enabled')}</div>
{tools.length < MAX_TOOLS_NUM && (
<>
<div className='ml-3 mr-1 h-3.5 w-px bg-gray-200'></div>
<OperationBtn type="add" onClick={() => {
setSelectedProviderId(undefined)
setIsShowChooseTool(true)
}} />
</>
)}
</div>
}
>
<div className='flex items-center flex-wrap justify-between'>
{tools.map((item: AgentTool & { icon: any; collection?: Collection }, index) => (
<div key={index}
className={cn((item.isDeleted || item.notAuthor) ? 'bg-white/50' : 'bg-white', (item.enabled && !item.isDeleted && !item.notAuthor) && 'shadow-xs', index > 1 && 'mt-1', 'group relative flex justify-between items-center last-of-type:mb-0 pl-2.5 py-2 pr-3 w-full rounded-lg border-[0.5px] border-gray-200 ')}
style={{
width: 'calc(50% - 2px)',
}}
>
<div className='flex items-center'>
{(item.isDeleted || item.notAuthor)
? (
<DefaultToolIcon className='w-6 h-6' />
)
: (
typeof item.icon === 'string'
? (
<div
className='w-6 h-6 bg-cover bg-center rounded-md'
style={{
backgroundImage: `url(${item.icon})`,
}}
></div>
)
: (
<AppIcon
className='rounded-md'
size='tiny'
icon={item.icon?.content}
background={item.icon?.background}
/>
))}
<div
title={item.tool_name}
className={cn((item.isDeleted || item.notAuthor) ? 'line-through opacity-50' : 'group-hover:max-w-[70px]', 'ml-2 max-w-[200px] leading-[18px] text-[13px] font-medium text-gray-800 truncate')}
>
{item.tool_label || item.tool_name}
</div>
</div>
<div className='flex items-center'>
{(item.isDeleted || item.notAuthor)
? (
<div className='flex items-center'>
<TooltipPlus
popupContent={t(`tools.${item.isDeleted ? 'toolRemoved' : 'notAuthorized'}`)}
>
<div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
if (item.notAuthor) {
setSelectedProviderId(item.provider_id)
setIsShowChooseTool(true)
}
}}>
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
</div>
</TooltipPlus>
<div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
const newModelConfig = produce(modelConfig, (draft) => {
draft.agentConfig.tools.splice(index, 1)
})
setModelConfig(newModelConfig)
}}>
<Trash03 className='w-4 h-4 text-gray-500' />
</div>
<div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div>
</div>
)
: (
<div className='hidden group-hover:flex items-center'>
{item.provider_type === CollectionType.builtIn && (
<TooltipPlus
popupContent={t('tools.setBuiltInTools.infoAndSetting')}
>
<div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
setCurrentTool(item)
setIsShowSettingTool(true)
}}>
<InfoCircle className='w-4 h-4 text-gray-500' />
</div>
</TooltipPlus>
)}
<div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
const newModelConfig = produce(modelConfig, (draft) => {
draft.agentConfig.tools.splice(index, 1)
})
setModelConfig(newModelConfig)
}}>
<Trash03 className='w-4 h-4 text-gray-500' />
</div>
<div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div>
</div>
)}
<div className={cn((item.isDeleted || item.notAuthor) && 'opacity-50')}>
<Switch
defaultValue={(item.isDeleted || item.notAuthor) ? false : item.enabled}
disabled={(item.isDeleted || item.notAuthor)}
size='md'
onChange={(enabled) => {
const newModelConfig = produce(modelConfig, (draft) => {
(draft.agentConfig.tools[index] as any).enabled = enabled
})
setModelConfig(newModelConfig)
}} />
</div>
</div>
</div>
))}
</div >
</Panel >
{isShowChooseTool && (
<ChooseTool
show
onHide={() => setIsShowChooseTool(false)}
selectedProviderId={selectedProviderId}
/>
)}
{
isShowSettingTool && (
<SettingBuiltInTool
toolName={currentTool?.tool_name as string}
setting={currentTool?.tool_parameters as any}
collection={currentTool?.collection as Collection}
onSave={handleToolSettingChange}
onHide={() => setIsShowSettingTool(false)}
/>)
}
</>
)
}
export default React.memo(AgentTools)

View File

@ -0,0 +1,192 @@
'use client'
import type { FC } from 'react'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import cn from 'classnames'
import Drawer from '@/app/components/base/drawer-plus'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
import type { Collection, Tool } from '@/app/components/tools/types'
import { fetchBuiltInToolList } from '@/service/tools'
import I18n from '@/context/i18n'
import Button from '@/app/components/base/button'
import Loading from '@/app/components/base/loading'
import { DiagonalDividingLine } from '@/app/components/base/icons/src/public/common'
type Props = {
collection: Collection
toolName: string
setting?: Record<string, any>
readonly?: boolean
onHide: () => void
onSave?: (value: Record<string, any>) => void
}
const SettingBuiltInTool: FC<Props> = ({
collection,
toolName,
setting = {},
readonly,
onHide,
onSave,
}) => {
const { locale } = useContext(I18n)
const { t } = useTranslation()
const [isLoading, setIsLoading] = useState(true)
const [tools, setTools] = useState<Tool[]>([])
const currTool = tools.find(tool => tool.name === toolName)
const formSchemas = currTool ? toolParametersToFormSchemas(currTool.parameters) : []
const infoSchemas = formSchemas.filter((item: any) => item.form === 'llm')
const settingSchemas = formSchemas.filter((item: any) => item.form !== 'llm')
const hasSetting = settingSchemas.length > 0
const [tempSetting, setTempSetting] = useState(setting)
const [currType, setCurrType] = useState('info')
const isInfoActive = currType === 'info'
useEffect(() => {
if (!collection)
return
(async () => {
setIsLoading(true)
try {
const list = await fetchBuiltInToolList(collection.name) as Tool[]
setTools(list)
const currTool = list.find(tool => tool.name === toolName)
if (currTool) {
const formSchemas = toolParametersToFormSchemas(currTool.parameters)
setTempSetting(addDefaultValue(setting, formSchemas))
}
}
catch (e) { }
setIsLoading(false)
})()
}, [collection?.name])
useEffect(() => {
setCurrType((!readonly && hasSetting) ? 'setting' : 'info')
}, [hasSetting])
const isValid = (() => {
let valid = true
settingSchemas.forEach((item: any) => {
if (item.required && !tempSetting[item.name])
valid = false
})
return valid
})()
const infoUI = (
<div className='pt-2'>
<div className='leading-5 text-sm font-medium text-gray-900'>
{t('tools.setBuiltInTools.toolDescription')}
</div>
<div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'>
{currTool?.description[locale === 'en' ? 'en_US' : 'zh_Hans']}
</div>
{infoSchemas.length > 0 && (
<div className='mt-6'>
<div className='flex items-center mb-4 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>
<div className='mr-3'>{t('tools.setBuiltInTools.parameters')}</div>
<div className='grow w-0 h-px bg-[#f3f4f6]'></div>
</div>
<div className='space-y-4'>
{infoSchemas.map((item: any, index) => (
<div key={index}>
<div className='flex items-center space-x-2 leading-[18px]'>
<div className='text-[13px] font-semibold text-gray-900'>{item.label[locale === 'en' ? 'en_US' : 'zh_Hans']}</div>
<div className='text-xs font-medium text-gray-500'>{item.type === 'number-input' ? t('tools.setBuiltInTools.number') : t('tools.setBuiltInTools.string')}</div>
{item.required && (
<div className='text-xs font-medium text-[#EC4A0A]'>{t('tools.setBuiltInTools.required')}</div>
)}
</div>
{item.human_description && (
<div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'>
{item.human_description?.[locale === 'en' ? 'en_US' : 'zh_Hans']}
</div>
)}
</div>
))}
</div>
</div>
)}
</div>
)
const setttingUI = (
<Form
value={tempSetting}
onChange={setTempSetting}
formSchemas={settingSchemas as any}
isEditMode={false}
showOnVariableMap={{}}
validating={false}
inputClassName='!bg-gray-50'
readonly={readonly}
/>
)
return (
<Drawer
isShow
onHide={onHide}
title={(
<div className='flex'>
<div
className='w-6 h-6 bg-cover bg-center rounded-md'
style={{
backgroundImage: `url(${collection.icon})`,
}}
></div>
<div className='ml-2 leading-6 text-base font-semibold text-gray-900'>{currTool?.label[locale === 'en' ? 'en_US' : 'zh_Hans']}</div>
{(hasSetting && !readonly) && (<>
<DiagonalDividingLine className='mx-4' />
<div className='flex space-x-6'>
<div
className={cn(isInfoActive ? 'text-gray-900 font-semibold' : 'font-normal text-gray-600 cursor-pointer', 'relative text-base')}
onClick={() => setCurrType('info')}
>
{t('tools.setBuiltInTools.info')}
{isInfoActive && <div className='absolute left-0 bottom-[-16px] w-full h-0.5 bg-primary-600'></div>}
</div>
<div className={cn(!isInfoActive ? 'text-gray-900 font-semibold' : 'font-normal text-gray-600 cursor-pointer', 'relative text-base ')}
onClick={() => setCurrType('setting')}
>
{t('tools.setBuiltInTools.setting')}
{!isInfoActive && <div className='absolute left-0 bottom-[-16px] w-full h-0.5 bg-primary-600'></div>}
</div>
</div>
</>)}
</div>
)}
panelClassName='mt-[65px] !w-[480px]'
maxWidthClassName='!max-w-[480px]'
height='calc(100vh - 65px)'
headerClassName='!border-b-black/5'
body={
<div className='h-full pt-3'>
{isLoading
? <div className='flex h-full items-center'>
<Loading type='app' />
</div>
: (<div className='flex flex-col h-full'>
<div className='grow h-0 overflow-y-auto px-6'>
{isInfoActive ? infoUI : setttingUI}
</div>
{!readonly && !isInfoActive && (
<div className='mt-2 shrink-0 flex justify-end py-4 px-6 space-x-2 rounded-b-[10px] bg-gray-50 border-t border-black/5'>
<Button className='flex items-center h-8 !px-3 !text-[13px] font-medium !text-gray-700' onClick={onHide}>{t('common.operation.cancel')}</Button>
<Button className='flex items-center h-8 !px-3 !text-[13px] font-medium' type='primary' disabled={!isValid} onClick={() => onSave?.(addDefaultValue(tempSetting, formSchemas))}>{t('common.operation.save')}</Button>
</div>
)}
</div>)}
</div>
}
isShowMask={true}
clickOutsideNotOpen={false}
/>
)
}
export default React.memo(SettingBuiltInTool)

View File

@ -0,0 +1,141 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import copy from 'copy-to-clipboard'
import cn from 'classnames'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import { Clipboard, ClipboardCheck } from '@/app/components/base/icons/src/vender/line/files'
import PromptEditor from '@/app/components/base/prompt-editor'
import type { ExternalDataTool } from '@/models/common'
import ConfigContext from '@/context/debug-configuration'
import { useModalContext } from '@/context/modal-context'
import { useToastContext } from '@/app/components/base/toast'
import s from '@/app/components/app/configuration/config-prompt/style.module.css'
type Props = {
className?: string
type: 'first-prompt' | 'next-iteration'
value: string
onChange: (value: string) => void
}
const Editor: FC<Props> = ({
className,
type,
value,
onChange,
}) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const [isCopied, setIsCopied] = React.useState(false)
const {
modelConfig,
hasSetBlockStatus,
dataSets,
showSelectDataSet,
externalDataToolsConfig,
setExternalDataToolsConfig,
} = useContext(ConfigContext)
const promptVariables = modelConfig.configs.prompt_variables
const { setShowExternalDataToolModal } = useModalContext()
const isFirstPrompt = type === 'first-prompt'
const editorHeight = isFirstPrompt ? 'h-[336px]' : 'h-[52px]'
const handleOpenExternalDataToolModal = () => {
setShowExternalDataToolModal({
payload: {},
onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
setExternalDataToolsConfig([...externalDataToolsConfig, newExternalDataTool])
},
onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
for (let i = 0; i < promptVariables.length; i++) {
if (promptVariables[i].key === newExternalDataTool.variable) {
notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: promptVariables[i].key }) })
return false
}
}
for (let i = 0; i < externalDataToolsConfig.length; i++) {
if (externalDataToolsConfig[i].variable === newExternalDataTool.variable) {
notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: externalDataToolsConfig[i].variable }) })
return false
}
}
return true
},
})
}
return (
<div className={cn(className, s.gradientBorder, 'relative')}>
<div className='rounded-xl bg-white'>
<div className={cn(s.boxHeader, 'flex justify-between items-center h-11 pt-2 pr-3 pb-1 pl-4 rounded-tl-xl rounded-tr-xl bg-white hover:shadow-xs')}>
<div className='text-sm font-semibold uppercase text-indigo-800'>{t(`appDebug.agent.${isFirstPrompt ? 'firstPrompt' : 'nextIteration'}`)}</div>
<div className={cn(s.optionWrap, 'items-center space-x-1')}>
{!isCopied
? (
<Clipboard className='h-6 w-6 p-1 text-gray-500 cursor-pointer' onClick={() => {
copy(value)
setIsCopied(true)
}} />
)
: (
<ClipboardCheck className='h-6 w-6 p-1 text-gray-500' />
)}
</div>
</div>
<div className={cn(editorHeight, ' px-4 min-h-[102px] overflow-y-auto text-sm text-gray-700')}>
<PromptEditor
className={editorHeight}
value={value}
contextBlock={{
show: true,
selectable: !hasSetBlockStatus.context,
datasets: dataSets.map(item => ({
id: item.id,
name: item.name,
type: item.data_source_type,
})),
onAddContext: showSelectDataSet,
}}
variableBlock={{
variables: modelConfig.configs.prompt_variables.map(item => ({
name: item.name,
value: item.key,
})),
externalTools: externalDataToolsConfig.map(item => ({
name: item.label!,
variableName: item.variable!,
icon: item.icon,
icon_background: item.icon_background,
})),
onAddExternalTool: handleOpenExternalDataToolModal,
}}
historyBlock={{
show: false,
selectable: false,
history: {
user: '',
assistant: '',
},
onEditRole: () => { },
}}
queryBlock={{
show: false,
selectable: false,
}}
onChange={onChange}
onBlur={() => { }}
/>
</div>
<div className='pl-4 pb-2 flex'>
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{value.length}</div>
</div>
</div>
</div>
)
}
export default React.memo(Editor)

View File

@ -0,0 +1,165 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import AgentSetting from '../agent/agent-setting'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { BubbleText } from '@/app/components/base/icons/src/vender/solid/education'
import Radio from '@/app/components/base/radio/ui'
import { ChevronDown } from '@/app/components/base/icons/src/vender/solid/arrows'
import { CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
import type { AgentConfig } from '@/models/debug'
type Props = {
value: string
disabled: boolean
onChange: (value: string) => void
isFunctionCall: boolean
isChatModel: boolean
agentConfig?: AgentConfig
onAgentSettingChange: (payload: AgentConfig) => void
}
type ItemProps = {
text: string
disabled: boolean
value: string
isChecked: boolean
description: string
Icon: any
onClick: (value: string) => void
}
const SelectItem: FC<ItemProps> = ({ text, value, Icon, isChecked, description, onClick, disabled }) => {
return (
<div
className={cn(disabled ? 'opacity-50' : 'cursor-pointer', isChecked ? 'border-[2px] border-indigo-600 shadow-sm' : 'border border-gray-100', 'mb-2 p-3 pr-4 rounded-xl bg-gray-25 hover:bg-gray-50')}
onClick={() => !disabled && onClick(value)}
>
<div className='flex items-center justify-between'>
<div className='flex items-center '>
<div className='mr-3 p-1 bg-indigo-50 rounded-lg'>
<Icon className='w-4 h-4 text-indigo-600' />
</div>
<div className='leading-5 text-sm font-medium text-gray-900'>{text}</div>
</div>
<Radio isChecked={isChecked} />
</div>
<div className='ml-9 leading-[18px] text-xs font-normal text-gray-500'>{description}</div>
</div>
)
}
const AssistantTypePicker: FC<Props> = ({
value,
disabled,
onChange,
onAgentSettingChange,
isFunctionCall,
isChatModel,
agentConfig,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const handleChange = (chosenValue: string) => {
if (value === chosenValue)
return
onChange(chosenValue)
if (chosenValue !== 'agent')
setOpen(false)
}
const isAgent = value === 'agent'
const [isShowAgentSetting, setIsShowAgentSetting] = useState(false)
const agentConfigUI = (
<>
<div className='my-4 h-[1px] bg-gray-100'></div>
<div
className={cn(isAgent ? 'group cursor-pointer hover:bg-primary-50' : 'opacity-30', 'p-3 pr-4 rounded-xl bg-gray-50 ')}
onClick={() => {
if (isAgent) {
setOpen(false)
setIsShowAgentSetting(true)
}
}}
>
<div className='flex items-center justify-between'>
<div className='flex items-center '>
<div className='mr-3 p-1 bg-gray-200 group-hover:bg-white rounded-lg'>
<Settings04 className='w-4 h-4 text-gray-600 group-hover:text-[#155EEF]' />
</div>
<div className='leading-5 text-sm font-medium text-gray-900 group-hover:text-[#155EEF]'>{t('appDebug.agent.setting.name')}</div>
</div>
<ArrowUpRight className='w-4 h-4 text-gray-500 group-hover:text-[#155EEF]' />
</div>
<div className='ml-9 leading-[18px] text-xs font-normal text-gray-500'>{t('appDebug.agent.setting.description')}</div>
</div>
</>
)
return (
<>
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={{
mainAxis: 8,
crossAxis: -2,
}}
>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<div className={cn(open && 'bg-gray-50', 'flex items-center h-8 px-3 border border-black/5 rounded-lg cursor-pointer select-none space-x-1 text-indigo-600')}>
{isAgent ? <BubbleText className='w-3 h-3' /> : <CuteRobote className='w-3 h-3' />}
<div className='text-xs font-medium'>{t(`appDebug.assistantType.${isAgent ? 'agentAssistant' : 'chatAssistant'}.name`)}</div>
<ChevronDown className='w-3 h-3' />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{ zIndex: 1000 }}>
<div className='relative left-0.5 p-6 bg-white border border-black/[0.08] shadow-lg rounded-xl w-[480px]'>
<div className='mb-2 leading-5 text-sm font-semibold text-gray-900'>{t('appDebug.assistantType.name')}</div>
<SelectItem
Icon={BubbleText}
value='chat'
disabled={disabled}
text={t('appDebug.assistantType.chatAssistant.name')}
description={t('appDebug.assistantType.chatAssistant.description')}
isChecked={!isAgent}
onClick={handleChange}
/>
<SelectItem
Icon={CuteRobote}
value='agent'
disabled={disabled}
text={t('appDebug.assistantType.agentAssistant.name')}
description={t('appDebug.assistantType.agentAssistant.description')}
isChecked={isAgent}
onClick={handleChange}
/>
{!disabled && agentConfigUI}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
{isShowAgentSetting && (
<AgentSetting
isFunctionCall={isFunctionCall}
payload={agentConfig as AgentConfig}
isChatModel={isChatModel}
onSave={(payloadNew) => {
onAgentSettingChange(payloadNew)
setIsShowAgentSetting(false)
}}
onCancel={() => setIsShowAgentSetting(false)}
/>
)}
</>
)
}
export default React.memo(AssistantTypePicker)

View File

@ -9,10 +9,10 @@ export type IAutomaticBtnProps = {
const leftIcon = ( const leftIcon = (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.31346 0.905711C4.21464 0.708087 4.01266 0.583252 3.79171 0.583252C3.57076 0.583252 3.36877 0.708087 3.26996 0.905711L2.81236 1.82091C2.64757 2.15048 2.59736 2.24532 2.53635 2.32447C2.47515 2.40386 2.40398 2.47503 2.32459 2.53623C2.24544 2.59724 2.1506 2.64745 1.82103 2.81224L0.905833 3.26984C0.708209 3.36865 0.583374 3.57064 0.583374 3.79159C0.583374 4.01254 0.708209 4.21452 0.905833 4.31333L1.82103 4.77094C2.1506 4.93572 2.24544 4.98593 2.32459 5.04694C2.40398 5.10814 2.47515 5.17931 2.53635 5.2587C2.59736 5.33785 2.64758 5.43269 2.81236 5.76226L3.26996 6.67746C3.36877 6.87508 3.57076 6.99992 3.79171 6.99992C4.01266 6.99992 4.21465 6.87508 4.31346 6.67746L4.77106 5.76226C4.93584 5.43269 4.98605 5.33786 5.04707 5.2587C5.10826 5.17931 5.17943 5.10814 5.25883 5.04694C5.33798 4.98593 5.43282 4.93572 5.76238 4.77094L6.67758 4.31333C6.87521 4.21452 7.00004 4.01254 7.00004 3.79159C7.00004 3.57064 6.87521 3.36865 6.67758 3.26984L5.76238 2.81224C5.43282 2.64745 5.33798 2.59724 5.25883 2.53623C5.17943 2.47503 5.10826 2.40386 5.04707 2.32447C4.98605 2.24532 4.93584 2.15048 4.77106 1.82091L4.31346 0.905711Z" fill="#444CE7"/> <path d="M4.31346 0.905711C4.21464 0.708087 4.01266 0.583252 3.79171 0.583252C3.57076 0.583252 3.36877 0.708087 3.26996 0.905711L2.81236 1.82091C2.64757 2.15048 2.59736 2.24532 2.53635 2.32447C2.47515 2.40386 2.40398 2.47503 2.32459 2.53623C2.24544 2.59724 2.1506 2.64745 1.82103 2.81224L0.905833 3.26984C0.708209 3.36865 0.583374 3.57064 0.583374 3.79159C0.583374 4.01254 0.708209 4.21452 0.905833 4.31333L1.82103 4.77094C2.1506 4.93572 2.24544 4.98593 2.32459 5.04694C2.40398 5.10814 2.47515 5.17931 2.53635 5.2587C2.59736 5.33785 2.64758 5.43269 2.81236 5.76226L3.26996 6.67746C3.36877 6.87508 3.57076 6.99992 3.79171 6.99992C4.01266 6.99992 4.21465 6.87508 4.31346 6.67746L4.77106 5.76226C4.93584 5.43269 4.98605 5.33786 5.04707 5.2587C5.10826 5.17931 5.17943 5.10814 5.25883 5.04694C5.33798 4.98593 5.43282 4.93572 5.76238 4.77094L6.67758 4.31333C6.87521 4.21452 7.00004 4.01254 7.00004 3.79159C7.00004 3.57064 6.87521 3.36865 6.67758 3.26984L5.76238 2.81224C5.43282 2.64745 5.33798 2.59724 5.25883 2.53623C5.17943 2.47503 5.10826 2.40386 5.04707 2.32447C4.98605 2.24532 4.93584 2.15048 4.77106 1.82091L4.31346 0.905711Z" fill="#444CE7" />
<path d="M11.375 1.74992C11.375 1.42775 11.1139 1.16659 10.7917 1.16659C10.4695 1.16659 10.2084 1.42775 10.2084 1.74992V2.62492H9.33337C9.01121 2.62492 8.75004 2.88609 8.75004 3.20825C8.75004 3.53042 9.01121 3.79159 9.33337 3.79159H10.2084V4.66659C10.2084 4.98875 10.4695 5.24992 10.7917 5.24992C11.1139 5.24992 11.375 4.98875 11.375 4.66659V3.79159H12.25C12.5722 3.79159 12.8334 3.53042 12.8334 3.20825C12.8334 2.88609 12.5722 2.62492 12.25 2.62492H11.375V1.74992Z" fill="#444CE7"/> <path d="M11.375 1.74992C11.375 1.42775 11.1139 1.16659 10.7917 1.16659C10.4695 1.16659 10.2084 1.42775 10.2084 1.74992V2.62492H9.33337C9.01121 2.62492 8.75004 2.88609 8.75004 3.20825C8.75004 3.53042 9.01121 3.79159 9.33337 3.79159H10.2084V4.66659C10.2084 4.98875 10.4695 5.24992 10.7917 5.24992C11.1139 5.24992 11.375 4.98875 11.375 4.66659V3.79159H12.25C12.5722 3.79159 12.8334 3.53042 12.8334 3.20825C12.8334 2.88609 12.5722 2.62492 12.25 2.62492H11.375V1.74992Z" fill="#444CE7" />
<path d="M3.79171 9.33325C3.79171 9.01109 3.53054 8.74992 3.20837 8.74992C2.88621 8.74992 2.62504 9.01109 2.62504 9.33325V10.2083H1.75004C1.42787 10.2083 1.16671 10.4694 1.16671 10.7916C1.16671 11.1138 1.42787 11.3749 1.75004 11.3749H2.62504V12.2499C2.62504 12.5721 2.88621 12.8333 3.20837 12.8333C3.53054 12.8333 3.79171 12.5721 3.79171 12.2499V11.3749H4.66671C4.98887 11.3749 5.25004 11.1138 5.25004 10.7916C5.25004 10.4694 4.98887 10.2083 4.66671 10.2083H3.79171V9.33325Z" fill="#444CE7"/> <path d="M3.79171 9.33325C3.79171 9.01109 3.53054 8.74992 3.20837 8.74992C2.88621 8.74992 2.62504 9.01109 2.62504 9.33325V10.2083H1.75004C1.42787 10.2083 1.16671 10.4694 1.16671 10.7916C1.16671 11.1138 1.42787 11.3749 1.75004 11.3749H2.62504V12.2499C2.62504 12.5721 2.88621 12.8333 3.20837 12.8333C3.53054 12.8333 3.79171 12.5721 3.79171 12.2499V11.3749H4.66671C4.98887 11.3749 5.25004 11.1138 5.25004 10.7916C5.25004 10.4694 4.98887 10.2083 4.66671 10.2083H3.79171V9.33325Z" fill="#444CE7" />
<path d="M10.4385 6.73904C10.3396 6.54142 10.1377 6.41659 9.91671 6.41659C9.69576 6.41659 9.49377 6.54142 9.39496 6.73904L8.84014 7.84869C8.67535 8.17826 8.62514 8.27309 8.56413 8.35225C8.50293 8.43164 8.43176 8.50281 8.35237 8.56401C8.27322 8.62502 8.17838 8.67523 7.84881 8.84001L6.73917 9.39484C6.54154 9.49365 6.41671 9.69564 6.41671 9.91659C6.41671 10.1375 6.54154 10.3395 6.73917 10.4383L7.84881 10.9932C8.17838 11.1579 8.27322 11.2082 8.35237 11.2692C8.43176 11.3304 8.50293 11.4015 8.56413 11.4809C8.62514 11.5601 8.67535 11.6549 8.84014 11.9845L9.39496 13.0941C9.49377 13.2918 9.69576 13.4166 9.91671 13.4166C10.1377 13.4166 10.3396 13.2918 10.4385 13.0941L10.9933 11.9845C11.1581 11.6549 11.2083 11.5601 11.2693 11.4809C11.3305 11.4015 11.4017 11.3304 11.481 11.2692C11.5602 11.2082 11.655 11.1579 11.9846 10.9932L13.0942 10.4383C13.2919 10.3395 13.4167 10.1375 13.4167 9.91659C13.4167 9.69564 13.2919 9.49365 13.0942 9.39484L11.9846 8.84001C11.655 8.67523 11.5602 8.62502 11.481 8.56401C11.4017 8.50281 11.3305 8.43164 11.2693 8.35225C11.2083 8.27309 11.1581 8.17826 10.9933 7.84869L10.4385 6.73904Z" fill="#444CE7"/> <path d="M10.4385 6.73904C10.3396 6.54142 10.1377 6.41659 9.91671 6.41659C9.69576 6.41659 9.49377 6.54142 9.39496 6.73904L8.84014 7.84869C8.67535 8.17826 8.62514 8.27309 8.56413 8.35225C8.50293 8.43164 8.43176 8.50281 8.35237 8.56401C8.27322 8.62502 8.17838 8.67523 7.84881 8.84001L6.73917 9.39484C6.54154 9.49365 6.41671 9.69564 6.41671 9.91659C6.41671 10.1375 6.54154 10.3395 6.73917 10.4383L7.84881 10.9932C8.17838 11.1579 8.27322 11.2082 8.35237 11.2692C8.43176 11.3304 8.50293 11.4015 8.56413 11.4809C8.62514 11.5601 8.67535 11.6549 8.84014 11.9845L9.39496 13.0941C9.49377 13.2918 9.69576 13.4166 9.91671 13.4166C10.1377 13.4166 10.3396 13.2918 10.4385 13.0941L10.9933 11.9845C11.1581 11.6549 11.2083 11.5601 11.2693 11.4809C11.3305 11.4015 11.4017 11.3304 11.481 11.2692C11.5602 11.2082 11.655 11.1579 11.9846 10.9932L13.0942 10.4383C13.2919 10.3395 13.4167 10.1375 13.4167 9.91659C13.4167 9.69564 13.2919 9.49365 13.0942 9.39484L11.9846 8.84001C11.655 8.67523 11.5602 8.62502 11.481 8.56401C11.4017 8.50281 11.3305 8.43164 11.2693 8.35225C11.2083 8.27309 11.1581 8.17826 10.9933 7.84869L10.4385 6.73904Z" fill="#444CE7" />
</svg> </svg>
) )
const AutomaticBtn: FC<IAutomaticBtnProps> = ({ const AutomaticBtn: FC<IAutomaticBtnProps> = ({
@ -25,7 +25,7 @@ const AutomaticBtn: FC<IAutomaticBtnProps> = ({
onClick={onClick} onClick={onClick}
> >
{leftIcon} {leftIcon}
<span className='text-xs font-semibold text-indigo-600 uppercase'>{t('appDebug.operation.automatic')}</span> <span className='text-xs font-semibold text-indigo-600'>{t('appDebug.operation.automatic')}</span>
</div> </div>
) )
} }

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 108 KiB

View File

@ -6,6 +6,7 @@
height: 360px; height: 360px;
background: center center no-repeat; background: center center no-repeat;
background-size: contain; background-size: contain;
border-radius: 8px;
} }
.wrap:hover .preview { .wrap:hover .preview {
@ -13,7 +14,7 @@
} }
.openingStatementPreview { .openingStatementPreview {
background-image: url(./preview-imgs/opening-statement.svg); background-image: url(./preview-imgs/opening-statement.png);
} }
.suggestedQuestionsAfterAnswerPreview { .suggestedQuestionsAfterAnswerPreview {

View File

@ -5,7 +5,6 @@ import { useContext } from 'use-context-selector'
import produce from 'immer' import produce from 'immer'
import { useBoolean, useScroll } from 'ahooks' import { useBoolean, useScroll } from 'ahooks'
import DatasetConfig from '../dataset-config' import DatasetConfig from '../dataset-config'
import Tools from '../tools'
import ChatGroup from '../features/chat-group' import ChatGroup from '../features/chat-group'
import ExperienceEnchanceGroup from '../features/experience-enchance-group' import ExperienceEnchanceGroup from '../features/experience-enchance-group'
import Toolbox from '../toolbox' import Toolbox from '../toolbox'
@ -15,11 +14,12 @@ import useAnnotationConfig from '../toolbox/annotation/use-annotation-config'
import AddFeatureBtn from './feature/add-feature-btn' import AddFeatureBtn from './feature/add-feature-btn'
import ChooseFeature from './feature/choose-feature' import ChooseFeature from './feature/choose-feature'
import useFeature from './feature/use-feature' import useFeature from './feature/use-feature'
import AgentTools from './agent/agent-tools'
import AdvancedModeWaring from '@/app/components/app/configuration/prompt-mode/advanced-mode-waring' import AdvancedModeWaring from '@/app/components/app/configuration/prompt-mode/advanced-mode-waring'
import ConfigContext from '@/context/debug-configuration' import ConfigContext from '@/context/debug-configuration'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt' import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import ConfigVar from '@/app/components/app/configuration/config-var' import ConfigVar from '@/app/components/app/configuration/config-var'
import type { CitationConfig, ModelConfig, ModerationConfig, MoreLikeThisConfig, PromptVariable, SpeechToTextConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug' import { type CitationConfig, type ModelConfig, type ModerationConfig, type MoreLikeThisConfig, PromptMode, type PromptVariable, type SpeechToTextConfig, type SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
import { AppType, ModelModeType } from '@/types/app' import { AppType, ModelModeType } from '@/types/app'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import ConfigParamModal from '@/app/components/app/configuration/toolbox/annotation/config-param-modal' import ConfigParamModal from '@/app/components/app/configuration/toolbox/annotation/config-param-modal'
@ -32,11 +32,15 @@ const Config: FC = () => {
mode, mode,
isAdvancedMode, isAdvancedMode,
modelModeType, modelModeType,
isAgent,
canReturnToSimpleMode, canReturnToSimpleMode,
setPromptMode,
hasSetBlockStatus, hasSetBlockStatus,
showHistoryModal, showHistoryModal,
introduction, introduction,
setIntroduction, setIntroduction,
suggestedQuestions,
setSuggestedQuestions,
modelConfig, modelConfig,
setModelConfig, setModelConfig,
setPrevPromptConfig, setPrevPromptConfig,
@ -187,12 +191,12 @@ const Config: FC = () => {
<> <>
<div <div
ref={wrapRef} ref={wrapRef}
className="relative px-6 pb-[50px] overflow-y-auto h-full" className="grow h-0 relative px-6 pb-[50px] overflow-y-auto"
> >
<AddFeatureBtn toBottomHeight={toBottomHeight} onClick={showChooseFeatureTrue} /> <AddFeatureBtn toBottomHeight={toBottomHeight} onClick={showChooseFeatureTrue} />
{ {
(isAdvancedMode && canReturnToSimpleMode) && ( (isAdvancedMode && canReturnToSimpleMode && !isAgent) && (
<AdvancedModeWaring /> <AdvancedModeWaring onReturnToSimpleMode={() => setPromptMode(PromptMode.simple)} />
) )
} }
{showChooseFeature && ( {showChooseFeature && (
@ -223,8 +227,10 @@ const Config: FC = () => {
{/* Dataset */} {/* Dataset */}
<DatasetConfig /> <DatasetConfig />
<Tools /> {/* Tools */}
{(isAgent && isChatApp) && (
<AgentTools />
)}
<ConfigVision /> <ConfigVision />
{/* Chat History */} {/* Chat History */}
@ -244,6 +250,8 @@ const Config: FC = () => {
{ {
value: introduction, value: introduction,
onChange: setIntroduction, onChange: setIntroduction,
suggestedQuestions,
onSuggestedQuestionsChange: setSuggestedQuestions,
} }
} }
isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer} isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer}

View File

@ -30,6 +30,7 @@ const DatasetConfig: FC = () => {
modelConfig, modelConfig,
setModelConfig, setModelConfig,
showSelectDataSet, showSelectDataSet,
isAgent,
} = useContext(ConfigContext) } = useContext(ConfigContext)
const hasData = dataSet.length > 0 const hasData = dataSet.length > 0
@ -72,7 +73,7 @@ const DatasetConfig: FC = () => {
title={t('appDebug.feature.dataSet.title')} title={t('appDebug.feature.dataSet.title')}
headerRight={ headerRight={
<div className='flex items-center gap-1'> <div className='flex items-center gap-1'>
<ParamsConfig /> {!isAgent && <ParamsConfig />}
<OperationBtn type="add" onClick={showSelectDataSet} /> <OperationBtn type="add" onClick={showSelectDataSet} />
</div> </div>
} }

View File

@ -156,7 +156,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
<div className={labelClass}> <div className={labelClass}>
{t('datasetSettings.form.desc')} {t('datasetSettings.form.desc')}
</div> </div>
<div className='grow'> <div className='w-full'>
<textarea <textarea
value={localeCurrentDataset.description || ''} value={localeCurrentDataset.description || ''}
onChange={e => handleValueChange('description', e.target.value)} onChange={e => handleValueChange('description', e.target.value)}
@ -173,12 +173,12 @@ const SettingsModal: FC<SettingsModalProps> = ({
<div className={labelClass}> <div className={labelClass}>
<div>{t('datasetSettings.form.permissions')}</div> <div>{t('datasetSettings.form.permissions')}</div>
</div> </div>
<div className='w-full sm:w-[480px]'> <div className='w-full'>
<PermissionsRadio <PermissionsRadio
disable={!localeCurrentDataset?.embedding_available} disable={!localeCurrentDataset?.embedding_available}
value={localeCurrentDataset.permission} value={localeCurrentDataset.permission}
onChange={v => handleValueChange('permission', v!)} onChange={v => handleValueChange('permission', v!)}
itemClassName='sm:!w-[227px]' itemClassName='sm:!w-[280px]'
/> />
</div> </div>
</div> </div>
@ -192,7 +192,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
disable={!localeCurrentDataset?.embedding_available} disable={!localeCurrentDataset?.embedding_available}
value={indexMethod} value={indexMethod}
onChange={v => setIndexMethod(v!)} onChange={v => setIndexMethod(v!)}
itemClassName='sm:!w-[227px]' itemClassName='sm:!w-[280px]'
/> />
</div> </div>
</div> </div>
@ -201,7 +201,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
<div className={labelClass}> <div className={labelClass}>
{t('datasetSettings.form.embeddingModel')} {t('datasetSettings.form.embeddingModel')}
</div> </div>
<div className='grow'> <div className='w-full'>
<div className='w-full h-9 rounded-lg bg-gray-100 opacity-60'> <div className='w-full h-9 rounded-lg bg-gray-100 opacity-60'>
<ModelSelector <ModelSelector
readonly readonly

View File

@ -4,7 +4,7 @@ import useSWR from 'swr'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import cn from 'classnames' import cn from 'classnames'
import produce from 'immer' import produce, { setAutoFreeze } from 'immer'
import { useBoolean, useGetState } from 'ahooks' import { useBoolean, useGetState } from 'ahooks'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -12,7 +12,7 @@ import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
import FormattingChanged from '../base/warning-mask/formatting-changed' import FormattingChanged from '../base/warning-mask/formatting-changed'
import GroupName from '../base/group-name' import GroupName from '../base/group-name'
import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset' import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset'
import { AppType, ModelModeType, TransferMethod } from '@/types/app' import { AgentStrategy, AppType, ModelModeType, TransferMethod } from '@/types/app'
import PromptValuePanel, { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel' import PromptValuePanel, { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
import type { IChatItem } from '@/app/components/app/chat/type' import type { IChatItem } from '@/app/components/app/chat/type'
import Chat from '@/app/components/app/chat' import Chat from '@/app/components/app/chat'
@ -44,6 +44,8 @@ const Debug: FC<IDebug> = ({
const { const {
appId, appId,
mode, mode,
isFunctionCall,
collectionList,
modelModeType, modelModeType,
hasSetBlockStatus, hasSetBlockStatus,
isAdvancedMode, isAdvancedMode,
@ -51,6 +53,7 @@ const Debug: FC<IDebug> = ({
chatPromptConfig, chatPromptConfig,
completionPromptConfig, completionPromptConfig,
introduction, introduction,
suggestedQuestions,
suggestedQuestionsAfterAnswerConfig, suggestedQuestionsAfterAnswerConfig,
speechToTextConfig, speechToTextConfig,
citationConfig, citationConfig,
@ -66,7 +69,6 @@ const Debug: FC<IDebug> = ({
completionParams, completionParams,
hasSetContextVar, hasSetContextVar,
datasetConfigs, datasetConfigs,
externalDataToolsConfig,
visionConfig, visionConfig,
annotationConfig, annotationConfig,
} = useContext(ConfigContext) } = useContext(ConfigContext)
@ -74,6 +76,13 @@ const Debug: FC<IDebug> = ({
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([]) const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
const chatListDomRef = useRef<HTMLDivElement>(null) const chatListDomRef = useRef<HTMLDivElement>(null)
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
useEffect(() => {
setAutoFreeze(false)
return () => {
setAutoFreeze(true)
}
}, [])
useEffect(() => { useEffect(() => {
// scroll to bottom // scroll to bottom
if (chatListDomRef.current) if (chatListDomRef.current)
@ -88,9 +97,10 @@ const Debug: FC<IDebug> = ({
content: getIntroduction(), content: getIntroduction(),
isAnswer: true, isAnswer: true,
isOpeningStatement: true, isOpeningStatement: true,
suggestedQuestions,
}]) }])
} }
}, [introduction, modelConfig.configs.prompt_variables, inputs]) }, [introduction, suggestedQuestions, modelConfig.configs.prompt_variables, inputs])
const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
const [abortController, setAbortController] = useState<AbortController | null>(null) const [abortController, setAbortController] = useState<AbortController | null>(null)
@ -117,6 +127,7 @@ const Debug: FC<IDebug> = ({
content: getIntroduction(), content: getIntroduction(),
isAnswer: true, isAnswer: true,
isOpeningStatement: true, isOpeningStatement: true,
suggestedQuestions,
}] }]
: []) : [])
setIsShowSuggestion(false) setIsShowSuggestion(false)
@ -150,7 +161,9 @@ const Debug: FC<IDebug> = ({
} }
} }
let hasEmptyInput = '' let hasEmptyInput = ''
const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required }) => { const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required, type }) => {
if (type === 'api')
return false
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) // compatible with old version }) // compatible with old version
@ -178,6 +191,7 @@ const Debug: FC<IDebug> = ({
const doShowSuggestion = isShowSuggestion && !isResponsing const doShowSuggestion = isShowSuggestion && !isResponsing
const [suggestQuestions, setSuggestQuestions] = useState<string[]>([]) const [suggestQuestions, setSuggestQuestions] = useState<string[]>([])
const [userQuery, setUserQuery] = useState('')
const onSend = async (message: string, files?: VisionFile[]) => { const onSend = async (message: string, files?: VisionFile[]) => {
if (isResponsing) { if (isResponsing) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
@ -196,7 +210,28 @@ const Debug: FC<IDebug> = ({
}, },
})) }))
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
const updateCurrentQA = ({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
}: {
responseItem: IChatItem
questionId: string
placeholderAnswerId: string
questionItem: IChatItem
}) => {
// closesure new list is outdated.
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({ ...responseItem })
})
setChatList(newListWithAnswer)
}
const postModelConfig: BackendModelConfig = { const postModelConfig: BackendModelConfig = {
pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '', pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '',
prompt_type: promptMode, prompt_type: promptMode,
@ -212,10 +247,9 @@ const Debug: FC<IDebug> = ({
speech_to_text: speechToTextConfig, speech_to_text: speechToTextConfig,
retriever_resource: citationConfig, retriever_resource: citationConfig,
sensitive_word_avoidance: moderationConfig, sensitive_word_avoidance: moderationConfig,
external_data_tools: externalDataToolsConfig,
agent_mode: { agent_mode: {
enabled: true, ...modelConfig.agentConfig,
tools: [...postDatasets], strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
}, },
model: { model: {
provider: modelConfig.provider, provider: modelConfig.provider,
@ -223,7 +257,12 @@ const Debug: FC<IDebug> = ({
mode: modelConfig.mode, mode: modelConfig.mode,
completion_params: completionParams as any, completion_params: completionParams as any,
}, },
dataset_configs: datasetConfigs, dataset_configs: {
...datasetConfigs,
datasets: {
datasets: [...postDatasets],
} as any,
},
file_upload: { file_upload: {
image: visionConfig, image: visionConfig,
}, },
@ -273,12 +312,17 @@ const Debug: FC<IDebug> = ({
const newList = [...getChatList(), questionItem, placeholderAnswerItem] const newList = [...getChatList(), questionItem, placeholderAnswerItem]
setChatList(newList) setChatList(newList)
let isAgentMode = false
// answer // answer
const responseItem: IChatItem = { const responseItem: IChatItem = {
id: `${Date.now()}`, id: `${Date.now()}`,
content: '', content: '',
agent_thoughts: [],
message_files: [],
isAnswer: true, isAnswer: true,
} }
let hasSetResponseId = false
let _newConversationId: null | string = null let _newConversationId: null | string = null
@ -290,25 +334,32 @@ const Debug: FC<IDebug> = ({
setAbortController(abortController) setAbortController(abortController)
}, },
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
responseItem.content = responseItem.content + message // console.log('onData', message)
if (!isAgentMode) {
responseItem.content = responseItem.content + message
}
else {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
if (lastThought)
lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
}
if (messageId && !hasSetResponseId) {
responseItem.id = messageId
hasSetResponseId = true
}
if (isFirstMessage && newConversationId) { if (isFirstMessage && newConversationId) {
setConversationId(newConversationId) setConversationId(newConversationId)
_newConversationId = newConversationId _newConversationId = newConversationId
} }
setMessageTaskId(taskId) setMessageTaskId(taskId)
if (messageId)
responseItem.id = messageId
// closesure new list is outdated. updateCurrentQA({
const newListWithAnswer = produce( responseItem,
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), questionId,
(draft) => { placeholderAnswerId,
if (!draft.find(item => item.id === questionId)) questionItem,
draft.push({ ...questionItem }) })
draft.push({ ...responseItem })
})
setChatList(newListWithAnswer)
}, },
async onCompleted(hasError?: boolean) { async onCompleted(hasError?: boolean) {
setResponsingFalse() setResponsingFalse()
@ -346,6 +397,45 @@ const Debug: FC<IDebug> = ({
setIsShowSuggestion(true) setIsShowSuggestion(true)
} }
}, },
onFile(file) {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
if (lastThought)
responseItem.agent_thoughts![responseItem.agent_thoughts!.length - 1].message_files = [...(lastThought as any).message_files, file]
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
onThought(thought) {
isAgentMode = true
const response = responseItem as any
if (thought.message_id && !hasSetResponseId)
response.id = thought.message_id
if (response.agent_thoughts.length === 0) {
response.agent_thoughts.push(thought)
}
else {
const lastThought = response.agent_thoughts[response.agent_thoughts.length - 1]
// thought changed but still the same thought, so update.
if (lastThought.id === thought.id) {
thought.thought = lastThought.thought
thought.message_files = lastThought.message_files
responseItem.agent_thoughts![response.agent_thoughts.length - 1] = thought
}
else {
responseItem.agent_thoughts!.push(thought)
}
}
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
onMessageEnd: (messageEnd) => { onMessageEnd: (messageEnd) => {
if (messageEnd.metadata?.annotation_reply) { if (messageEnd.metadata?.annotation_reply) {
responseItem.id = messageEnd.id responseItem.id = messageEnd.id
@ -435,19 +525,23 @@ const Debug: FC<IDebug> = ({
speech_to_text: speechToTextConfig, speech_to_text: speechToTextConfig,
retriever_resource: citationConfig, retriever_resource: citationConfig,
sensitive_word_avoidance: moderationConfig, sensitive_word_avoidance: moderationConfig,
external_data_tools: externalDataToolsConfig,
more_like_this: moreLikeThisConfig, more_like_this: moreLikeThisConfig,
agent_mode: {
enabled: true,
tools: [...postDatasets],
},
model: { model: {
provider: modelConfig.provider, provider: modelConfig.provider,
name: modelConfig.model_id, name: modelConfig.model_id,
mode: modelConfig.mode, mode: modelConfig.mode,
completion_params: completionParams as any, completion_params: completionParams as any,
}, },
dataset_configs: datasetConfigs, agent_mode: {
enabled: false,
tools: [],
},
dataset_configs: {
...datasetConfigs,
datasets: {
datasets: [...postDatasets],
} as any,
},
file_upload: { file_upload: {
image: visionConfig, image: visionConfig,
}, },
@ -506,6 +600,14 @@ const Debug: FC<IDebug> = ({
} }
}) })
const allToolIcons = (() => {
const icons: Record<string, any> = {}
modelConfig.agentConfig.tools?.forEach((item: any) => {
icons[item.tool_name] = collectionList.find((collection: any) => collection.id === item.provider_id)?.icon
})
return icons
})()
return ( return (
<> <>
<div className="shrink-0"> <div className="shrink-0">
@ -539,6 +641,8 @@ const Debug: FC<IDebug> = ({
<div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}> <div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}>
<Chat <Chat
chatList={chatList} chatList={chatList}
query={userQuery}
onQueryChange={setUserQuery}
onSend={onSend} onSend={onSend}
checkCanSend={checkCanSend} checkCanSend={checkCanSend}
feedbackDisabled feedbackDisabled
@ -563,6 +667,7 @@ const Debug: FC<IDebug> = ({
supportAnnotation supportAnnotation
appId={appId} appId={appId}
onChatListChange={setChatList} onChatListChange={setChatList}
allToolIcons={allToolIcons}
/> />
</div> </div>
</div> </div>

View File

@ -7,6 +7,7 @@ import { useContext } from 'use-context-selector'
import produce from 'immer' import produce from 'immer'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { ReactSortable } from 'react-sortablejs'
import ConfigContext from '@/context/debug-configuration' import ConfigContext from '@/context/debug-configuration'
import Panel from '@/app/components/app/configuration/base/feature-panel' import Panel from '@/app/components/app/configuration/base/feature-panel'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
@ -15,11 +16,16 @@ import { getInputKeys } from '@/app/components/base/block-input'
import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var' import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var'
import { getNewVar } from '@/utils/var' import { getNewVar } from '@/utils/var'
import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight' import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
import { Plus, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
const MAX_QUESTION_NUM = 3
export type IOpeningStatementProps = { export type IOpeningStatementProps = {
value: string value: string
readonly?: boolean readonly?: boolean
onChange?: (value: string) => void onChange?: (value: string) => void
suggestedQuestions?: string[]
onSuggestedQuestionsChange?: (value: string[]) => void
} }
// regex to match the {{}} and replace it with a span // regex to match the {{}} and replace it with a span
@ -29,6 +35,8 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
value = '', value = '',
readonly, readonly,
onChange, onChange,
suggestedQuestions = [],
onSuggestedQuestionsChange = () => { },
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { const {
@ -42,6 +50,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
const inputRef = useRef<HTMLTextAreaElement>(null) const inputRef = useRef<HTMLTextAreaElement>(null)
const [isFocus, { setTrue: didSetFocus, setFalse: setBlur }] = useBoolean(false) const [isFocus, { setTrue: didSetFocus, setFalse: setBlur }] = useBoolean(false)
const setFocus = () => { const setFocus = () => {
didSetFocus() didSetFocus()
setTimeout(() => { setTimeout(() => {
@ -58,6 +67,8 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
setTempValue(value || '') setTempValue(value || '')
}, [value]) }, [value])
const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(suggestedQuestions || [])
const coloredContent = (tempValue || '') const coloredContent = (tempValue || '')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
@ -75,6 +86,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
const handleCancel = () => { const handleCancel = () => {
setBlur() setBlur()
setTempValue(value) setTempValue(value)
setTempSuggestedQuestions(suggestedQuestions)
} }
const handleConfirm = () => { const handleConfirm = () => {
@ -97,6 +109,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
} }
setBlur() setBlur()
onChange?.(tempValue) onChange?.(tempValue)
onSuggestedQuestionsChange(tempSuggestedQuestions)
} }
const cancelAutoAddVar = () => { const cancelAutoAddVar = () => {
@ -107,7 +120,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
const autoAddVar = () => { const autoAddVar = () => {
const newModelConfig = produce(modelConfig, (draft) => { const newModelConfig = produce(modelConfig, (draft) => {
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key))] draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key, 'string'))]
}) })
onChange?.(tempValue) onChange?.(tempValue)
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
@ -116,12 +129,99 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
} }
const headerRight = !readonly ? ( const headerRight = !readonly ? (
<OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpner') as string} onClick={handleEdit} /> isFocus ? (
<div className='flex items-center space-x-1'>
<div className='px-3 leading-[18px] text-xs font-medium text-gray-700 cursor-pointer' onClick={handleCancel}>{t('common.operation.cancel')}</div>
<Button className='!h-8 !px-3 text-xs' onClick={handleConfirm} type="primary">{t('common.operation.save')}</Button>
</div>
) : (
<OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpner') as string} onClick={handleEdit} />
)
) : null ) : null
const renderQuestions = () => {
return isFocus ? (
<div>
<div className='flex items-center py-2'>
<div className='shrink-0 flex space-x-0.5 leading-[18px] text-xs font-medium text-gray-500'>
<div className='uppercase'>{t('appDebug.openingStatement.openingQuestion')}</div>
<div>·</div>
<div>{tempSuggestedQuestions.length}/{MAX_QUESTION_NUM}</div>
</div>
<div className='ml-3 grow w-0 h-px bg-[#243, 244, 246]'></div>
</div>
<ReactSortable
className="space-y-1"
list={tempSuggestedQuestions.map((name, index) => {
return {
id: index,
name,
}
})}
setList={list => setTempSuggestedQuestions(list.map(item => item.name))}
handle='.handle'
ghostClass="opacity-50"
animation={150}
>
{tempSuggestedQuestions.map((question, index) => {
return (
<div className='group relative rounded-lg border border-gray-200 flex items-center pl-2.5 hover:border-gray-300 hover:bg-white' key={index}>
<div className='handle flex items-center justify-center w-4 h-4 cursor-grab'>
<svg width="6" height="10" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M1 2C1.55228 2 2 1.55228 2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2ZM1 6C1.55228 6 2 5.55228 2 5C2 4.44772 1.55228 4 1 4C0.447715 4 0 4.44772 0 5C0 5.55228 0.447715 6 1 6ZM6 1C6 1.55228 5.55228 2 5 2C4.44772 2 4 1.55228 4 1C4 0.447715 4.44772 0 5 0C5.55228 0 6 0.447715 6 1ZM5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6ZM2 9C2 9.55229 1.55228 10 1 10C0.447715 10 0 9.55229 0 9C0 8.44771 0.447715 8 1 8C1.55228 8 2 8.44771 2 9ZM5 10C5.55228 10 6 9.55229 6 9C6 8.44771 5.55228 8 5 8C4.44772 8 4 8.44771 4 9C4 9.55229 4.44772 10 5 10Z" fill="#98A2B3" />
</svg>
</div>
<input
type="input"
value={question || ''}
onChange={(e) => {
const value = e.target.value
setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => {
if (index === i)
return value
return item
}))
}}
className={'w-full overflow-x-auto pl-1.5 pr-8 text-sm leading-9 text-gray-900 border-0 grow h-9 bg-transparent focus:outline-none cursor-pointer rounded-lg'}
/>
<div
className='block absolute top-1/2 translate-y-[-50%] right-1.5 p-1 rounded-md cursor-pointer hover:bg-[#FEE4E2] hover:text-[#D92D20]'
onClick={() => {
setTempSuggestedQuestions(tempSuggestedQuestions.filter((_, i) => index !== i))
}}
>
<Trash03 className='w-3.5 h-3.5' />
</div>
</div>
)
})}</ReactSortable>
{tempSuggestedQuestions.length < MAX_QUESTION_NUM && (
<div
onClick={() => { setTempSuggestedQuestions([...tempSuggestedQuestions, '']) }}
className='mt-1 flex items-center h-9 px-3 gap-2 rounded-lg cursor-pointer text-gray-400 bg-gray-100 hover:bg-gray-200'>
<Plus className='w-4 h-4'></Plus>
<div className='text-gray-500 text-[13px]'>{t('appDebug.variableConig.addOption')}</div>
</div>
)}
</div>
) : (
<div className='mt-1.5 flex flex-wrap'>
{tempSuggestedQuestions.map((question, index) => {
return (
<div key={index} className='mt-1 mr-1 max-w-full truncate last:mr-0 shrink-0 leading-8 items-center px-2.5 rounded-lg border border-gray-200 shadow-xs bg-white text-[13px] font-normal text-gray-900 cursor-pointer'>
{question}
</div>
)
})}
</div>
)
}
return ( return (
<Panel <Panel
className={cn(isShowConfirmAddVar && 'h-[220px]', 'relative mt-4')} className={cn(isShowConfirmAddVar && 'h-[220px]', 'relative mt-4 !bg-gray-25')}
title={t('appDebug.openingStatement.title')} title={t('appDebug.openingStatement.title')}
headerIcon={ headerIcon={
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -137,34 +237,24 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
<> <>
{isFocus {isFocus
? ( ? (
<textarea <div>
ref={inputRef} <textarea
value={tempValue} ref={inputRef}
rows={3} value={tempValue}
onChange={e => setTempValue(e.target.value)} rows={3}
className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none " onChange={e => setTempValue(e.target.value)}
placeholder={t('appDebug.openingStatement.placeholder') as string} className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none "
> placeholder={t('appDebug.openingStatement.placeholder') as string}
</textarea> >
</textarea>
</div>
) )
: ( : (
<div dangerouslySetInnerHTML={{ <div dangerouslySetInnerHTML={{
__html: coloredContent, __html: coloredContent,
}}></div> }}></div>
)} )}
{renderQuestions()}
{/* Operation Bar */}
{isFocus && (
<div className='mt-2 flex items-center justify-between'>
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div>
<div className='flex gap-2'>
<Button className='!h-8 text-sm' onClick={handleCancel}>{t('common.operation.cancel')}</Button>
<Button className='!h-8 text-sm' onClick={handleConfirm} type="primary">{t('common.operation.save')}</Button>
</div>
</div>
)}
</>) : ( </>) : (
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div> <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div>
)} )}

View File

@ -11,9 +11,9 @@ import { clone, isEqual } from 'lodash-es'
import { CodeBracketIcon } from '@heroicons/react/20/solid' import { CodeBracketIcon } from '@heroicons/react/20/solid'
import Button from '../../base/button' import Button from '../../base/button'
import Loading from '../../base/loading' import Loading from '../../base/loading'
import s from './style.module.css'
import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config' import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config'
import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal' import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal'
import AssistantTypePicker from './config/assistant-type-picker'
import type { import type {
AnnotationReplyConfig, AnnotationReplyConfig,
DatasetConfigs, DatasetConfigs,
@ -37,10 +37,9 @@ import { fetchAppDetail, updateAppModelConfig } from '@/service/apps'
import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from '@/utils/model-config' import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from '@/utils/model-config'
import { fetchDatasets } from '@/service/datasets' import { fetchDatasets } from '@/service/datasets'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app' import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app'
import { FlipBackward } from '@/app/components/base/icons/src/vender/line/arrows'
import { PromptMode } from '@/models/debug' import { PromptMode } from '@/models/debug'
import { ANNOTATION_DEFAULT, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset' import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset'
import I18n from '@/context/i18n' import I18n from '@/context/i18n'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
@ -49,6 +48,8 @@ import Drawer from '@/app/components/base/drawer'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { fetchCollectionList } from '@/service/tools'
import { type Collection } from '@/app/components/tools/types'
type PublichConfig = { type PublichConfig = {
modelConfig: ModelConfig modelConfig: ModelConfig
@ -75,6 +76,7 @@ const Configuration: FC = () => {
const [isShowDebugPanel, { setTrue: showDebugPanel, setFalse: hideDebugPanel }] = useBoolean(false) const [isShowDebugPanel, { setTrue: showDebugPanel, setFalse: hideDebugPanel }] = useBoolean(false)
const [introduction, setIntroduction] = useState<string>('') const [introduction, setIntroduction] = useState<string>('')
const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([])
const [controlClearChatMessage, setControlClearChatMessage] = useState(0) const [controlClearChatMessage, setControlClearChatMessage] = useState(0)
const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({ const [prevPromptConfig, setPrevPromptConfig] = useState<PromptConfig>({
prompt_template: '', prompt_template: '',
@ -141,8 +143,23 @@ const Configuration: FC = () => {
retriever_resource: null, retriever_resource: null,
sensitive_word_avoidance: null, sensitive_word_avoidance: null,
dataSets: [], dataSets: [],
agentConfig: DEFAULT_AGENT_SETTING,
}) })
const isChatApp = mode === AppType.chat
const isAgent = modelConfig.agentConfig?.enabled
const setIsAgent = (value: boolean) => {
const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.agentConfig.enabled = value
})
doSetModelConfig(newModelConfig)
}
const isOpenAI = modelConfig.provider === 'openai'
const isFunctionCall = isOpenAI && modelConfig.mode === ModelModeType.chat
const [collectionList, setCollectionList] = useState<Collection[]>([])
useEffect(() => {
}, [])
const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>({ const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>({
retrieval_model: RETRIEVE_TYPE.oneWay, retrieval_model: RETRIEVE_TYPE.oneWay,
reranking_model: { reranking_model: {
@ -152,6 +169,9 @@ const Configuration: FC = () => {
top_k: 2, top_k: 2,
score_threshold_enabled: false, score_threshold_enabled: false,
score_threshold: 0.7, score_threshold: 0.7,
datasets: {
datasets: [],
},
}) })
const setModelConfig = (newModelConfig: ModelConfig) => { const setModelConfig = (newModelConfig: ModelConfig) => {
@ -165,7 +185,7 @@ const Configuration: FC = () => {
}, [modelModeType]) }, [modelModeType])
const [dataSets, setDataSets] = useState<DataSet[]>([]) const [dataSets, setDataSets] = useState<DataSet[]>([])
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key const contextVar = modelConfig.configs.prompt_variables.find((item: any) => item.is_context_var)?.key
const hasSetContextVar = !!contextVar const hasSetContextVar = !!contextVar
const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false) const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false)
const selectedIds = dataSets.map(item => item.id) const selectedIds = dataSets.map(item => item.id)
@ -335,83 +355,131 @@ const Configuration: FC = () => {
} }
useEffect(() => { useEffect(() => {
fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => { (async () => {
setMode(res.mode) const collectionList = await fetchCollectionList() as Collection[]
const modelConfig = res.model_config setCollectionList(collectionList)
const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => {
doSetPromptMode(promptMode) setMode(res.mode)
if (promptMode === PromptMode.advanced) { const modelConfig = res.model_config
setChatPromptConfig(modelConfig.chat_prompt_config || clone(DEFAULT_CHAT_PROMPT_CONFIG) as any) const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple
setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any) doSetPromptMode(promptMode)
setCanReturnToSimpleMode(false) if (promptMode === PromptMode.advanced) {
} setChatPromptConfig(modelConfig.chat_prompt_config || clone(DEFAULT_CHAT_PROMPT_CONFIG) as any)
setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any)
setCanReturnToSimpleMode(false)
}
const model = res.model_config.model const model = res.model_config.model
let datasets: any = null let datasets: any = null
if (modelConfig.agent_mode?.enabled) // old dataset struct
datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled) if (modelConfig.agent_mode?.tools?.find(({ dataset }: any) => dataset?.enabled))
datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled)
// new dataset struct
else if (modelConfig.dataset_configs.datasets?.datasets?.length > 0)
datasets = modelConfig.dataset_configs?.datasets?.datasets
if (dataSets && datasets?.length && datasets?.length > 0) { if (dataSets && datasets?.length && datasets?.length > 0) {
const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } }) const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } })
datasets = dataSetsWithDetail datasets = dataSetsWithDetail
setDataSets(datasets) setDataSets(datasets)
} }
setIntroduction(modelConfig.opening_statement) setIntroduction(modelConfig.opening_statement)
if (modelConfig.more_like_this) setSuggestedQuestions(modelConfig.suggested_questions || [])
setMoreLikeThisConfig(modelConfig.more_like_this) if (modelConfig.more_like_this)
setMoreLikeThisConfig(modelConfig.more_like_this)
if (modelConfig.suggested_questions_after_answer) if (modelConfig.suggested_questions_after_answer)
setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer) setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer)
if (modelConfig.speech_to_text) if (modelConfig.speech_to_text)
setSpeechToTextConfig(modelConfig.speech_to_text) setSpeechToTextConfig(modelConfig.speech_to_text)
if (modelConfig.retriever_resource) if (modelConfig.retriever_resource)
setCitationConfig(modelConfig.retriever_resource) setCitationConfig(modelConfig.retriever_resource)
if (modelConfig.annotation_reply) if (modelConfig.annotation_reply)
setAnnotationConfig(modelConfig.annotation_reply, true) setAnnotationConfig(modelConfig.annotation_reply, true)
if (modelConfig.sensitive_word_avoidance) if (modelConfig.sensitive_word_avoidance)
setModerationConfig(modelConfig.sensitive_word_avoidance) setModerationConfig(modelConfig.sensitive_word_avoidance)
if (modelConfig.external_data_tools) if (modelConfig.external_data_tools)
setExternalDataToolsConfig(modelConfig.external_data_tools) setExternalDataToolsConfig(modelConfig.external_data_tools)
const config = { const config = {
modelConfig: { modelConfig: {
provider: model.provider, provider: model.provider,
model_id: model.name, model_id: model.name,
mode: model.mode, mode: model.mode,
configs: { configs: {
prompt_template: modelConfig.pre_prompt, prompt_template: modelConfig.pre_prompt,
prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form, modelConfig.dataset_query_variable), prompt_variables: userInputsFormToPromptVariables(
[
...modelConfig.user_input_form,
...(
modelConfig.external_data_tools?.length
? modelConfig.external_data_tools.map((item: any) => {
return {
external_data_tool: {
variable: item.variable as string,
label: item.label as string,
enabled: item.enabled,
type: item.type as string,
config: item.config,
required: true,
icon: item.icon,
icon_background: item.icon_background,
},
}
})
: []
),
],
modelConfig.dataset_query_variable,
),
},
opening_statement: modelConfig.opening_statement,
more_like_this: modelConfig.more_like_this,
suggested_questions_after_answer: modelConfig.suggested_questions_after_answer,
speech_to_text: modelConfig.speech_to_text,
retriever_resource: modelConfig.retriever_resource,
sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
external_data_tools: modelConfig.external_data_tools,
dataSets: datasets || [],
// eslint-disable-next-line multiline-ternary
agentConfig: res.is_agent ? {
max_iteration: DEFAULT_AGENT_SETTING.max_iteration,
...modelConfig.agent_mode,
// remove dataset
enabled: true, // modelConfig.agent_mode?.enabled is not correct. old app: the value of app with dataset's is always true
tools: modelConfig.agent_mode?.tools.filter((tool: any) => {
return !tool.dataset
}).map((tool: any) => {
return {
...tool,
isDeleted: res.deleted_tools?.includes(tool.tool_name),
notAuthor: collectionList.find(c => tool.provider_id === c.id)?.is_team_authorization === false,
}
}),
} : DEFAULT_AGENT_SETTING,
}, },
opening_statement: modelConfig.opening_statement, completionParams: model.completion_params,
more_like_this: modelConfig.more_like_this, }
suggested_questions_after_answer: modelConfig.suggested_questions_after_answer,
speech_to_text: modelConfig.speech_to_text,
retriever_resource: modelConfig.retriever_resource,
sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
external_data_tools: modelConfig.external_data_tools,
dataSets: datasets || [],
},
completionParams: model.completion_params,
}
if (modelConfig.file_upload) if (modelConfig.file_upload)
setVisionConfig(modelConfig.file_upload.image, true) setVisionConfig(modelConfig.file_upload.image, true)
syncToPublishedConfig(config) syncToPublishedConfig(config)
setPublishedConfig(config) setPublishedConfig(config)
setDatasetConfigs({ setDatasetConfigs({
retrieval_model: RETRIEVE_TYPE.oneWay, retrieval_model: RETRIEVE_TYPE.oneWay,
...modelConfig.dataset_configs, ...modelConfig.dataset_configs,
})
setHasFetchedDetail(true)
}) })
setHasFetchedDetail(true) })()
})
}, [appId]) }, [appId])
const promptEmpty = (() => { const promptEmpty = (() => {
@ -420,7 +488,7 @@ const Configuration: FC = () => {
if (isAdvancedMode) { if (isAdvancedMode) {
if (modelModeType === ModelModeType.chat) if (modelModeType === ModelModeType.chat)
return chatPromptConfig.prompt.every(({ text }) => !text) return chatPromptConfig.prompt.every(({ text }: any) => !text)
else else
return !completionPromptConfig.prompt.text return !completionPromptConfig.prompt.text
@ -487,15 +555,15 @@ const Configuration: FC = () => {
user_input_form: promptVariablesToUserInputsForm(promptVariables), user_input_form: promptVariablesToUserInputsForm(promptVariables),
dataset_query_variable: contextVar || '', dataset_query_variable: contextVar || '',
opening_statement: introduction || '', opening_statement: introduction || '',
suggested_questions: suggestedQuestions || [],
more_like_this: moreLikeThisConfig, more_like_this: moreLikeThisConfig,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig, suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
speech_to_text: speechToTextConfig, speech_to_text: speechToTextConfig,
retriever_resource: citationConfig, retriever_resource: citationConfig,
sensitive_word_avoidance: moderationConfig, sensitive_word_avoidance: moderationConfig,
external_data_tools: externalDataToolsConfig,
agent_mode: { agent_mode: {
enabled: true, ...modelConfig.agentConfig,
tools: [...postDatasets], strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
}, },
model: { model: {
provider: modelConfig.provider, provider: modelConfig.provider,
@ -503,7 +571,12 @@ const Configuration: FC = () => {
mode: modelConfig.mode, mode: modelConfig.mode,
completion_params: completionParams as any, completion_params: completionParams as any,
}, },
dataset_configs: datasetConfigs, dataset_configs: {
...datasetConfigs,
datasets: {
datasets: [...postDatasets],
} as any,
},
file_upload: { file_upload: {
image: visionConfig, image: visionConfig,
}, },
@ -558,6 +631,10 @@ const Configuration: FC = () => {
modelModeType, modelModeType,
promptMode, promptMode,
isAdvancedMode, isAdvancedMode,
isAgent,
isOpenAI,
isFunctionCall,
collectionList,
setPromptMode, setPromptMode,
canReturnToSimpleMode, canReturnToSimpleMode,
setCanReturnToSimpleMode, setCanReturnToSimpleMode,
@ -572,6 +649,8 @@ const Configuration: FC = () => {
conversationId, conversationId,
introduction, introduction,
setIntroduction, setIntroduction,
suggestedQuestions,
setSuggestedQuestions,
setConversationId, setConversationId,
controlClearChatMessage, controlClearChatMessage,
setControlClearChatMessage, setControlClearChatMessage,
@ -614,70 +693,73 @@ const Configuration: FC = () => {
> >
<> <>
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className='flex items-center justify-between px-6 shrink-0 py-3 flex-wrap gap-y-2'> <div className='flex grow h-[200px]'>
<div className='flex items-end'> <div className="w-full sm:w-1/2 shrink-0 flex flex-col h-full">
<div className={s.promptTitle}></div> {/* Header Left */}
<div className='flex items-center h-[14px] space-x-1 text-xs'> <div className='flex justify-between items-center px-6 h-14'>
{/* modelModeType missing can not load template */} <div className='flex items-center'>
{(!isAdvancedMode && modelModeType) && ( <div className='leading-6 text-base font-semibold text-gray-900'>{t('appDebug.orchestrate')}</div>
<div <div className='flex items-center h-[14px] space-x-1 text-xs'>
onClick={() => setPromptMode(PromptMode.advanced)} {isAdvancedMode && (
className={'cursor-pointer text-indigo-600'} <div className='ml-1 flex items-center h-5 px-1.5 border border-gray-100 rounded-md text-[11px] font-medium text-gray-500 uppercase'>{t('appDebug.promptMode.advanced')}</div>
>
{t('appDebug.promptMode.simple')}
</div>
)}
{isAdvancedMode && (
<div className='flex items-center space-x-2'>
<div className={cn(locale === 'en' && 'italic', `${s.advancedPromptMode} text-indigo-600`)}>{t('appDebug.promptMode.advanced')}</div>
{canReturnToSimpleMode && (
<div
onClick={() => setPromptMode(PromptMode.simple)}
className='flex items-center h-6 px-2 bg-indigo-600 shadow-xs border border-gray-200 rounded-lg text-white text-xs font-semibold cursor-pointer space-x-1'
>
<FlipBackward className='w-3 h-3 text-white' />
<div className='text-xs font-semibold uppercase'>{t('appDebug.promptMode.switchBack')}</div>
</div>
)} )}
</div> </div>
</div>
{isChatApp && (
<AssistantTypePicker
value={isAgent ? 'agent' : 'assistant'}
disabled={isAdvancedMode && !canReturnToSimpleMode}
onChange={(value: string) => {
setIsAgent(value === 'agent')
if (value === 'agent')
setPromptMode(PromptMode.simple)
}}
isFunctionCall={isFunctionCall}
isChatModel={modelConfig.mode === ModelModeType.chat}
agentConfig={modelConfig.agentConfig}
onAgentSettingChange={(config) => {
const nextConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.agentConfig = config
})
setModelConfig(nextConfig)
}}
/>
)} )}
</div> </div>
</div>
<div className='flex items-center flex-wrap gap-y-2 gap-x-2'>
{/* Model and Parameters */}
<ModelParameterModal
isAdvancedMode={isAdvancedMode}
mode={mode}
provider={modelConfig.provider}
completionParams={completionParams}
modelId={modelConfig.model_id}
setModel={setModel as any}
onCompletionParamsChange={(newParams: FormValue) => {
setCompletionParams(newParams)
}}
/>
<div className='w-[1px] h-[14px] bg-gray-200'></div>
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
{isMobile && (
<Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
<span className='mr-1'>{t('appDebug.operation.debugConfig')}</span>
<CodeBracketIcon className="h-4 w-4 text-gray-500" />
</Button>
)}
<Button type='primary' onClick={() => handlePublish(false)} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
</div>
</div>
<div className='flex grow h-[200px]'>
<div className="w-full sm:w-1/2 shrink-0">
<Config /> <Config />
</div> </div>
{!isMobile && <div className="relative w-1/2 grow h-full overflow-y-auto py-4 px-6 bg-gray-50 flex flex-col rounded-tl-2xl border-t border-l" style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}> {!isMobile && <div className="relative w-1/2 h-full overflow-y-auto flex flex-col " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<Debug {/* Header Right */}
hasSetAPIKEY={hasSettedApiKey} <div className='flex justify-end items-center flex-wrap px-6 h-14 space-x-2'>
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })} {/* Model and Parameters */}
inputs={inputs} <ModelParameterModal
/> isAdvancedMode={isAdvancedMode}
mode={mode}
provider={modelConfig.provider}
completionParams={completionParams}
modelId={modelConfig.model_id}
setModel={setModel as any}
onCompletionParamsChange={(newParams: FormValue) => {
setCompletionParams(newParams)
}}
/>
<div className='w-[1px] h-[14px] bg-gray-200'></div>
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
{isMobile && (
<Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
<span className='mr-1'>{t('appDebug.operation.debugConfig')}</span>
<CodeBracketIcon className="h-4 w-4 text-gray-500" />
</Button>
)}
<Button type='primary' onClick={() => handlePublish(false)} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
</div>
<div className='flex flex-col grow h-0 px-6 py-4 rounded-tl-2xl border-t border-l bg-gray-50 '>
<Debug
hasSetAPIKEY={hasSettedApiKey}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
inputs={inputs}
/>
</div>
</div>} </div>}
</div> </div>
</div> </div>

View File

@ -4,8 +4,15 @@ import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import I18n from '@/context/i18n' import I18n from '@/context/i18n'
import { FlipBackward } from '@/app/components/base/icons/src/vender/line/arrows'
const AdvancedModeWarning: FC = () => { type Props = {
onReturnToSimpleMode: () => void
}
const AdvancedModeWarning: FC<Props> = ({
onReturnToSimpleMode,
}) => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n) const { locale } = useContext(I18n)
const [show, setShow] = React.useState(true) const [show, setShow] = React.useState(true)
@ -26,10 +33,20 @@ const AdvancedModeWarning: FC = () => {
</a> </a>
</div> </div>
<div <div className='flex items-center space-x-1'>
className='flex items-center h-6 px-2 rounded-md bg-[#fff] border border-gray-200 shadow-xs text-xs font-medium text-primary-600 cursor-pointer' <div
onClick={() => setShow(false)} onClick={onReturnToSimpleMode}
>{t('appDebug.promptMode.advancedWarning.ok')}</div> className='shrink-0 flex items-center h-6 px-2 bg-indigo-600 shadow-xs border border-gray-200 rounded-lg text-white text-xs font-semibold cursor-pointer space-x-1'
>
<FlipBackward className='w-3 h-3 text-white' />
<div className='text-xs font-semibold uppercase'>{t('appDebug.promptMode.switchBack')}</div>
</div>
<div
className='flex items-center h-6 px-2 rounded-md bg-[#fff] border border-gray-200 shadow-xs text-xs font-medium text-primary-600 cursor-pointer'
onClick={() => setShow(false)}
>{t('appDebug.promptMode.advancedWarning.ok')}</div>
</div>
</div> </div>
</div> </div>
) )

View File

@ -1,10 +1,3 @@
.promptTitle {
width: 72px;
height: 31px;
background: url(./images/prompt.svg) no-repeat 0 0;
background-size: contain;
}
.advancedPromptMode { .advancedPromptMode {
position: relative; position: relative;
} }
@ -18,4 +11,4 @@
height: 3px; height: 3px;
background-color: rgba(68, 76, 231, 0.18); background-color: rgba(68, 76, 231, 0.18);
transform: skewX(-30deg); transform: skewX(-30deg);
} }

View File

@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next'
import FormGeneration from '../toolbox/moderation/form-generation' import FormGeneration from '../toolbox/moderation/form-generation'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import AppIcon from '@/app/components/base/app-icon'
import EmojiPicker from '@/app/components/base/emoji-picker' import EmojiPicker from '@/app/components/base/emoji-picker'
import ApiBasedExtensionSelector from '@/app/components/header/account-setting/api-based-extension-page/selector' import ApiBasedExtensionSelector from '@/app/components/header/account-setting/api-based-extension-page/selector'
import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education' import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
@ -18,6 +17,7 @@ import type {
ExternalDataTool, ExternalDataTool,
} from '@/models/common' } from '@/models/common'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import AppIcon from '@/app/components/base/app-icon'
const systemTypes = ['api'] const systemTypes = ['api']
type ExternalDataToolModalProps = { type ExternalDataToolModalProps = {
@ -184,7 +184,8 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
return ( return (
<Modal <Modal
isShow isShow
onClose={() => {}} onClose={() => { }}
wrapperClassName='z-[101]'
className='!p-8 !pb-6 !max-w-none !w-[640px]' className='!p-8 !pb-6 !max-w-none !w-[640px]'
> >
<div className='mb-2 text-xl font-semibold text-gray-900'> <div className='mb-2 text-xl font-semibold text-gray-900'>
@ -285,6 +286,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
{ {
showEmojiPicker && ( showEmojiPicker && (
<EmojiPicker <EmojiPicker
className='!z-[200]'
onSelect={(icon, icon_background) => { onSelect={(icon, icon_background) => {
handleValueChange({ icon, icon_background }) handleValueChange({ icon, icon_background })
setShowEmojiPicker(false) setShowEmojiPicker(false)

View File

@ -1,3 +1,4 @@
// abandoned
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'

View File

@ -69,7 +69,7 @@ const Filter: FC<IFilterProps> = ({ appId, queryParams, setQueryParams }: IFilte
type="text" type="text"
name="query" name="query"
className="block w-[240px] bg-gray-100 shadow-sm rounded-md border-0 py-1.5 pl-10 text-gray-900 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none sm:text-sm sm:leading-6" className="block w-[240px] bg-gray-100 shadow-sm rounded-md border-0 py-1.5 pl-10 text-gray-900 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none sm:text-sm sm:leading-6"
placeholder={t('common.operation.search')} placeholder={t('common.operation.search')!}
value={queryParams.keyword} value={queryParams.keyword}
onChange={(e) => { onChange={(e) => {
setQueryParams({ ...queryParams, keyword: e.target.value }) setQueryParams({ ...queryParams, keyword: e.target.value })

View File

@ -34,6 +34,7 @@ import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/comp
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name' import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import TextGeneration from '@/app/components/app/text-generate/item' import TextGeneration from '@/app/components/app/text-generate/item'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
type IConversationList = { type IConversationList = {
logs?: ChatConversationsResponse | CompletionConversationsResponse logs?: ChatConversationsResponse | CompletionConversationsResponse
@ -80,15 +81,17 @@ const getFormattedChatList = (messages: ChatMessage[]) => {
content: item.inputs.query || item.inputs.default_input || item.query, // text generation: item.inputs.query; chat: item.query content: item.inputs.query || item.inputs.default_input || item.query, // text generation: item.inputs.query; chat: item.query
isAnswer: false, isAnswer: false,
log: item.message as any, log: item.message as any,
message_files: item.message_files, message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [],
}) })
newChatList.push({ newChatList.push({
id: item.id, id: item.id,
content: item.answer, content: item.answer,
agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
feedback: item.feedbacks.find(item => item.from_source === 'user'), // user feedback feedback: item.feedbacks.find(item => item.from_source === 'user'), // user feedback
adminFeedback: item.feedbacks.find(item => item.from_source === 'admin'), // admin feedback adminFeedback: item.feedbacks.find(item => item.from_source === 'admin'), // admin feedback
feedbackDisabled: false, feedbackDisabled: false,
isAnswer: true, isAnswer: true,
message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
more: { more: {
time: dayjs.unix(item.created_at).format('hh:mm A'), time: dayjs.unix(item.created_at).format('hh:mm A'),
tokens: item.answer_tokens + item.message_tokens, tokens: item.answer_tokens + item.message_tokens,

View File

@ -8,7 +8,7 @@ import style from './style.module.css'
init({ data }) init({ data })
export type AppIconProps = { export type AppIconProps = {
size?: 'tiny' | 'small' | 'medium' | 'large' size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'
rounded?: boolean rounded?: boolean
icon?: string icon?: string
background?: string background?: string

View File

@ -1,15 +1,23 @@
.appIcon { .appIcon {
@apply flex items-center justify-center relative w-9 h-9 text-lg bg-teal-100 rounded-lg grow-0 shrink-0; @apply flex items-center justify-center relative w-9 h-9 text-lg bg-teal-100 rounded-lg grow-0 shrink-0;
} }
.appIcon.large { .appIcon.large {
@apply w-10 h-10; @apply w-10 h-10;
} }
.appIcon.small { .appIcon.small {
@apply w-8 h-8; @apply w-8 h-8;
} }
.appIcon.tiny { .appIcon.tiny {
@apply w-6 h-6 text-base; @apply w-6 h-6 text-base;
} }
.appIcon.xs {
@apply w-3 h-3 text-base;
}
.appIcon.rounded { .appIcon.rounded {
@apply rounded-full; @apply rounded-full;
} }

View File

@ -1,6 +1,7 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useRef } from 'react' import React, { useRef } from 'react'
import cn from 'classnames'
import Drawer from '@/app/components/base/drawer' import Drawer from '@/app/components/base/drawer'
import { XClose } from '@/app/components/base/icons/src/vender/line/general' import { XClose } from '@/app/components/base/icons/src/vender/line/general'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
@ -8,21 +9,33 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
type Props = { type Props = {
isShow: boolean isShow: boolean
onHide: () => void onHide: () => void
panelClassName?: string
maxWidthClassName?: string maxWidthClassName?: string
contentClassName?: string
headerClassName?: string
height?: number | string height?: number | string
title: string | JSX.Element title: string | JSX.Element
titleDescription?: string | JSX.Element
body: JSX.Element body: JSX.Element
foot?: JSX.Element foot?: JSX.Element
isShowMask?: boolean
clickOutsideNotOpen?: boolean
} }
const DrawerPlus: FC<Props> = ({ const DrawerPlus: FC<Props> = ({
isShow, isShow,
onHide, onHide,
panelClassName = '',
maxWidthClassName = '!max-w-[640px]', maxWidthClassName = '!max-w-[640px]',
height = 'calc(100vh - 72px)', height = 'calc(100vh - 72px)',
contentClassName,
headerClassName,
title, title,
titleDescription,
body, body,
foot, foot,
isShowMask,
clickOutsideNotOpen = true,
}) => { }) => {
const ref = useRef(null) const ref = useRef(null)
const media = useBreakpoints() const media = useBreakpoints()
@ -33,26 +46,33 @@ const DrawerPlus: FC<Props> = ({
return ( return (
// clickOutsideNotOpen to fix confirm modal click cause drawer close // clickOutsideNotOpen to fix confirm modal click cause drawer close
<Drawer isOpen={isShow} clickOutsideNotOpen onClose={onHide} footer={null} mask={isMobile} panelClassname={`mt-16 mx-2 sm:mr-2 mb-3 !p-0 ${maxWidthClassName} rounded-xl`}> <Drawer isOpen={isShow} clickOutsideNotOpen={clickOutsideNotOpen} onClose={onHide} footer={null} mask={isMobile || isShowMask} panelClassname={`mt-16 mx-2 sm:mr-2 mb-3 !p-0 ${panelClassName} ${maxWidthClassName} rounded-xl`}>
<div <div
className='w-full flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl' className={cn(contentClassName, 'w-full flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl')}
style={{ style={{
height, height,
}} }}
ref={ref} ref={ref}
> >
<div className='shrink-0 flex justify-between items-center pl-6 pr-5 h-14 border-b border-b-gray-100'> <div className={cn(headerClassName, 'shrink-0 border-b border-b-gray-100 py-4')}>
<div className='text-base font-semibold text-gray-900'> <div className='flex justify-between items-center pl-6 pr-5 h-6'>
{title} <div className='text-base font-semibold text-gray-900'>
</div> {title}
<div className='flex items-center'> </div>
<div <div className='flex items-center'>
onClick={onHide} <div
className='flex justify-center items-center w-6 h-6 cursor-pointer' onClick={onHide}
> className='flex justify-center items-center w-6 h-6 cursor-pointer'
<XClose className='w-4 h-4 text-gray-500' /> >
<XClose className='w-4 h-4 text-gray-500' />
</div>
</div> </div>
</div> </div>
{titleDescription && (
<div className='pl-6 pr-10 leading-[18px] text-xs font-normal text-gray-500'>
{titleDescription}
</div>
)}
</div> </div>
<div className='grow overflow-y-auto'> <div className='grow overflow-y-auto'>
{body} {body}

View File

@ -65,12 +65,14 @@ type IEmojiPickerProps = {
isModal?: boolean isModal?: boolean
onSelect?: (emoji: string, background: string) => void onSelect?: (emoji: string, background: string) => void
onClose?: () => void onClose?: () => void
className?: string
} }
const EmojiPicker: FC<IEmojiPickerProps> = ({ const EmojiPicker: FC<IEmojiPickerProps> = ({
isModal = true, isModal = true,
onSelect, onSelect,
onClose, onClose,
className,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { categories } = data as EmojiMartData const { categories } = data as EmojiMartData
@ -84,7 +86,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
onClose={() => { }} onClose={() => { }}
isShow isShow
closable={false} closable={false}
wrapperClassName='!z-40' wrapperClassName={`!z-40 ${className}`}
className={cn(s.container, '!w-[362px] !p-0')} className={cn(s.container, '!w-[362px] !p-0')}
> >
<div className='flex flex-col items-center w-full p-3'> <div className='flex flex-col items-center w-full p-3'>

View File

@ -0,0 +1,3 @@
<svg width="7" height="20" viewBox="0 0 7 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Line 3" d="M1 19.3544L5.94174 0.645657" stroke="#EAECF0" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 193 B

View File

@ -0,0 +1,13 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6139_55194)">
<path d="M8 0.5C6.4467 0.5 5.1875 1.7592 5.1875 3.3125C5.1875 4.8658 6.4467 6.125 8 6.125C9.5533 6.125 10.8125 4.8658 10.8125 3.3125C10.8125 1.7592 9.5533 0.5 8 0.5Z" fill="#155EEF"/>
<path d="M15.5 8C15.5 6.4467 14.2408 5.1875 12.6875 5.1875C11.1342 5.1875 9.875 6.4467 9.875 8C9.875 9.5533 11.1342 10.8125 12.6875 10.8125C14.2408 10.8125 15.5 9.5533 15.5 8Z" fill="#155EEF"/>
<path d="M8 9.875C6.4467 9.875 5.1875 11.1342 5.1875 12.6875C5.1875 14.2408 6.4467 15.5 8 15.5C9.5533 15.5 10.8125 14.2408 10.8125 12.6875C10.8125 11.1342 9.5533 9.875 8 9.875Z" fill="#155EEF"/>
<path d="M6.125 8C6.125 6.4467 4.8658 5.1875 3.3125 5.1875C1.7592 5.1875 0.5 6.4467 0.5 8C0.5 9.5533 1.7592 10.8125 3.3125 10.8125C4.8658 10.8125 6.125 9.5533 6.125 8Z" fill="#155EEF"/>
</g>
<defs>
<clipPath id="clip0_6139_55194">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1010 B

View File

@ -0,0 +1,13 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6139_55192)">
<path d="M10.25 3.3125C10.25 4.55514 9.24264 5.5625 8 5.5625C6.75736 5.5625 5.75 4.55514 5.75 3.3125C5.75 2.06986 6.75736 1.0625 8 1.0625C9.24264 1.0625 10.25 2.06986 10.25 3.3125Z" stroke="#667085" stroke-width="1.5" stroke-linecap="square"/>
<path d="M12.6875 10.25C11.4449 10.25 10.4375 9.24264 10.4375 8C10.4375 6.75736 11.4449 5.75 12.6875 5.75C13.9301 5.75 14.9375 6.75736 14.9375 8C14.9375 9.24264 13.9301 10.25 12.6875 10.25Z" stroke="#667085" stroke-width="1.5" stroke-linecap="square"/>
<path d="M10.25 12.6875C10.25 13.9301 9.24264 14.9375 8 14.9375C6.75736 14.9375 5.75 13.9301 5.75 12.6875C5.75 11.4449 6.75736 10.4375 8 10.4375C9.24264 10.4375 10.25 11.4449 10.25 12.6875Z" stroke="#667085" stroke-width="1.5" stroke-linecap="square"/>
<path d="M3.3125 10.25C2.06986 10.25 1.0625 9.24264 1.0625 8C1.0625 6.75736 2.06986 5.75 3.3125 5.75C4.55514 5.75 5.5625 6.75736 5.5625 8C5.5625 9.24264 4.55514 10.25 3.3125 10.25Z" stroke="#667085" stroke-width="1.5" stroke-linecap="square"/>
</g>
<defs>
<clipPath id="clip0_6139_55192">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.0625 0.5C2.92341 0.5 2 1.42341 2 2.5625V13.4375C2 14.5766 2.92341 15.5 4.0625 15.5H12.6875C13.4124 15.5 14 14.9124 14 14.1875V1.8125C14 1.08763 13.4124 0.5 12.6875 0.5H4.0625ZM3.125 13.25V13.4375C3.125 13.9553 3.54473 14.375 4.0625 14.375H12.6875C12.7911 14.375 12.875 14.2911 12.875 14.1875V12.1117C12.8138 12.1205 12.7512 12.125 12.6875 12.125H4.25C3.62868 12.125 3.125 12.6287 3.125 13.25ZM5.5625 3.6875C5.25184 3.6875 5 3.93934 5 4.25C5 4.56066 5.25184 4.8125 5.5625 4.8125H10.4375C10.7482 4.8125 11 4.56066 11 4.25C11 3.93934 10.7482 3.6875 10.4375 3.6875H5.5625ZM5 7.25C5 6.93934 5.25184 6.6875 5.5625 6.6875H8.1875C8.49816 6.6875 8.75 6.93934 8.75 7.25C8.75 7.56066 8.49816 7.8125 8.1875 7.8125H5.5625C5.25184 7.8125 5 7.56066 5 7.25Z" fill="#155EEF"/>
</svg>

After

Width:  |  Height:  |  Size: 915 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.4375 8V10.8125C13.4375 11.2267 13.1017 11.5625 12.6875 11.5625H4.25C3.31802 11.5625 2.5625 12.318 2.5625 13.25C2.5625 14.182 3.31802 14.9375 4.25 14.9375H6.5M5.5625 4.25H10.4375M5.5625 7.25H8.1875M4.0625 1.0625H12.6875C13.1017 1.0625 13.4375 1.39829 13.4375 1.8125V14.1875C13.4375 14.6017 13.1017 14.9375 12.6875 14.9375H4.0625C3.23407 14.9375 2.5625 14.2659 2.5625 13.4375V2.5625C2.5625 1.73407 3.23407 1.0625 4.0625 1.0625Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 628 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0C8.41421 0 8.75 0.335786 8.75 0.75V1.5H12.5C13.3284 1.5 14 2.17157 14 3V8.25C14 8.80521 13.6984 9.28997 13.25 9.54933V10.1893L14.5303 11.4697C14.8232 11.7626 14.8232 12.2374 14.5303 12.5303C14.2374 12.8232 13.7626 12.8232 13.4697 12.5303L13.0108 12.0714C12.3429 14.2033 10.3521 15.75 8 15.75C5.64793 15.75 3.65711 14.2033 2.98923 12.0714L2.53033 12.5303C2.23744 12.8232 1.76256 12.8232 1.46967 12.5303C1.17678 12.2374 1.17678 11.7626 1.46967 11.4697L2.75 10.1893L2.75 9.54933C2.30165 9.28997 2 8.80521 2 8.25V3C2 2.17157 2.67157 1.5 3.5 1.5H7.25V0.75C7.25 0.335786 7.58579 0 8 0ZM3.5 3V8.25H12.5V3H3.5Z" fill="#155EEF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 4.5C6.16421 4.5 6.5 4.83579 6.5 5.25V6C6.5 6.41421 6.16421 6.75 5.75 6.75C5.33579 6.75 5 6.41421 5 6V5.25C5 4.83579 5.33579 4.5 5.75 4.5ZM10.25 4.5C10.6642 4.5 11 4.83579 11 5.25V6C11 6.41421 10.6642 6.75 10.25 6.75C9.83579 6.75 9.5 6.41421 9.5 6V5.25C9.5 4.83579 9.83579 4.5 10.25 4.5Z" fill="#155EEF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 1.99997H3.33594C2.92172 1.99997 2.58594 2.33576 2.58594 2.74997V8.37497C2.58594 8.78919 2.92172 9.12497 3.33594 9.12497H12.6641C13.0783 9.12497 13.4141 8.78919 13.4141 8.37497V2.74997C13.4141 2.33576 13.0783 1.99997 12.6641 1.99997H8ZM8 1.99997V0.820923M5.5625 4.99997V6.12497M10.4375 4.99997V6.12497M3.3125 9.12497V9.87497M3.3125 9.87497V10.4375C3.3125 13.0263 5.41117 15.125 8 15.125C10.5888 15.125 12.6875 13.0263 12.6875 10.4375V9.87497M3.3125 9.87497L1.8125 11.375M12.6875 9.87497V9.12497M12.6875 9.87497L14.1875 11.375" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 726 B

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.88756 1.30591C7.96013 1.26962 8.01898 1.21078 8.05527 1.1382L8.41395 0.420826C8.55215 0.144433 8.94658 0.144433 9.08478 0.420826L9.44346 1.1382C9.47975 1.21078 9.5386 1.26962 9.61117 1.30591L10.3285 1.6646C10.6049 1.80279 10.6049 2.19722 10.3285 2.33542L9.61117 2.6941C9.5386 2.73039 9.47975 2.78924 9.44346 2.86181L9.08478 3.57919C8.94658 3.85558 8.55215 3.85558 8.41395 3.57919L8.05527 2.86181C8.01898 2.78924 7.96013 2.73039 7.88756 2.6941L7.17019 2.33542C6.89379 2.19722 6.89379 1.80279 7.17019 1.6646L7.88756 1.30591Z" fill="#155EEF"/>
<path d="M3.88756 2.55591C3.96013 2.51962 4.01898 2.46078 4.05527 2.3882L4.28895 1.92083C4.42715 1.64443 4.82158 1.64443 4.95977 1.92083L5.19346 2.3882C5.22975 2.46078 5.2886 2.51962 5.36117 2.55591L5.82854 2.7896C6.10494 2.92779 6.10494 3.32222 5.82854 3.46042L5.36117 3.6941C5.2886 3.73039 5.22975 3.78924 5.19346 3.86181L4.95978 4.32919C4.82158 4.60558 4.42715 4.60558 4.28895 4.32919L4.05527 3.86181C4.01898 3.78924 3.96013 3.73039 3.88756 3.6941L3.42019 3.46042C3.14379 3.32222 3.14379 2.92779 3.42019 2.7896L3.88756 2.55591Z" fill="#155EEF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.9417 1.91015C14.1985 2.08507 14.2648 2.43499 14.0899 2.69173L12.0062 5.75001H13.4375C13.7482 5.75001 14 6.00185 14 6.31251V14.1875C14 14.9124 13.4124 15.5 12.6875 15.5H3.3125C2.58763 15.5 2 14.9124 2 14.1875V6.31251C2 6.00185 2.25184 5.75001 2.5625 5.75001H10.6449L13.1601 2.05829C13.3351 1.80155 13.685 1.73523 13.9417 1.91015ZM6.3125 8.75C6.00184 8.75 5.75 9.00184 5.75 9.3125C5.75 9.62316 6.00184 9.875 6.3125 9.875H9.6875C9.99816 9.875 10.25 9.62316 10.25 9.3125C10.25 9.00184 9.99816 8.75 9.6875 8.75H6.3125Z" fill="#155EEF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,14 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7278_16509)">
<path d="M13.4375 13.9375V6.3125H2.5625V13.9375C2.5625 14.4898 3.01022 14.9375 3.5625 14.9375H12.4375C12.9898 14.9375 13.4375 14.4898 13.4375 13.9375Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.6249 2.375L11.1733 5.97327" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.3125 9.3125H9.6875" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.74536 1.43634L8.75 1.42705L8.75464 1.43634C8.8756 1.67825 9.07175 1.8744 9.31366 1.99536L9.32295 2L9.31366 2.00464C9.07175 2.1256 8.8756 2.32175 8.75464 2.56366L8.75 2.57295L8.74536 2.56366C8.6244 2.32175 8.42825 2.1256 8.18634 2.00464L8.17705 2L8.18634 1.99536C8.42825 1.8744 8.6244 1.67825 8.74536 1.43634L8.07454 1.10093L8.74536 1.43634Z" stroke="#667085" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
<path d="M4.51955 2.71615L4.625 2.66343L4.51955 2.71615L4.44925 2.57555L4.31195 2.30095C4.44093 2.55892 4.80907 2.55892 4.93805 2.30095L4.80075 2.57555L4.51955 2.71615ZM4.625 2.88765C4.69208 2.97794 4.77206 3.05792 4.86235 3.125C4.77206 3.19208 4.69208 3.27206 4.625 3.36235C4.55792 3.27206 4.47794 3.19208 4.38765 3.125C4.47794 3.05792 4.55792 2.97794 4.625 2.88765ZM4.16343 3.125L4.21615 3.01955L4.21615 3.01955L4.16343 3.125Z" stroke="#667085" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_7278_16509">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.5">
<rect width="24" height="24" rx="6" fill="#E5E7EB"/>
<rect x="0.25" y="0.25" width="23.5" height="23.5" rx="5.75" stroke="black" stroke-opacity="0.05" stroke-width="0.5"/>
<path d="M11.8876 5.30588C11.9601 5.26959 12.019 5.21074 12.0553 5.13817L12.414 4.4208C12.5522 4.1444 12.9466 4.1444 13.0848 4.4208L13.4435 5.13817C13.4797 5.21074 13.5386 5.26959 13.6112 5.30588L14.3285 5.66457C14.6049 5.80276 14.6049 6.19719 14.3285 6.33539L13.6112 6.69407C13.5386 6.73036 13.4797 6.78921 13.4435 6.86178L13.0848 7.57916C12.9466 7.85555 12.5522 7.85555 12.414 7.57916L12.0553 6.86178C12.019 6.78921 11.9601 6.73036 11.8876 6.69407L11.1702 6.33539C10.8938 6.19719 10.8938 5.80276 11.1702 5.66457L11.8876 5.30588Z" fill="#667085"/>
<path d="M7.88756 6.55588C7.96013 6.51959 8.01898 6.46074 8.05527 6.38817L8.28895 5.9208C8.42715 5.6444 8.82158 5.6444 8.95978 5.9208L9.19346 6.38817C9.22975 6.46074 9.2886 6.51959 9.36117 6.55588L9.82854 6.78956C10.1049 6.92776 10.1049 7.32219 9.82854 7.46039L9.36117 7.69407C9.2886 7.73036 9.22975 7.78921 9.19346 7.86178L8.95978 8.32915C8.82158 8.60555 8.42715 8.60555 8.28895 8.32915L8.05527 7.86178C8.01898 7.78921 7.96013 7.73036 7.88756 7.69407L7.42019 7.46039C7.14379 7.32219 7.14379 6.92776 7.42019 6.78957L7.88756 6.55588Z" fill="#667085"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.9417 5.91012C18.1985 6.08504 18.2648 6.43496 18.0899 6.6917L16.0062 9.74998H17.4375C17.7482 9.74998 18 10.0018 18 10.3125V18.1875C18 18.9124 17.4124 19.5 16.6875 19.5H7.3125C6.58763 19.5 6 18.9123 6 18.1875V10.3125C6 10.0018 6.25184 9.74998 6.5625 9.74998H14.6449L17.1601 6.05826C17.3351 5.80152 17.685 5.7352 17.9417 5.91012ZM10.3125 12.75C10.0018 12.75 9.75 13.0018 9.75 13.3125C9.75 13.6231 10.0018 13.875 10.3125 13.875H13.6875C13.9982 13.875 14.25 13.6231 14.25 13.3125C14.25 13.0018 13.9982 12.75 13.6875 12.75H10.3125Z" fill="#667085"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 12H20M20 12L14 6M20 12L14 18" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 226 B

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-down">
<path id="Icon" d="M6 9L12 15L18 9" stroke="#101828" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 249 B

View File

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 5C3.44772 5 3 5.44772 3 6C3 6.55228 3.44772 7 4 7H20C20.5523 7 21 6.55228 21 6C21 5.44772 20.5523 5 20 5H4Z" fill="black"/>
<path d="M17.9191 9.60608C17.7616 9.2384 17.4 9 17 9C16.6 9 16.2384 9.2384 16.0809 9.60608L14.7384 12.7384L11.6061 14.0809C11.2384 14.2384 11 14.6 11 15C11 15.4 11.2384 15.7616 11.6061 15.9191L14.7384 17.2616L16.0809 20.3939C16.2384 20.7616 16.6 21 17 21C17.4 21 17.7616 20.7616 17.9191 20.3939L19.2616 17.2616L22.3939 15.9191C22.7616 15.7616 23 15.4 23 15C23 14.6 22.7616 14.2384 22.3939 14.0809L19.2616 12.7384L17.9191 9.60608Z" fill="black"/>
<path d="M4 11C3.44772 11 3 11.4477 3 12C3 12.5523 3.44772 13 4 13H9C9.55228 13 10 12.5523 10 12C10 11.4477 9.55228 11 9 11H4Z" fill="black"/>
<path d="M4 17C3.44772 17 3 17.4477 3 18C3 18.5523 3.44772 19 4 19H7C7.55228 19 8 18.5523 8 18C8 17.4477 7.55228 17 7 17H4Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 968 B

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="cute-robote">
<path id="Icon" fill-rule="evenodd" clip-rule="evenodd" d="M12 1C12.5523 1 13 1.44772 13 2V3H17C18.6569 3 20 4.34315 20 6V11C20 11.8885 19.6138 12.6868 19 13.2361V14.5858L20.7071 16.2929C21.0976 16.6834 21.0976 17.3166 20.7071 17.7071C20.3166 18.0976 19.6834 18.0976 19.2929 17.7071L18.681 17.0952C17.7905 19.9377 15.1361 22 12 22C8.8639 22 6.20948 19.9377 5.31897 17.0952L4.70711 17.7071C4.31658 18.0976 3.68342 18.0976 3.29289 17.7071C2.90237 17.3166 2.90237 16.6834 3.29289 16.2929L5 14.5858V13.2361C4.38625 12.6868 4 11.8885 4 11V6C4 4.34315 5.34315 3 7 3H11V2C11 1.44772 11.4477 1 12 1ZM7 5C6.44772 5 6 5.44772 6 6V11C6 11.5523 6.44772 12 7 12H17C17.5523 12 18 11.5523 18 11V6C18 5.44772 17.5523 5 17 5H7ZM9 7C9.55228 7 10 7.44772 10 8V9C10 9.55228 9.55228 10 9 10C8.44772 10 8 9.55228 8 9V8C8 7.44772 8.44772 7 9 7ZM15 7C15.5523 7 16 7.44772 16 8V9C16 9.55228 15.5523 10 15 10C14.4477 10 14 9.55228 14 9V8C14 7.44772 14.4477 7 15 7Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="api-connection">
<g id="vector">
<path d="M4.36364 11.8182C4.36364 7.60073 7.78255 4.18182 12 4.18182C14.8252 4.18182 17.2934 5.71543 18.6154 8.00079C18.9171 8.52231 19.5844 8.70053 20.106 8.39884C20.6275 8.09716 20.8057 7.42982 20.504 6.9083C18.8081 3.97648 15.6355 2 12 2C6.9463 2 2.78441 5.81824 2.24174 10.7273H1.09091C0.488417 10.7273 0 11.2157 0 11.8182C0 12.4207 0.488417 12.9091 1.09091 12.9091H2.24174C2.78441 17.8181 6.9463 21.6364 12 21.6364C15.6355 21.6364 18.8081 19.6599 20.504 16.7281C20.8057 16.2065 20.6275 15.5392 20.106 15.2375C19.5844 14.9358 18.9171 15.1141 18.6154 15.6356C17.2934 17.9209 14.8252 19.4545 12 19.4545C7.78255 19.4545 4.36364 16.0356 4.36364 11.8182Z" fill="black"/>
<path d="M12 6.36364C8.98754 6.36364 6.54545 8.80572 6.54545 11.8182C6.54545 14.8306 8.98754 17.2727 12 17.2727C14.6389 17.2727 16.84 15.3988 17.3454 12.9091H22.9091C23.5116 12.9091 24 12.4207 24 11.8182C24 11.2157 23.5116 10.7273 22.9091 10.7273H17.3454C16.84 8.23756 14.6389 6.36364 12 6.36364Z" fill="black"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="bubble-text">
<path id="vector" fill-rule="evenodd" clip-rule="evenodd" d="M2 9C2 5.68629 4.68629 3 8 3H16C19.3137 3 22 5.68629 22 9V15C22 18.3137 19.3137 21 16 21H3C2.44772 21 2 20.5523 2 20V9ZM9 9C8.44772 9 8 9.44772 8 10C8 10.5523 8.44772 11 9 11H15C15.5523 11 16 10.5523 16 10C16 9.44772 15.5523 9 15 9H9ZM9 13C8.44772 13 8 13.4477 8 14C8 14.5523 8.44772 15 9 15H12C12.5523 15 13 14.5523 13 14C13 13.4477 12.5523 13 12 13H9Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 560 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5836 3.8721C12.3615 3.99329 12.1665 4.11496 12 4.22818C11.8335 4.11496 11.6385 3.99329 11.4164 3.8721C10.6185 3.4369 9.45449 3 8 3C6.48169 3 4.96498 3.60857 3.83296 4.81606C2.69616 6.02865 2 7.78592 2 10C2 13.3448 4.37277 16.1023 6.58187 17.9272C7.71336 18.8619 8.86688 19.6065 9.7917 20.1203C10.2539 20.377 10.6687 20.5816 11.004 20.7253C11.1707 20.7967 11.3289 20.858 11.4705 20.9033C11.5784 20.9378 11.7841 21 12 21C12.2159 21 12.4216 20.9378 12.5295 20.9033C12.6711 20.858 12.8293 20.7967 12.996 20.7253C13.3313 20.5816 13.7461 20.377 14.2083 20.1203C15.1331 19.6065 16.2866 18.8619 17.4181 17.9272C19.6272 16.1023 22 13.3448 22 10C22 7.78592 21.3038 6.02865 20.167 4.81606C19.035 3.60857 17.5183 3 16 3C14.5455 3 13.3815 3.4369 12.5836 3.8721Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 881 B

View File

@ -0,0 +1,19 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="unblur">
<g id="vector">
<path d="M9.5 6.25C9.5 6.80228 9.05228 7.25 8.5 7.25C7.94772 7.25 7.5 6.80228 7.5 6.25C7.5 5.69772 7.94772 5.25 8.5 5.25C9.05228 5.25 9.5 5.69772 9.5 6.25Z" fill="black"/>
<path d="M6 6.25C6 6.80228 5.55228 7.25 5 7.25C4.44772 7.25 4 6.80228 4 6.25C4 5.69772 4.44772 5.25 5 5.25C5.55228 5.25 6 5.69772 6 6.25Z" fill="black"/>
<path d="M9.5 17.75C9.5 18.3023 9.05228 18.75 8.5 18.75C7.94772 18.75 7.5 18.3023 7.5 17.75C7.5 17.1977 7.94772 16.75 8.5 16.75C9.05228 16.75 9.5 17.1977 9.5 17.75Z" fill="black"/>
<path d="M9.25 3.25C9.25 3.66421 8.91421 4 8.5 4C8.08579 4 7.75 3.66421 7.75 3.25C7.75 2.83579 8.08579 2.5 8.5 2.5C8.91421 2.5 9.25 2.83579 9.25 3.25Z" fill="black"/>
<path d="M3 10C3 10.4142 2.66421 10.75 2.25 10.75C1.83579 10.75 1.5 10.4142 1.5 10C1.5 9.58579 1.83579 9.25 2.25 9.25C2.66421 9.25 3 9.58579 3 10Z" fill="black"/>
<path d="M3 14C3 14.4142 2.66421 14.75 2.25 14.75C1.83579 14.75 1.5 14.4142 1.5 14C1.5 13.5858 1.83579 13.25 2.25 13.25C2.66421 13.25 3 13.5858 3 14Z" fill="black"/>
<path d="M9.25 20.75C9.25 21.1642 8.91421 21.5 8.5 21.5C8.08579 21.5 7.75 21.1642 7.75 20.75C7.75 20.3358 8.08579 20 8.5 20C8.91421 20 9.25 20.3358 9.25 20.75Z" fill="black"/>
<path d="M10 10C10 10.8284 9.32843 11.5 8.5 11.5C7.67157 11.5 7 10.8284 7 10C7 9.17157 7.67157 8.5 8.5 8.5C9.32843 8.5 10 9.17157 10 10Z" fill="black"/>
<path d="M10 14C10 14.8284 9.32843 15.5 8.5 15.5C7.67157 15.5 7 14.8284 7 14C7 13.1716 7.67157 12.5 8.5 12.5C9.32843 12.5 10 13.1716 10 14Z" fill="black"/>
<path d="M6 10C6 10.5523 5.55228 11 5 11C4.44772 11 4 10.5523 4 10C4 9.44772 4.44772 9 5 9C5.55228 9 6 9.44772 6 10Z" fill="black"/>
<path d="M6 14C6 14.5523 5.55228 15 5 15C4.44772 15 4 14.5523 4 14C4 13.4477 4.44772 13 5 13C5.55228 13 6 13.4477 6 14Z" fill="black"/>
<path d="M6 17.75C6 18.3023 5.55228 18.75 5 18.75C4.44772 18.75 4 18.3023 4 17.75C4 17.1977 4.44772 16.75 5 16.75C5.55228 16.75 6 17.1977 6 17.75Z" fill="black"/>
<path d="M12 2C11.4477 2 11 2.44772 11 3V21C11 21.5523 11.4477 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2Z" fill="black"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="search-md">
<path id="Solid" fill-rule="evenodd" clip-rule="evenodd" d="M11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C13.125 20 15.078 19.2635 16.6177 18.0319L20.2929 21.7071C20.6834 22.0976 21.3166 22.0976 21.7071 21.7071C22.0976 21.3166 22.0976 20.6834 21.7071 20.2929L18.0319 16.6177C19.2635 15.078 20 13.125 20 11C20 6.02944 15.9706 2 11 2ZM4 11C4 7.13401 7.13401 4 11 4C14.866 4 18 7.13401 18 11C18 14.866 14.866 18 11 18C7.13401 18 4 14.866 4 11Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 596 B

View File

@ -0,0 +1,28 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "7",
"height": "20",
"viewBox": "0 0 7 20",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Line 3",
"d": "M1 19.3544L5.94174 0.645657",
"stroke": "#EAECF0",
"stroke-linecap": "round"
},
"children": []
}
]
},
"name": "DiagonalDividingLine"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './DiagonalDividingLine.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'DiagonalDividingLine'
export default Icon

View File

@ -1,3 +1,4 @@
export { default as DiagonalDividingLine } from './DiagonalDividingLine'
export { default as Dify } from './Dify' export { default as Dify } from './Dify'
export { default as Github } from './Github' export { default as Github } from './Github'
export { default as MessageChatSquare } from './MessageChatSquare' export { default as MessageChatSquare } from './MessageChatSquare'

View File

@ -0,0 +1,96 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"clip-path": "url(#clip0_6139_55192)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M10.25 3.3125C10.25 4.55514 9.24264 5.5625 8 5.5625C6.75736 5.5625 5.75 4.55514 5.75 3.3125C5.75 2.06986 6.75736 1.0625 8 1.0625C9.24264 1.0625 10.25 2.06986 10.25 3.3125Z",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "square"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M12.6875 10.25C11.4449 10.25 10.4375 9.24264 10.4375 8C10.4375 6.75736 11.4449 5.75 12.6875 5.75C13.9301 5.75 14.9375 6.75736 14.9375 8C14.9375 9.24264 13.9301 10.25 12.6875 10.25Z",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "square"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M10.25 12.6875C10.25 13.9301 9.24264 14.9375 8 14.9375C6.75736 14.9375 5.75 13.9301 5.75 12.6875C5.75 11.4449 6.75736 10.4375 8 10.4375C9.24264 10.4375 10.25 11.4449 10.25 12.6875Z",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "square"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M3.3125 10.25C2.06986 10.25 1.0625 9.24264 1.0625 8C1.0625 6.75736 2.06986 5.75 3.3125 5.75C4.55514 5.75 5.5625 6.75736 5.5625 8C5.5625 9.24264 4.55514 10.25 3.3125 10.25Z",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "square"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_6139_55192"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "16",
"height": "16",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "Explore"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Explore.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Explore'
export default Icon

View File

@ -0,0 +1,88 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"clip-path": "url(#clip0_6139_55194)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M8 0.5C6.4467 0.5 5.1875 1.7592 5.1875 3.3125C5.1875 4.8658 6.4467 6.125 8 6.125C9.5533 6.125 10.8125 4.8658 10.8125 3.3125C10.8125 1.7592 9.5533 0.5 8 0.5Z",
"fill": "#155EEF"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M15.5 8C15.5 6.4467 14.2408 5.1875 12.6875 5.1875C11.1342 5.1875 9.875 6.4467 9.875 8C9.875 9.5533 11.1342 10.8125 12.6875 10.8125C14.2408 10.8125 15.5 9.5533 15.5 8Z",
"fill": "#155EEF"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M8 9.875C6.4467 9.875 5.1875 11.1342 5.1875 12.6875C5.1875 14.2408 6.4467 15.5 8 15.5C9.5533 15.5 10.8125 14.2408 10.8125 12.6875C10.8125 11.1342 9.5533 9.875 8 9.875Z",
"fill": "#155EEF"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6.125 8C6.125 6.4467 4.8658 5.1875 3.3125 5.1875C1.7592 5.1875 0.5 6.4467 0.5 8C0.5 9.5533 1.7592 10.8125 3.3125 10.8125C4.8658 10.8125 6.125 9.5533 6.125 8Z",
"fill": "#155EEF"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_6139_55194"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "16",
"height": "16",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "ExploreActive"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ExploreActive.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'ExploreActive'
export default Icon

View File

@ -0,0 +1,2 @@
export { default as ExploreActive } from './ExploreActive'
export { default as Explore } from './Explore'

View File

@ -0,0 +1,29 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M13.4375 8V10.8125C13.4375 11.2267 13.1017 11.5625 12.6875 11.5625H4.25C3.31802 11.5625 2.5625 12.318 2.5625 13.25C2.5625 14.182 3.31802 14.9375 4.25 14.9375H6.5M5.5625 4.25H10.4375M5.5625 7.25H8.1875M4.0625 1.0625H12.6875C13.1017 1.0625 13.4375 1.39829 13.4375 1.8125V14.1875C13.4375 14.6017 13.1017 14.9375 12.6875 14.9375H4.0625C3.23407 14.9375 2.5625 14.2659 2.5625 13.4375V2.5625C2.5625 1.73407 3.23407 1.0625 4.0625 1.0625Z",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
"name": "Knowledge"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Knowledge.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Knowledge'
export default Icon

View File

@ -0,0 +1,28 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M4.0625 0.5C2.92341 0.5 2 1.42341 2 2.5625V13.4375C2 14.5766 2.92341 15.5 4.0625 15.5H12.6875C13.4124 15.5 14 14.9124 14 14.1875V1.8125C14 1.08763 13.4124 0.5 12.6875 0.5H4.0625ZM3.125 13.25V13.4375C3.125 13.9553 3.54473 14.375 4.0625 14.375H12.6875C12.7911 14.375 12.875 14.2911 12.875 14.1875V12.1117C12.8138 12.1205 12.7512 12.125 12.6875 12.125H4.25C3.62868 12.125 3.125 12.6287 3.125 13.25ZM5.5625 3.6875C5.25184 3.6875 5 3.93934 5 4.25C5 4.56066 5.25184 4.8125 5.5625 4.8125H10.4375C10.7482 4.8125 11 4.56066 11 4.25C11 3.93934 10.7482 3.6875 10.4375 3.6875H5.5625ZM5 7.25C5 6.93934 5.25184 6.6875 5.5625 6.6875H8.1875C8.49816 6.6875 8.75 6.93934 8.75 7.25C8.75 7.56066 8.49816 7.8125 8.1875 7.8125H5.5625C5.25184 7.8125 5 7.56066 5 7.25Z",
"fill": "#155EEF"
},
"children": []
}
]
},
"name": "KnowledgeActive"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './KnowledgeActive.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'KnowledgeActive'
export default Icon

View File

@ -0,0 +1,2 @@
export { default as KnowledgeActive } from './KnowledgeActive'
export { default as Knowledge } from './Knowledge'

View File

@ -0,0 +1,29 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M8 1.99997H3.33594C2.92172 1.99997 2.58594 2.33576 2.58594 2.74997V8.37497C2.58594 8.78919 2.92172 9.12497 3.33594 9.12497H12.6641C13.0783 9.12497 13.4141 8.78919 13.4141 8.37497V2.74997C13.4141 2.33576 13.0783 1.99997 12.6641 1.99997H8ZM8 1.99997V0.820923M5.5625 4.99997V6.12497M10.4375 4.99997V6.12497M3.3125 9.12497V9.87497M3.3125 9.87497V10.4375C3.3125 13.0263 5.41117 15.125 8 15.125C10.5888 15.125 12.6875 13.0263 12.6875 10.4375V9.87497M3.3125 9.87497L1.8125 11.375M12.6875 9.87497V9.12497M12.6875 9.87497L14.1875 11.375",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
"name": "Robot"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Robot.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Robot'
export default Icon

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M8 0C8.41421 0 8.75 0.335786 8.75 0.75V1.5H12.5C13.3284 1.5 14 2.17157 14 3V8.25C14 8.80521 13.6984 9.28997 13.25 9.54933V10.1893L14.5303 11.4697C14.8232 11.7626 14.8232 12.2374 14.5303 12.5303C14.2374 12.8232 13.7626 12.8232 13.4697 12.5303L13.0108 12.0714C12.3429 14.2033 10.3521 15.75 8 15.75C5.64793 15.75 3.65711 14.2033 2.98923 12.0714L2.53033 12.5303C2.23744 12.8232 1.76256 12.8232 1.46967 12.5303C1.17678 12.2374 1.17678 11.7626 1.46967 11.4697L2.75 10.1893L2.75 9.54933C2.30165 9.28997 2 8.80521 2 8.25V3C2 2.17157 2.67157 1.5 3.5 1.5H7.25V0.75C7.25 0.335786 7.58579 0 8 0ZM3.5 3V8.25H12.5V3H3.5Z",
"fill": "#155EEF"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M5.75 4.5C6.16421 4.5 6.5 4.83579 6.5 5.25V6C6.5 6.41421 6.16421 6.75 5.75 6.75C5.33579 6.75 5 6.41421 5 6V5.25C5 4.83579 5.33579 4.5 5.75 4.5ZM10.25 4.5C10.6642 4.5 11 4.83579 11 5.25V6C11 6.41421 10.6642 6.75 10.25 6.75C9.83579 6.75 9.5 6.41421 9.5 6V5.25C9.5 4.83579 9.83579 4.5 10.25 4.5Z",
"fill": "#155EEF"
},
"children": []
}
]
},
"name": "RobotActive"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './RobotActive.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'RobotActive'
export default Icon

View File

@ -0,0 +1,2 @@
export { default as RobotActive } from './RobotActive'
export { default as Robot } from './Robot'

View File

@ -0,0 +1,112 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"clip-path": "url(#clip0_7278_16509)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M13.4375 13.9375V6.3125H2.5625V13.9375C2.5625 14.4898 3.01022 14.9375 3.5625 14.9375H12.4375C12.9898 14.9375 13.4375 14.4898 13.4375 13.9375Z",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M13.6249 2.375L11.1733 5.97327",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6.3125 9.3125H9.6875",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M8.74536 1.43634L8.75 1.42705L8.75464 1.43634C8.8756 1.67825 9.07175 1.8744 9.31366 1.99536L9.32295 2L9.31366 2.00464C9.07175 2.1256 8.8756 2.32175 8.75464 2.56366L8.75 2.57295L8.74536 2.56366C8.6244 2.32175 8.42825 2.1256 8.18634 2.00464L8.17705 2L8.18634 1.99536C8.42825 1.8744 8.6244 1.67825 8.74536 1.43634L8.07454 1.10093L8.74536 1.43634Z",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "square",
"stroke-linejoin": "round"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M4.51955 2.71615L4.625 2.66343L4.51955 2.71615L4.44925 2.57555L4.31195 2.30095C4.44093 2.55892 4.80907 2.55892 4.93805 2.30095L4.80075 2.57555L4.51955 2.71615ZM4.625 2.88765C4.69208 2.97794 4.77206 3.05792 4.86235 3.125C4.77206 3.19208 4.69208 3.27206 4.625 3.36235C4.55792 3.27206 4.47794 3.19208 4.38765 3.125C4.47794 3.05792 4.55792 2.97794 4.625 2.88765ZM4.16343 3.125L4.21615 3.01955L4.21615 3.01955L4.16343 3.125Z",
"stroke": "#667085",
"stroke-width": "1.5",
"stroke-linecap": "square",
"stroke-linejoin": "round"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_7278_16509"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "16",
"height": "16",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "Tools"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Tools.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Tools'
export default Icon

View File

@ -0,0 +1,46 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M7.88756 1.30591C7.96013 1.26962 8.01898 1.21078 8.05527 1.1382L8.41395 0.420826C8.55215 0.144433 8.94658 0.144433 9.08478 0.420826L9.44346 1.1382C9.47975 1.21078 9.5386 1.26962 9.61117 1.30591L10.3285 1.6646C10.6049 1.80279 10.6049 2.19722 10.3285 2.33542L9.61117 2.6941C9.5386 2.73039 9.47975 2.78924 9.44346 2.86181L9.08478 3.57919C8.94658 3.85558 8.55215 3.85558 8.41395 3.57919L8.05527 2.86181C8.01898 2.78924 7.96013 2.73039 7.88756 2.6941L7.17019 2.33542C6.89379 2.19722 6.89379 1.80279 7.17019 1.6646L7.88756 1.30591Z",
"fill": "#155EEF"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M3.88756 2.55591C3.96013 2.51962 4.01898 2.46078 4.05527 2.3882L4.28895 1.92083C4.42715 1.64443 4.82158 1.64443 4.95977 1.92083L5.19346 2.3882C5.22975 2.46078 5.2886 2.51962 5.36117 2.55591L5.82854 2.7896C6.10494 2.92779 6.10494 3.32222 5.82854 3.46042L5.36117 3.6941C5.2886 3.73039 5.22975 3.78924 5.19346 3.86181L4.95978 4.32919C4.82158 4.60558 4.42715 4.60558 4.28895 4.32919L4.05527 3.86181C4.01898 3.78924 3.96013 3.73039 3.88756 3.6941L3.42019 3.46042C3.14379 3.32222 3.14379 2.92779 3.42019 2.7896L3.88756 2.55591Z",
"fill": "#155EEF"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M13.9417 1.91015C14.1985 2.08507 14.2648 2.43499 14.0899 2.69173L12.0062 5.75001H13.4375C13.7482 5.75001 14 6.00185 14 6.31251V14.1875C14 14.9124 13.4124 15.5 12.6875 15.5H3.3125C2.58763 15.5 2 14.9124 2 14.1875V6.31251C2 6.00185 2.25184 5.75001 2.5625 5.75001H10.6449L13.1601 2.05829C13.3351 1.80155 13.685 1.73523 13.9417 1.91015ZM6.3125 8.75C6.00184 8.75 5.75 9.00184 5.75 9.3125C5.75 9.62316 6.00184 9.875 6.3125 9.875H9.6875C9.99816 9.875 10.25 9.62316 10.25 9.3125C10.25 9.00184 9.99816 8.75 9.6875 8.75H6.3125Z",
"fill": "#155EEF"
},
"children": []
}
]
},
"name": "ToolsActive"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ToolsActive.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'ToolsActive'
export default Icon

View File

@ -0,0 +1,2 @@
export { default as ToolsActive } from './ToolsActive'
export { default as Tools } from './Tools'

View File

@ -0,0 +1,81 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"opacity": "0.5"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "24",
"height": "24",
"rx": "6",
"fill": "#E5E7EB"
},
"children": []
},
{
"type": "element",
"name": "rect",
"attributes": {
"x": "0.25",
"y": "0.25",
"width": "23.5",
"height": "23.5",
"rx": "5.75",
"stroke": "black",
"stroke-opacity": "0.05",
"stroke-width": "0.5"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M11.8876 5.30588C11.9601 5.26959 12.019 5.21074 12.0553 5.13817L12.414 4.4208C12.5522 4.1444 12.9466 4.1444 13.0848 4.4208L13.4435 5.13817C13.4797 5.21074 13.5386 5.26959 13.6112 5.30588L14.3285 5.66457C14.6049 5.80276 14.6049 6.19719 14.3285 6.33539L13.6112 6.69407C13.5386 6.73036 13.4797 6.78921 13.4435 6.86178L13.0848 7.57916C12.9466 7.85555 12.5522 7.85555 12.414 7.57916L12.0553 6.86178C12.019 6.78921 11.9601 6.73036 11.8876 6.69407L11.1702 6.33539C10.8938 6.19719 10.8938 5.80276 11.1702 5.66457L11.8876 5.30588Z",
"fill": "#667085"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M7.88756 6.55588C7.96013 6.51959 8.01898 6.46074 8.05527 6.38817L8.28895 5.9208C8.42715 5.6444 8.82158 5.6444 8.95978 5.9208L9.19346 6.38817C9.22975 6.46074 9.2886 6.51959 9.36117 6.55588L9.82854 6.78956C10.1049 6.92776 10.1049 7.32219 9.82854 7.46039L9.36117 7.69407C9.2886 7.73036 9.22975 7.78921 9.19346 7.86178L8.95978 8.32915C8.82158 8.60555 8.42715 8.60555 8.28895 8.32915L8.05527 7.86178C8.01898 7.78921 7.96013 7.73036 7.88756 7.69407L7.42019 7.46039C7.14379 7.32219 7.14379 6.92776 7.42019 6.78957L7.88756 6.55588Z",
"fill": "#667085"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M17.9417 5.91012C18.1985 6.08504 18.2648 6.43496 18.0899 6.6917L16.0062 9.74998H17.4375C17.7482 9.74998 18 10.0018 18 10.3125V18.1875C18 18.9124 17.4124 19.5 16.6875 19.5H7.3125C6.58763 19.5 6 18.9123 6 18.1875V10.3125C6 10.0018 6.25184 9.74998 6.5625 9.74998H14.6449L17.1601 6.05826C17.3351 5.80152 17.685 5.7352 17.9417 5.91012ZM10.3125 12.75C10.0018 12.75 9.75 13.0018 9.75 13.3125C9.75 13.6231 10.0018 13.875 10.3125 13.875H13.6875C13.9982 13.875 14.25 13.6231 14.25 13.3125C14.25 13.0018 13.9982 12.75 13.6875 12.75H10.3125Z",
"fill": "#667085"
},
"children": []
}
]
}
]
},
"name": "DefaultToolIcon"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './DefaultToolIcon.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'DefaultToolIcon'
export default Icon

View File

@ -0,0 +1 @@
export { default as DefaultToolIcon } from './DefaultToolIcon'

View File

@ -0,0 +1,29 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M4 12H20M20 12L14 6M20 12L14 18",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
"name": "ArrowNarrowRight"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ArrowNarrowRight.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'ArrowNarrowRight'
export default Icon

View File

@ -1,4 +1,5 @@
export { default as ArrowNarrowLeft } from './ArrowNarrowLeft' export { default as ArrowNarrowLeft } from './ArrowNarrowLeft'
export { default as ArrowNarrowRight } from './ArrowNarrowRight'
export { default as ArrowUpRight } from './ArrowUpRight' export { default as ArrowUpRight } from './ArrowUpRight'
export { default as ChevronDownDouble } from './ChevronDownDouble' export { default as ChevronDownDouble } from './ChevronDownDouble'
export { default as ChevronDown } from './ChevronDown' export { default as ChevronDown } from './ChevronDown'

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "chevron-down"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M6 9L12 15L18 9",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "ChevronDown"
}

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