mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
chore(web): new lint setup (#30020)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
@ -1,13 +1,13 @@
|
||||
import type { ChatItemInTree } from '../types'
|
||||
import { get } from 'lodash-es'
|
||||
import { buildChatItemTree, getThreadMessages } from '../utils'
|
||||
import type { ChatItemInTree } from '../types'
|
||||
import branchedTestMessages from './branchedTestMessages.json'
|
||||
import legacyTestMessages from './legacyTestMessages.json'
|
||||
import mixedTestMessages from './mixedTestMessages.json'
|
||||
import multiRootNodesMessages from './multiRootNodesMessages.json'
|
||||
import multiRootNodesWithLegacyTestMessages from './multiRootNodesWithLegacyTestMessages.json'
|
||||
import realWorldMessages from './realWorldMessages.json'
|
||||
import partialMessages from './partialMessages.json'
|
||||
import realWorldMessages from './realWorldMessages.json'
|
||||
|
||||
function visitNode(tree: ChatItemInTree | ChatItemInTree[], path: string): ChatItemInTree {
|
||||
return get(tree, path)
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import Chat from '../chat'
|
||||
import type { FileEntity } from '../../file-uploader/types'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
OnSend,
|
||||
} from '../types'
|
||||
import { useChat } from '../chat/hooks'
|
||||
import { getLastAnswer, isValidGeneratedAnswer } from '../utils'
|
||||
import { useChatWithHistoryContext } from './context'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import InputsForm from '@/app/components/base/chat/chat-with-history/inputs-form'
|
||||
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import {
|
||||
fetchSuggestedQuestions,
|
||||
getUrl,
|
||||
stopChatMessageResponding,
|
||||
} from '@/service/share'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { FileEntity } from '../../file-uploader/types'
|
||||
import { formatBooleanInputs } from '@/utils/model-config'
|
||||
import Avatar from '../../avatar'
|
||||
import Chat from '../chat'
|
||||
import { useChat } from '../chat/hooks'
|
||||
import { getLastAnswer, isValidGeneratedAnswer } from '../utils'
|
||||
import { useChatWithHistoryContext } from './context'
|
||||
|
||||
const ChatWrapper = () => {
|
||||
const {
|
||||
@ -151,11 +151,7 @@ const ChatWrapper = () => {
|
||||
const doRegenerate = useCallback((chatItem: ChatItem, editedQuestion?: { message: string, files?: FileEntity[] }) => {
|
||||
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
|
||||
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
||||
doSend(editedQuestion ? editedQuestion.message : question.content,
|
||||
editedQuestion ? editedQuestion.files : question.message_files,
|
||||
true,
|
||||
isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null,
|
||||
)
|
||||
doSend(editedQuestion ? editedQuestion.message : question.content, editedQuestion ? editedQuestion.files : question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
||||
}, [chatList, doSend])
|
||||
|
||||
const messageList = useMemo(() => {
|
||||
@ -178,12 +174,12 @@ const ChatWrapper = () => {
|
||||
else {
|
||||
return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} />
|
||||
}
|
||||
},
|
||||
[
|
||||
}, [
|
||||
inputsForms.length,
|
||||
isMobile,
|
||||
currentConversationId,
|
||||
collapsed, allInputsHidden,
|
||||
collapsed,
|
||||
allInputsHidden,
|
||||
])
|
||||
|
||||
const welcome = useMemo(() => {
|
||||
@ -198,17 +194,17 @@ const ChatWrapper = () => {
|
||||
return null
|
||||
if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) {
|
||||
return (
|
||||
<div className='flex min-h-[50vh] items-center justify-center px-4 py-12'>
|
||||
<div className='flex max-w-[720px] grow gap-4'>
|
||||
<div className="flex min-h-[50vh] items-center justify-center px-4 py-12">
|
||||
<div className="flex max-w-[720px] grow gap-4">
|
||||
<AppIcon
|
||||
size='xl'
|
||||
size="xl"
|
||||
iconType={appData?.site.icon_type}
|
||||
icon={appData?.site.icon}
|
||||
background={appData?.site.icon_background}
|
||||
imageUrl={appData?.site.icon_url}
|
||||
/>
|
||||
<div className='w-0 grow'>
|
||||
<div className='body-lg-regular grow rounded-2xl bg-chat-bubble-bg px-4 py-3 text-text-primary'>
|
||||
<div className="w-0 grow">
|
||||
<div className="body-lg-regular grow rounded-2xl bg-chat-bubble-bg px-4 py-3 text-text-primary">
|
||||
<Markdown content={welcomeMessage.content} />
|
||||
<SuggestedQuestions item={welcomeMessage} />
|
||||
</div>
|
||||
@ -220,24 +216,24 @@ const ChatWrapper = () => {
|
||||
return (
|
||||
<div className={cn('flex min-h-[50vh] flex-col items-center justify-center gap-3 py-12')}>
|
||||
<AppIcon
|
||||
size='xl'
|
||||
size="xl"
|
||||
iconType={appData?.site.icon_type}
|
||||
icon={appData?.site.icon}
|
||||
background={appData?.site.icon_background}
|
||||
imageUrl={appData?.site.icon_url}
|
||||
/>
|
||||
<div className='max-w-[768px] px-4'>
|
||||
<Markdown className='!body-2xl-regular !text-text-tertiary' content={welcomeMessage.content} />
|
||||
<div className="max-w-[768px] px-4">
|
||||
<Markdown className="!body-2xl-regular !text-text-tertiary" content={welcomeMessage.content} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
[
|
||||
}, [
|
||||
appData?.site.icon,
|
||||
appData?.site.icon_background,
|
||||
appData?.site.icon_type,
|
||||
appData?.site.icon_url,
|
||||
chatList, collapsed,
|
||||
chatList,
|
||||
collapsed,
|
||||
currentConversationId,
|
||||
inputsForms.length,
|
||||
respondingState,
|
||||
@ -245,17 +241,19 @@ const ChatWrapper = () => {
|
||||
])
|
||||
|
||||
const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon)
|
||||
? <AnswerIcon
|
||||
iconType={appData.site.icon_type}
|
||||
icon={appData.site.icon}
|
||||
background={appData.site.icon_background}
|
||||
imageUrl={appData.site.icon_url}
|
||||
/>
|
||||
? (
|
||||
<AnswerIcon
|
||||
iconType={appData.site.icon_type}
|
||||
icon={appData.site.icon}
|
||||
background={appData.site.icon_background}
|
||||
imageUrl={appData.site.icon_url}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
|
||||
return (
|
||||
<div
|
||||
className='h-full overflow-hidden bg-chatbot-bg'
|
||||
className="h-full overflow-hidden bg-chatbot-bg"
|
||||
>
|
||||
<Chat
|
||||
appData={appData ?? undefined}
|
||||
@ -263,19 +261,19 @@ const ChatWrapper = () => {
|
||||
chatList={messageList}
|
||||
isResponding={respondingState}
|
||||
chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[768px] ${isMobile && 'px-4'}`}
|
||||
chatFooterClassName='pb-4'
|
||||
chatFooterClassName="pb-4"
|
||||
chatFooterInnerClassName={`mx-auto w-full max-w-[768px] ${isMobile ? 'px-2' : 'px-4'}`}
|
||||
onSend={doSend}
|
||||
inputs={currentConversationId ? currentConversationInputs as any : newConversationInputs}
|
||||
inputsForm={inputsForms}
|
||||
onRegenerate={doRegenerate}
|
||||
onStopResponding={handleStop}
|
||||
chatNode={
|
||||
chatNode={(
|
||||
<>
|
||||
{chatNode}
|
||||
{welcome}
|
||||
</>
|
||||
}
|
||||
)}
|
||||
allToolIcons={appMeta?.tool_icons || {}}
|
||||
onFeedback={handleFeedback}
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
@ -287,11 +285,14 @@ const ChatWrapper = () => {
|
||||
sidebarCollapseState={sidebarCollapseState}
|
||||
questionIcon={
|
||||
initUserVariables?.avatar_url
|
||||
? <Avatar
|
||||
avatar={initUserVariables.avatar_url}
|
||||
name={initUserVariables.name || 'user'}
|
||||
size={40}
|
||||
/> : undefined
|
||||
? (
|
||||
<Avatar
|
||||
avatar={initUserVariables.avatar_url}
|
||||
name={initUserVariables.name || 'user'}
|
||||
size={40}
|
||||
/>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import type { RefObject } from 'react'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import type { ThemeBuilder } from '../embedded-chatbot/theme/theme-context'
|
||||
import type {
|
||||
Callback,
|
||||
ChatConfig,
|
||||
ChatItemInTree,
|
||||
Feedback,
|
||||
} from '../types'
|
||||
import type { ThemeBuilder } from '../embedded-chatbot/theme/theme-context'
|
||||
import type {
|
||||
AppConversationData,
|
||||
AppData,
|
||||
@ -16,6 +15,7 @@ import type {
|
||||
ConversationItem,
|
||||
} from '@/models/share'
|
||||
import { noop } from 'lodash-es'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
|
||||
export type ChatWithHistoryContextValue = {
|
||||
appMeta?: AppMeta | null
|
||||
@ -52,10 +52,10 @@ export type ChatWithHistoryContextValue = {
|
||||
clearChatList?: boolean
|
||||
setClearChatList: (state: boolean) => void
|
||||
isResponding?: boolean
|
||||
setIsResponding: (state: boolean) => void,
|
||||
currentConversationInputs: Record<string, any> | null,
|
||||
setCurrentConversationInputs: (v: Record<string, any>) => void,
|
||||
allInputsHidden: boolean,
|
||||
setIsResponding: (state: boolean) => void
|
||||
currentConversationInputs: Record<string, any> | null
|
||||
setCurrentConversationInputs: (v: Record<string, any>) => void
|
||||
allInputsHidden: boolean
|
||||
initUserVariables?: {
|
||||
name?: string
|
||||
avatar_url?: string
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import {
|
||||
RiMenuLine,
|
||||
} from '@remixicon/react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
|
||||
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import { useChatWithHistoryContext } from './context'
|
||||
import MobileOperationDropdown from './header/mobile-operation-dropdown'
|
||||
import Operation from './header/operation'
|
||||
import Sidebar from './sidebar'
|
||||
import MobileOperationDropdown from './header/mobile-operation-dropdown'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
|
||||
const HeaderInMobile = () => {
|
||||
const {
|
||||
@ -65,22 +65,22 @@ const HeaderInMobile = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex shrink-0 items-center gap-1 bg-mask-top2bottom-gray-50-to-transparent px-2 py-3'>
|
||||
<ActionButton size='l' className='shrink-0' onClick={() => setShowSidebar(true)}>
|
||||
<RiMenuLine className='h-[18px] w-[18px]' />
|
||||
<div className="flex shrink-0 items-center gap-1 bg-mask-top2bottom-gray-50-to-transparent px-2 py-3">
|
||||
<ActionButton size="l" className="shrink-0" onClick={() => setShowSidebar(true)}>
|
||||
<RiMenuLine className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
<div className='flex grow items-center justify-center'>
|
||||
<div className="flex grow items-center justify-center">
|
||||
{!currentConversationId && (
|
||||
<>
|
||||
<AppIcon
|
||||
className='mr-2'
|
||||
size='tiny'
|
||||
className="mr-2"
|
||||
size="tiny"
|
||||
icon={appData?.site.icon}
|
||||
iconType={appData?.site.icon_type}
|
||||
imageUrl={appData?.site.icon_url}
|
||||
background={appData?.site.icon_background}
|
||||
/>
|
||||
<div className='system-md-semibold truncate text-text-secondary'>
|
||||
<div className="system-md-semibold truncate text-text-secondary">
|
||||
{appData?.site.title}
|
||||
</div>
|
||||
</>
|
||||
@ -104,24 +104,26 @@ const HeaderInMobile = () => {
|
||||
/>
|
||||
</div>
|
||||
{showSidebar && (
|
||||
<div className='fixed inset-0 z-50 flex bg-background-overlay p-1'
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex bg-background-overlay p-1"
|
||||
onClick={() => setShowSidebar(false)}
|
||||
>
|
||||
<div className='flex h-full w-[calc(100vw_-_40px)] rounded-xl bg-components-panel-bg shadow-lg backdrop-blur-sm' onClick={e => e.stopPropagation()}>
|
||||
<div className="flex h-full w-[calc(100vw_-_40px)] rounded-xl bg-components-panel-bg shadow-lg backdrop-blur-sm" onClick={e => e.stopPropagation()}>
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showChatSettings && (
|
||||
<div className='fixed inset-0 z-50 flex justify-end bg-background-overlay p-1'
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex justify-end bg-background-overlay p-1"
|
||||
onClick={() => setShowChatSettings(false)}
|
||||
>
|
||||
<div className='flex h-full w-[calc(100vw_-_40px)] flex-col rounded-xl bg-components-panel-bg shadow-lg backdrop-blur-sm' onClick={e => e.stopPropagation()}>
|
||||
<div className='flex items-center gap-3 rounded-t-2xl border-b border-divider-subtle px-4 py-3'>
|
||||
<Message3Fill className='h-6 w-6 shrink-0' />
|
||||
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
|
||||
<div className="flex h-full w-[calc(100vw_-_40px)] flex-col rounded-xl bg-components-panel-bg shadow-lg backdrop-blur-sm" onClick={e => e.stopPropagation()}>
|
||||
<div className="flex items-center gap-3 rounded-t-2xl border-b border-divider-subtle px-4 py-3">
|
||||
<Message3Fill className="h-6 w-6 shrink-0" />
|
||||
<div className="system-xl-semibold grow text-text-secondary">{t('share.chat.chatSettingsTitle')}</div>
|
||||
</div>
|
||||
<div className='p-4'>
|
||||
<div className="p-4">
|
||||
<InputsFormContent />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import {
|
||||
RiEditBoxLine,
|
||||
RiLayoutRight2Line,
|
||||
RiResetLeftLine,
|
||||
} from '@remixicon/react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown'
|
||||
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import {
|
||||
useChatWithHistoryContext,
|
||||
} from '../context'
|
||||
import Operation from './operation'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
const Header = () => {
|
||||
const {
|
||||
@ -72,14 +72,14 @@ const Header = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex h-14 shrink-0 items-center justify-between p-3'>
|
||||
<div className="flex h-14 shrink-0 items-center justify-between p-3">
|
||||
<div className={cn('flex items-center gap-1 transition-all duration-200 ease-in-out', !isSidebarCollapsed && 'user-select-none opacity-0')}>
|
||||
<ActionButton className={cn(!isSidebarCollapsed && 'cursor-default')} size='l' onClick={() => handleSidebarCollapse(false)}>
|
||||
<RiLayoutRight2Line className='h-[18px] w-[18px]' />
|
||||
<ActionButton className={cn(!isSidebarCollapsed && 'cursor-default')} size="l" onClick={() => handleSidebarCollapse(false)}>
|
||||
<RiLayoutRight2Line className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
<div className='mr-1 shrink-0'>
|
||||
<div className="mr-1 shrink-0">
|
||||
<AppIcon
|
||||
size='large'
|
||||
size="large"
|
||||
iconType={appData?.site.icon_type}
|
||||
icon={appData?.site.icon}
|
||||
background={appData?.site.icon_background}
|
||||
@ -91,7 +91,7 @@ const Header = () => {
|
||||
)}
|
||||
{currentConversationId && currentConversationItem && isSidebarCollapsed && (
|
||||
<>
|
||||
<div className='p-1 text-divider-deep'>/</div>
|
||||
<div className="p-1 text-divider-deep">/</div>
|
||||
<Operation
|
||||
title={currentConversationItem?.name || ''}
|
||||
isPinned={!!isPin}
|
||||
@ -103,8 +103,8 @@ const Header = () => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className='flex items-center px-1'>
|
||||
<div className='h-[14px] w-px bg-divider-regular'></div>
|
||||
<div className="flex items-center px-1">
|
||||
<div className="h-[14px] w-px bg-divider-regular"></div>
|
||||
</div>
|
||||
{isSidebarCollapsed && (
|
||||
<Tooltip
|
||||
@ -113,24 +113,24 @@ const Header = () => {
|
||||
>
|
||||
<div>
|
||||
<ActionButton
|
||||
size='l'
|
||||
size="l"
|
||||
state={(!currentConversationId || isResponding) ? ActionButtonState.Disabled : ActionButtonState.Default}
|
||||
disabled={!currentConversationId || isResponding}
|
||||
onClick={handleNewConversation}
|
||||
>
|
||||
<RiEditBoxLine className='h-[18px] w-[18px]' />
|
||||
<RiEditBoxLine className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className="flex items-center gap-1">
|
||||
{currentConversationId && (
|
||||
<Tooltip
|
||||
popupContent={t('share.chat.resetChat')}
|
||||
>
|
||||
<ActionButton size='l' onClick={handleNewConversation}>
|
||||
<RiResetLeftLine className='h-[18px] w-[18px]' />
|
||||
<ActionButton size="l" onClick={handleNewConversation}>
|
||||
<RiResetLeftLine className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiMoreFill,
|
||||
} from '@remixicon/react'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
|
||||
type Props = {
|
||||
handleResetChat: () => void
|
||||
@ -24,7 +24,7 @@ const MobileOperationDropdown = ({
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
placement="bottom-end"
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: -4,
|
||||
@ -33,20 +33,20 @@ const MobileOperationDropdown = ({
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setOpen(v => !v)}
|
||||
>
|
||||
<ActionButton size='l' state={open ? ActionButtonState.Hover : ActionButtonState.Default}>
|
||||
<RiMoreFill className='h-[18px] w-[18px]' />
|
||||
<ActionButton size="l" state={open ? ActionButtonState.Hover : ActionButtonState.Default}>
|
||||
<RiMoreFill className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className="z-40">
|
||||
<div
|
||||
className={'min-w-[160px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-sm'}
|
||||
className="min-w-[160px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-sm"
|
||||
>
|
||||
<div className='system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={handleResetChat}>
|
||||
<span className='grow'>{t('share.chat.resetChat')}</span>
|
||||
<div className="system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover" onClick={handleResetChat}>
|
||||
<span className="grow">{t('share.chat.resetChat')}</span>
|
||||
</div>
|
||||
{!hideViewChatSettings && (
|
||||
<div className='system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={handleViewChatSettings}>
|
||||
<span className='grow'>{t('share.chat.viewChatSettings')}</span>
|
||||
<div className="system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover" onClick={handleViewChatSettings}>
|
||||
<span className="grow">{t('share.chat.viewChatSettings')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import type { Placement } from '@floating-ui/react'
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
} from '@remixicon/react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -44,25 +44,25 @@ const Operation: FC<Props> = ({
|
||||
onClick={() => setOpen(v => !v)}
|
||||
>
|
||||
<div className={cn('flex cursor-pointer items-center rounded-lg p-1.5 pl-2 text-text-secondary hover:bg-state-base-hover', open && 'bg-state-base-hover')}>
|
||||
<div className='system-md-semibold'>{title}</div>
|
||||
<RiArrowDownSLine className='h-4 w-4 ' />
|
||||
<div className="system-md-semibold">{title}</div>
|
||||
<RiArrowDownSLine className="h-4 w-4 " />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className="z-50">
|
||||
<div
|
||||
className={'min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-sm'}
|
||||
className="min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-sm"
|
||||
>
|
||||
<div className={cn('system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover')} onClick={togglePin}>
|
||||
<span className='grow'>{isPinned ? t('explore.sidebar.action.unpin') : t('explore.sidebar.action.pin')}</span>
|
||||
<span className="grow">{isPinned ? t('explore.sidebar.action.unpin') : t('explore.sidebar.action.pin')}</span>
|
||||
</div>
|
||||
{isShowRenameConversation && (
|
||||
<div className={cn('system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover')} onClick={onRenameConversation}>
|
||||
<span className='grow'>{t('explore.sidebar.action.rename')}</span>
|
||||
<span className="grow">{t('explore.sidebar.action.rename')}</span>
|
||||
</div>
|
||||
)}
|
||||
{isShowDelete && (
|
||||
<div className={cn('system-md-regular group flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive')} onClick={onDelete} >
|
||||
<span className='grow'>{t('explore.sidebar.action.delete')}</span>
|
||||
<div className={cn('system-md-regular group flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive')} onClick={onDelete}>
|
||||
<span className="grow">{t('explore.sidebar.action.delete')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,3 +1,17 @@
|
||||
import type {
|
||||
Callback,
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
Feedback,
|
||||
} from '../types'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import type {
|
||||
AppData,
|
||||
ConversationItem,
|
||||
} from '@/models/share'
|
||||
import { useLocalStorageState } from 'ahooks'
|
||||
import { produce } from 'immer'
|
||||
import { noop } from 'lodash-es'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -7,18 +21,12 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { useLocalStorageState } from 'ahooks'
|
||||
import { produce } from 'immer'
|
||||
import type {
|
||||
Callback,
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
Feedback,
|
||||
} from '../types'
|
||||
import { CONVERSATION_ID_INFO } from '../constants'
|
||||
import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams, getRawInputsFromUrlParams, getRawUserVariablesFromUrlParams } from '../utils'
|
||||
import { addFileInfos, sortAgentSorts } from '../../../tools/utils'
|
||||
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import { useAppFavicon } from '@/hooks/use-app-favicon'
|
||||
import { changeLanguage } from '@/i18n-config/i18next-config'
|
||||
import {
|
||||
delConversation,
|
||||
fetchChatList,
|
||||
@ -29,18 +37,10 @@ import {
|
||||
unpinConversation,
|
||||
updateFeedback,
|
||||
} from '@/service/share'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import type {
|
||||
AppData,
|
||||
ConversationItem,
|
||||
} from '@/models/share'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { changeLanguage } from '@/i18n-config/i18next-config'
|
||||
import { useAppFavicon } from '@/hooks/use-app-favicon'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import { addFileInfos, sortAgentSorts } from '../../../tools/utils'
|
||||
import { CONVERSATION_ID_INFO } from '../constants'
|
||||
import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams, getRawInputsFromUrlParams, getRawUserVariablesFromUrlParams } from '../utils'
|
||||
|
||||
function getFormattedChatList(messages: any[]) {
|
||||
const newChatList: ChatItem[] = []
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useThemeContext } from '../embedded-chatbot/theme/theme-context'
|
||||
import ChatWrapper from './chat-wrapper'
|
||||
import {
|
||||
ChatWithHistoryContext,
|
||||
useChatWithHistoryContext,
|
||||
} from './context'
|
||||
import { useChatWithHistory } from './hooks'
|
||||
import Sidebar from './sidebar'
|
||||
import Header from './header'
|
||||
import HeaderInMobile from './header-in-mobile'
|
||||
import ChatWrapper from './chat-wrapper'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { useChatWithHistory } from './hooks'
|
||||
import Sidebar from './sidebar'
|
||||
|
||||
type ChatWithHistoryProps = {
|
||||
className?: string
|
||||
@ -56,12 +56,14 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
'flex h-full bg-background-default-burn',
|
||||
isMobile && 'flex-col',
|
||||
className,
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
{!isMobile && (
|
||||
<div className={cn(
|
||||
'flex w-[236px] flex-col p-1 pr-0 transition-all duration-200 ease-in-out',
|
||||
isSidebarCollapsed && 'w-0 overflow-hidden !p-0',
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
<Sidebar />
|
||||
</div>
|
||||
)}
|
||||
@ -84,7 +86,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
<div className={cn('flex h-full flex-col overflow-hidden border-[0,5px] border-components-panel-border-subtle bg-chatbot-bg', isMobile ? 'rounded-t-2xl' : 'rounded-2xl')}>
|
||||
{!isMobile && <Header />}
|
||||
{appChatListDataLoading && (
|
||||
<Loading type='app' />
|
||||
<Loading type="app" />
|
||||
)}
|
||||
{!appChatListDataLoading && (
|
||||
<ChatWrapper key={chatShouldReloadKey} />
|
||||
@ -188,7 +190,8 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<ChatWithHistory className={className} />
|
||||
</ChatWithHistoryContext.Provider>
|
||||
)
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import React, { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useChatWithHistoryContext } from '../context'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import { PortalSelect } from '@/app/components/base/select'
|
||||
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { PortalSelect } from '@/app/components/base/select'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useChatWithHistoryContext } from '../context'
|
||||
|
||||
type Props = {
|
||||
showTip?: boolean
|
||||
@ -42,14 +42,14 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
const visibleInputsForms = inputsForms.filter(form => form.hide !== true)
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
<div className="space-y-4">
|
||||
{visibleInputsForms.map(form => (
|
||||
<div key={form.variable} className='space-y-1'>
|
||||
<div key={form.variable} className="space-y-1">
|
||||
{form.type !== InputVarType.checkbox && (
|
||||
<div className='flex h-6 items-center gap-1'>
|
||||
<div className='system-md-semibold text-text-secondary'>{form.label}</div>
|
||||
<div className="flex h-6 items-center gap-1">
|
||||
<div className="system-md-semibold text-text-secondary">{form.label}</div>
|
||||
{!form.required && (
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">{t('workflow.panel.optional')}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -62,7 +62,7 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
)}
|
||||
{form.type === InputVarType.number && (
|
||||
<Input
|
||||
type='number'
|
||||
type="number"
|
||||
value={inputsFormValue?.[form.variable] || ''}
|
||||
onChange={e => handleFormChange(form.variable, e.target.value)}
|
||||
placeholder={form.label}
|
||||
@ -85,7 +85,7 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
)}
|
||||
{form.type === InputVarType.select && (
|
||||
<PortalSelect
|
||||
popupClassName='w-[200px]'
|
||||
popupClassName="w-[200px]"
|
||||
value={inputsFormValue?.[form.variable] ?? form.default ?? ''}
|
||||
items={form.options.map((option: string) => ({ value: option, name: option }))}
|
||||
onSelect={item => handleFormChange(form.variable, item.value as string)}
|
||||
@ -124,16 +124,16 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
value={inputsFormValue?.[form.variable] || ''}
|
||||
onChange={v => handleFormChange(form.variable, v)}
|
||||
noWrapper
|
||||
className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1'
|
||||
className="bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1"
|
||||
placeholder={
|
||||
<div className='whitespace-pre'>{form.json_schema}</div>
|
||||
<div className="whitespace-pre">{form.json_schema}</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{showTip && (
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.chatFormTip')}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">{t('share.chat.chatFormTip')}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
|
||||
import { useChatWithHistoryContext } from '../context'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useChatWithHistoryContext } from '../context'
|
||||
|
||||
type Props = {
|
||||
collapsed: boolean
|
||||
@ -34,19 +34,21 @@ const InputsFormNode = ({
|
||||
<div className={cn(
|
||||
'w-full max-w-[672px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-md',
|
||||
collapsed && 'border border-components-card-border bg-components-card-bg shadow-none',
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center gap-3 rounded-t-2xl px-6 py-4',
|
||||
!collapsed && 'border-b border-divider-subtle',
|
||||
isMobile && 'px-4 py-3',
|
||||
)}>
|
||||
<Message3Fill className='h-6 w-6 shrink-0' />
|
||||
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
|
||||
)}
|
||||
>
|
||||
<Message3Fill className="h-6 w-6 shrink-0" />
|
||||
<div className="system-xl-semibold grow text-text-secondary">{t('share.chat.chatSettingsTitle')}</div>
|
||||
{collapsed && (
|
||||
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(false)}>{t('common.operation.edit')}</Button>
|
||||
<Button className="uppercase text-text-tertiary" size="small" variant="ghost" onClick={() => setCollapsed(false)}>{t('common.operation.edit')}</Button>
|
||||
)}
|
||||
{!collapsed && currentConversationId && (
|
||||
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button>
|
||||
<Button className="uppercase text-text-tertiary" size="small" variant="ghost" onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button>
|
||||
)}
|
||||
</div>
|
||||
{!collapsed && (
|
||||
@ -57,24 +59,26 @@ const InputsFormNode = ({
|
||||
{!collapsed && !currentConversationId && (
|
||||
<div className={cn('p-6', isMobile && 'p-4')}>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='w-full'
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
onClick={() => handleStartChat(() => setCollapsed(true))}
|
||||
style={
|
||||
themeBuilder?.theme
|
||||
? {
|
||||
backgroundColor: themeBuilder?.theme.primaryColor,
|
||||
}
|
||||
backgroundColor: themeBuilder?.theme.primaryColor,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>{t('share.chat.startChat')}</Button>
|
||||
>
|
||||
{t('share.chat.startChat')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{collapsed && (
|
||||
<div className='flex w-full max-w-[720px] items-center py-4'>
|
||||
<Divider bgStyle='gradient' className='h-px basis-1/2 rotate-180' />
|
||||
<Divider bgStyle='gradient' className='h-px basis-1/2' />
|
||||
<div className="flex w-full max-w-[720px] items-center py-4">
|
||||
<Divider bgStyle="gradient" className="h-px basis-1/2 rotate-180" />
|
||||
<Divider bgStyle="gradient" className="h-px basis-1/2" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiChatSettingsLine,
|
||||
} from '@remixicon/react'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
|
||||
const ViewFormDropdown = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -16,7 +16,7 @@ const ViewFormDropdown = () => {
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
placement="bottom-end"
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 4,
|
||||
@ -25,17 +25,17 @@ const ViewFormDropdown = () => {
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setOpen(v => !v)}
|
||||
>
|
||||
<ActionButton size='l' state={open ? ActionButtonState.Hover : ActionButtonState.Default}>
|
||||
<RiChatSettingsLine className='h-[18px] w-[18px]' />
|
||||
<ActionButton size="l" state={open ? ActionButtonState.Hover : ActionButtonState.Default}>
|
||||
<RiChatSettingsLine className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className="z-50">
|
||||
<div className='w-[400px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg backdrop-blur-sm'>
|
||||
<div className='flex items-center gap-3 rounded-t-2xl border-b border-divider-subtle px-6 py-4'>
|
||||
<Message3Fill className='h-6 w-6 shrink-0' />
|
||||
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
|
||||
<div className="w-[400px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg backdrop-blur-sm">
|
||||
<div className="flex items-center gap-3 rounded-t-2xl border-b border-divider-subtle px-6 py-4">
|
||||
<Message3Fill className="h-6 w-6 shrink-0" />
|
||||
<div className="system-xl-semibold grow text-text-secondary">{t('share.chat.chatSettingsTitle')}</div>
|
||||
</div>
|
||||
<div className='p-6'>
|
||||
<div className="p-6">
|
||||
<InputsFormContent />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import {
|
||||
RiEditBoxLine,
|
||||
RiExpandRightLine,
|
||||
RiLayoutLeft2Line,
|
||||
} from '@remixicon/react'
|
||||
import { useChatWithHistoryContext } from '../context'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Button from '@/app/components/base/button'
|
||||
import List from '@/app/components/base/chat/chat-with-history/sidebar/list'
|
||||
import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useChatWithHistoryContext } from '../context'
|
||||
|
||||
type Props = {
|
||||
isPanel?: boolean
|
||||
@ -83,13 +83,15 @@ const Sidebar = ({ isPanel, panelVisible }: Props) => {
|
||||
<div className={cn(
|
||||
'flex w-full grow flex-col',
|
||||
isPanel && 'rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-bg shadow-lg',
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
'flex shrink-0 items-center gap-3 p-3 pr-2',
|
||||
)}>
|
||||
<div className='shrink-0'>
|
||||
)}
|
||||
>
|
||||
<div className="shrink-0">
|
||||
<AppIcon
|
||||
size='large'
|
||||
size="large"
|
||||
iconType={appData?.site.icon_type}
|
||||
icon={appData?.site.icon}
|
||||
background={appData?.site.icon_background}
|
||||
@ -98,26 +100,26 @@ const Sidebar = ({ isPanel, panelVisible }: Props) => {
|
||||
</div>
|
||||
<div className={cn('system-md-semibold grow truncate text-text-secondary')}>{appData?.site.title}</div>
|
||||
{!isMobile && isSidebarCollapsed && (
|
||||
<ActionButton size='l' onClick={() => handleSidebarCollapse(false)}>
|
||||
<RiExpandRightLine className='h-[18px] w-[18px]' />
|
||||
<ActionButton size="l" onClick={() => handleSidebarCollapse(false)}>
|
||||
<RiExpandRightLine className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
)}
|
||||
{!isMobile && !isSidebarCollapsed && (
|
||||
<ActionButton size='l' onClick={() => handleSidebarCollapse(true)}>
|
||||
<RiLayoutLeft2Line className='h-[18px] w-[18px]' />
|
||||
<ActionButton size="l" onClick={() => handleSidebarCollapse(true)}>
|
||||
<RiLayoutLeft2Line className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
<div className='shrink-0 px-3 py-4'>
|
||||
<Button variant='secondary-accent' disabled={isResponding} className='w-full justify-center' onClick={handleNewConversation}>
|
||||
<RiEditBoxLine className='mr-1 h-4 w-4' />
|
||||
<div className="shrink-0 px-3 py-4">
|
||||
<Button variant="secondary-accent" disabled={isResponding} className="w-full justify-center" onClick={handleNewConversation}>
|
||||
<RiEditBoxLine className="mr-1 h-4 w-4" />
|
||||
{t('share.chat.newChat')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='h-0 grow space-y-2 overflow-y-auto px-3 pt-4'>
|
||||
<div className="h-0 grow space-y-2 overflow-y-auto px-3 pt-4">
|
||||
{/* pinned list */}
|
||||
{!!pinnedConversationList.length && (
|
||||
<div className='mb-4'>
|
||||
<div className="mb-4">
|
||||
<List
|
||||
isPin
|
||||
title={t('share.chat.pinnedTitle') || ''}
|
||||
@ -138,26 +140,27 @@ const Sidebar = ({ isPanel, panelVisible }: Props) => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex shrink-0 items-center justify-between p-3'>
|
||||
<div className="flex shrink-0 items-center justify-between p-3">
|
||||
<MenuDropdown
|
||||
hideLogout={isInstalledApp}
|
||||
placement='top-start'
|
||||
placement="top-start"
|
||||
data={appData?.site}
|
||||
forceClose={isPanel && !panelVisible}
|
||||
/>
|
||||
{/* powered by */}
|
||||
<div className='shrink-0'>
|
||||
<div className="shrink-0">
|
||||
{!appData?.custom_config?.remove_webapp_brand && (
|
||||
<div className={cn(
|
||||
'flex shrink-0 items-center gap-1.5 px-1',
|
||||
)}>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div>
|
||||
)}
|
||||
>
|
||||
<div className="system-2xs-medium-uppercase text-text-tertiary">{t('share.chat.poweredBy')}</div>
|
||||
{
|
||||
systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
|
||||
? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' />
|
||||
? <img src={systemFeatures.branding.workspace_logo} alt="logo" className="block h-5 w-auto" />
|
||||
: appData?.custom_config?.replace_webapp_logo
|
||||
? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' />
|
||||
: <DifyLogo size='small' />
|
||||
? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt="logo" className="block h-5 w-auto" />
|
||||
: <DifyLogo size="small" />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import { useHover } from 'ahooks'
|
||||
import {
|
||||
memo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useHover } from 'ahooks'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import Operation from '@/app/components/base/chat/chat-with-history/sidebar/operation'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
@ -36,9 +36,9 @@ const Item: FC<ItemProps> = ({
|
||||
)}
|
||||
onClick={() => onChangeConversation(item.id)}
|
||||
>
|
||||
<div className='grow truncate p-1 pl-0' title={item.name}>{item.name}</div>
|
||||
<div className="grow truncate p-1 pl-0" title={item.name}>{item.name}</div>
|
||||
{item.id !== '' && (
|
||||
<div className='shrink-0' onClick={e => e.stopPropagation()}>
|
||||
<div className="shrink-0" onClick={e => e.stopPropagation()}>
|
||||
<Operation
|
||||
isActive={isSelected}
|
||||
isPinned={!!isPin}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from 'react'
|
||||
import Item from './item'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import Item from './item'
|
||||
|
||||
type ListProps = {
|
||||
isPin?: boolean
|
||||
@ -19,9 +19,9 @@ const List: FC<ListProps> = ({
|
||||
currentConversationId,
|
||||
}) => {
|
||||
return (
|
||||
<div className='space-y-0.5'>
|
||||
<div className="space-y-0.5">
|
||||
{title && (
|
||||
<div className='system-xs-medium-uppercase px-3 pb-1 pt-2 text-text-tertiary'>{title}</div>
|
||||
<div className="system-xs-medium-uppercase px-3 pb-1 pt-2 text-text-tertiary">{title}</div>
|
||||
)}
|
||||
{list.map(item => (
|
||||
<Item
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
@ -8,10 +7,11 @@ import {
|
||||
RiPushpinLine,
|
||||
RiUnpinLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
@ -47,7 +47,7 @@ const Operation: FC<Props> = ({
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
placement="bottom-end"
|
||||
offset={4}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
@ -63,13 +63,13 @@ const Operation: FC<Props> = ({
|
||||
: ActionButtonState.Default
|
||||
}
|
||||
>
|
||||
<RiMoreFill className='h-4 w-4' />
|
||||
<RiMoreFill className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className="z-50">
|
||||
<div
|
||||
ref={ref}
|
||||
className={'min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-sm'}
|
||||
className="min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-sm"
|
||||
onMouseEnter={setIsHovering}
|
||||
onMouseLeave={setNotHovering}
|
||||
onClick={(e) => {
|
||||
@ -77,20 +77,20 @@ const Operation: FC<Props> = ({
|
||||
}}
|
||||
>
|
||||
<div className={cn('system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover')} onClick={togglePin}>
|
||||
{isPinned && <RiUnpinLine className='h-4 w-4 shrink-0 text-text-tertiary' />}
|
||||
{!isPinned && <RiPushpinLine className='h-4 w-4 shrink-0 text-text-tertiary' />}
|
||||
<span className='grow'>{isPinned ? t('explore.sidebar.action.unpin') : t('explore.sidebar.action.pin')}</span>
|
||||
{isPinned && <RiUnpinLine className="h-4 w-4 shrink-0 text-text-tertiary" />}
|
||||
{!isPinned && <RiPushpinLine className="h-4 w-4 shrink-0 text-text-tertiary" />}
|
||||
<span className="grow">{isPinned ? t('explore.sidebar.action.unpin') : t('explore.sidebar.action.pin')}</span>
|
||||
</div>
|
||||
{isShowRenameConversation && (
|
||||
<div className={cn('system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover')} onClick={onRenameConversation}>
|
||||
<RiEditLine className='h-4 w-4 shrink-0 text-text-tertiary' />
|
||||
<span className='grow'>{t('explore.sidebar.action.rename')}</span>
|
||||
<RiEditLine className="h-4 w-4 shrink-0 text-text-tertiary" />
|
||||
<span className="grow">{t('explore.sidebar.action.rename')}</span>
|
||||
</div>
|
||||
)}
|
||||
{isShowDelete && (
|
||||
<div className={cn('system-md-regular group flex cursor-pointer items-center space-x-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive')} onClick={onDelete} >
|
||||
<div className={cn('system-md-regular group flex cursor-pointer items-center space-x-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive')} onClick={onDelete}>
|
||||
<RiDeleteBinLine className={cn('h-4 w-4 shrink-0 text-text-tertiary group-hover:text-text-destructive')} />
|
||||
<span className='grow'>{t('explore.sidebar.action.delete')}</span>
|
||||
<span className="grow">{t('explore.sidebar.action.delete')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
|
||||
export type IRenameModalProps = {
|
||||
isShow: boolean
|
||||
@ -30,16 +30,17 @@ const RenameModal: FC<IRenameModalProps> = ({
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={'mt-6 text-sm font-medium leading-[21px] text-text-primary'}>{t('common.chat.conversationName')}</div>
|
||||
<Input className='mt-2 h-10 w-full'
|
||||
<div className="mt-6 text-sm font-medium leading-[21px] text-text-primary">{t('common.chat.conversationName')}</div>
|
||||
<Input
|
||||
className="mt-2 h-10 w-full"
|
||||
value={tempName}
|
||||
onChange={e => setTempName(e.target.value)}
|
||||
placeholder={t('common.chat.conversationNamePlaceholder') || ''}
|
||||
/>
|
||||
|
||||
<div className='mt-10 flex justify-end'>
|
||||
<Button className='mr-2 shrink-0' onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' className='shrink-0' onClick={() => onSave(tempName)} loading={saveLoading}>{t('common.operation.save')}</Button>
|
||||
<div className="mt-10 flex justify-end">
|
||||
<Button className="mr-2 shrink-0" onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant="primary" className="shrink-0" onClick={() => onSave(tempName)} loading={saveLoading}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type {
|
||||
ChatItem,
|
||||
} from '../../types'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import { memo } from 'react'
|
||||
import Thought from '@/app/components/base/chat/chat/thought'
|
||||
import { FileList } from '@/app/components/base/file-uploader'
|
||||
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
|
||||
type AgentContentProps = {
|
||||
item: ChatItem
|
||||
@ -29,7 +29,7 @@ const AgentContent: FC<AgentContentProps> = ({
|
||||
return (
|
||||
<div>
|
||||
{content ? <Markdown content={content} /> : agent_thoughts?.map((thought, index) => (
|
||||
<div key={index} className='px-2 py-1'>
|
||||
<div key={index} className="px-2 py-1">
|
||||
{thought.thought && (
|
||||
<Markdown content={thought.thought} />
|
||||
)}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type { ChatItem } from '../../types'
|
||||
import { memo } from 'react'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import type { ChatItem } from '../../types'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import Answer from '.'
|
||||
import { markdownContent } from './__mocks__/markdownContent'
|
||||
import { markdownContentSVG } from './__mocks__/markdownContentSVG'
|
||||
import Answer from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Other/Chat Answer',
|
||||
@ -45,9 +45,11 @@ export const Basic: Story = {
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
return (
|
||||
<div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@ -61,9 +63,11 @@ export const WithWorkflowProcess: Story = {
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
return (
|
||||
<div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@ -77,9 +81,11 @@ export const WithMarkdownContent: Story = {
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
return (
|
||||
<div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@ -93,8 +99,10 @@ export const WithMarkdownSVG: Story = {
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
return (
|
||||
<div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@ -2,26 +2,26 @@ import type {
|
||||
FC,
|
||||
ReactNode,
|
||||
} from 'react'
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
} from '../../types'
|
||||
import Operation from './operation'
|
||||
import type { AppData } from '@/models/share'
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import Citation from '@/app/components/base/chat/chat/citation'
|
||||
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
|
||||
import { FileList } from '@/app/components/base/file-uploader'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import ContentSwitch from '../content-switch'
|
||||
import AgentContent from './agent-content'
|
||||
import BasicContent from './basic-content'
|
||||
import SuggestedQuestions from './suggested-questions'
|
||||
import More from './more'
|
||||
import Operation from './operation'
|
||||
import SuggestedQuestions from './suggested-questions'
|
||||
import WorkflowProcessItem from './workflow-process'
|
||||
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
|
||||
import Citation from '@/app/components/base/chat/chat/citation'
|
||||
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
|
||||
import type { AppData } from '@/models/share'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { FileList } from '@/app/components/base/file-uploader'
|
||||
import ContentSwitch from '../content-switch'
|
||||
|
||||
type AnswerProps = {
|
||||
item: ChatItem
|
||||
@ -114,16 +114,16 @@ const Answer: FC<AnswerProps> = ({
|
||||
const contentIsEmpty = content.trim() === ''
|
||||
|
||||
return (
|
||||
<div className='mb-2 flex last:mb-0'>
|
||||
<div className='relative h-10 w-10 shrink-0'>
|
||||
<div className="mb-2 flex last:mb-0">
|
||||
<div className="relative h-10 w-10 shrink-0">
|
||||
{answerIcon || <AnswerIcon />}
|
||||
{responding && (
|
||||
<div className='absolute left-[-3px] top-[-3px] flex h-4 w-4 items-center rounded-full border-[0.5px] border-divider-subtle bg-background-section-burn pl-[6px] shadow-xs'>
|
||||
<LoadingAnim type='avatar' />
|
||||
<div className="absolute left-[-3px] top-[-3px] flex h-4 w-4 items-center rounded-full border-[0.5px] border-divider-subtle bg-background-section-burn pl-[6px] shadow-xs">
|
||||
<LoadingAnim type="avatar" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='chat-answer-container group ml-4 w-0 grow pb-4' ref={containerRef}>
|
||||
<div className="chat-answer-container group ml-4 w-0 grow pb-4" ref={containerRef}>
|
||||
<div className={cn('group relative pr-10', chatAnswerContainerInner)}>
|
||||
<div
|
||||
ref={contentRef}
|
||||
@ -156,8 +156,8 @@ const Answer: FC<AnswerProps> = ({
|
||||
}
|
||||
{
|
||||
responding && contentIsEmpty && !hasAgentThoughts && (
|
||||
<div className='flex h-5 w-6 items-center justify-center'>
|
||||
<LoadingAnim type='text' />
|
||||
<div className="flex h-5 w-6 items-center justify-center">
|
||||
<LoadingAnim type="text" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -178,7 +178,7 @@ const Answer: FC<AnswerProps> = ({
|
||||
{
|
||||
!!allFiles?.length && (
|
||||
<FileList
|
||||
className='my-1'
|
||||
className="my-1"
|
||||
files={allFiles}
|
||||
showDeleteAction={false}
|
||||
showDownloadAction
|
||||
@ -189,7 +189,7 @@ const Answer: FC<AnswerProps> = ({
|
||||
{
|
||||
!!message_files?.length && (
|
||||
<FileList
|
||||
className='my-1'
|
||||
className="my-1"
|
||||
files={message_files}
|
||||
showDeleteAction={false}
|
||||
showDownloadAction
|
||||
@ -200,7 +200,7 @@ const Answer: FC<AnswerProps> = ({
|
||||
{
|
||||
annotation?.id && annotation.authorName && (
|
||||
<EditTitle
|
||||
className='mt-1'
|
||||
className="mt-1"
|
||||
title={t('appAnnotation.editBy', { author: annotation.authorName })}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ChatItem } from '../../types'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ChatItem } from '../../types'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
|
||||
type MoreProps = {
|
||||
@ -13,33 +13,33 @@ const More: FC<MoreProps> = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='system-xs-regular mt-1 flex items-center text-text-quaternary opacity-0 group-hover:opacity-100'>
|
||||
<div className="system-xs-regular mt-1 flex items-center text-text-quaternary opacity-0 group-hover:opacity-100">
|
||||
{
|
||||
more && (
|
||||
<>
|
||||
<div
|
||||
className='mr-2 max-w-[25%] shrink-0 truncate'
|
||||
className="mr-2 max-w-[25%] shrink-0 truncate"
|
||||
title={`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`}
|
||||
>
|
||||
{`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`}
|
||||
</div>
|
||||
<div
|
||||
className='mr-2 max-w-[25%] shrink-0 truncate'
|
||||
className="mr-2 max-w-[25%] shrink-0 truncate"
|
||||
title={`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`}
|
||||
>
|
||||
{`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`}
|
||||
</div>
|
||||
{more.tokens_per_second && (
|
||||
<div
|
||||
className='mr-2 max-w-[25%] shrink-0 truncate'
|
||||
className="mr-2 max-w-[25%] shrink-0 truncate"
|
||||
title={`${more.tokens_per_second} tokens/s`}
|
||||
>
|
||||
{`${more.tokens_per_second} tokens/s`}
|
||||
</div>
|
||||
)}
|
||||
<div className='mx-2 shrink-0'>·</div>
|
||||
<div className="mx-2 shrink-0">·</div>
|
||||
<div
|
||||
className='max-w-[25%] shrink-0 truncate'
|
||||
className="max-w-[25%] shrink-0 truncate"
|
||||
title={more.time}
|
||||
>
|
||||
{more.time}
|
||||
|
||||
@ -1,32 +1,32 @@
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
ChatItem,
|
||||
Feedback,
|
||||
} from '../../types'
|
||||
import {
|
||||
RiClipboardLine,
|
||||
RiResetLeftLine,
|
||||
RiThumbDownLine,
|
||||
RiThumbUpLine,
|
||||
} from '@remixicon/react'
|
||||
import type {
|
||||
ChatItem,
|
||||
Feedback,
|
||||
} from '../../types'
|
||||
import { useChatContext } from '../context'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import AnnotationCtrlButton from '@/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal'
|
||||
import Log from '@/app/components/base/chat/chat/log'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import NewAudioButton from '@/app/components/base/new-audio-button'
|
||||
import Log from '@/app/components/base/chat/chat/log'
|
||||
import AnnotationCtrlButton from '@/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
import NewAudioButton from '@/app/components/base/new-audio-button'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useChatContext } from '../context'
|
||||
|
||||
type OperationProps = {
|
||||
item: ChatItem
|
||||
@ -190,113 +190,121 @@ const Operation: FC<OperationProps> = ({
|
||||
<div className={cn(
|
||||
'ml-1 items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm',
|
||||
hasUserFeedback ? 'flex' : 'hidden group-hover:flex',
|
||||
)}>
|
||||
{hasUserFeedback ? (
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(displayUserFeedback, userFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
<ActionButton
|
||||
state={displayUserFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Destructive}
|
||||
onClick={() => handleFeedback(null, undefined, 'user')}
|
||||
>
|
||||
{displayUserFeedback?.rating === 'like'
|
||||
? <RiThumbUpLine className='h-4 w-4' />
|
||||
: <RiThumbDownLine className='h-4 w-4' />}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>
|
||||
<ActionButton
|
||||
state={displayUserFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Default}
|
||||
onClick={() => handleLikeClick('user')}
|
||||
>
|
||||
<RiThumbUpLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
state={displayUserFeedback?.rating === 'dislike' ? ActionButtonState.Destructive : ActionButtonState.Default}
|
||||
onClick={() => handleDislikeClick('user')}
|
||||
>
|
||||
<RiThumbDownLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
>
|
||||
{hasUserFeedback
|
||||
? (
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(displayUserFeedback, userFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
<ActionButton
|
||||
state={displayUserFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Destructive}
|
||||
onClick={() => handleFeedback(null, undefined, 'user')}
|
||||
>
|
||||
{displayUserFeedback?.rating === 'like'
|
||||
? <RiThumbUpLine className="h-4 w-4" />
|
||||
: <RiThumbDownLine className="h-4 w-4" />}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<ActionButton
|
||||
state={displayUserFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Default}
|
||||
onClick={() => handleLikeClick('user')}
|
||||
>
|
||||
<RiThumbUpLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
state={displayUserFeedback?.rating === 'dislike' ? ActionButtonState.Destructive : ActionButtonState.Default}
|
||||
onClick={() => handleDislikeClick('user')}
|
||||
>
|
||||
<RiThumbDownLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{shouldShowAdminFeedbackBar && (
|
||||
<div className={cn(
|
||||
'ml-1 items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm',
|
||||
(hasAdminFeedback || hasUserFeedback) ? 'flex' : 'hidden group-hover:flex',
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
{/* User Feedback Display */}
|
||||
{displayUserFeedback?.rating && (
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(displayUserFeedback, userFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
{displayUserFeedback.rating === 'like' ? (
|
||||
<ActionButton state={ActionButtonState.Active}>
|
||||
<RiThumbUpLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
) : (
|
||||
<ActionButton state={ActionButtonState.Destructive}>
|
||||
<RiThumbDownLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)}
|
||||
{displayUserFeedback.rating === 'like'
|
||||
? (
|
||||
<ActionButton state={ActionButtonState.Active}>
|
||||
<RiThumbUpLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
)
|
||||
: (
|
||||
<ActionButton state={ActionButtonState.Destructive}>
|
||||
<RiThumbDownLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
)}
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Admin Feedback Controls */}
|
||||
{displayUserFeedback?.rating && <div className='mx-1 h-3 w-[0.5px] bg-components-actionbar-border' />}
|
||||
{hasAdminFeedback ? (
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
<ActionButton
|
||||
state={adminLocalFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Destructive}
|
||||
onClick={() => handleFeedback(null, undefined, 'admin')}
|
||||
>
|
||||
{adminLocalFeedback?.rating === 'like'
|
||||
? <RiThumbUpLine className='h-4 w-4' />
|
||||
: <RiThumbDownLine className='h-4 w-4' />}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
<ActionButton
|
||||
state={adminLocalFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Default}
|
||||
onClick={() => handleLikeClick('admin')}
|
||||
{displayUserFeedback?.rating && <div className="mx-1 h-3 w-[0.5px] bg-components-actionbar-border" />}
|
||||
{hasAdminFeedback
|
||||
? (
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
<RiThumbUpLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
<ActionButton
|
||||
state={adminLocalFeedback?.rating === 'dislike' ? ActionButtonState.Destructive : ActionButtonState.Default}
|
||||
onClick={() => handleDislikeClick('admin')}
|
||||
>
|
||||
<RiThumbDownLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<ActionButton
|
||||
state={adminLocalFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Destructive}
|
||||
onClick={() => handleFeedback(null, undefined, 'admin')}
|
||||
>
|
||||
{adminLocalFeedback?.rating === 'like'
|
||||
? <RiThumbUpLine className="h-4 w-4" />
|
||||
: <RiThumbDownLine className="h-4 w-4" />}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
<ActionButton
|
||||
state={adminLocalFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Default}
|
||||
onClick={() => handleLikeClick('admin')}
|
||||
>
|
||||
<RiThumbUpLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
|
||||
popupClassName={feedbackTooltipClassName}
|
||||
>
|
||||
<ActionButton
|
||||
state={adminLocalFeedback?.rating === 'dislike' ? ActionButtonState.Destructive : ActionButtonState.Default}
|
||||
onClick={() => handleDislikeClick('admin')}
|
||||
>
|
||||
<RiThumbDownLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showPromptLog && !isOpeningStatement && (
|
||||
<div className='hidden group-hover:block'>
|
||||
<div className="hidden group-hover:block">
|
||||
<Log logItem={item} />
|
||||
</div>
|
||||
)}
|
||||
{!isOpeningStatement && (
|
||||
<div className='ml-1 hidden items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex'>
|
||||
<div className="ml-1 hidden items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex">
|
||||
{(config?.text_to_speech?.enabled) && (
|
||||
<NewAudioButton
|
||||
id={id}
|
||||
@ -307,12 +315,13 @@ const Operation: FC<OperationProps> = ({
|
||||
<ActionButton onClick={() => {
|
||||
copy(content)
|
||||
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
|
||||
}}>
|
||||
<RiClipboardLine className='h-4 w-4' />
|
||||
}}
|
||||
>
|
||||
<RiClipboardLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
{!noChatInput && (
|
||||
<ActionButton onClick={() => onRegenerate?.(item)}>
|
||||
<RiResetLeftLine className='h-4 w-4' />
|
||||
<RiResetLeftLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
)}
|
||||
{(config?.supportAnnotation && config.annotation_reply?.enabled) && (
|
||||
@ -352,9 +361,9 @@ const Operation: FC<OperationProps> = ({
|
||||
confirmButtonText={t('common.operation.submit') || 'Submit'}
|
||||
cancelButtonText={t('common.operation.cancel') || 'Cancel'}
|
||||
>
|
||||
<div className='space-y-3'>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className='system-sm-semibold mb-2 block text-text-secondary'>
|
||||
<label className="system-sm-semibold mb-2 block text-text-secondary">
|
||||
{t('common.feedback.content') || 'Feedback Content'}
|
||||
</label>
|
||||
<Textarea
|
||||
@ -362,7 +371,7 @@ const Operation: FC<OperationProps> = ({
|
||||
onChange={e => setFeedbackContent(e.target.value)}
|
||||
placeholder={t('common.feedback.placeholder') || 'Please describe what went wrong or how we can improve...'}
|
||||
rows={4}
|
||||
className='w-full'
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type { ChatItem } from '../../types'
|
||||
import { memo } from 'react'
|
||||
import { useChatContext } from '../context'
|
||||
|
||||
type SuggestedQuestionsProps = {
|
||||
@ -20,15 +20,16 @@ const SuggestedQuestions: FC<SuggestedQuestionsProps> = ({
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='flex flex-wrap'>
|
||||
<div className="flex flex-wrap">
|
||||
{suggestedQuestions.filter(q => !!q && q.trim()).map((question, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='system-sm-medium mr-1 mt-1 inline-flex max-w-full shrink-0 cursor-pointer flex-wrap rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3.5 py-2 text-components-button-secondary-accent-text shadow-xs last:mr-0 hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover'
|
||||
className="system-sm-medium mr-1 mt-1 inline-flex max-w-full shrink-0 cursor-pointer flex-wrap rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3.5 py-2 text-components-button-secondary-accent-text shadow-xs last:mr-0 hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover"
|
||||
onClick={() => onSend?.(question)}
|
||||
>
|
||||
{question}
|
||||
</div>),
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ToolInfoInThought } from '../type'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowRightSLine,
|
||||
RiHammerFill,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import type { ToolInfoInThought } from '../type'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type ToolDetailProps = {
|
||||
@ -35,29 +35,29 @@ const ToolDetail = ({
|
||||
)}
|
||||
onClick={() => setExpand(!expand)}
|
||||
>
|
||||
{isFinished && <RiHammerFill className='mr-1 h-3.5 w-3.5' />}
|
||||
{!isFinished && <RiLoader2Line className='mr-1 h-3.5 w-3.5 animate-spin' />}
|
||||
{isFinished && <RiHammerFill className="mr-1 h-3.5 w-3.5" />}
|
||||
{!isFinished && <RiLoader2Line className="mr-1 h-3.5 w-3.5 animate-spin" />}
|
||||
{t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
|
||||
<div className='mx-1 text-text-secondary'>{toolLabel}</div>
|
||||
{!expand && <RiArrowRightSLine className='h-4 w-4' />}
|
||||
{expand && <RiArrowDownSLine className='ml-auto h-4 w-4' />}
|
||||
<div className="mx-1 text-text-secondary">{toolLabel}</div>
|
||||
{!expand && <RiArrowRightSLine className="h-4 w-4" />}
|
||||
{expand && <RiArrowDownSLine className="ml-auto h-4 w-4" />}
|
||||
</div>
|
||||
{
|
||||
expand && (
|
||||
<>
|
||||
<div className='mx-1 mb-0.5 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary'>
|
||||
<div className='system-xs-semibold-uppercase flex h-7 items-center justify-between px-2 pt-1'>
|
||||
<div className="mx-1 mb-0.5 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary">
|
||||
<div className="system-xs-semibold-uppercase flex h-7 items-center justify-between px-2 pt-1">
|
||||
{t('tools.thought.requestTitle')}
|
||||
</div>
|
||||
<div className='code-xs-regular break-words px-3 pb-2 pt-1'>
|
||||
<div className="code-xs-regular break-words px-3 pb-2 pt-1">
|
||||
{input}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mx-1 mb-1 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary'>
|
||||
<div className='system-xs-semibold-uppercase flex h-7 items-center justify-between px-2 pt-1'>
|
||||
<div className="mx-1 mb-1 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary">
|
||||
<div className="system-xs-semibold-uppercase flex h-7 items-center justify-between px-2 pt-1">
|
||||
{t('tools.thought.responseTitle')}
|
||||
</div>
|
||||
<div className='code-xs-regular break-words px-3 pb-2 pt-1'>
|
||||
<div className="code-xs-regular break-words px-3 pb-2 pt-1">
|
||||
{output}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type { ChatItem, WorkflowProcess } from '../../types'
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
RiErrorWarningFill,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ChatItem, WorkflowProcess } from '../../types'
|
||||
import TracingPanel from '@/app/components/workflow/run/tracing-panel'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import TracingPanel from '@/app/components/workflow/run/tracing-panel'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type WorkflowProcessProps = {
|
||||
data: WorkflowProcess
|
||||
@ -39,7 +39,8 @@ const WorkflowProcessItem = ({
|
||||
setCollapse(!expand)
|
||||
}, [expand])
|
||||
|
||||
if (readonly) return null
|
||||
if (readonly)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -58,17 +59,17 @@ const WorkflowProcessItem = ({
|
||||
>
|
||||
{
|
||||
running && (
|
||||
<RiLoader2Line className='mr-1 h-3.5 w-3.5 shrink-0 animate-spin text-text-tertiary' />
|
||||
<RiLoader2Line className="mr-1 h-3.5 w-3.5 shrink-0 animate-spin text-text-tertiary" />
|
||||
)
|
||||
}
|
||||
{
|
||||
succeeded && (
|
||||
<CheckCircle className='mr-1 h-3.5 w-3.5 shrink-0 text-text-success' />
|
||||
<CheckCircle className="mr-1 h-3.5 w-3.5 shrink-0 text-text-success" />
|
||||
)
|
||||
}
|
||||
{
|
||||
failed && (
|
||||
<RiErrorWarningFill className='mr-1 h-3.5 w-3.5 shrink-0 text-text-destructive' />
|
||||
<RiErrorWarningFill className="mr-1 h-3.5 w-3.5 shrink-0 text-text-destructive" />
|
||||
)
|
||||
}
|
||||
<div className={cn('system-xs-medium text-text-secondary', !collapse && 'grow')}>
|
||||
@ -78,14 +79,12 @@ const WorkflowProcessItem = ({
|
||||
</div>
|
||||
{
|
||||
!collapse && (
|
||||
<div className='mt-1.5'>
|
||||
{
|
||||
<TracingPanel
|
||||
list={data.tracing}
|
||||
hideNodeInfo={hideInfo}
|
||||
hideNodeProcessDetail={hideProcessDetail}
|
||||
/>
|
||||
}
|
||||
<div className="mt-1.5">
|
||||
<TracingPanel
|
||||
list={data.tracing}
|
||||
hideNodeInfo={hideInfo}
|
||||
hideNodeProcessDetail={hideProcessDetail}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,33 +1,33 @@
|
||||
import type { Theme } from '../../embedded-chatbot/theme/theme-context'
|
||||
import type {
|
||||
EnableType,
|
||||
OnSend,
|
||||
} from '../../types'
|
||||
import type { InputForm } from '../type'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import { decode } from 'html-entities'
|
||||
import Recorder from 'js-audio-recorder'
|
||||
import {
|
||||
useCallback,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Recorder from 'js-audio-recorder'
|
||||
import { decode } from 'html-entities'
|
||||
import type {
|
||||
EnableType,
|
||||
OnSend,
|
||||
} from '../../types'
|
||||
import type { Theme } from '../../embedded-chatbot/theme/theme-context'
|
||||
import type { InputForm } from '../type'
|
||||
import { useCheckInputsForms } from '../check-input-forms-hooks'
|
||||
import { useTextAreaHeight } from './hooks'
|
||||
import Operation from './operation'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import FeatureBar from '@/app/components/base/features/new-feature-panel/feature-bar'
|
||||
import { FileListInChatInput } from '@/app/components/base/file-uploader'
|
||||
import { useFile } from '@/app/components/base/file-uploader/hooks'
|
||||
import {
|
||||
FileContextProvider,
|
||||
useFileStore,
|
||||
} from '@/app/components/base/file-uploader/store'
|
||||
import VoiceInput from '@/app/components/base/voice-input'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import FeatureBar from '@/app/components/base/features/new-feature-panel/feature-bar'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import VoiceInput from '@/app/components/base/voice-input'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useCheckInputsForms } from '../check-input-forms-hooks'
|
||||
import { useTextAreaHeight } from './hooks'
|
||||
import Operation from './operation'
|
||||
|
||||
type ChatInputAreaProps = {
|
||||
botName?: string
|
||||
@ -130,7 +130,8 @@ const ChatInputArea = ({
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
|
||||
// if isComposing, exit
|
||||
if (isComposingRef.current) return
|
||||
if (isComposingRef.current)
|
||||
return
|
||||
e.preventDefault()
|
||||
setQuery(query.replace(/\n$/, ''))
|
||||
historyRef.current.push(query)
|
||||
@ -186,16 +187,16 @@ const ChatInputArea = ({
|
||||
disabled && 'pointer-events-none border-components-panel-border opacity-50 shadow-none',
|
||||
)}
|
||||
>
|
||||
<div className='relative max-h-[158px] overflow-y-auto overflow-x-hidden px-[9px] pt-[9px]'>
|
||||
<div className="relative max-h-[158px] overflow-y-auto overflow-x-hidden px-[9px] pt-[9px]">
|
||||
<FileListInChatInput fileConfig={visionConfig!} />
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className='flex items-center justify-between'
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<div className='relative flex w-full grow items-center'>
|
||||
<div className="relative flex w-full grow items-center">
|
||||
<div
|
||||
ref={textValueRef}
|
||||
className='body-lg-regular pointer-events-none invisible absolute h-auto w-auto whitespace-pre p-1 leading-6'
|
||||
className="body-lg-regular pointer-events-none invisible absolute h-auto w-auto whitespace-pre p-1 leading-6"
|
||||
>
|
||||
{query}
|
||||
</div>
|
||||
@ -234,7 +235,7 @@ const ChatInputArea = ({
|
||||
</div>
|
||||
{
|
||||
isMultipleLine && (
|
||||
<div className='px-[9px]'>{operation}</div>
|
||||
<div className="px-[9px]">{operation}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import type { FC, Ref } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type { Theme } from '../../embedded-chatbot/theme/theme-context'
|
||||
import type {
|
||||
EnableType,
|
||||
} from '../../types'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import {
|
||||
RiMicLine,
|
||||
RiSendPlane2Fill,
|
||||
} from '@remixicon/react'
|
||||
import type {
|
||||
EnableType,
|
||||
} from '../../types'
|
||||
import type { Theme } from '../../embedded-chatbot/theme/theme-context'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { memo } from 'react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { FileUploaderInChatInput } from '@/app/components/base/file-uploader'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type OperationProps = {
|
||||
@ -19,8 +19,8 @@ type OperationProps = {
|
||||
speechToTextConfig?: EnableType
|
||||
onShowVoiceInput?: () => void
|
||||
onSend: () => void
|
||||
theme?: Theme | null,
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
theme?: Theme | null
|
||||
ref?: Ref<HTMLDivElement>
|
||||
}
|
||||
const Operation: FC<OperationProps> = ({
|
||||
ref,
|
||||
@ -37,35 +37,35 @@ const Operation: FC<OperationProps> = ({
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className='flex items-center pl-1'
|
||||
className="flex items-center pl-1"
|
||||
ref={ref}
|
||||
>
|
||||
<div className='flex items-center space-x-1'>
|
||||
<div className="flex items-center space-x-1">
|
||||
{fileConfig?.enabled && <FileUploaderInChatInput fileConfig={fileConfig} />}
|
||||
{
|
||||
speechToTextConfig?.enabled && (
|
||||
<ActionButton
|
||||
size='l'
|
||||
size="l"
|
||||
onClick={onShowVoiceInput}
|
||||
>
|
||||
<RiMicLine className='h-5 w-5' />
|
||||
<RiMicLine className="h-5 w-5" />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
className='ml-3 w-8 px-0'
|
||||
variant='primary'
|
||||
className="ml-3 w-8 px-0"
|
||||
variant="primary"
|
||||
onClick={onSend}
|
||||
style={
|
||||
theme
|
||||
? {
|
||||
backgroundColor: theme.primaryColor,
|
||||
}
|
||||
backgroundColor: theme.primaryColor,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<RiSendPlane2Fill className='h-4 w-4' />
|
||||
<RiSendPlane2Fill className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { InputForm } from './type'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { InputForm } from './type'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import type { CitationItem } from '../type'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Popup from './popup'
|
||||
|
||||
export type Resources = {
|
||||
@ -76,17 +76,17 @@ const Citation: FC<CitationProps> = ({
|
||||
const resourcesLength = resources.length
|
||||
|
||||
return (
|
||||
<div className='-mb-1 mt-3'>
|
||||
<div className='system-xs-medium mb-2 flex items-center text-text-tertiary'>
|
||||
<div className="-mb-1 mt-3">
|
||||
<div className="system-xs-medium mb-2 flex items-center text-text-tertiary">
|
||||
{t('common.chat.citation.title')}
|
||||
<div className='ml-2 h-px grow bg-divider-regular' />
|
||||
<div className="ml-2 h-px grow bg-divider-regular" />
|
||||
</div>
|
||||
<div className='relative flex flex-wrap'>
|
||||
<div className="relative flex flex-wrap">
|
||||
{
|
||||
resources.map((res, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='absolute left-0 top-0 -z-10 mb-1 mr-1 h-7 w-auto max-w-[240px] whitespace-nowrap pl-7 pr-2 text-xs opacity-0'
|
||||
className="absolute left-0 top-0 -z-10 mb-1 mr-1 h-7 w-auto max-w-[240px] whitespace-nowrap pl-7 pr-2 text-xs opacity-0"
|
||||
ref={(ele: any) => (elesRef.current[index] = ele!)}
|
||||
>
|
||||
{res.documentName}
|
||||
@ -95,7 +95,7 @@ const Citation: FC<CitationProps> = ({
|
||||
}
|
||||
{
|
||||
resources.slice(0, showMore ? resourcesLength : limitNumberInOneLine).map((res, index) => (
|
||||
<div key={index} className='mb-1 mr-1 cursor-pointer'>
|
||||
<div key={index} className="mb-1 mr-1 cursor-pointer">
|
||||
<Popup
|
||||
data={res}
|
||||
showHitInfo={showHitInfo}
|
||||
@ -106,13 +106,13 @@ const Citation: FC<CitationProps> = ({
|
||||
{
|
||||
limitNumberInOneLine < resourcesLength && (
|
||||
<div
|
||||
className='system-xs-medium flex h-7 cursor-pointer items-center rounded-lg bg-components-panel-bg px-2 text-text-tertiary'
|
||||
className="system-xs-medium flex h-7 cursor-pointer items-center rounded-lg bg-components-panel-bg px-2 text-text-tertiary"
|
||||
onClick={() => setShowMore(v => !v)}
|
||||
>
|
||||
{
|
||||
!showMore
|
||||
? `+ ${resourcesLength - limitNumberInOneLine}`
|
||||
: <RiArrowDownSLine className='h-4 w-4 rotate-180 text-text-tertiary' />
|
||||
: <RiArrowDownSLine className="h-4 w-4 rotate-180 text-text-tertiary" />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import { Fragment, useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from './tooltip'
|
||||
import ProgressTooltip from './progress-tooltip'
|
||||
import type { Resources } from './index'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Link from 'next/link'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FileIcon from '@/app/components/base/file-icon'
|
||||
import {
|
||||
Hash02,
|
||||
Target04,
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import {
|
||||
BezierCurve03,
|
||||
TypeSquare,
|
||||
} from '@/app/components/base/icons/src/vender/line/editor'
|
||||
import {
|
||||
Hash02,
|
||||
Target04,
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import ProgressTooltip from './progress-tooltip'
|
||||
import Tooltip from './tooltip'
|
||||
|
||||
type PopupProps = {
|
||||
data: Resources
|
||||
@ -33,43 +33,43 @@ const Popup: FC<PopupProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const fileType = data.dataSourceType !== 'notion'
|
||||
? (/\.([^.]*)$/g.exec(data.documentName)?.[1] || '')
|
||||
? (/\.([^.]*)$/.exec(data.documentName)?.[1] || '')
|
||||
: 'notion'
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='top-start'
|
||||
placement="top-start"
|
||||
offset={{
|
||||
mainAxis: 8,
|
||||
crossAxis: -2,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className='flex h-7 max-w-[240px] items-center rounded-lg bg-components-button-secondary-bg px-2'>
|
||||
<FileIcon type={fileType} className='mr-1 h-4 w-4 shrink-0' />
|
||||
<div className='truncate text-xs text-text-tertiary'>{data.documentName}</div>
|
||||
<div className="flex h-7 max-w-[240px] items-center rounded-lg bg-components-button-secondary-bg px-2">
|
||||
<FileIcon type={fileType} className="mr-1 h-4 w-4 shrink-0" />
|
||||
<div className="truncate text-xs text-text-tertiary">{data.documentName}</div>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent style={{ zIndex: 1000 }}>
|
||||
<div className='max-w-[360px] rounded-xl bg-background-section-burn shadow-lg backdrop-blur-[5px]'>
|
||||
<div className='px-4 pb-2 pt-3'>
|
||||
<div className='flex h-[18px] items-center'>
|
||||
<FileIcon type={fileType} className='mr-1 h-4 w-4 shrink-0' />
|
||||
<div className='system-xs-medium truncate text-text-tertiary'>{data.documentName}</div>
|
||||
<div className="max-w-[360px] rounded-xl bg-background-section-burn shadow-lg backdrop-blur-[5px]">
|
||||
<div className="px-4 pb-2 pt-3">
|
||||
<div className="flex h-[18px] items-center">
|
||||
<FileIcon type={fileType} className="mr-1 h-4 w-4 shrink-0" />
|
||||
<div className="system-xs-medium truncate text-text-tertiary">{data.documentName}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='max-h-[450px] overflow-y-auto rounded-lg bg-components-panel-bg px-4 py-0.5'>
|
||||
<div className='w-full'>
|
||||
<div className="max-h-[450px] overflow-y-auto rounded-lg bg-components-panel-bg px-4 py-0.5">
|
||||
<div className="w-full">
|
||||
{
|
||||
data.sources.map((source, index) => (
|
||||
<Fragment key={index}>
|
||||
<div className='group py-3'>
|
||||
<div className='mb-2 flex items-center justify-between'>
|
||||
<div className='flex h-5 items-center rounded-md border border-divider-subtle px-1.5'>
|
||||
<Hash02 className='mr-0.5 h-3 w-3 text-text-quaternary' />
|
||||
<div className='text-[11px] font-medium text-text-tertiary'>
|
||||
<div className="group py-3">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<div className="flex h-5 items-center rounded-md border border-divider-subtle px-1.5">
|
||||
<Hash02 className="mr-0.5 h-3 w-3 text-text-quaternary" />
|
||||
<div className="text-[11px] font-medium text-text-tertiary">
|
||||
{source.segment_position || index + 1}
|
||||
</div>
|
||||
</div>
|
||||
@ -77,31 +77,32 @@ const Popup: FC<PopupProps> = ({
|
||||
showHitInfo && (
|
||||
<Link
|
||||
href={`/datasets/${source.dataset_id}/documents/${source.document_id}`}
|
||||
className='hidden h-[18px] items-center text-xs text-text-accent group-hover:flex'>
|
||||
className="hidden h-[18px] items-center text-xs text-text-accent group-hover:flex"
|
||||
>
|
||||
{t('common.chat.citation.linkToDataset')}
|
||||
<ArrowUpRight className='ml-1 h-3 w-3' />
|
||||
<ArrowUpRight className="ml-1 h-3 w-3" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='break-words text-[13px] text-text-secondary'>{source.content}</div>
|
||||
<div className="break-words text-[13px] text-text-secondary">{source.content}</div>
|
||||
{
|
||||
showHitInfo && (
|
||||
<div className='system-xs-medium mt-2 flex flex-wrap items-center text-text-quaternary'>
|
||||
<div className="system-xs-medium mt-2 flex flex-wrap items-center text-text-quaternary">
|
||||
<Tooltip
|
||||
text={t('common.chat.citation.characters')}
|
||||
data={source.word_count}
|
||||
icon={<TypeSquare className='mr-1 h-3 w-3' />}
|
||||
icon={<TypeSquare className="mr-1 h-3 w-3" />}
|
||||
/>
|
||||
<Tooltip
|
||||
text={t('common.chat.citation.hitCount')}
|
||||
data={source.hit_count}
|
||||
icon={<Target04 className='mr-1 h-3 w-3' />}
|
||||
icon={<Target04 className="mr-1 h-3 w-3" />}
|
||||
/>
|
||||
<Tooltip
|
||||
text={t('common.chat.citation.vectorHash')}
|
||||
data={source.index_node_hash?.substring(0, 7)}
|
||||
icon={<BezierCurve03 className='mr-1 h-3 w-3' />}
|
||||
icon={<BezierCurve03 className="mr-1 h-3 w-3" />}
|
||||
/>
|
||||
{
|
||||
source.score && (
|
||||
@ -114,7 +115,7 @@ const Popup: FC<PopupProps> = ({
|
||||
</div>
|
||||
{
|
||||
index !== data.sources.length - 1 && (
|
||||
<div className='my-1 h-px bg-divider-regular' />
|
||||
<div className="my-1 h-px bg-divider-regular" />
|
||||
)
|
||||
}
|
||||
</Fragment>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
@ -21,22 +21,24 @@ const ProgressTooltip: FC<ProgressTooltipProps> = ({
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='top-start'
|
||||
placement="top-start"
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onMouseEnter={() => setOpen(true)}
|
||||
onMouseLeave={() => setOpen(false)}
|
||||
>
|
||||
<div className='flex grow items-center'>
|
||||
<div className='mr-1 h-1.5 w-16 overflow-hidden rounded-[3px] border border-components-progress-gray-border'>
|
||||
<div className='h-full bg-components-progress-gray-progress' style={{ width: `${data * 100}%` }}></div>
|
||||
<div className="flex grow items-center">
|
||||
<div className="mr-1 h-1.5 w-16 overflow-hidden rounded-[3px] border border-components-progress-gray-border">
|
||||
<div className="h-full bg-components-progress-gray-progress" style={{ width: `${data * 100}%` }}></div>
|
||||
</div>
|
||||
{data}
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent style={{ zIndex: 1001 }}>
|
||||
<div className='system-xs-medium rounded-lg bg-components-tooltip-bg p-3 text-text-quaternary shadow-lg'>
|
||||
{t('common.chat.citation.hitScore')} {data}
|
||||
<div className="system-xs-medium rounded-lg bg-components-tooltip-bg p-3 text-text-quaternary shadow-lg">
|
||||
{t('common.chat.citation.hitScore')}
|
||||
{' '}
|
||||
{data}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
@ -23,20 +23,22 @@ const Tooltip: FC<TooltipProps> = ({
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='top-start'
|
||||
placement="top-start"
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onMouseEnter={() => setOpen(true)}
|
||||
onMouseLeave={() => setOpen(false)}
|
||||
>
|
||||
<div className='mr-6 flex items-center'>
|
||||
<div className="mr-6 flex items-center">
|
||||
{icon}
|
||||
{data}
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent style={{ zIndex: 1001 }}>
|
||||
<div className='system-xs-medium rounded-lg bg-components-tooltip-bg p-3 text-text-quaternary shadow-lg'>
|
||||
{text} {data}
|
||||
<div className="system-xs-medium rounded-lg bg-components-tooltip-bg p-3 text-text-quaternary shadow-lg">
|
||||
{text}
|
||||
{' '}
|
||||
{data}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
|
||||
@ -16,7 +16,8 @@ export default function ContentSwitch({
|
||||
return (
|
||||
count && count > 1 && currentIndex !== undefined && (
|
||||
<div className="flex items-center justify-center pt-3.5 text-sm">
|
||||
<button type="button"
|
||||
<button
|
||||
type="button"
|
||||
className={`${prevDisabled ? 'opacity-30' : 'opacity-100'}`}
|
||||
disabled={prevDisabled}
|
||||
onClick={() => !prevDisabled && switchSibling('prev')}
|
||||
@ -24,9 +25,13 @@ export default function ContentSwitch({
|
||||
<ChevronRight className="h-[14px] w-[14px] rotate-180 text-text-primary" />
|
||||
</button>
|
||||
<span className="px-2 text-xs text-text-primary">
|
||||
{currentIndex + 1} / {count}
|
||||
{currentIndex + 1}
|
||||
{' '}
|
||||
/
|
||||
{count}
|
||||
</span>
|
||||
<button type="button"
|
||||
<button
|
||||
type="button"
|
||||
className={`${nextDisabled ? 'opacity-30' : 'opacity-100'}`}
|
||||
disabled={nextDisabled}
|
||||
onClick={() => !nextDisabled && switchSibling('next')}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import type { ChatProps } from './index'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
|
||||
export type ChatContextValue = Pick<ChatProps, 'config'
|
||||
| 'isResponding'
|
||||
@ -15,8 +15,7 @@ export type ChatContextValue = Pick<ChatProps, 'config'
|
||||
| 'onAnnotationEdited'
|
||||
| 'onAnnotationAdded'
|
||||
| 'onAnnotationRemoved'
|
||||
| 'onFeedback'
|
||||
>
|
||||
| 'onFeedback'>
|
||||
|
||||
const ChatContext = createContext<ChatContextValue>({
|
||||
chatList: [],
|
||||
@ -55,7 +54,8 @@ export const ChatContextProvider = ({
|
||||
onAnnotationAdded,
|
||||
onAnnotationRemoved,
|
||||
onFeedback,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ChatContext.Provider>
|
||||
)
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
ChatItemInTree,
|
||||
Inputs,
|
||||
} from '../types'
|
||||
import type { InputForm } from './type'
|
||||
import type AudioPlayer from '@/app/components/base/audio-btn/audio'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { Annotation } from '@/models/log'
|
||||
import { produce, setAutoFreeze } from 'immer'
|
||||
import { noop, uniqBy } from 'lodash-es'
|
||||
import { useParams, usePathname } from 'next/navigation'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -6,36 +19,22 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { produce, setAutoFreeze } from 'immer'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import { useParams, usePathname } from 'next/navigation'
|
||||
import { v4 as uuidV4 } from 'uuid'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
ChatItemInTree,
|
||||
Inputs,
|
||||
} from '../types'
|
||||
import { getThreadMessages } from '../utils'
|
||||
import type { InputForm } from './type'
|
||||
import {
|
||||
getProcessedInputs,
|
||||
processOpeningStatement,
|
||||
} from './utils'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { ssePost } from '@/service/base'
|
||||
import type { Annotation } from '@/models/log'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
|
||||
import type AudioPlayer from '@/app/components/base/audio-btn/audio'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import {
|
||||
getProcessedFiles,
|
||||
getProcessedFilesFromResponse,
|
||||
} from '@/app/components/base/file-uploader/utils'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { ssePost } from '@/service/base'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { getThreadMessages } from '../utils'
|
||||
import {
|
||||
getProcessedInputs,
|
||||
processOpeningStatement,
|
||||
} from './utils'
|
||||
|
||||
type GetAbortController = (abortController: AbortController) => void
|
||||
type SendCallback = {
|
||||
@ -382,12 +381,12 @@ export const useChat = (
|
||||
...newResponseItem.message,
|
||||
...(newResponseItem.message[newResponseItem.message.length - 1].role !== 'assistant'
|
||||
? [
|
||||
{
|
||||
role: 'assistant',
|
||||
text: newResponseItem.answer,
|
||||
files: newResponseItem.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
|
||||
},
|
||||
]
|
||||
{
|
||||
role: 'assistant',
|
||||
text: newResponseItem.answer,
|
||||
files: newResponseItem.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
more: {
|
||||
@ -633,7 +632,8 @@ export const useChat = (
|
||||
parentId: data.parent_message_id,
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
return true
|
||||
}, [
|
||||
t,
|
||||
|
||||
@ -2,6 +2,18 @@ import type {
|
||||
FC,
|
||||
ReactNode,
|
||||
} from 'react'
|
||||
import type { ThemeBuilder } from '../embedded-chatbot/theme/theme-context'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
Feedback,
|
||||
OnRegenerate,
|
||||
OnSend,
|
||||
} from '../types'
|
||||
import type { InputForm } from './type'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
import type { AppData } from '@/models/share'
|
||||
import { debounce } from 'lodash-es'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
@ -10,30 +22,18 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
Feedback,
|
||||
OnRegenerate,
|
||||
OnSend,
|
||||
} from '../types'
|
||||
import type { ThemeBuilder } from '../embedded-chatbot/theme/theme-context'
|
||||
import Question from './question'
|
||||
import Answer from './answer'
|
||||
import ChatInputArea from './chat-input-area'
|
||||
import TryToAsk from './try-to-ask'
|
||||
import { ChatContextProvider } from './context'
|
||||
import type { InputForm } from './type'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import AgentLogModal from '@/app/components/base/agent-log-modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
import AgentLogModal from '@/app/components/base/agent-log-modal'
|
||||
import PromptLogModal from '@/app/components/base/prompt-log-modal'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import type { AppData } from '@/models/share'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Answer from './answer'
|
||||
import ChatInputArea from './chat-input-area'
|
||||
import { ChatContextProvider } from './context'
|
||||
import Question from './question'
|
||||
import TryToAsk from './try-to-ask'
|
||||
|
||||
export type ChatProps = {
|
||||
appData?: AppData
|
||||
@ -205,9 +205,11 @@ const Chat: FC<ChatProps> = ({
|
||||
useEffect(() => {
|
||||
const setUserScrolled = () => {
|
||||
const container = chatContainerRef.current
|
||||
if (!container) return
|
||||
if (!container)
|
||||
return
|
||||
|
||||
if (isAutoScrollingRef.current) return
|
||||
if (isAutoScrollingRef.current)
|
||||
return
|
||||
|
||||
const distanceToBottom = container.scrollHeight - container.clientHeight - container.scrollTop
|
||||
const SCROLL_UP_THRESHOLD = 100
|
||||
@ -216,7 +218,8 @@ const Chat: FC<ChatProps> = ({
|
||||
}
|
||||
|
||||
const container = chatContainerRef.current
|
||||
if (!container) return
|
||||
if (!container)
|
||||
return
|
||||
|
||||
container.addEventListener('scroll', setUserScrolled)
|
||||
return () => container.removeEventListener('scroll', setUserScrolled)
|
||||
@ -255,7 +258,7 @@ const Chat: FC<ChatProps> = ({
|
||||
onAnnotationRemoved={onAnnotationRemoved}
|
||||
onFeedback={onFeedback}
|
||||
>
|
||||
<div className='relative h-full'>
|
||||
<div className="relative h-full">
|
||||
<div
|
||||
ref={chatContainerRef}
|
||||
className={cn('relative h-full overflow-y-auto overflow-x-hidden', chatContainerClassName)}
|
||||
@ -311,10 +314,10 @@ const Chat: FC<ChatProps> = ({
|
||||
>
|
||||
{
|
||||
!noStopResponding && isResponding && (
|
||||
<div className='mb-2 flex justify-center'>
|
||||
<Button className='border-components-panel-border bg-components-panel-bg text-components-button-secondary-text' onClick={onStopResponding}>
|
||||
<StopCircle className='mr-[5px] h-3.5 w-3.5' />
|
||||
<span className='text-xs font-normal'>{t('appDebug.operation.stopResponding')}</span>
|
||||
<div className="mb-2 flex justify-center">
|
||||
<Button className="border-components-panel-border bg-components-panel-bg text-components-button-secondary-text" onClick={onStopResponding}>
|
||||
<StopCircle className="mr-[5px] h-3.5 w-3.5" />
|
||||
<span className="text-xs font-normal">{t('appDebug.operation.stopResponding')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import s from './style.module.css'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import s from './style.module.css'
|
||||
|
||||
export type ILoadingAnimProps = {
|
||||
type: 'text' | 'avatar'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from 'react'
|
||||
import { RiFileList3Line } from '@remixicon/react'
|
||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||
import { RiFileList3Line } from '@remixicon/react'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
|
||||
@ -19,7 +19,7 @@ const Log: FC<LogProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'
|
||||
className="ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
@ -33,7 +33,7 @@ const Log: FC<LogProps> = ({
|
||||
}}
|
||||
>
|
||||
<ActionButton>
|
||||
<RiFileList3Line className='h-4 w-4' />
|
||||
<RiFileList3Line className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
|
||||
import type { ChatItem } from '../types'
|
||||
import Question from './question'
|
||||
import { User } from '@/app/components/base/icons/src/public/avatar'
|
||||
import Question from './question'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Other/Chat Question',
|
||||
@ -26,8 +26,10 @@ export const Default: Story = {
|
||||
content: 'You are a helpful assistant.',
|
||||
} satisfies ChatItem,
|
||||
theme: undefined,
|
||||
questionIcon: <div className='h-full w-full rounded-full border-[0.5px] border-black/5'>
|
||||
<User className='h-full w-full' />
|
||||
</div>,
|
||||
questionIcon: (
|
||||
<div className="h-full w-full rounded-full border-[0.5px] border-black/5">
|
||||
<User className="h-full w-full" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@ -2,6 +2,10 @@ import type {
|
||||
FC,
|
||||
ReactNode,
|
||||
} from 'react'
|
||||
import type { Theme } from '../embedded-chatbot/theme/theme-context'
|
||||
import type { ChatItem } from '../types'
|
||||
import { RiClipboardLine, RiEditLine } from '@remixicon/react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
@ -9,21 +13,17 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type { ChatItem } from '../types'
|
||||
import type { Theme } from '../embedded-chatbot/theme/theme-context'
|
||||
import { CssTransform } from '../embedded-chatbot/theme/utils'
|
||||
import ContentSwitch from './content-switch'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import { FileList } from '@/app/components/base/file-uploader'
|
||||
import { User } from '@/app/components/base/icons/src/public/avatar'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import { FileList } from '@/app/components/base/file-uploader'
|
||||
import ActionButton from '../../action-button'
|
||||
import { RiClipboardLine, RiEditLine } from '@remixicon/react'
|
||||
import Toast from '../../toast'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import ActionButton from '../../action-button'
|
||||
import Button from '../../button'
|
||||
import Toast from '../../toast'
|
||||
import { CssTransform } from '../embedded-chatbot/theme/utils'
|
||||
import ContentSwitch from './content-switch'
|
||||
import { useChatContext } from './context'
|
||||
|
||||
type QuestionProps = {
|
||||
@ -101,7 +101,7 @@ const Question: FC<QuestionProps> = ({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='mb-2 flex justify-end last:mb-0'>
|
||||
<div className="mb-2 flex justify-end last:mb-0">
|
||||
<div className={cn('group relative mr-4 flex max-w-full items-start overflow-x-hidden pl-14', isEditing && 'flex-1')}>
|
||||
<div className={cn('mr-2 gap-1', isEditing ? 'hidden' : 'flex')}>
|
||||
<div
|
||||
@ -111,23 +111,26 @@ const Question: FC<QuestionProps> = ({
|
||||
<ActionButton onClick={() => {
|
||||
copy(content)
|
||||
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
|
||||
}}>
|
||||
<RiClipboardLine className='h-4 w-4' />
|
||||
}}
|
||||
>
|
||||
<RiClipboardLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
{enableEdit && <ActionButton onClick={handleEdit}>
|
||||
<RiEditLine className='h-4 w-4' />
|
||||
</ActionButton>}
|
||||
{enableEdit && (
|
||||
<ActionButton onClick={handleEdit}>
|
||||
<RiEditLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className='w-full rounded-2xl bg-background-gradient-bg-fill-chat-bubble-bg-3 px-4 py-3 text-sm text-text-primary'
|
||||
className="w-full rounded-2xl bg-background-gradient-bg-fill-chat-bubble-bg-3 px-4 py-3 text-sm text-text-primary"
|
||||
style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}}
|
||||
>
|
||||
{
|
||||
!!message_files?.length && (
|
||||
<FileList
|
||||
className='mb-2'
|
||||
className="mb-2"
|
||||
files={message_files}
|
||||
showDeleteAction={false}
|
||||
showDownloadAction={true}
|
||||
@ -136,41 +139,46 @@ const Question: FC<QuestionProps> = ({
|
||||
}
|
||||
{!isEditing
|
||||
? <Markdown content={content} />
|
||||
: <div className="
|
||||
: (
|
||||
<div className="
|
||||
flex flex-col gap-2 rounded-xl
|
||||
border border-components-chat-input-border bg-components-panel-bg-blur p-[9px] shadow-md
|
||||
">
|
||||
<div className="max-h-[158px] overflow-y-auto overflow-x-hidden">
|
||||
<Textarea
|
||||
className={cn(
|
||||
'body-lg-regular w-full p-1 leading-6 text-text-tertiary outline-none',
|
||||
)}
|
||||
autoFocus
|
||||
minRows={1}
|
||||
value={editedContent}
|
||||
onChange={e => setEditedContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant='ghost' onClick={handleCancelEditing}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' onClick={handleResend}>{t('common.chat.resend')}</Button>
|
||||
</div>
|
||||
</div>}
|
||||
{!isEditing && <ContentSwitch
|
||||
count={item.siblingCount}
|
||||
currentIndex={item.siblingIndex}
|
||||
prevDisabled={!item.prevSibling}
|
||||
nextDisabled={!item.nextSibling}
|
||||
switchSibling={handleSwitchSibling}
|
||||
/>}
|
||||
"
|
||||
>
|
||||
<div className="max-h-[158px] overflow-y-auto overflow-x-hidden">
|
||||
<Textarea
|
||||
className={cn(
|
||||
'body-lg-regular w-full p-1 leading-6 text-text-tertiary outline-none',
|
||||
)}
|
||||
autoFocus
|
||||
minRows={1}
|
||||
value={editedContent}
|
||||
onChange={e => setEditedContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="ghost" onClick={handleCancelEditing}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant="primary" onClick={handleResend}>{t('common.chat.resend')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isEditing && (
|
||||
<ContentSwitch
|
||||
count={item.siblingCount}
|
||||
currentIndex={item.siblingIndex}
|
||||
prevDisabled={!item.prevSibling}
|
||||
nextDisabled={!item.nextSibling}
|
||||
switchSibling={handleSwitchSibling}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='mt-1 h-[18px]' />
|
||||
<div className="mt-1 h-[18px]" />
|
||||
</div>
|
||||
<div className='h-10 w-10 shrink-0'>
|
||||
<div className="h-10 w-10 shrink-0">
|
||||
{
|
||||
questionIcon || (
|
||||
<div className='h-full w-full rounded-full border-[0.5px] border-black/5'>
|
||||
<User className='h-full w-full' />
|
||||
<div className="h-full w-full rounded-full border-[0.5px] border-black/5">
|
||||
<User className="h-full w-full" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { ThoughtItem, ToolInfoInThought } from '../type'
|
||||
import React from 'react'
|
||||
import ToolDetail from '@/app/components/base/chat/chat/answer/tool-detail'
|
||||
|
||||
export type IThoughtProps = {
|
||||
@ -45,7 +45,7 @@ const Thought: FC<IThoughtProps> = ({
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='my-2 space-y-2'>
|
||||
<div className="my-2 space-y-2">
|
||||
{toolThoughtList.map((item: ToolInfoInThought, index) => (
|
||||
<ToolDetail
|
||||
key={index}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { OnSend } from '../types'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { OnSend } from '../types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
@ -16,20 +16,20 @@ const TryToAsk: FC<TryToAskProps> = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='mb-2 py-2'>
|
||||
<div className="mb-2 py-2">
|
||||
<div className="mb-2.5 flex items-center justify-between gap-2">
|
||||
<Divider bgStyle='gradient' className='h-px !w-auto grow rotate-180' />
|
||||
<div className='system-xs-medium-uppercase shrink-0 text-text-tertiary'>{t('appDebug.feature.suggestedQuestionsAfterAnswer.tryToAsk')}</div>
|
||||
<Divider bgStyle='gradient' className='h-px !w-auto grow' />
|
||||
<Divider bgStyle="gradient" className="h-px !w-auto grow rotate-180" />
|
||||
<div className="system-xs-medium-uppercase shrink-0 text-text-tertiary">{t('appDebug.feature.suggestedQuestionsAfterAnswer.tryToAsk')}</div>
|
||||
<Divider bgStyle="gradient" className="h-px !w-auto grow" />
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center">
|
||||
{
|
||||
suggestedQuestions.map((suggestQuestion, index) => (
|
||||
<Button
|
||||
size='small'
|
||||
size="small"
|
||||
key={index}
|
||||
variant='secondary-accent'
|
||||
className='mb-1 mr-1 last:mr-0'
|
||||
variant="secondary-accent"
|
||||
className="mb-1 mr-1 last:mr-0"
|
||||
onClick={() => onSend(suggestQuestion)}
|
||||
>
|
||||
{suggestQuestion}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { Annotation, MessageRating } from '@/models/log'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { InputVarType } from '@/app/components/workflow/types'
|
||||
import type { Annotation, MessageRating } from '@/models/log'
|
||||
import type { FileResponse } from '@/types/workflow'
|
||||
|
||||
export type MessageMore = {
|
||||
@ -92,7 +92,7 @@ export type IChatItem = {
|
||||
useCurrentUserAvatar?: boolean
|
||||
isOpeningStatement?: boolean
|
||||
suggestedQuestions?: string[]
|
||||
log?: { role: string; text: string; files?: FileEntity[] }[]
|
||||
log?: { role: string, text: string, files?: FileEntity[] }[]
|
||||
agent_thoughts?: ThoughtItem[]
|
||||
message_files?: FileEntity[]
|
||||
workflow_run_id?: string
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { InputForm } from './type'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
|
||||
export const processOpeningStatement = (openingStatement: string, inputs: Record<string, any>, inputsForm: InputForm[]) => {
|
||||
if (!openingStatement)
|
||||
@ -32,7 +32,7 @@ export const getProcessedInputs = (inputs: Record<string, any>, inputsForm: Inpu
|
||||
inputsForm.forEach((item) => {
|
||||
const inputValue = inputs[item.variable]
|
||||
// set boolean type default value
|
||||
if(item.type === InputVarType.checkbox) {
|
||||
if (item.type === InputVarType.checkbox) {
|
||||
processedInputs[item.variable] = !!inputValue
|
||||
return
|
||||
}
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import Chat from '../chat'
|
||||
import type { FileEntity } from '../../file-uploader/types'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
OnSend,
|
||||
} from '../types'
|
||||
import { useChat } from '../chat/hooks'
|
||||
import { getLastAnswer, isValidGeneratedAnswer } from '../utils'
|
||||
import { useEmbeddedChatbotContext } from './context'
|
||||
import { isDify } from './utils'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
||||
import InputsForm from '@/app/components/base/chat/embedded-chatbot/inputs-form'
|
||||
import LogoAvatar from '@/app/components/base/logo/logo-embedded-chat-avatar'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import {
|
||||
fetchSuggestedQuestions,
|
||||
getUrl,
|
||||
stopChatMessageResponding,
|
||||
} from '@/service/share'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import LogoAvatar from '@/app/components/base/logo/logo-embedded-chat-avatar'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { FileEntity } from '../../file-uploader/types'
|
||||
import Avatar from '../../avatar'
|
||||
import Chat from '../chat'
|
||||
import { useChat } from '../chat/hooks'
|
||||
import { getLastAnswer, isValidGeneratedAnswer } from '../utils'
|
||||
import { useEmbeddedChatbotContext } from './context'
|
||||
import { isDify } from './utils'
|
||||
|
||||
const ChatWrapper = () => {
|
||||
const {
|
||||
@ -149,11 +149,7 @@ const ChatWrapper = () => {
|
||||
const doRegenerate = useCallback((chatItem: ChatItem, editedQuestion?: { message: string, files?: FileEntity[] }) => {
|
||||
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
|
||||
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
|
||||
doSend(editedQuestion ? editedQuestion.message : question.content,
|
||||
editedQuestion ? editedQuestion.files : question.message_files,
|
||||
true,
|
||||
isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null,
|
||||
)
|
||||
doSend(editedQuestion ? editedQuestion.message : question.content, editedQuestion ? editedQuestion.files : question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
||||
}, [chatList, doSend])
|
||||
|
||||
const messageList = useMemo(() => {
|
||||
@ -171,7 +167,7 @@ const ChatWrapper = () => {
|
||||
if (isMobile) {
|
||||
if (!currentConversationId)
|
||||
return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} />
|
||||
return <div className='mb-4'></div>
|
||||
return <div className="mb-4"></div>
|
||||
}
|
||||
else {
|
||||
return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} />
|
||||
@ -191,15 +187,15 @@ const ChatWrapper = () => {
|
||||
if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) {
|
||||
return (
|
||||
<div className={cn('flex items-center justify-center px-4 py-12', isMobile ? 'min-h-[30vh] py-0' : 'h-[50vh]')}>
|
||||
<div className='flex max-w-[720px] grow gap-4'>
|
||||
<div className="flex max-w-[720px] grow gap-4">
|
||||
<AppIcon
|
||||
size='xl'
|
||||
size="xl"
|
||||
iconType={appData?.site.icon_type}
|
||||
icon={appData?.site.icon}
|
||||
background={appData?.site.icon_background}
|
||||
imageUrl={appData?.site.icon_url}
|
||||
/>
|
||||
<div className='body-lg-regular grow rounded-2xl bg-chat-bubble-bg px-4 py-3 text-text-primary'>
|
||||
<div className="body-lg-regular grow rounded-2xl bg-chat-bubble-bg px-4 py-3 text-text-primary">
|
||||
<Markdown content={welcomeMessage.content} />
|
||||
<SuggestedQuestions item={welcomeMessage} />
|
||||
</div>
|
||||
@ -210,29 +206,31 @@ const ChatWrapper = () => {
|
||||
return (
|
||||
<div className={cn('flex min-h-[50vh] flex-col items-center justify-center gap-3 py-12', isMobile ? 'min-h-[30vh] py-0' : 'h-[50vh]')}>
|
||||
<AppIcon
|
||||
size='xl'
|
||||
size="xl"
|
||||
iconType={appData?.site.icon_type}
|
||||
icon={appData?.site.icon}
|
||||
background={appData?.site.icon_background}
|
||||
imageUrl={appData?.site.icon_url}
|
||||
/>
|
||||
<div className='max-w-[768px] px-4'>
|
||||
<Markdown className='!body-2xl-regular !text-text-tertiary' content={welcomeMessage.content} />
|
||||
<div className="max-w-[768px] px-4">
|
||||
<Markdown className="!body-2xl-regular !text-text-tertiary" content={welcomeMessage.content} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState, allInputsHidden])
|
||||
|
||||
const answerIcon = isDify()
|
||||
? <LogoAvatar className='relative shrink-0' />
|
||||
? <LogoAvatar className="relative shrink-0" />
|
||||
: (appData?.site && appData.site.use_icon_as_answer_icon)
|
||||
? <AnswerIcon
|
||||
iconType={appData.site.icon_type}
|
||||
icon={appData.site.icon}
|
||||
background={appData.site.icon_background}
|
||||
imageUrl={appData.site.icon_url}
|
||||
/>
|
||||
: null
|
||||
? (
|
||||
<AnswerIcon
|
||||
iconType={appData.site.icon_type}
|
||||
icon={appData.site.icon}
|
||||
background={appData.site.icon_background}
|
||||
imageUrl={appData.site.icon_url}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
|
||||
return (
|
||||
<Chat
|
||||
@ -248,12 +246,12 @@ const ChatWrapper = () => {
|
||||
inputsForm={inputsForms}
|
||||
onRegenerate={doRegenerate}
|
||||
onStopResponding={handleStop}
|
||||
chatNode={
|
||||
chatNode={(
|
||||
<>
|
||||
{chatNode}
|
||||
{welcome}
|
||||
</>
|
||||
}
|
||||
)}
|
||||
allToolIcons={appMeta?.tool_icons || {}}
|
||||
onFeedback={handleFeedback}
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
@ -264,11 +262,14 @@ const ChatWrapper = () => {
|
||||
inputDisabled={inputDisabled}
|
||||
questionIcon={
|
||||
initUserVariables?.avatar_url
|
||||
? <Avatar
|
||||
avatar={initUserVariables.avatar_url}
|
||||
name={initUserVariables.name || 'user'}
|
||||
size={40}
|
||||
/> : undefined
|
||||
? (
|
||||
<Avatar
|
||||
avatar={initUserVariables.avatar_url}
|
||||
name={initUserVariables.name || 'user'}
|
||||
size={40}
|
||||
/>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { RefObject } from 'react'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
@ -15,6 +14,7 @@ import type {
|
||||
ConversationItem,
|
||||
} from '@/models/share'
|
||||
import { noop } from 'lodash-es'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
|
||||
export type EmbeddedChatbotContextValue = {
|
||||
appMeta: AppMeta | null
|
||||
@ -45,9 +45,9 @@ export type EmbeddedChatbotContextValue = {
|
||||
clearChatList?: boolean
|
||||
setClearChatList: (state: boolean) => void
|
||||
isResponding?: boolean
|
||||
setIsResponding: (state: boolean) => void,
|
||||
currentConversationInputs: Record<string, any> | null,
|
||||
setCurrentConversationInputs: (v: Record<string, any>) => void,
|
||||
setIsResponding: (state: boolean) => void
|
||||
currentConversationInputs: Record<string, any> | null
|
||||
setCurrentConversationInputs: (v: Record<string, any>) => void
|
||||
allInputsHidden: boolean
|
||||
initUserVariables?: {
|
||||
name?: string
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { RiCollapseDiagonal2Line, RiExpandDiagonal2Line, RiResetLeftLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Theme } from '../theme/theme-context'
|
||||
import { CssTransform } from '../theme/utils'
|
||||
import { RiCollapseDiagonal2Line, RiExpandDiagonal2Line, RiResetLeftLine } from '@remixicon/react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import {
|
||||
useEmbeddedChatbotContext,
|
||||
} from '../context'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { CssTransform } from '../theme/utils'
|
||||
|
||||
export type IHeaderProps = {
|
||||
isMobile?: boolean
|
||||
@ -59,7 +59,8 @@ const Header: FC<IHeaderProps> = ({
|
||||
}, [parentOrigin])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isIframe) return
|
||||
if (!isIframe)
|
||||
return
|
||||
|
||||
const listener = (event: MessageEvent) => handleMessageReceived(event)
|
||||
window.addEventListener('message', listener)
|
||||
@ -70,7 +71,8 @@ const Header: FC<IHeaderProps> = ({
|
||||
}, [isIframe, handleMessageReceived])
|
||||
|
||||
const handleToggleExpand = useCallback(() => {
|
||||
if (!isIframe || !showToggleExpandButton) return
|
||||
if (!isIframe || !showToggleExpandButton)
|
||||
return
|
||||
setExpanded(!expanded)
|
||||
window.parent.postMessage({
|
||||
type: 'dify-chatbot-expand-change',
|
||||
@ -79,38 +81,39 @@ const Header: FC<IHeaderProps> = ({
|
||||
|
||||
if (!isMobile) {
|
||||
return (
|
||||
<div className='flex h-14 shrink-0 items-center justify-end p-3'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className="flex h-14 shrink-0 items-center justify-end p-3">
|
||||
<div className="flex items-center gap-1">
|
||||
{/* powered by */}
|
||||
<div className='shrink-0'>
|
||||
<div className="shrink-0">
|
||||
{!appData?.custom_config?.remove_webapp_brand && (
|
||||
<div className={cn(
|
||||
'flex shrink-0 items-center gap-1.5 px-2',
|
||||
)}>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div>
|
||||
)}
|
||||
>
|
||||
<div className="system-2xs-medium-uppercase text-text-tertiary">{t('share.chat.poweredBy')}</div>
|
||||
{
|
||||
systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
|
||||
? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' />
|
||||
? <img src={systemFeatures.branding.workspace_logo} alt="logo" className="block h-5 w-auto" />
|
||||
: appData?.custom_config?.replace_webapp_logo
|
||||
? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' />
|
||||
: <DifyLogo size='small' />
|
||||
? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt="logo" className="block h-5 w-auto" />
|
||||
: <DifyLogo size="small" />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{currentConversationId && (
|
||||
<Divider type='vertical' className='h-3.5' />
|
||||
<Divider type="vertical" className="h-3.5" />
|
||||
)}
|
||||
{
|
||||
showToggleExpandButton && (
|
||||
<Tooltip
|
||||
popupContent={expanded ? t('share.chat.collapse') : t('share.chat.expand')}
|
||||
>
|
||||
<ActionButton size='l' onClick={handleToggleExpand}>
|
||||
<ActionButton size="l" onClick={handleToggleExpand}>
|
||||
{
|
||||
expanded
|
||||
? <RiCollapseDiagonal2Line className='h-[18px] w-[18px]' />
|
||||
: <RiExpandDiagonal2Line className='h-[18px] w-[18px]' />
|
||||
? <RiCollapseDiagonal2Line className="h-[18px] w-[18px]" />
|
||||
: <RiExpandDiagonal2Line className="h-[18px] w-[18px]" />
|
||||
}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
@ -120,8 +123,8 @@ const Header: FC<IHeaderProps> = ({
|
||||
<Tooltip
|
||||
popupContent={t('share.chat.resetChat')}
|
||||
>
|
||||
<ActionButton size='l' onClick={onCreateNewChat}>
|
||||
<RiResetLeftLine className='h-[18px] w-[18px]' />
|
||||
<ActionButton size="l" onClick={onCreateNewChat}>
|
||||
<RiResetLeftLine className="h-[18px] w-[18px]" />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
@ -141,19 +144,19 @@ const Header: FC<IHeaderProps> = ({
|
||||
<div className="flex grow items-center space-x-3">
|
||||
{customerIcon}
|
||||
<div
|
||||
className='system-md-semibold truncate'
|
||||
className="system-md-semibold truncate"
|
||||
style={CssTransform(theme?.colorFontOnHeaderStyle ?? '')}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className="flex items-center gap-1">
|
||||
{
|
||||
showToggleExpandButton && (
|
||||
<Tooltip
|
||||
popupContent={expanded ? t('share.chat.collapse') : t('share.chat.expand')}
|
||||
>
|
||||
<ActionButton size='l' onClick={handleToggleExpand}>
|
||||
<ActionButton size="l" onClick={handleToggleExpand}>
|
||||
{
|
||||
expanded
|
||||
? <RiCollapseDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
|
||||
@ -167,7 +170,7 @@ const Header: FC<IHeaderProps> = ({
|
||||
<Tooltip
|
||||
popupContent={t('share.chat.resetChat')}
|
||||
>
|
||||
<ActionButton size='l' onClick={onCreateNewChat}>
|
||||
<ActionButton size="l" onClick={onCreateNewChat}>
|
||||
<RiResetLeftLine className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
Feedback,
|
||||
} from '../types'
|
||||
import type {
|
||||
// AppData,
|
||||
ConversationItem,
|
||||
} from '@/models/share'
|
||||
import { useLocalStorageState } from 'ahooks'
|
||||
import { produce } from 'immer'
|
||||
import { noop } from 'lodash-es'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -7,33 +19,21 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { useLocalStorageState } from 'ahooks'
|
||||
import { produce } from 'immer'
|
||||
import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
Feedback,
|
||||
} from '../types'
|
||||
import { CONVERSATION_ID_INFO } from '../constants'
|
||||
import { buildChatItemTree, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams, getProcessedUserVariablesFromUrlParams } from '../utils'
|
||||
import { getProcessedFilesFromResponse } from '../../file-uploader/utils'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import { changeLanguage } from '@/i18n-config/i18next-config'
|
||||
import {
|
||||
fetchChatList,
|
||||
fetchConversations,
|
||||
generationConversationName,
|
||||
updateFeedback,
|
||||
} from '@/service/share'
|
||||
import type {
|
||||
// AppData,
|
||||
ConversationItem,
|
||||
} from '@/models/share'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { changeLanguage } from '@/i18n-config/i18next-config'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import { getProcessedFilesFromResponse } from '../../file-uploader/utils'
|
||||
import { CONVERSATION_ID_INFO } from '../constants'
|
||||
import { buildChatItemTree, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams, getProcessedUserVariablesFromUrlParams } from '../utils'
|
||||
|
||||
function getFormattedChatList(messages: any[]) {
|
||||
const newChatList: ChatItem[] = []
|
||||
@ -112,8 +112,7 @@ export const useEmbeddedChatbot = () => {
|
||||
defaultValue: {},
|
||||
})
|
||||
const allowResetChat = !conversationId
|
||||
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || conversationId || '',
|
||||
[appId, conversationIdInfo, userId, conversationId])
|
||||
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || conversationId || '', [appId, conversationIdInfo, userId, conversationId])
|
||||
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
|
||||
if (appId) {
|
||||
let prevValue = conversationIdInfo?.[appId || '']
|
||||
|
||||
@ -3,23 +3,23 @@ import {
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ChatWrapper from '@/app/components/base/chat/embedded-chatbot/chat-wrapper'
|
||||
import Header from '@/app/components/base/chat/embedded-chatbot/header'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import {
|
||||
EmbeddedChatbotContext,
|
||||
useEmbeddedChatbotContext,
|
||||
} from './context'
|
||||
import { useEmbeddedChatbot } from './hooks'
|
||||
import { isDify } from './utils'
|
||||
import { useThemeContext } from './theme/theme-context'
|
||||
import { CssTransform } from './theme/utils'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header'
|
||||
import Header from '@/app/components/base/chat/embedded-chatbot/header'
|
||||
import ChatWrapper from '@/app/components/base/chat/embedded-chatbot/chat-wrapper'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { isDify } from './utils'
|
||||
|
||||
const Chatbot = () => {
|
||||
const {
|
||||
@ -46,7 +46,7 @@ const Chatbot = () => {
|
||||
useDocumentTitle(site?.title || 'Chat')
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className="relative">
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col rounded-2xl',
|
||||
@ -64,7 +64,7 @@ const Chatbot = () => {
|
||||
/>
|
||||
<div className={cn('flex grow flex-col overflow-y-auto', isMobile && 'm-[0.5px] !h-[calc(100vh_-_3rem)] rounded-2xl bg-chatbot-bg')}>
|
||||
{appChatListDataLoading && (
|
||||
<Loading type='app' />
|
||||
<Loading type="app" />
|
||||
)}
|
||||
{!appChatListDataLoading && (
|
||||
<ChatWrapper key={chatShouldReloadKey} />
|
||||
@ -73,18 +73,19 @@ const Chatbot = () => {
|
||||
</div>
|
||||
{/* powered by */}
|
||||
{isMobile && (
|
||||
<div className='flex h-[60px] shrink-0 items-center pl-2'>
|
||||
<div className="flex h-[60px] shrink-0 items-center pl-2">
|
||||
{!appData?.custom_config?.remove_webapp_brand && (
|
||||
<div className={cn(
|
||||
'flex shrink-0 items-center gap-1.5 px-2',
|
||||
)}>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div>
|
||||
)}
|
||||
>
|
||||
<div className="system-2xs-medium-uppercase text-text-tertiary">{t('share.chat.poweredBy')}</div>
|
||||
{
|
||||
systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
|
||||
? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' />
|
||||
? <img src={systemFeatures.branding.workspace_logo} alt="logo" className="block h-5 w-auto" />
|
||||
: appData?.custom_config?.replace_webapp_logo
|
||||
? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' />
|
||||
: <DifyLogo size='small' />
|
||||
? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt="logo" className="block h-5 w-auto" />
|
||||
: <DifyLogo size="small" />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
@ -133,43 +134,46 @@ const EmbeddedChatbotWrapper = () => {
|
||||
initUserVariables,
|
||||
} = useEmbeddedChatbot()
|
||||
|
||||
return <EmbeddedChatbotContext.Provider value={{
|
||||
appData,
|
||||
appParams,
|
||||
appMeta,
|
||||
appChatListDataLoading,
|
||||
currentConversationId,
|
||||
currentConversationItem,
|
||||
appPrevChatList,
|
||||
pinnedConversationList,
|
||||
conversationList,
|
||||
newConversationInputs,
|
||||
newConversationInputsRef,
|
||||
handleNewConversationInputsChange,
|
||||
inputsForms,
|
||||
handleNewConversation,
|
||||
handleStartChat,
|
||||
handleChangeConversation,
|
||||
handleNewConversationCompleted,
|
||||
chatShouldReloadKey,
|
||||
isMobile,
|
||||
isInstalledApp,
|
||||
allowResetChat,
|
||||
appId,
|
||||
handleFeedback,
|
||||
currentChatInstanceRef,
|
||||
themeBuilder,
|
||||
clearChatList,
|
||||
setClearChatList,
|
||||
isResponding,
|
||||
setIsResponding,
|
||||
currentConversationInputs,
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
}}>
|
||||
<Chatbot />
|
||||
</EmbeddedChatbotContext.Provider>
|
||||
return (
|
||||
<EmbeddedChatbotContext.Provider value={{
|
||||
appData,
|
||||
appParams,
|
||||
appMeta,
|
||||
appChatListDataLoading,
|
||||
currentConversationId,
|
||||
currentConversationItem,
|
||||
appPrevChatList,
|
||||
pinnedConversationList,
|
||||
conversationList,
|
||||
newConversationInputs,
|
||||
newConversationInputsRef,
|
||||
handleNewConversationInputsChange,
|
||||
inputsForms,
|
||||
handleNewConversation,
|
||||
handleStartChat,
|
||||
handleChangeConversation,
|
||||
handleNewConversationCompleted,
|
||||
chatShouldReloadKey,
|
||||
isMobile,
|
||||
isInstalledApp,
|
||||
allowResetChat,
|
||||
appId,
|
||||
handleFeedback,
|
||||
currentChatInstanceRef,
|
||||
themeBuilder,
|
||||
clearChatList,
|
||||
setClearChatList,
|
||||
isResponding,
|
||||
setIsResponding,
|
||||
currentConversationInputs,
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
}}
|
||||
>
|
||||
<Chatbot />
|
||||
</EmbeddedChatbotContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const EmbeddedChatbot = () => {
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import React, { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEmbeddedChatbotContext } from '../context'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import { PortalSelect } from '@/app/components/base/select'
|
||||
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { PortalSelect } from '@/app/components/base/select'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useEmbeddedChatbotContext } from '../context'
|
||||
|
||||
type Props = {
|
||||
showTip?: boolean
|
||||
@ -42,14 +42,14 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
const visibleInputsForms = inputsForms.filter(form => form.hide !== true)
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
<div className="space-y-4">
|
||||
{visibleInputsForms.map(form => (
|
||||
<div key={form.variable} className='space-y-1'>
|
||||
<div key={form.variable} className="space-y-1">
|
||||
{form.type !== InputVarType.checkbox && (
|
||||
<div className='flex h-6 items-center gap-1'>
|
||||
<div className='system-md-semibold text-text-secondary'>{form.label}</div>
|
||||
<div className="flex h-6 items-center gap-1">
|
||||
<div className="system-md-semibold text-text-secondary">{form.label}</div>
|
||||
{!form.required && (
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">{t('workflow.panel.optional')}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -62,7 +62,7 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
)}
|
||||
{form.type === InputVarType.number && (
|
||||
<Input
|
||||
type='number'
|
||||
type="number"
|
||||
value={inputsFormValue?.[form.variable] || ''}
|
||||
onChange={e => handleFormChange(form.variable, e.target.value)}
|
||||
placeholder={form.label}
|
||||
@ -85,7 +85,7 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
)}
|
||||
{form.type === InputVarType.select && (
|
||||
<PortalSelect
|
||||
popupClassName='w-[200px]'
|
||||
popupClassName="w-[200px]"
|
||||
value={inputsFormValue?.[form.variable] ?? form.default ?? ''}
|
||||
items={form.options.map((option: string) => ({ value: option, name: option }))}
|
||||
onSelect={item => handleFormChange(form.variable, item.value as string)}
|
||||
@ -124,16 +124,16 @@ const InputsFormContent = ({ showTip }: Props) => {
|
||||
value={inputsFormValue?.[form.variable] || ''}
|
||||
onChange={v => handleFormChange(form.variable, v)}
|
||||
noWrapper
|
||||
className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1'
|
||||
className="bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1"
|
||||
placeholder={
|
||||
<div className='whitespace-pre'>{form.json_schema}</div>
|
||||
<div className="whitespace-pre">{form.json_schema}</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{showTip && (
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.chatFormTip')}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">{t('share.chat.chatFormTip')}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import InputsFormContent from '@/app/components/base/chat/embedded-chatbot/inputs-form/content'
|
||||
import { useEmbeddedChatbotContext } from '../context'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useEmbeddedChatbotContext } from '../context'
|
||||
|
||||
type Props = {
|
||||
collapsed: boolean
|
||||
@ -34,19 +34,21 @@ const InputsFormNode = ({
|
||||
<div className={cn(
|
||||
'w-full max-w-[672px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-md',
|
||||
collapsed && 'border border-components-card-border bg-components-card-bg shadow-none',
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center gap-3 rounded-t-2xl px-6 py-4',
|
||||
!collapsed && 'border-b border-divider-subtle',
|
||||
isMobile && 'px-4 py-3',
|
||||
)}>
|
||||
<Message3Fill className='h-6 w-6 shrink-0' />
|
||||
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
|
||||
)}
|
||||
>
|
||||
<Message3Fill className="h-6 w-6 shrink-0" />
|
||||
<div className="system-xl-semibold grow text-text-secondary">{t('share.chat.chatSettingsTitle')}</div>
|
||||
{collapsed && (
|
||||
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(false)}>{t('common.operation.edit')}</Button>
|
||||
<Button className="uppercase text-text-tertiary" size="small" variant="ghost" onClick={() => setCollapsed(false)}>{t('common.operation.edit')}</Button>
|
||||
)}
|
||||
{!collapsed && currentConversationId && (
|
||||
<Button className='uppercase text-text-tertiary' size='small' variant='ghost' onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button>
|
||||
<Button className="uppercase text-text-tertiary" size="small" variant="ghost" onClick={() => setCollapsed(true)}>{t('common.operation.close')}</Button>
|
||||
)}
|
||||
</div>
|
||||
{!collapsed && (
|
||||
@ -57,24 +59,26 @@ const InputsFormNode = ({
|
||||
{!collapsed && !currentConversationId && (
|
||||
<div className={cn('p-6', isMobile && 'p-4')}>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='w-full'
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
onClick={() => handleStartChat(() => setCollapsed(true))}
|
||||
style={
|
||||
themeBuilder?.theme
|
||||
? {
|
||||
backgroundColor: themeBuilder?.theme.primaryColor,
|
||||
}
|
||||
backgroundColor: themeBuilder?.theme.primaryColor,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>{t('share.chat.startChat')}</Button>
|
||||
>
|
||||
{t('share.chat.startChat')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{collapsed && (
|
||||
<div className='flex w-full max-w-[720px] items-center py-4'>
|
||||
<Divider bgStyle='gradient' className='h-px basis-1/2 rotate-180' />
|
||||
<Divider bgStyle='gradient' className='h-px basis-1/2' />
|
||||
<div className="flex w-full max-w-[720px] items-center py-4">
|
||||
<Divider bgStyle="gradient" className="h-px basis-1/2 rotate-180" />
|
||||
<Divider bgStyle="gradient" className="h-px basis-1/2" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiChatSettingsLine,
|
||||
} from '@remixicon/react'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import InputsFormContent from '@/app/components/base/chat/embedded-chatbot/inputs-form/content'
|
||||
import { Message3Fill } from '@/app/components/base/icons/src/public/other'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
@ -20,7 +20,7 @@ const ViewFormDropdown = ({ iconColor }: Props) => {
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
placement="bottom-end"
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 4,
|
||||
@ -29,17 +29,17 @@ const ViewFormDropdown = ({ iconColor }: Props) => {
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setOpen(v => !v)}
|
||||
>
|
||||
<ActionButton size='l' state={open ? ActionButtonState.Hover : ActionButtonState.Default}>
|
||||
<ActionButton size="l" state={open ? ActionButtonState.Hover : ActionButtonState.Default}>
|
||||
<RiChatSettingsLine className={cn('h-[18px] w-[18px]', iconColor)} />
|
||||
</ActionButton>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className="z-50">
|
||||
<div className='w-[400px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg backdrop-blur-sm'>
|
||||
<div className='flex items-center gap-3 rounded-t-2xl border-b border-divider-subtle px-6 py-4'>
|
||||
<Message3Fill className='h-6 w-6 shrink-0' />
|
||||
<div className='system-xl-semibold grow text-text-secondary'>{t('share.chat.chatSettingsTitle')}</div>
|
||||
<div className="w-[400px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg backdrop-blur-sm">
|
||||
<div className="flex items-center gap-3 rounded-t-2xl border-b border-divider-subtle px-6 py-4">
|
||||
<Message3Fill className="h-6 w-6 shrink-0" />
|
||||
<div className="system-xl-semibold grow text-text-secondary">{t('share.chat.chatSettingsTitle')}</div>
|
||||
</div>
|
||||
<div className='p-6'>
|
||||
<div className="p-6">
|
||||
<InputsFormContent />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import type {
|
||||
ModelConfig,
|
||||
VisionSettings,
|
||||
} from '@/types/app'
|
||||
import type { IChatItem } from '@/app/components/base/chat/chat/type'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import type { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
|
||||
export type { VisionFile } from '@/types/app'
|
||||
export { TransferMethod } from '@/types/app'
|
||||
export type {
|
||||
Inputs,
|
||||
PromptVariable,
|
||||
} from '@/models/debug'
|
||||
export type { VisionFile } from '@/types/app'
|
||||
export { TransferMethod } from '@/types/app'
|
||||
|
||||
export type UserInputForm = {
|
||||
default: string
|
||||
@ -85,7 +85,7 @@ export type OnSend = {
|
||||
(message: string, files: FileEntity[] | undefined, isRegenerate: boolean, lastAnswer?: ChatItem | null): void
|
||||
}
|
||||
|
||||
export type OnRegenerate = (chatItem: ChatItem, editedQuestion?: { message: string; files?: FileEntity[] }) => void
|
||||
export type OnRegenerate = (chatItem: ChatItem, editedQuestion?: { message: string, files?: FileEntity[] }) => void
|
||||
|
||||
export type Callback = {
|
||||
onSuccess: () => void
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { UUID_NIL } from './constants'
|
||||
import type { IChatItem } from './chat/type'
|
||||
import type { ChatItem, ChatItemInTree } from './types'
|
||||
import { UUID_NIL } from './constants'
|
||||
|
||||
async function decodeBase64AndDecompress(base64String: string) {
|
||||
try {
|
||||
@ -154,10 +154,12 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] {
|
||||
if (
|
||||
!parentMessageId
|
||||
|| !allMessages.some(item => item.id === parentMessageId) // parent message might not be fetched yet, in this case we will append the question to the root nodes
|
||||
)
|
||||
) {
|
||||
rootNodes.push(questionNode)
|
||||
else
|
||||
}
|
||||
else {
|
||||
map[parentMessageId]?.children!.push(questionNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,13 +233,13 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch
|
||||
}
|
||||
|
||||
export {
|
||||
getRawInputsFromUrlParams,
|
||||
buildChatItemTree,
|
||||
getLastAnswer,
|
||||
getProcessedInputsFromUrlParams,
|
||||
getProcessedSystemVariablesFromUrlParams,
|
||||
getProcessedUserVariablesFromUrlParams,
|
||||
getRawInputsFromUrlParams,
|
||||
getRawUserVariablesFromUrlParams,
|
||||
isValidGeneratedAnswer,
|
||||
getLastAnswer,
|
||||
buildChatItemTree,
|
||||
getThreadMessages,
|
||||
isValidGeneratedAnswer,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user