Merge branch 'main' into feat/hitl-frontend

This commit is contained in:
twwu
2025-12-12 17:59:08 +08:00
742 changed files with 26364 additions and 17398 deletions

View File

@ -0,0 +1,49 @@
'use client'
import type { FC } from 'react'
import React, { useEffect } from 'react'
import * as amplitude from '@amplitude/analytics-browser'
import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser'
import { AMPLITUDE_API_KEY, IS_CLOUD_EDITION } from '@/config'
export type IAmplitudeProps = {
sessionReplaySampleRate?: number
}
// Check if Amplitude should be enabled
export const isAmplitudeEnabled = () => {
return IS_CLOUD_EDITION && !!AMPLITUDE_API_KEY
}
const AmplitudeProvider: FC<IAmplitudeProps> = ({
sessionReplaySampleRate = 1,
}) => {
useEffect(() => {
// Only enable in Saas edition with valid API key
if (!isAmplitudeEnabled())
return
// Initialize Amplitude
amplitude.init(AMPLITUDE_API_KEY, {
defaultTracking: {
sessions: true,
pageViews: true,
formInteractions: true,
fileDownloads: true,
},
// Enable debug logs in development environment
logLevel: amplitude.Types.LogLevel.Warn,
})
// Add Session Replay plugin
const sessionReplay = sessionReplayPlugin({
sampleRate: sessionReplaySampleRate,
})
amplitude.add(sessionReplay)
}, [])
// This is a client component that renders nothing
return null
}
export default React.memo(AmplitudeProvider)

View File

@ -0,0 +1,2 @@
export { default, isAmplitudeEnabled } from './AmplitudeProvider'
export { resetUser, setUserId, setUserProperties, trackEvent } from './utils'

View File

@ -0,0 +1,46 @@
import * as amplitude from '@amplitude/analytics-browser'
import { isAmplitudeEnabled } from './AmplitudeProvider'
/**
* Track custom event
* @param eventName Event name
* @param eventProperties Event properties (optional)
*/
export const trackEvent = (eventName: string, eventProperties?: Record<string, any>) => {
if (!isAmplitudeEnabled())
return
amplitude.track(eventName, eventProperties)
}
/**
* Set user ID
* @param userId User ID
*/
export const setUserId = (userId: string) => {
if (!isAmplitudeEnabled())
return
amplitude.setUserId(userId)
}
/**
* Set user properties
* @param properties User properties
*/
export const setUserProperties = (properties: Record<string, any>) => {
if (!isAmplitudeEnabled())
return
const identifyEvent = new amplitude.Identify()
Object.entries(properties).forEach(([key, value]) => {
identifyEvent.set(key, value)
})
amplitude.identify(identifyEvent)
}
/**
* Reset user (e.g., when user logs out)
*/
export const resetUser = () => {
if (!isAmplitudeEnabled())
return
amplitude.reset()
}

View File

