Feat: Support re-segmentation (#114)
Co-authored-by: John Wang <takatost@gmail.com> Co-authored-by: Jyong <718720800@qq.com> Co-authored-by: 金伟强 <iamjoel007@gmail.com>
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean, useClickAway } from 'ahooks'
|
||||
import { ChevronDownIcon, Cog8ToothIcon, InformationCircleIcon } from '@heroicons/react/24/outline'
|
||||
import ParamItem from './param-item'
|
||||
import Radio from '@/app/components/base/radio'
|
||||
import Panel from '@/app/components/base/panel'
|
||||
import type { CompletionParams } from '@/models/debug'
|
||||
import { Cog8ToothIcon, InformationCircleIcon, ChevronDownIcon } from '@heroicons/react/24/outline'
|
||||
import { AppType } from '@/types/app'
|
||||
import { TONE_LIST } from '@/config'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
@@ -51,7 +51,7 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const isChatApp = mode === AppType.chat
|
||||
const availableModels = options.filter((item) => item.type === mode)
|
||||
const availableModels = options.filter(item => item.type === mode)
|
||||
const [isShowConfig, { setFalse: hideConfig, toggle: toogleShowConfig }] = useBoolean(false)
|
||||
const configContentRef = React.useRef(null)
|
||||
useClickAway(() => {
|
||||
@@ -116,14 +116,14 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
||||
onShowUseGPT4Confirm()
|
||||
return
|
||||
}
|
||||
if(id !== 'gpt-4' && completionParams.max_tokens > 4000) {
|
||||
if (id !== 'gpt-4' && completionParams.max_tokens > 4000) {
|
||||
Toast.notify({
|
||||
type: 'warning',
|
||||
message: t('common.model.params.setToCurrentModelMaxTokenTip')
|
||||
message: t('common.model.params.setToCurrentModelMaxTokenTip'),
|
||||
})
|
||||
onCompletionParamsChange({
|
||||
...completionParams,
|
||||
max_tokens: 4000
|
||||
max_tokens: 4000,
|
||||
})
|
||||
}
|
||||
setModelId(id)
|
||||
@@ -153,7 +153,7 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
||||
setToneId(id)
|
||||
onCompletionParamsChange({
|
||||
...tone.config,
|
||||
max_tokens: completionParams.max_tokens
|
||||
max_tokens: completionParams.max_tokens,
|
||||
} as CompletionParams)
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
||||
return (
|
||||
<div className='relative' ref={configContentRef}>
|
||||
<div
|
||||
className={cn(`flex items-center border h-8 px-2.5 space-x-2 rounded-lg`, disabled ? diabledStyle : ableStyle)}
|
||||
className={cn('flex items-center border h-8 px-2.5 space-x-2 rounded-lg', disabled ? diabledStyle : ableStyle)}
|
||||
onClick={() => !disabled && toogleShowConfig()}
|
||||
>
|
||||
<ModelIcon />
|
||||
@@ -206,14 +206,14 @@ const ConifgModel: FC<IConifgModelProps> = ({
|
||||
<div className="flex items-center justify-between my-5 h-9">
|
||||
<div>{t('appDebug.modelConfig.model')}</div>
|
||||
{/* model selector */}
|
||||
<div className="relative" style={{zIndex: 30}}>
|
||||
<div ref={triggerRef} onClick={() => !selectModelDisabled && toogleOption()} className={cn(selectModelDisabled ? 'cursor-not-allowed' : 'cursor-pointer', "flex items-center h-9 px-3 space-x-2 rounded-lg bg-gray-50 ")}>
|
||||
<div className="relative" style={{ zIndex: 30 }}>
|
||||
<div ref={triggerRef} onClick={() => !selectModelDisabled && toogleOption()} className={cn(selectModelDisabled ? 'cursor-not-allowed' : 'cursor-pointer', 'flex items-center h-9 px-3 space-x-2 rounded-lg bg-gray-50 ')}>
|
||||
<ModelIcon />
|
||||
<div className="text-sm gray-900">{selectedModel?.name}</div>
|
||||
{!selectModelDisabled && <ChevronDownIcon className={cn(isShowOption && 'rotate-180', 'w-[14px] h-[14px] text-gray-500')} />}
|
||||
</div>
|
||||
{isShowOption && (
|
||||
<div className={cn(isChatApp ? 'w-[159px]' : 'w-[179px]', "absolute right-0 bg-gray-50 rounded-lg shadow")}>
|
||||
<div className={cn(isChatApp ? 'w-[159px]' : 'w-[179px]', 'absolute right-0 bg-gray-50 rounded-lg shadow')}>
|
||||
{availableModels.map(item => (
|
||||
<div key={item.id} onClick={handleSelectModel(item.id)} className="flex items-center h-9 px-3 rounded-lg cursor-pointer hover:bg-gray-100">
|
||||
<ModelIcon className='mr-2' />
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
'use client'
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { DataSet } from '@/models/datasets'
|
||||
import Link from 'next/link'
|
||||
import TypeIcon from '../type-icon'
|
||||
import s from './style.module.css'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { fetchDatasets } from '@/service/datasets'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import Link from 'next/link'
|
||||
|
||||
import s from './style.module.css'
|
||||
|
||||
export interface ISelectDataSetProps {
|
||||
export type ISelectDataSetProps = {
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
selectedIds: string[]
|
||||
@@ -37,20 +37,19 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
|
||||
const { data } = await fetchDatasets({ url: '/datasets', params: { page: 1 } })
|
||||
setDataSets(data)
|
||||
setLoaded(true)
|
||||
setSelected(data.filter((item) => selectedIds.includes(item.id)))
|
||||
setSelected(data.filter(item => selectedIds.includes(item.id)))
|
||||
})()
|
||||
}, [])
|
||||
const toggleSelect = (dataSet: DataSet) => {
|
||||
const isSelected = selected.some((item) => item.id === dataSet.id)
|
||||
const isSelected = selected.some(item => item.id === dataSet.id)
|
||||
if (isSelected) {
|
||||
setSelected(selected.filter((item) => item.id !== dataSet.id))
|
||||
setSelected(selected.filter(item => item.id !== dataSet.id))
|
||||
}
|
||||
else {
|
||||
if (canSelectMulti) {
|
||||
if (canSelectMulti)
|
||||
setSelected([...selected, dataSet])
|
||||
} else {
|
||||
else
|
||||
setSelected([dataSet])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +73,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
|
||||
<div className='flex items-center justify-center mt-6 rounded-lg space-x-1 h-[128px] text-[13px] border'
|
||||
style={{
|
||||
background: 'rgba(0, 0, 0, 0.02)',
|
||||
borderColor: 'rgba(0, 0, 0, 0.02'
|
||||
borderColor: 'rgba(0, 0, 0, 0.02',
|
||||
}}
|
||||
>
|
||||
<span className='text-gray-500'>{t('appDebug.feature.dataSet.noDataSet')}</span>
|
||||
@@ -85,7 +84,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
|
||||
{datasets && datasets?.length > 0 && (
|
||||
<>
|
||||
<div className='mt-7 space-y-1 max-h-[286px] overflow-y-auto'>
|
||||
{datasets.map((item) => (
|
||||
{datasets.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={cn(s.item, selected.some(i => i.id === item.id) && s.selected, 'flex justify-between items-center h-10 px-2 rounded-lg bg-white border border-gray-200 cursor-pointer')}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/* eslint-disable multiline-ternary */
|
||||
'use client'
|
||||
import React, { FC, useEffect, useRef, useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import produce from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
import Button from '@/app/components/base/button'
|
||||
import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
|
||||
@@ -14,7 +16,7 @@ import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/conf
|
||||
import { getNewVar } from '@/utils/var'
|
||||
import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
|
||||
|
||||
export interface IOpeningStatementProps {
|
||||
export type IOpeningStatementProps = {
|
||||
promptTemplate: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
@@ -25,7 +27,7 @@ const regex = /\{\{([^}]+)\}\}/g
|
||||
|
||||
const OpeningStatement: FC<IOpeningStatementProps> = ({
|
||||
value = '',
|
||||
onChange
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
@@ -60,8 +62,6 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
||||
.replace(/>/g, '>')
|
||||
.replace(regex, varHighlightHTML({ name: '$1' })) // `<span class="${highLightClassName}">{{$1}}</span>`
|
||||
.replace(/\n/g, '<br />')
|
||||
|
||||
|
||||
|
||||
const handleEdit = () => {
|
||||
setFocus()
|
||||
@@ -76,15 +76,15 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
||||
|
||||
const handleConfirm = () => {
|
||||
const keys = getInputKeys(tempValue)
|
||||
const promptKeys = promptVariables.map((item) => item.key)
|
||||
const promptKeys = promptVariables.map(item => item.key)
|
||||
let notIncludeKeys: string[] = []
|
||||
|
||||
if (promptKeys.length === 0) {
|
||||
if (keys.length > 0) {
|
||||
if (keys.length > 0)
|
||||
notIncludeKeys = keys
|
||||
}
|
||||
} else {
|
||||
notIncludeKeys = keys.filter((key) => !promptKeys.includes(key))
|
||||
}
|
||||
else {
|
||||
notIncludeKeys = keys.filter(key => !promptKeys.includes(key))
|
||||
}
|
||||
|
||||
if (notIncludeKeys.length > 0) {
|
||||
@@ -104,7 +104,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
||||
|
||||
const autoAddVar = () => {
|
||||
const newModelConfig = produce(modelConfig, (draft) => {
|
||||
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map((key) => getNewVar(key))]
|
||||
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key))]
|
||||
})
|
||||
onChange(tempValue)
|
||||
setModelConfig(newModelConfig)
|
||||
@@ -130,26 +130,30 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
||||
isFocus={isFocus}
|
||||
>
|
||||
<div className='text-gray-700 text-sm'>
|
||||
{(hasValue || (!hasValue && isFocus)) ? (
|
||||
<>
|
||||
{isFocus ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={tempValue}
|
||||
rows={3}
|
||||
onChange={e => setTempValue(e.target.value)}
|
||||
className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none "
|
||||
placeholder={t('appDebug.openingStatement.placeholder') as string}
|
||||
>
|
||||
</textarea>
|
||||
) : (
|
||||
<div dangerouslySetInnerHTML={{
|
||||
__html: coloredContent
|
||||
}}></div>
|
||||
)}
|
||||
{(hasValue || (!hasValue && isFocus))
|
||||
? (
|
||||
<>
|
||||
{isFocus
|
||||
? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={tempValue}
|
||||
rows={3}
|
||||
onChange={e => setTempValue(e.target.value)}
|
||||
className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none "
|
||||
placeholder={t('appDebug.openingStatement.placeholder') as string}
|
||||
>
|
||||
</textarea>
|
||||
)
|
||||
: (
|
||||
<div dangerouslySetInnerHTML={{
|
||||
__html: coloredContent,
|
||||
}}></div>
|
||||
)}
|
||||
|
||||
{/* Operation Bar */}
|
||||
{isFocus && (
|
||||
{/* Operation Bar */}
|
||||
{isFocus
|
||||
&& (
|
||||
<div className='mt-2 flex items-center justify-between'>
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div>
|
||||
|
||||
@@ -160,9 +164,9 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
</>) : (
|
||||
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div>
|
||||
)}
|
||||
</>) : (
|
||||
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div>
|
||||
)}
|
||||
|
||||
{isShowConfirmAddVar && (
|
||||
<ConfirmAddVar
|
||||
|
||||
@@ -6,12 +6,12 @@ import { useContext } from 'use-context-selector'
|
||||
import {
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/solid'
|
||||
import VarIcon from '../base/icons/var-icon'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import type { PromptVariable } from '@/models/debug'
|
||||
import { AppType } from '@/types/app'
|
||||
import Select from '@/app/components/base/select'
|
||||
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
|
||||
import VarIcon from '../base/icons/var-icon'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
export type IPromptValuePanelProps = {
|
||||
@@ -71,17 +71,19 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
|
||||
</div>
|
||||
<div className='mt-2 leading-normal'>
|
||||
{
|
||||
(promptTemplate && promptTemplate?.trim()) ? (
|
||||
<div
|
||||
className="max-h-48 overflow-y-auto text-sm text-gray-700 break-all"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: format(replaceStringWithValuesWithFormat(promptTemplate.replace(/</g, '<').replace(/>/g, '>'), promptVariables, inputs)),
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
) : (
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noPrompt')}</div>
|
||||
)
|
||||
(promptTemplate && promptTemplate?.trim())
|
||||
? (
|
||||
<div
|
||||
className="max-h-48 overflow-y-auto text-sm text-gray-700 break-all"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: format(replaceStringWithValuesWithFormat(promptTemplate.replace(/</g, '<').replace(/>/g, '>'), promptVariables, inputs)),
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noPrompt')}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,37 +107,41 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
|
||||
)}
|
||||
</div>
|
||||
{
|
||||
promptVariables.length > 0 ? (
|
||||
<div className="space-y-3 ">
|
||||
{promptVariables.map(({ key, name, type, options, max_length, required }) => (
|
||||
<div key={key} className="flex items-center justify-between">
|
||||
<div className="mr-1 shrink-0 w-[120px] text-sm text-gray-900">{name || key}</div>
|
||||
{type === 'select' ? (
|
||||
<Select
|
||||
className='w-full'
|
||||
defaultValue={inputs[key] as string}
|
||||
onSelect={(i) => { handleInputValueChange(key, i.value as string) }}
|
||||
items={(options || []).map(i => ({ name: i, value: i }))}
|
||||
allowSearch={false}
|
||||
bgClassName='bg-gray-50'
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
className="w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
|
||||
placeholder={`${name}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
|
||||
type="text"
|
||||
value={inputs[key] ? `${inputs[key]}` : ''}
|
||||
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
|
||||
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
|
||||
/>
|
||||
)}
|
||||
promptVariables.length > 0
|
||||
? (
|
||||
<div className="space-y-3 ">
|
||||
{promptVariables.map(({ key, name, type, options, max_length, required }) => (
|
||||
<div key={key} className="flex items-center justify-between">
|
||||
<div className="mr-1 shrink-0 w-[120px] text-sm text-gray-900">{name || key}</div>
|
||||
{type === 'select'
|
||||
? (
|
||||
<Select
|
||||
className='w-full'
|
||||
defaultValue={inputs[key] as string}
|
||||
onSelect={(i) => { handleInputValueChange(key, i.value as string) }}
|
||||
items={(options || []).map(i => ({ name: i, value: i }))}
|
||||
allowSearch={false}
|
||||
bgClassName='bg-gray-50'
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<input
|
||||
className="w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
|
||||
placeholder={`${name}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
|
||||
type="text"
|
||||
value={inputs[key] ? `${inputs[key]}` : ''}
|
||||
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
|
||||
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noVar')}</div>
|
||||
)
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noVar')}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
'use client'
|
||||
import React, { FC, useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { Feedbacktype } from '@/app/components/app/chat'
|
||||
import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { Feedbacktype } from '@/app/components/app/chat'
|
||||
import { fetchMoreLikeThis, updateFeedback } from '@/service/share'
|
||||
|
||||
const MAX_DEPTH = 3
|
||||
export interface IGenerationItemProps {
|
||||
export type IGenerationItemProps = {
|
||||
className?: string
|
||||
content: string
|
||||
messageId?: string | null
|
||||
@@ -24,13 +25,13 @@ export interface IGenerationItemProps {
|
||||
onFeedback?: (feedback: Feedbacktype) => void
|
||||
onSave?: (messageId: string) => void
|
||||
isMobile?: boolean
|
||||
isInstalledApp: boolean,
|
||||
installedAppId?: string,
|
||||
isInstalledApp: boolean
|
||||
installedAppId?: string
|
||||
}
|
||||
|
||||
export const SimpleBtn = ({ className, onClick, children }: {
|
||||
className?: string
|
||||
onClick?: () => void,
|
||||
onClick?: () => void
|
||||
children: React.ReactNode
|
||||
}) => (
|
||||
<div
|
||||
@@ -88,7 +89,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
const [childMessageId, setChildMessageId] = useState<string | null>(null)
|
||||
const hasChild = !!childMessageId
|
||||
const [childFeedback, setChildFeedback] = useState<Feedbacktype>({
|
||||
rating: null
|
||||
rating: null,
|
||||
})
|
||||
|
||||
const handleFeedback = async (childFeedback: Feedbacktype) => {
|
||||
@@ -126,115 +127,120 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
}
|
||||
|
||||
const mainStyle = (() => {
|
||||
const res: any = !isTop ? {
|
||||
background: depth % 2 === 0 ? 'linear-gradient(90.07deg, #F9FAFB 0.05%, rgba(249, 250, 251, 0) 99.93%)' : '#fff'
|
||||
} : {}
|
||||
const res: any = !isTop
|
||||
? {
|
||||
background: depth % 2 === 0 ? 'linear-gradient(90.07deg, #F9FAFB 0.05%, rgba(249, 250, 251, 0) 99.93%)' : '#fff',
|
||||
}
|
||||
: {}
|
||||
|
||||
if (hasChild) {
|
||||
if (hasChild)
|
||||
res.boxShadow = '0px 1px 2px rgba(16, 24, 40, 0.05)'
|
||||
}
|
||||
|
||||
return res
|
||||
})()
|
||||
return (
|
||||
<div className={cn(className, isTop ? 'rounded-xl border border-gray-200 bg-white' : 'rounded-br-xl !mt-0')}
|
||||
style={isTop ? {
|
||||
boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)'
|
||||
} : {}}
|
||||
style={isTop
|
||||
? {
|
||||
boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)',
|
||||
}
|
||||
: {}}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className='flex items-center h-10'><Loading type='area' /></div>
|
||||
) : (
|
||||
<div
|
||||
className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4')}
|
||||
style={mainStyle}
|
||||
>
|
||||
<Markdown content={content} />
|
||||
{messageId && (
|
||||
<div className='flex items-center justify-between mt-3'>
|
||||
<div className='flex items-center'>
|
||||
<SimpleBtn
|
||||
className={cn(isMobile && '!px-1.5', 'space-x-1')}
|
||||
onClick={() => {
|
||||
copy(content)
|
||||
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
|
||||
}}>
|
||||
{copyIcon}
|
||||
{!isMobile && <div>{t('common.operation.copy')}</div>}
|
||||
</SimpleBtn>
|
||||
{isInWebApp && (
|
||||
<>
|
||||
<SimpleBtn
|
||||
className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')}
|
||||
onClick={() => { onSave?.(messageId as string) }}
|
||||
>
|
||||
{saveIcon}
|
||||
{!isMobile && <div>{t('common.operation.save')}</div>}
|
||||
</SimpleBtn>
|
||||
{(moreLikeThis && depth < MAX_DEPTH) && (
|
||||
{isLoading
|
||||
? (
|
||||
<div className='flex items-center h-10'><Loading type='area' /></div>
|
||||
)
|
||||
: (
|
||||
<div
|
||||
className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4')}
|
||||
style={mainStyle}
|
||||
>
|
||||
<Markdown content={content} />
|
||||
{messageId && (
|
||||
<div className='flex items-center justify-between mt-3'>
|
||||
<div className='flex items-center'>
|
||||
<SimpleBtn
|
||||
className={cn(isMobile && '!px-1.5', 'space-x-1')}
|
||||
onClick={() => {
|
||||
copy(content)
|
||||
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
|
||||
}}>
|
||||
{copyIcon}
|
||||
{!isMobile && <div>{t('common.operation.copy')}</div>}
|
||||
</SimpleBtn>
|
||||
{isInWebApp && (
|
||||
<>
|
||||
<SimpleBtn
|
||||
className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')}
|
||||
onClick={handleMoreLikeThis}
|
||||
onClick={() => { onSave?.(messageId as string) }}
|
||||
>
|
||||
{moreLikeThisIcon}
|
||||
{!isMobile && <div>{t('appDebug.feature.moreLikeThis.title')}</div>}
|
||||
</SimpleBtn>)}
|
||||
<div className="mx-3 w-[1px] h-[14px] bg-gray-200"></div>
|
||||
{!feedback?.rating && (
|
||||
<SimpleBtn className="!px-0">
|
||||
<>
|
||||
<div
|
||||
onClick={() => {
|
||||
onFeedback?.({
|
||||
rating: 'like'
|
||||
})
|
||||
}}
|
||||
className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
|
||||
<HandThumbUpIcon width={16} height={16} />
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
onFeedback?.({
|
||||
rating: 'dislike'
|
||||
})
|
||||
}}
|
||||
className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
|
||||
<HandThumbDownIcon width={16} height={16} />
|
||||
</div>
|
||||
</>
|
||||
{saveIcon}
|
||||
{!isMobile && <div>{t('common.operation.save')}</div>}
|
||||
</SimpleBtn>
|
||||
)}
|
||||
{feedback?.rating === 'like' && (
|
||||
<div
|
||||
onClick={() => {
|
||||
onFeedback?.({
|
||||
rating: null
|
||||
})
|
||||
}}
|
||||
className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-primary-600 border border-primary-200 bg-primary-100 hover:border-primary-300 hover:bg-primary-200'>
|
||||
<HandThumbUpIcon width={16} height={16} />
|
||||
</div>
|
||||
)}
|
||||
{feedback?.rating === 'dislike' && (
|
||||
<div
|
||||
onClick={() => {
|
||||
onFeedback?.({
|
||||
rating: null
|
||||
})
|
||||
}}
|
||||
className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-red-600 border border-red-200 bg-red-100 hover:border-red-300 hover:bg-red-200'>
|
||||
<HandThumbDownIcon width={16} height={16} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{(moreLikeThis && depth < MAX_DEPTH) && (
|
||||
<SimpleBtn
|
||||
className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')}
|
||||
onClick={handleMoreLikeThis}
|
||||
>
|
||||
{moreLikeThisIcon}
|
||||
{!isMobile && <div>{t('appDebug.feature.moreLikeThis.title')}</div>}
|
||||
</SimpleBtn>)}
|
||||
<div className="mx-3 w-[1px] h-[14px] bg-gray-200"></div>
|
||||
{!feedback?.rating && (
|
||||
<SimpleBtn className="!px-0">
|
||||
<>
|
||||
<div
|
||||
onClick={() => {
|
||||
onFeedback?.({
|
||||
rating: 'like',
|
||||
})
|
||||
}}
|
||||
className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
|
||||
<HandThumbUpIcon width={16} height={16} />
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
onFeedback?.({
|
||||
rating: 'dislike',
|
||||
})
|
||||
}}
|
||||
className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
|
||||
<HandThumbDownIcon width={16} height={16} />
|
||||
</div>
|
||||
</>
|
||||
</SimpleBtn>
|
||||
)}
|
||||
{feedback?.rating === 'like' && (
|
||||
<div
|
||||
onClick={() => {
|
||||
onFeedback?.({
|
||||
rating: null,
|
||||
})
|
||||
}}
|
||||
className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-primary-600 border border-primary-200 bg-primary-100 hover:border-primary-300 hover:bg-primary-200'>
|
||||
<HandThumbUpIcon width={16} height={16} />
|
||||
</div>
|
||||
)}
|
||||
{feedback?.rating === 'dislike' && (
|
||||
<div
|
||||
onClick={() => {
|
||||
onFeedback?.({
|
||||
rating: null,
|
||||
})
|
||||
}}
|
||||
className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-red-600 border border-red-200 bg-red-100 hover:border-red-300 hover:bg-red-200'>
|
||||
<HandThumbDownIcon width={16} height={16} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='text-xs text-gray-500'>{content?.length} {t('common.unit.char')}</div>
|
||||
</div>
|
||||
<div className='text-xs text-gray-500'>{content?.length} {t('common.unit.char')}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
{((childMessageId || isQuerying) && depth < 3) && (
|
||||
<div className='pl-4'>
|
||||
|
||||
Reference in New Issue
Block a user