feat(CustomCollapse, ThinkingBlock, KnowledgeSearchPopup): add expand icons and adjust styles for improved UI

- Introduced ChevronRight expand icons in CustomCollapse and ThinkingBlock components for better visual feedback.
- Adjusted height and styling in KnowledgeSearchPopup for enhanced usability and consistency.
- Updated various styled components to improve layout and user interaction.
This commit is contained in:
Teo 2025-06-14 00:28:33 +08:00
parent eb832cc25a
commit 36fa3af9e9
5 changed files with 145 additions and 85 deletions

View File

@ -1,5 +1,6 @@
import { Collapse } from 'antd'
import { merge } from 'lodash'
import { ChevronRight } from 'lucide-react'
import { FC, memo, useMemo, useState } from 'react'
interface CustomCollapseProps {
@ -78,6 +79,14 @@ const CustomCollapse: FC<CustomCollapseProps> = ({
destroyInactivePanel={destroyInactivePanel}
collapsible={collapsible}
onChange={setActiveKeys}
expandIcon={({ isActive }) => (
<ChevronRight
size={16}
color="var(--color-text-3)"
strokeWidth={1.5}
style={{ transform: isActive ? 'rotate(90deg)' : 'rotate(0deg)' }}
/>
)}
items={[
{
styles: collapseItemStyles,

View File

@ -42,7 +42,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
paddingBottom: 16
},
body: {
height: '85vh',
height: '80vh',
maxHeight: 'inherit',
padding: 0
}

View File

@ -3,7 +3,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage'
import { lightbulbVariants } from '@renderer/utils/motionVariants'
import { Collapse, message as antdMessage, Tooltip } from 'antd'
import { Lightbulb } from 'lucide-react'
import { ChevronRight, Lightbulb } from 'lucide-react'
import { motion } from 'motion/react'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -57,6 +57,14 @@ const ThinkingBlock: React.FC<Props> = ({ block }) => {
size="small"
onChange={() => setActiveKey((key) => (key ? '' : 'thought'))}
className="message-thought-container"
expandIcon={({ isActive }) => (
<ChevronRight
color="var(--color-text-3)"
size={16}
strokeWidth={1.5}
style={{ transform: isActive ? 'rotate(90deg)' : 'rotate(0deg)' }}
/>
)}
expandIconPosition="end"
items={[
{

View File

@ -1,14 +1,15 @@
import { CopyOutlined } from '@ant-design/icons'
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import { HStack } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { searchKnowledgeBase } from '@renderer/services/KnowledgeService'
import { FileType, KnowledgeBase } from '@renderer/types'
import { Input, List, message, Modal, Spin, Tooltip, Typography } from 'antd'
import { useRef, useState } from 'react'
import { Divider, Input, List, message, Modal, Spin, Tooltip, Typography } from 'antd'
import { Search } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
const { Search } = Input
const { Text, Paragraph } = Typography
interface ShowParams {
@ -25,7 +26,6 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
const [results, setResults] = useState<Array<ExtractChunkData & { file: FileType | null }>>([])
const [searchKeyword, setSearchKeyword] = useState('')
const { t } = useTranslation()
const searchInputRef = useRef<any>(null)
const handleSearch = async (value: string) => {
if (!value.trim()) {
@ -84,77 +84,98 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
return (
<Modal
title={t('knowledge.search')}
title={null}
open={open}
onOk={onOk}
onCancel={onCancel}
afterClose={onClose}
afterOpenChange={(visible) => visible && searchInputRef.current?.focus()}
width={800}
width={700}
footer={null}
centered
transitionName="animation-move-down">
<SearchContainer>
<Search
placeholder={t('knowledge.search_placeholder')}
closable={false}
transitionName="animation-move-down"
styles={{
content: {
borderRadius: 20,
padding: 0,
overflow: 'hidden',
paddingBottom: 12
},
body: {
maxHeight: '80vh',
overflow: 'hidden',
padding: 0
}
}}>
<HStack style={{ padding: '0 12px', marginTop: 8 }}>
<Input
prefix={
<SearchIcon>
<Search size={15} />
</SearchIcon>
}
value={searchKeyword}
placeholder={t('knowledge.search')}
allowClear
enterButton
size="large"
onSearch={handleSearch}
ref={searchInputRef}
autoFocus
spellCheck={false}
style={{ paddingLeft: 0 }}
variant="borderless"
size="middle"
onChange={(e) => setSearchKeyword(e.target.value)}
onPressEnter={() => handleSearch(searchKeyword)}
/>
<ResultsContainer>
{loading ? (
<LoadingContainer>
<Spin size="large" />
</LoadingContainer>
) : (
<List
dataSource={results}
renderItem={(item) => (
<List.Item>
<ResultItem>
<TagContainer>
<ScoreTag>Score: {(item.score * 100).toFixed(1)}%</ScoreTag>
<Tooltip title={t('common.copy')}>
<CopyButton onClick={() => handleCopy(item.pageContent)}>
<CopyOutlined />
</CopyButton>
</Tooltip>
</TagContainer>
<Paragraph style={{ userSelect: 'text' }}>{highlightText(item.pageContent)}</Paragraph>
<MetadataContainer>
<Text type="secondary">
{t('knowledge.source')}:{' '}
{item.file ? (
<a href={`http://file/${item.file.name}`} target="_blank" rel="noreferrer">
{item.file.origin_name}
</a>
) : (
item.metadata.source
)}
</Text>
</MetadataContainer>
</ResultItem>
</List.Item>
)}
/>
)}
</ResultsContainer>
</SearchContainer>
</HStack>
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} />
<ResultsContainer>
{loading ? (
<LoadingContainer>
<Spin size="large" />
</LoadingContainer>
) : (
<List
dataSource={results}
renderItem={(item) => (
<List.Item>
<ResultItem>
<MetadataContainer>
<Text type="secondary" ellipsis>
{t('knowledge.source')}:{' '}
{item.file ? (
<a href={`http://file/${item.file.name}`} target="_blank" rel="noreferrer">
{item.file.origin_name}
</a>
) : (
item.metadata.source
)}
</Text>
<ScoreTag>Score: {(item.score * 100).toFixed(1)}%</ScoreTag>
</MetadataContainer>
<TagContainer>
<Tooltip title={t('common.copy')}>
<CopyButton onClick={() => handleCopy(item.pageContent)}>
<CopyOutlined />
</CopyButton>
</Tooltip>
</TagContainer>
<Paragraph style={{ userSelect: 'text', marginBottom: 0 }}>
{highlightText(item.pageContent)}
</Paragraph>
</ResultItem>
</List.Item>
)}
/>
)}
</ResultsContainer>
</Modal>
)
}
const SearchContainer = styled.div`
display: flex;
flex-direction: column;
gap: 20px;
`
const ResultsContainer = styled.div`
max-height: 60vh;
padding: 0 16px;
overflow-y: auto;
max-height: 70vh;
`
const LoadingContainer = styled.div`
@ -164,21 +185,29 @@ const LoadingContainer = styled.div`
height: 200px;
`
const TagContainer = styled.div`
position: absolute;
top: 58px;
right: 16px;
display: flex;
align-items: center;
gap: 8px;
opacity: 0;
transition: opacity 0.2s;
`
const ResultItem = styled.div`
width: 100%;
position: relative;
padding: 16px;
background: var(--color-background-soft);
border-radius: 8px;
`
const TagContainer = styled.div`
position: absolute;
top: 8px;
right: 8px;
display: flex;
align-items: center;
gap: 8px;
&:hover {
${TagContainer} {
opacity: 1 !important;
}
}
`
const ScoreTag = styled.div`
@ -187,6 +216,7 @@ const ScoreTag = styled.div`
color: white;
border-radius: 4px;
font-size: 12px;
flex-shrink: 0;
`
const CopyButton = styled.div`
@ -195,7 +225,7 @@ const CopyButton = styled.div`
justify-content: center;
width: 24px;
height: 24px;
background: var(--color-background);
background: var(--color-background-mute);
color: var(--color-text);
border-radius: 4px;
cursor: pointer;
@ -208,12 +238,35 @@ const CopyButton = styled.div`
`
const MetadataContainer = styled.div`
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--color-border);
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid var(--color-border);
user-select: text;
`
const SearchIcon = styled.div`
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: var(--color-background-soft);
margin-right: 2px;
&.back-icon {
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: var(--color-background-mute);
}
}
`
const TopViewKey = 'KnowledgeSearchPopup'
export default class KnowledgeSearchPopup {

View File

@ -15,7 +15,6 @@ import { sortBy } from 'lodash'
import { ChevronDown } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface ShowParams {
base: KnowledgeBase
@ -271,15 +270,6 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
const TopViewKey = 'KnowledgeSettingsPopup'
const AdvancedSettingsButton = styled.div`
cursor: pointer;
margin-bottom: 16px;
margin-top: -10px;
color: var(--color-primary);
display: flex;
align-items: center;
`
export default class KnowledgeSettingsPopup {
static hide() {
TopView.hide(TopViewKey)