@ -284,7 +284,6 @@ const ChatWrapper = () => {
themeBuilder={themeBuilder}
switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)}
inputDisabled={inputDisabled}
isMobile={isMobile}
sidebarCollapseState={sidebarCollapseState}
questionIcon={
initUserVariables?.avatar_url

View File

@ -11,7 +11,10 @@ import {
RiThumbDownLine,
RiThumbUpLine,
} from '@remixicon/react'
import type { ChatItem } from '../../types'
import type {
ChatItem,
Feedback,
} from '../../types'
import { useChatContext } from '../context'
import copy from 'copy-to-clipboard'
import Toast from '@/app/components/base/toast'
@ -22,6 +25,7 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu
import NewAudioButton from '@/app/components/base/new-audio-button'
import Modal from '@/app/components/base/modal/modal'
import Textarea from '@/app/components/base/textarea'
import Tooltip from '@/app/components/base/tooltip'
import cn from '@/utils/classnames'
type OperationProps = {
@ -67,8 +71,9 @@ const Operation: FC<OperationProps> = ({
agent_thoughts,
humanInputFormData,
} = item
const [localFeedback, setLocalFeedback] = useState(config?.supportAnnotation ? adminFeedback : feedback)
const [userLocalFeedback, setUserLocalFeedback] = useState(feedback)
const [adminLocalFeedback, setAdminLocalFeedback] = useState(adminFeedback)
const [feedbackTarget, setFeedbackTarget] = useState<'user' | 'admin'>('user')
// Separate feedback types for display
const userFeedback = feedback
@ -80,24 +85,68 @@ const Operation: FC<OperationProps> = ({
return messageContent
}, [agent_thoughts, messageContent])
const handleFeedback = async (rating: 'like' | 'dislike' | null, content?: string) => {
const displayUserFeedback = userLocalFeedback ?? userFeedback
const hasUserFeedback = !!displayUserFeedback?.rating
const hasAdminFeedback = !!adminLocalFeedback?.rating
const shouldShowUserFeedbackBar = !isOpeningStatement && config?.supportFeedback && !!onFeedback && !config?.supportAnnotation
const shouldShowAdminFeedbackBar = !isOpeningStatement && config?.supportFeedback && !!onFeedback && !!config?.supportAnnotation
const userFeedbackLabel = t('appLog.table.header.userRate') || 'User feedback'
const adminFeedbackLabel = t('appLog.table.header.adminRate') || 'Admin feedback'
const feedbackTooltipClassName = 'max-w-[260px]'
const buildFeedbackTooltip = (feedbackData?: Feedback | null, label = userFeedbackLabel) => {
if (!feedbackData?.rating)
return label
const ratingLabel = feedbackData.rating === 'like'
? (t('appLog.detail.operation.like') || 'like')
: (t('appLog.detail.operation.dislike') || 'dislike')
const feedbackText = feedbackData.content?.trim()
if (feedbackText)
return `${label}: ${ratingLabel} - ${feedbackText}`
return `${label}: ${ratingLabel}`
}
const handleFeedback = async (rating: 'like' | 'dislike' | null, content?: string, target: 'user' | 'admin' = 'user') => {
if (!config?.supportFeedback || !onFeedback)
return
await onFeedback?.(id, { rating, content })
setLocalFeedback({ rating })
// Update admin feedback state separately if annotation is supported
if (config?.supportAnnotation)
setAdminLocalFeedback(rating ? { rating } : undefined)
const nextFeedback = rating === null ? { rating: null } : { rating, content }
if (target === 'admin')
setAdminLocalFeedback(nextFeedback)
else
setUserLocalFeedback(nextFeedback)
}
const handleThumbsDown = () => {
const handleLikeClick = (target: 'user' | 'admin') => {
const currentRating = target === 'admin' ? adminLocalFeedback?.rating : displayUserFeedback?.rating
if (currentRating === 'like') {
handleFeedback(null, undefined, target)
return
}
handleFeedback('like', undefined, target)
}
const handleDislikeClick = (target: 'user' | 'admin') => {
const currentRating = target === 'admin' ? adminLocalFeedback?.rating : displayUserFeedback?.rating
if (currentRating === 'dislike') {
handleFeedback(null, undefined, target)
return
}
setFeedbackTarget(target)
setIsShowFeedbackModal(true)
}
const handleFeedbackSubmit = async () => {
await handleFeedback('dislike', feedbackContent)
await handleFeedback('dislike', feedbackContent, feedbackTarget)
setFeedbackContent('')
setIsShowFeedbackModal(false)
}
@ -117,12 +166,13 @@ const Operation: FC<OperationProps> = ({
width += 26
if (!isOpeningStatement && config?.supportAnnotation && config?.annotation_reply?.enabled)
width += 26
if (config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement)
width += 60 + 8
if (config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement)
width += 28 + 8
if (shouldShowUserFeedbackBar)
width += hasUserFeedback ? 28 + 8 : 60 + 8
if (shouldShowAdminFeedbackBar)
width += (hasAdminFeedback ? 28 : 60) + 8 + (hasUserFeedback ? 28 : 0)
return width
}, [isOpeningStatement, showPromptLog, config?.text_to_speech?.enabled, config?.supportAnnotation, config?.annotation_reply?.enabled, config?.supportFeedback, localFeedback?.rating, onFeedback])
}, [config?.annotation_reply?.enabled, config?.supportAnnotation, config?.text_to_speech?.enabled, hasAdminFeedback, hasUserFeedback, isOpeningStatement, shouldShowAdminFeedbackBar, shouldShowUserFeedbackBar, showPromptLog])
const positionRight = useMemo(() => operationWidth < maxSize, [operationWidth, maxSize])
@ -137,6 +187,110 @@ const Operation: FC<OperationProps> = ({
)}
style={(!hasWorkflowProcess && positionRight) ? { left: contentWidth + 8 } : {}}
>
{shouldShowUserFeedbackBar && (
<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>
</>
)}
</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>
)}
</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')}
>
<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'>
<Log logItem={item} />
@ -177,69 +331,6 @@ const Operation: FC<OperationProps> = ({
)}
</div>
)}
{!isOpeningStatement && config?.supportFeedback && !localFeedback?.rating && onFeedback && (
<div className='ml-1 hidden items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex'>
{!localFeedback?.rating && (
<>
<ActionButton onClick={() => handleFeedback('like')}>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
<ActionButton onClick={handleThumbsDown}>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
</>
)}
</div>
)}
{!isOpeningStatement && config?.supportFeedback && onFeedback && (
<div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'>
{/* User Feedback Display */}
{userFeedback?.rating && (
<div className='flex items-center'>
<span className='mr-1 text-xs text-text-tertiary'>User</span>
{userFeedback.rating === 'like' ? (
<ActionButton state={ActionButtonState.Active} title={userFeedback.content ? `User liked this response: ${userFeedback.content}` : 'User liked this response'}>
<RiThumbUpLine className='h-3 w-3' />
</ActionButton>
) : (
<ActionButton state={ActionButtonState.Destructive} title={userFeedback.content ? `User disliked this response: ${userFeedback.content}` : 'User disliked this response'}>
<RiThumbDownLine className='h-3 w-3' />
</ActionButton>
)}
</div>
)}
{/* Admin Feedback Controls */}
{config?.supportAnnotation && (
<div className='flex items-center'>
{userFeedback?.rating && <div className='mx-1 h-3 w-[0.5px] bg-components-actionbar-border' />}
{!adminLocalFeedback?.rating ? (
<>
<ActionButton onClick={() => handleFeedback('like')}>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
<ActionButton onClick={handleThumbsDown}>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
</>
) : (
<>
{adminLocalFeedback.rating === 'like' ? (
<ActionButton state={ActionButtonState.Active} onClick={() => handleFeedback(null)}>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
) : (
<ActionButton state={ActionButtonState.Destructive} onClick={() => handleFeedback(null)}>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
)}
</>
)}
</div>
)}
</div>
)}
</div>
<EditReplyModal
isShow={isShowReplyModal}

