mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
* feat(toast): add toast utility functions to global interface Expose error, success, warning, and info toast functions globally for consistent notification handling * refactor(toast): use ToastPropsColored type to enforce color consistency Create ToastPropsColored type to explicitly omit color property from ToastProps * refactor(toast): simplify toast functions using factory pattern Create a factory function to generate toast functions instead of repeating similar code. This improves maintainability and reduces code duplication. * fix: replace window.message with window.toast for copy notifications * refactor(toast): update type definition to use Parameters utility Use Parameters utility type to derive ToastPropsColored from addToast parameters for better type safety * feat(types): add RequireSome utility type for making specific properties required * feat(toast): add loading toast functionality Add loading toast type to support promises in toast notifications. This enables showing loading states for async operations. * chore: add claude script to package.json * build(eslint): add packages dist folder to ignore patterns * refactor: migrate message to toast * refactor: update toast import path from @heroui/react to @heroui/toast * docs(toast): add JSDoc comments for toast functions * fix(toast): set default timeout for loading toasts Make loading toasts disappear immediately by default when timeout is not specified * fix(translate): replace window.message with window.toast for consistency Use window.toast consistently across the translation page for displaying notifications to maintain a uniform user experience and simplify the codebase. * refactor: remove deprecated message interface from window The MessageInstance interface from antd was marked as deprecated and is no longer needed in the window object. * refactor(toast): consolidate toast utilities into single export Move all toast-related functions into a single utility export to reduce code duplication and improve maintainability. Update all imports to use the new utility function. * docs(useOcr): remove redundant comment in ocr function * docs: update comments from Chinese to English Update error log messages in CodeToolsPage to use English instead of Chinese for better consistency and maintainability * feat(toast): add no-drag style and adjust toast placement add custom CSS class to disable drag on toast elements move toast placement to top-center and adjust timeout settings
205 lines
6.3 KiB
TypeScript
205 lines
6.3 KiB
TypeScript
import Ellipsis from '@renderer/components/Ellipsis'
|
|
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
|
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
|
import { DynamicVirtualList } from '@renderer/components/VirtualList'
|
|
import { useKnowledge } from '@renderer/hooks/useKnowledge'
|
|
import FileItem from '@renderer/pages/files/FileItem'
|
|
import { getProviderName } from '@renderer/services/ProviderService'
|
|
import { KnowledgeBase, KnowledgeItem } from '@renderer/types'
|
|
import { Button, Dropdown, Tooltip } from 'antd'
|
|
import dayjs from 'dayjs'
|
|
import { PlusIcon } from 'lucide-react'
|
|
import { FC, useCallback, useMemo } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import styled from 'styled-components'
|
|
|
|
import StatusIcon from '../components/StatusIcon'
|
|
import {
|
|
ClickableSpan,
|
|
FlexAlignCenter,
|
|
ItemContainer,
|
|
ItemHeader,
|
|
KnowledgeEmptyView,
|
|
RefreshIcon,
|
|
ResponsiveButton,
|
|
StatusIconWrapper
|
|
} from '../KnowledgeContent'
|
|
|
|
interface KnowledgeContentProps {
|
|
selectedBase: KnowledgeBase
|
|
}
|
|
|
|
const getDisplayTime = (item: KnowledgeItem) => {
|
|
const timestamp = item.updated_at && item.updated_at > item.created_at ? item.updated_at : item.created_at
|
|
return dayjs(timestamp).format('MM-DD HH:mm')
|
|
}
|
|
|
|
const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
|
const { t } = useTranslation()
|
|
|
|
const { base, urlItems, refreshItem, addUrl, removeItem, getProcessingStatus, updateItem } = useKnowledge(
|
|
selectedBase.id || ''
|
|
)
|
|
|
|
const providerName = getProviderName(base?.model)
|
|
const disabled = !base?.version || !providerName
|
|
|
|
const reversedItems = useMemo(() => [...urlItems].reverse(), [urlItems])
|
|
const estimateSize = useCallback(() => 75, [])
|
|
|
|
if (!base) {
|
|
return null
|
|
}
|
|
|
|
const handleAddUrl = async () => {
|
|
if (disabled) {
|
|
return
|
|
}
|
|
|
|
const urlInput = await PromptPopup.show({
|
|
title: t('knowledge.add_url'),
|
|
message: '',
|
|
inputPlaceholder: t('knowledge.url_placeholder'),
|
|
inputProps: {
|
|
rows: 10,
|
|
onPressEnter: () => {}
|
|
}
|
|
})
|
|
|
|
if (urlInput) {
|
|
// Split input by newlines and filter out empty lines
|
|
const urls = urlInput.split('\n').filter((url) => url.trim())
|
|
|
|
for (const url of urls) {
|
|
try {
|
|
new URL(url.trim())
|
|
if (!urlItems.find((item) => item.content === url.trim())) {
|
|
addUrl(url.trim())
|
|
} else {
|
|
window.toast.success(t('knowledge.url_added'))
|
|
}
|
|
} catch (e) {
|
|
// Skip invalid URLs silently
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleEditRemark = async (item: KnowledgeItem) => {
|
|
if (disabled) {
|
|
return
|
|
}
|
|
|
|
const editedRemark: string | undefined = await PromptPopup.show({
|
|
title: t('knowledge.edit_remark'),
|
|
message: '',
|
|
inputPlaceholder: t('knowledge.edit_remark_placeholder'),
|
|
defaultValue: item.remark || '',
|
|
inputProps: {
|
|
maxLength: 100,
|
|
rows: 1
|
|
}
|
|
})
|
|
|
|
if (editedRemark !== undefined && editedRemark !== null) {
|
|
updateItem({
|
|
...item,
|
|
remark: editedRemark,
|
|
updated_at: Date.now()
|
|
})
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ItemContainer>
|
|
<ItemHeader>
|
|
<ResponsiveButton
|
|
type="primary"
|
|
icon={<PlusIcon size={16} />}
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
handleAddUrl()
|
|
}}
|
|
disabled={disabled}>
|
|
{t('knowledge.add_url')}
|
|
</ResponsiveButton>
|
|
</ItemHeader>
|
|
<ItemFlexColumn>
|
|
{urlItems.length === 0 && <KnowledgeEmptyView />}
|
|
<DynamicVirtualList
|
|
list={reversedItems}
|
|
estimateSize={estimateSize}
|
|
overscan={2}
|
|
scrollerStyle={{ paddingRight: 2 }}
|
|
itemContainerStyle={{ paddingBottom: 10 }}
|
|
autoHideScrollbar>
|
|
{(item) => (
|
|
<FileItem
|
|
key={item.id}
|
|
fileInfo={{
|
|
name: (
|
|
<Dropdown
|
|
menu={{
|
|
items: [
|
|
{
|
|
key: 'edit',
|
|
icon: <EditIcon size={14} />,
|
|
label: t('knowledge.edit_remark'),
|
|
onClick: () => handleEditRemark(item)
|
|
},
|
|
{
|
|
key: 'copy',
|
|
icon: <CopyIcon size={14} />,
|
|
label: t('common.copy'),
|
|
onClick: () => {
|
|
navigator.clipboard.writeText(item.content as string)
|
|
window.toast.success(t('message.copied'))
|
|
}
|
|
}
|
|
]
|
|
}}
|
|
trigger={['contextMenu']}>
|
|
<ClickableSpan>
|
|
<Tooltip title={item.content as string}>
|
|
<Ellipsis>
|
|
<a href={item.content as string} target="_blank" rel="noopener noreferrer">
|
|
{item.remark || (item.content as string)}
|
|
</a>
|
|
</Ellipsis>
|
|
</Tooltip>
|
|
</ClickableSpan>
|
|
</Dropdown>
|
|
),
|
|
ext: '.url',
|
|
extra: getDisplayTime(item),
|
|
actions: (
|
|
<FlexAlignCenter>
|
|
{item.uniqueId && <Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />}
|
|
<StatusIconWrapper>
|
|
<StatusIcon sourceId={item.id} base={base} getProcessingStatus={getProcessingStatus} type="url" />
|
|
</StatusIconWrapper>
|
|
<Button
|
|
type="text"
|
|
danger
|
|
onClick={() => removeItem(item)}
|
|
icon={<DeleteIcon size={14} className="lucide-custom" />}
|
|
/>
|
|
</FlexAlignCenter>
|
|
)
|
|
}}
|
|
/>
|
|
)}
|
|
</DynamicVirtualList>
|
|
</ItemFlexColumn>
|
|
</ItemContainer>
|
|
)
|
|
}
|
|
|
|
const ItemFlexColumn = styled.div`
|
|
padding: 20px 16px;
|
|
height: calc(100vh - 135px);
|
|
`
|
|
|
|
export default KnowledgeUrls
|