cherry-studio/src/renderer/src/pages/knowledge/items/KnowledgeUrls.tsx
Phantom cf7584bb63
refactor(toast): migrate message to toast (#10023)
* 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
2025-09-10 15:16:53 +08:00

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