View File

@ -71,7 +71,6 @@ export type ChatProps = {
onFeatureBarClick?: (state: boolean) => void
noSpacing?: boolean
inputDisabled?: boolean
isMobile?: boolean
sidebarCollapseState?: boolean
hideAvatar?: boolean
onHumanInputFormSubmit?: (formID: string, formData: any) => void
@ -113,7 +112,6 @@ const Chat: FC<ChatProps> = ({
onFeatureBarClick,
noSpacing,
inputDisabled,
isMobile,
sidebarCollapseState,
hideAvatar,
onHumanInputFormSubmit,
@ -331,7 +329,6 @@ const Chat: FC<ChatProps> = ({
<TryToAsk
suggestedQuestions={suggestedQuestions}
onSend={onSend}
isMobile={isMobile}
/>
)
}

View File

@ -4,28 +4,25 @@ import { useTranslation } from 'react-i18next'
import type { OnSend } from '../types'
import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import cn from '@/utils/classnames'
type TryToAskProps = {
suggestedQuestions: string[]
onSend: OnSend
isMobile?: boolean
}
const TryToAsk: FC<TryToAskProps> = ({
suggestedQuestions,
onSend,
isMobile,
}) => {
const { t } = useTranslation()
return (
<div className='mb-2 py-2'>
<div className={cn('mb-2.5 flex items-center justify-between gap-2', isMobile && 'justify-end')}>
<Divider bgStyle='gradient' className='h-px grow rotate-180' />
<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>
{!isMobile && <Divider bgStyle='gradient' className='h-px grow' />}
<Divider bgStyle='gradient' className='h-px !w-auto grow' />
</div>
<div className={cn('flex flex-wrap justify-center', isMobile && 'justify-end')}>
<div className="flex flex-wrap justify-center">
{
suggestedQuestions.map((suggestQuestion, index) => (
<Button

View File

@ -262,7 +262,6 @@ const ChatWrapper = () => {
themeBuilder={themeBuilder}
switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)}
inputDisabled={inputDisabled}
isMobile={isMobile}
questionIcon={
initUserVariables?.avatar_url
? <Avatar

View File

@ -0,0 +1,23 @@
import React from 'react'
type ImageRenderProps = {
sourceUrl: string
name: string
}
const ImageRender = ({
sourceUrl,
name,
}: ImageRenderProps) => {
return (
<div className='size-full border-[2px] border-effects-image-frame shadow-xs'>
<img
className='size-full object-cover'
src={sourceUrl}
alt={name}
/>
</div>
)
}
export default React.memo(ImageRender)

View File

@ -0,0 +1,87 @@
import React, { useCallback } from 'react'
import ImageRender from './image-render'
import type { VariantProps } from 'class-variance-authority'
import { cva } from 'class-variance-authority'
import cn from '@/utils/classnames'
import { getFileAppearanceType } from '../file-uploader/utils'
import { FileTypeIcon } from '../file-uploader'
import Tooltip from '../tooltip'
const FileThumbVariants = cva(
'flex items-center justify-center cursor-pointer',
{
variants: {
size: {
sm: 'size-6',
md: 'size-8',
},
},
defaultVariants: {
size: 'sm',
},
},
)
export type FileEntity = {
name: string
size: number
extension: string
mimeType: string
sourceUrl: string
}
type FileThumbProps = {
file: FileEntity
className?: string
onClick?: (file: FileEntity) => void
} & VariantProps<typeof FileThumbVariants>
const FileThumb = ({
file,
size,
className,
onClick,
}: FileThumbProps) => {
const { name, mimeType, sourceUrl } = file
const isImage = mimeType.startsWith('image/')
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
e.preventDefault()
onClick?.(file)
}, [onClick, file])
return (
<Tooltip
popupContent={name}
popupClassName='p-1.5 rounded-lg system-xs-medium text-text-secondary'
position='top'
>
<div
className={cn(
FileThumbVariants({ size, className }),
isImage
? 'p-px'
: 'rounded-md border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs hover:bg-components-panel-on-panel-item-bg-alt',
)}
onClick={handleClick}
>
{
isImage ? (
<ImageRender
sourceUrl={sourceUrl}
name={name}
/>
) : (
<FileTypeIcon
type={getFileAppearanceType(name, mimeType)}
size='sm'
/>
)
}
</div>
</Tooltip>
)
}
export default React.memo(FileThumb)

View File

@ -26,10 +26,21 @@ export const getFileUploadErrorMessage = (error: any, defaultMessage: string, t:
return defaultMessage
}
type FileUploadResponse = {
created_at: number
created_by: string
extension: string
id: string
mime_type: string
name: string
preview_url: string | null
size: number
source_url: string
}
type FileUploadParams = {
file: File
onProgressCallback: (progress: number) => void
onSuccessCallback: (res: { id: string }) => void
onSuccessCallback: (res: FileUploadResponse) => void
onErrorCallback: (error?: any) => void
}
type FileUpload = (v: FileUploadParams, isPublic?: boolean, url?: string) => void
@ -53,8 +64,8 @@ export const fileUpload: FileUpload = ({
data: formData,
onprogress: onProgress,
}, isPublic, url)
.then((res: { id: string }) => {
onSuccessCallback(res)
.then((res) => {
onSuccessCallback(res as FileUploadResponse)
})
.catch((error) => {
onErrorCallback(error)
@ -174,9 +185,9 @@ export const getProcessedFilesFromResponse = (files: FileResponse[]) => {
const detectedTypeFromMime = getSupportFileType('', fileItem.mime_type)
if (detectedTypeFromFileName
&& detectedTypeFromMime
&& detectedTypeFromFileName === detectedTypeFromMime
&& detectedTypeFromFileName !== fileItem.type)
&& detectedTypeFromMime
&& detectedTypeFromFileName === detectedTypeFromMime
&& detectedTypeFromFileName !== fileItem.type)
supportFileType = detectedTypeFromFileName
}

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6C19 5.44771 18.5523 5 18 5H6C5.44771 5 5 5.44771 5 6V18C5 18.5523 5.44771 19 6 19H18C18.5523 19 19 18.5523 19 18V6ZM9.73926 13.1533C10.0706 12.7115 10.6978 12.6218 11.1396 12.9531C11.5815 13.2845 11.6712 13.9117 11.3398 14.3535L9.46777 16.8486C9.14935 17.2732 8.55487 17.3754 8.11328 17.0811L6.98828 16.3311C6.52878 16.0247 6.40465 15.4039 6.71094 14.9443C7.01729 14.4848 7.63813 14.3606 8.09766 14.667L8.43457 14.8916L9.73926 13.1533ZM16 14C16.5523 14 17 14.4477 17 15C17 15.5523 16.5523 16 16 16H14C13.4477 16 13 15.5523 13 15C13 14.4477 13.4477 14 14 14H16ZM9.73926 7.15234C10.0706 6.71052 10.6978 6.62079 11.1396 6.95215C11.5815 7.28352 11.6712 7.91071 11.3398 8.35254L9.46777 10.8477C9.14936 11.2722 8.55487 11.3744 8.11328 11.0801L6.98828 10.3301C6.52884 10.0238 6.40476 9.40286 6.71094 8.94336C7.0173 8.48384 7.63814 8.35965 8.09766 8.66602L8.43457 8.89062L9.73926 7.15234ZM16.0576 8C16.6099 8 17.0576 8.44772 17.0576 9C17.0576 9.55228 16.6099 10 16.0576 10H14.0576C13.5055 9.99985 13.0576 9.55219 13.0576 9C13.0576 8.44781 13.5055 8.00015 14.0576 8H16.0576ZM21 18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V6C3 4.34315 4.34315 3 6 3H18C19.6569 3 21 4.34315 21 6V18Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,26 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M19 6C19 5.44771 18.5523 5 18 5H6C5.44771 5 5 5.44771 5 6V18C5 18.5523 5.44771 19 6 19H18C18.5523 19 19 18.5523 19 18V6ZM9.73926 13.1533C10.0706 12.7115 10.6978 12.6218 11.1396 12.9531C11.5815 13.2845 11.6712 13.9117 11.3398 14.3535L9.46777 16.8486C9.14935 17.2732 8.55487 17.3754 8.11328 17.0811L6.98828 16.3311C6.52878 16.0247 6.40465 15.4039 6.71094 14.9443C7.01729 14.4848 7.63813 14.3606 8.09766 14.667L8.43457 14.8916L9.73926 13.1533ZM16 14C16.5523 14 17 14.4477 17 15C17 15.5523 16.5523 16 16 16H14C13.4477 16 13 15.5523 13 15C13 14.4477 13.4477 14 14 14H16ZM9.73926 7.15234C10.0706 6.71052 10.6978 6.62079 11.1396 6.95215C11.5815 7.28352 11.6712 7.91071 11.3398 8.35254L9.46777 10.8477C9.14936 11.2722 8.55487 11.3744 8.11328 11.0801L6.98828 10.3301C6.52884 10.0238 6.40476 9.40286 6.71094 8.94336C7.0173 8.48384 7.63814 8.35965 8.09766 8.66602L8.43457 8.89062L9.73926 7.15234ZM16.0576 8C16.6099 8 17.0576 8.44772 17.0576 9C17.0576 9.55228 16.6099 10 16.0576 10H14.0576C13.5055 9.99985 13.0576 9.55219 13.0576 9C13.0576 8.44781 13.5055 8.00015 14.0576 8H16.0576ZM21 18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V6C3 4.34315 4.34315 3 6 3H18C19.6569 3 21 4.34315 21 6V18Z",
"fill": "currentColor"
},
"children": []
}
]
},
"name": "SquareChecklist"
}

View File

@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './SquareChecklist.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'SquareChecklist'
export default Icon

View File

@ -6,3 +6,4 @@ export { default as Mcp } from './Mcp'
export { default as NoToolPlaceholder } from './NoToolPlaceholder'
export { default as Openai } from './Openai'
export { default as ReplayLine } from './ReplayLine'
export { default as SquareChecklist } from './SquareChecklist'

View File

@ -110,7 +110,7 @@ const NotionPageSelector = ({
setCurrentCredential(credential)
onSelect([]) // Clear selected pages when changing credential
onSelectCredential?.(credential.credentialId)
}, [invalidPreImportNotionPages, onSelect, onSelectCredential])
}, [datasetId, invalidPreImportNotionPages, notionCredentials, onSelect, onSelectCredential])
const handleSelectPages = useCallback((newSelectedPagesId: Set<string>) => {
const selectedPages = Array.from(newSelectedPagesId).map(pageId => pagesMapAndSelectedPagesId[0][pageId])

View File

@ -1,9 +1,8 @@
'use client'
import { useTranslation } from 'react-i18next'
import React, { Fragment, useMemo } from 'react'
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
import { RiArrowDownSLine } from '@remixicon/react'
import NotionIcon from '../../notion-icon'
import { CredentialIcon } from '@/app/components/datasets/common/credential-icon'
export type NotionCredential = {
credentialId: string
@ -23,14 +22,10 @@ const CredentialSelector = ({
items,
onSelect,
}: CredentialSelectorProps) => {
const { t } = useTranslation()
const currentCredential = items.find(item => item.credentialId === value)!
const getDisplayName = (item: NotionCredential) => {
return item.workspaceName || t('datasetPipeline.credentialSelector.name', {
credentialName: item.credentialName,
pluginName: 'Notion',
})
return item.workspaceName || item.credentialName
}
const currentDisplayName = useMemo(() => {
@ -43,10 +38,11 @@ const CredentialSelector = ({
({ open }) => (
<>
<MenuButton className={`flex h-7 items-center justify-center rounded-md p-1 pr-2 hover:bg-state-base-hover ${open && 'bg-state-base-hover'} cursor-pointer`}>
<NotionIcon
<CredentialIcon
className='mr-2'
src={currentCredential?.workspaceIcon}
avatarUrl={currentCredential?.workspaceIcon}
name={currentDisplayName}
size={20}
/>
<div
className='mr-1 w-[90px] truncate text-left text-sm font-medium text-text-secondary'
@ -80,10 +76,11 @@ const CredentialSelector = ({
className='flex h-9 cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover'
onClick={() => onSelect(item.credentialId)}
>
<NotionIcon
<CredentialIcon
className='mr-2 shrink-0'
src={item.workspaceIcon}
avatarUrl={item.workspaceIcon}
name={displayName}
size={20}
/>
<div
className='system-sm-medium mr-2 grow truncate text-text-secondary'

View File

@ -18,6 +18,7 @@ type PageSelectorProps = {
canPreview?: boolean
previewPageId?: string
onPreview?: (selectedPageId: string) => void
isMultipleChoice?: boolean
}
type NotionPageTreeItem = {
children: Set<string>
@ -139,8 +140,6 @@ const ItemComponent = ({ index, style, data }: ListChildComponentProps<{
checked={checkedIds.has(current.page_id)}
disabled={disabled}
onCheck={() => {
if (disabled)
return
handleCheck(index)
}}
/>

View File

@ -12,6 +12,7 @@ const PremiumBadgeVariants = cva(
size: {
s: 'premium-badge-s',
m: 'premium-badge-m',
custom: '',
},
color: {
blue: 'premium-badge-blue',
@ -33,7 +34,7 @@ const PremiumBadgeVariants = cva(
)
type PremiumBadgeProps = {
size?: 's' | 'm'
size?: 's' | 'm' | 'custom'
color?: 'blue' | 'indigo' | 'gray' | 'orange'
allowHover?: boolean
styleCss?: CSSProperties