mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-03 19:30:04 +08:00
refactor(ui): enhance CustomCollapse and ToolsCallingIcon components
- Refactored CustomCollapse to utilize Accordion and AccordionItem from HeroUI, simplifying props and improving functionality. - Updated ToolsCallingIcon to accept TooltipProps for better customization. - Revised stories for CustomCollapse to reflect new prop structure and added examples for various use cases. - Cleaned up unnecessary props and improved documentation in story files.
This commit is contained in:
parent
76b3ba5d7e
commit
1b04fd065d
@ -1,88 +1,42 @@
|
||||
import { Accordion, AccordionItem } from '@heroui/react'
|
||||
import { Accordion, AccordionItem, type AccordionItemProps, type AccordionProps } from '@heroui/react'
|
||||
import type { FC } from 'react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { memo } from 'react'
|
||||
|
||||
// 重新导出 HeroUI 的组件,方便直接使用
|
||||
export { Accordion, AccordionItem } from '@heroui/react'
|
||||
|
||||
interface CustomCollapseProps {
|
||||
label: React.ReactNode
|
||||
extra?: React.ReactNode
|
||||
children: React.ReactNode
|
||||
destroyInactivePanel?: boolean
|
||||
defaultActiveKey?: string[]
|
||||
activeKey?: string[]
|
||||
collapsible?: 'header' | 'icon' | 'disabled'
|
||||
onChange?: (activeKeys: string | string[]) => void
|
||||
style?: React.CSSProperties
|
||||
classNames?: {
|
||||
trigger?: string
|
||||
content?: string
|
||||
}
|
||||
className?: string
|
||||
variant?: 'light' | 'shadow' | 'bordered' | 'splitted'
|
||||
accordionProps?: Omit<AccordionProps, 'children'>
|
||||
accordionItemProps?: Omit<AccordionItemProps, 'children'>
|
||||
}
|
||||
|
||||
const CustomCollapse: FC<CustomCollapseProps> = ({
|
||||
label,
|
||||
extra,
|
||||
children,
|
||||
defaultActiveKey = ['1'],
|
||||
activeKey,
|
||||
collapsible,
|
||||
onChange,
|
||||
style,
|
||||
classNames,
|
||||
className = '',
|
||||
variant = 'bordered'
|
||||
}) => {
|
||||
const [expandedKeys, setExpandedKeys] = useState<Set<string>>(() => {
|
||||
if (activeKey !== undefined) {
|
||||
return new Set(activeKey)
|
||||
}
|
||||
return new Set(defaultActiveKey)
|
||||
})
|
||||
const CustomCollapse: FC<CustomCollapseProps> = ({ children, accordionProps = {}, accordionItemProps = {} }) => {
|
||||
// 解构 Accordion 的 props
|
||||
const {
|
||||
defaultExpandedKeys = ['1'],
|
||||
variant = 'bordered',
|
||||
className = '',
|
||||
isDisabled = false,
|
||||
...restAccordionProps
|
||||
} = accordionProps
|
||||
|
||||
useEffect(() => {
|
||||
if (activeKey !== undefined) {
|
||||
setExpandedKeys(new Set(activeKey))
|
||||
}
|
||||
}, [activeKey])
|
||||
|
||||
const handleSelectionChange = (keys: 'all' | Set<React.Key>) => {
|
||||
if (keys === 'all') return
|
||||
|
||||
const stringKeys = Array.from(keys).map((key) => String(key))
|
||||
const newExpandedKeys = new Set(stringKeys)
|
||||
|
||||
if (activeKey === undefined) {
|
||||
setExpandedKeys(newExpandedKeys)
|
||||
}
|
||||
|
||||
onChange?.(stringKeys.length === 1 ? stringKeys[0] : stringKeys)
|
||||
}
|
||||
|
||||
const isDisabled = collapsible === 'disabled'
|
||||
// 解构 AccordionItem 的 props
|
||||
const { title = 'Collapse Panel', ...restAccordionItemProps } = accordionItemProps
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
className={className}
|
||||
style={style}
|
||||
defaultExpandedKeys={defaultExpandedKeys}
|
||||
variant={variant}
|
||||
defaultExpandedKeys={activeKey === undefined ? defaultActiveKey : undefined}
|
||||
selectedKeys={activeKey !== undefined ? expandedKeys : undefined}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
className={className}
|
||||
isDisabled={isDisabled}
|
||||
selectionMode="multiple">
|
||||
selectionMode="multiple"
|
||||
{...restAccordionProps}>
|
||||
<AccordionItem
|
||||
key="1"
|
||||
aria-label={typeof label === 'string' ? label : 'collapse-item'}
|
||||
title={label}
|
||||
startContent={extra}
|
||||
classNames={{
|
||||
trigger: classNames?.trigger ?? '',
|
||||
content: classNames?.content ?? ''
|
||||
}}>
|
||||
aria-label={typeof title === 'string' ? title : 'collapse-item'}
|
||||
title={title}
|
||||
{...restAccordionItemProps}>
|
||||
{children}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
@ -1,22 +1,20 @@
|
||||
// Original: src/renderer/src/components/Icons/ToolsCallingIcon.tsx
|
||||
import { Tooltip } from '@heroui/react'
|
||||
import { Tooltip, type TooltipProps } from '@heroui/react'
|
||||
import { Wrench } from 'lucide-react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { cn } from '../../../utils'
|
||||
|
||||
interface ToolsCallingIconProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
className?: string
|
||||
iconClassName?: string
|
||||
TooltipProps?: TooltipProps
|
||||
}
|
||||
|
||||
const ToolsCallingIcon = ({ className, iconClassName, ...props }: ToolsCallingIconProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const ToolsCallingIcon = ({ className, iconClassName, TooltipProps, ...props }: ToolsCallingIconProps) => {
|
||||
return (
|
||||
<div className={cn('flex justify-center items-center', className)} {...props}>
|
||||
<Tooltip content={t('models.function_calling')} placement="top">
|
||||
<Tooltip placement="top" {...TooltipProps}>
|
||||
<Wrench className={cn('w-4 h-4 mr-1.5 text-[#00b96b]', iconClassName)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@ -13,55 +13,17 @@ const meta: Meta<typeof CustomCollapse> = {
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
label: {
|
||||
control: 'text',
|
||||
description: '面板标题'
|
||||
},
|
||||
extra: {
|
||||
control: false,
|
||||
description: '额外内容(副标题)'
|
||||
},
|
||||
children: {
|
||||
control: false,
|
||||
description: '面板内容'
|
||||
},
|
||||
defaultActiveKey: {
|
||||
accordionProps: {
|
||||
control: false,
|
||||
description: '默认激活的面板键值'
|
||||
description: 'Accordion 组件的属性'
|
||||
},
|
||||
activeKey: {
|
||||
accordionItemProps: {
|
||||
control: false,
|
||||
description: '当前激活的面板键值(受控模式)'
|
||||
},
|
||||
onChange: {
|
||||
control: false,
|
||||
description: '面板状态变化回调'
|
||||
},
|
||||
collapsible: {
|
||||
control: 'select',
|
||||
options: ['header', 'icon', 'disabled'],
|
||||
description: '折叠触发方式'
|
||||
},
|
||||
className: {
|
||||
control: 'text',
|
||||
description: '额外的 CSS 类名'
|
||||
},
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['light', 'shadow', 'bordered', 'splitted'],
|
||||
description: 'HeroUI 样式变体'
|
||||
},
|
||||
destroyInactivePanel: {
|
||||
control: 'boolean',
|
||||
description: '是否销毁非激活面板'
|
||||
},
|
||||
style: {
|
||||
control: false,
|
||||
description: '自定义样式'
|
||||
},
|
||||
styles: {
|
||||
control: false,
|
||||
description: '自定义头部和内容样式'
|
||||
description: 'AccordionItem 组件的属性'
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,7 +34,9 @@ type Story = StoryObj<typeof meta>
|
||||
// 基础用法
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
label: '默认折叠面板',
|
||||
accordionItemProps: {
|
||||
title: '默认折叠面板'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是折叠面板的内容。</p>
|
||||
@ -85,13 +49,17 @@ export const Default: Story = {
|
||||
// 带副标题
|
||||
export const WithSubtitle: Story = {
|
||||
args: {
|
||||
label: '带副标题的折叠面板',
|
||||
extra: <span className="text-sm text-gray-500">这是副标题内容</span>,
|
||||
defaultActiveKey: ['1'],
|
||||
accordionProps: {
|
||||
defaultExpandedKeys: ['1']
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: '带副标题的折叠面板',
|
||||
subtitle: <span className="text-sm text-gray-500">这是副标题内容</span>
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>面板内容</p>
|
||||
<p>可以在 extra 属性中设置副标题</p>
|
||||
<p>可以在 subtitle 属性中设置副标题</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -100,8 +68,12 @@ export const WithSubtitle: Story = {
|
||||
// HeroUI 样式变体
|
||||
export const VariantLight: Story = {
|
||||
args: {
|
||||
label: 'Light 变体',
|
||||
variant: 'light',
|
||||
accordionProps: {
|
||||
variant: 'light'
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: 'Light 变体'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是 HeroUI 的 Light 变体样式。</p>
|
||||
@ -112,10 +84,14 @@ export const VariantLight: Story = {
|
||||
|
||||
export const VariantShadow: Story = {
|
||||
args: {
|
||||
label: 'Shadow 变体',
|
||||
extra: '带阴影的面板样式',
|
||||
variant: 'shadow',
|
||||
className: 'p-2',
|
||||
accordionProps: {
|
||||
variant: 'shadow',
|
||||
className: 'p-2'
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: 'Shadow 变体',
|
||||
subtitle: '带阴影的面板样式'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是 HeroUI 的 Shadow 变体样式。</p>
|
||||
@ -126,8 +102,12 @@ export const VariantShadow: Story = {
|
||||
|
||||
export const VariantBordered: Story = {
|
||||
args: {
|
||||
label: 'Bordered 变体(默认)',
|
||||
variant: 'bordered',
|
||||
accordionProps: {
|
||||
variant: 'bordered'
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: 'Bordered 变体(默认)'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是 HeroUI 的 Bordered 变体样式。</p>
|
||||
@ -138,8 +118,12 @@ export const VariantBordered: Story = {
|
||||
|
||||
export const VariantSplitted: Story = {
|
||||
args: {
|
||||
label: 'Splitted 变体',
|
||||
variant: 'splitted',
|
||||
accordionProps: {
|
||||
variant: 'splitted'
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: 'Splitted 变体'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这是 HeroUI 的 Splitted 变体样式。</p>
|
||||
@ -151,12 +135,14 @@ export const VariantSplitted: Story = {
|
||||
// 富内容标题
|
||||
export const RichLabel: Story = {
|
||||
args: {
|
||||
label: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="text-default-500" size={20} />
|
||||
<span>设置面板</span>
|
||||
</div>
|
||||
),
|
||||
accordionItemProps: {
|
||||
title: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="text-default-500" size={20} />
|
||||
<span>设置面板</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<div className="space-y-3">
|
||||
@ -181,17 +167,19 @@ export const RichLabel: Story = {
|
||||
// 带警告提示
|
||||
export const WithWarning: Story = {
|
||||
args: {
|
||||
label: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Monitor className="text-primary" size={20} />
|
||||
<span>连接的设备</span>
|
||||
</div>
|
||||
),
|
||||
extra: (
|
||||
<p className="flex">
|
||||
2个问题需要<span className="text-primary ml-1">立即修复</span>
|
||||
</p>
|
||||
),
|
||||
accordionItemProps: {
|
||||
title: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Monitor className="text-primary" size={20} />
|
||||
<span>连接的设备</span>
|
||||
</div>
|
||||
),
|
||||
subtitle: (
|
||||
<p className="flex">
|
||||
2个问题需要<span className="text-primary ml-1">立即修复</span>
|
||||
</p>
|
||||
)
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p className="text-small">检测到以下设备连接异常:</p>
|
||||
@ -207,9 +195,13 @@ export const WithWarning: Story = {
|
||||
// 禁用状态
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
label: '禁用的折叠面板',
|
||||
collapsible: 'disabled',
|
||||
defaultActiveKey: ['1'],
|
||||
accordionProps: {
|
||||
isDisabled: true,
|
||||
defaultExpandedKeys: ['1']
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: '禁用的折叠面板'
|
||||
},
|
||||
children: (
|
||||
<div className="p-4">
|
||||
<p>这个面板被禁用了,无法操作。</p>
|
||||
@ -221,28 +213,36 @@ export const Disabled: Story = {
|
||||
// 受控模式
|
||||
export const ControlledMode: Story = {
|
||||
render: function ControlledMode() {
|
||||
const [activeKey, setActiveKey] = useState<string[]>(['1'])
|
||||
const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set(['1']))
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" onPress={() => setActiveKey(['1'])} color="primary">
|
||||
<Button size="sm" onPress={() => setSelectedKeys(new Set(['1']))} color="primary">
|
||||
展开
|
||||
</Button>
|
||||
<Button size="sm" onPress={() => setActiveKey([])} color="default">
|
||||
<Button size="sm" onPress={() => setSelectedKeys(new Set())} color="default">
|
||||
收起
|
||||
</Button>
|
||||
</div>
|
||||
<CustomCollapse
|
||||
label="受控的折叠面板"
|
||||
activeKey={activeKey}
|
||||
onChange={(keys) => setActiveKey(Array.isArray(keys) ? keys : [keys])}>
|
||||
accordionProps={{
|
||||
selectedKeys,
|
||||
onSelectionChange: (keys) => {
|
||||
if (keys !== 'all') {
|
||||
setSelectedKeys(keys as Set<string>)
|
||||
}
|
||||
}
|
||||
}}
|
||||
accordionItemProps={{
|
||||
title: '受控的折叠面板'
|
||||
}}>
|
||||
<div className="p-4">
|
||||
<p>这是一个受控的折叠面板</p>
|
||||
<p>通过按钮控制展开和收起状态</p>
|
||||
</div>
|
||||
</CustomCollapse>
|
||||
<div className="text-sm text-gray-600">当前状态:{activeKey.length > 0 ? '展开' : '收起'}</div>
|
||||
<div className="text-sm text-gray-600">当前状态:{selectedKeys.size > 0 ? '展开' : '收起'}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -252,17 +252,21 @@ export const ControlledMode: Story = {
|
||||
export const MultipleSinglePanels: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<CustomCollapse label="第一个面板" defaultActiveKey={['1']}>
|
||||
<CustomCollapse accordionProps={{ defaultExpandedKeys: ['1'] }} accordionItemProps={{ title: '第一个面板' }}>
|
||||
<div className="p-4">
|
||||
<p>第一个面板的内容</p>
|
||||
</div>
|
||||
</CustomCollapse>
|
||||
<CustomCollapse label="第二个面板" extra="带副标题">
|
||||
<CustomCollapse
|
||||
accordionItemProps={{
|
||||
title: '第二个面板',
|
||||
subtitle: '带副标题'
|
||||
}}>
|
||||
<div className="p-4">
|
||||
<p>第二个面板的内容</p>
|
||||
</div>
|
||||
</CustomCollapse>
|
||||
<CustomCollapse label="第三个面板(禁用)" collapsible="disabled">
|
||||
<CustomCollapse accordionProps={{ isDisabled: true }} accordionItemProps={{ title: '第三个面板(禁用)' }}>
|
||||
<div className="p-4">
|
||||
<p>这个面板被禁用了</p>
|
||||
</div>
|
||||
@ -330,22 +334,24 @@ export const NativeAccordionMultiple: Story = {
|
||||
// 富内容面板
|
||||
export const RichContent: Story = {
|
||||
args: {
|
||||
label: (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<Info className="text-default-500" size={20} />
|
||||
<span>详细信息</span>
|
||||
accordionItemProps: {
|
||||
title: (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<Info className="text-default-500" size={20} />
|
||||
<span>详细信息</span>
|
||||
</div>
|
||||
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
<Button size="sm" variant="flat" color="primary">
|
||||
保存
|
||||
</Button>
|
||||
<Button size="sm" variant="flat">
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
<Button size="sm" variant="flat" color="primary">
|
||||
保存
|
||||
</Button>
|
||||
<Button size="sm" variant="flat">
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
)
|
||||
},
|
||||
children: (
|
||||
<div className="p-4 space-y-4">
|
||||
<div>
|
||||
@ -371,11 +377,7 @@ export const RichContent: Story = {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">描述</label>
|
||||
<textarea
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
||||
rows={3}
|
||||
placeholder="请输入描述"
|
||||
/>
|
||||
<textarea className="w-full px-3 py-2 border border-gray-300 rounded-md" rows={3} placeholder="请输入描述" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -385,15 +387,19 @@ export const RichContent: Story = {
|
||||
// 自定义样式
|
||||
export const CustomStyles: Story = {
|
||||
args: {
|
||||
label: (
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertTriangle className="text-warning" size={16} />
|
||||
<span>警告面板</span>
|
||||
</div>
|
||||
),
|
||||
style: {
|
||||
backgroundColor: 'rgba(255, 193, 7, 0.1)',
|
||||
borderColor: 'var(--color-warning)'
|
||||
accordionProps: {
|
||||
style: {
|
||||
backgroundColor: 'rgba(255, 193, 7, 0.1)',
|
||||
borderColor: 'var(--color-warning)'
|
||||
}
|
||||
},
|
||||
accordionItemProps: {
|
||||
title: (
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertTriangle className="text-warning" size={16} />
|
||||
<span>警告面板</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
children: (
|
||||
<div className="p-4 bg-warning-50 dark:bg-warning-900/20">
|
||||
@ -425,7 +431,7 @@ export const NativeAccordionControlled: Story = {
|
||||
selectedKeys={activeKeys}
|
||||
onSelectionChange={(keys) => {
|
||||
if (keys !== 'all') {
|
||||
setActiveKeys(keys)
|
||||
setActiveKeys(keys as Set<string>)
|
||||
}
|
||||
}}>
|
||||
<AccordionItem key="1" title="受控面板 1">
|
||||
|
||||
@ -124,8 +124,7 @@ export const LoadingStatesInButtons: Story = {
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
||||
disabled
|
||||
>
|
||||
disabled>
|
||||
<SvgSpinners180Ring size="16" />
|
||||
<span>加载中...</span>
|
||||
</button>
|
||||
@ -133,8 +132,7 @@ export const LoadingStatesInButtons: Story = {
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600"
|
||||
disabled
|
||||
>
|
||||
disabled>
|
||||
<SvgSpinners180Ring size="16" />
|
||||
<span>保存中</span>
|
||||
</button>
|
||||
@ -142,8 +140,7 @@ export const LoadingStatesInButtons: Story = {
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 rounded bg-orange-500 px-4 py-2 text-white hover:bg-orange-600"
|
||||
disabled
|
||||
>
|
||||
disabled>
|
||||
<SvgSpinners180Ring size="16" />
|
||||
<span>上传中</span>
|
||||
</button>
|
||||
@ -151,8 +148,7 @@ export const LoadingStatesInButtons: Story = {
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 rounded border border-gray-300 bg-white px-4 py-2 text-gray-700 hover:bg-gray-50"
|
||||
disabled
|
||||
>
|
||||
disabled>
|
||||
<SvgSpinners180Ring size="16" className="text-gray-500" />
|
||||
<span>处理中</span>
|
||||
</button>
|
||||
@ -323,18 +319,15 @@ export const InteractiveLoadingDemo: Story = {
|
||||
onClick={() => {
|
||||
// 演示用途 - 在实际应用中这里会触发真实的加载状态
|
||||
alert(`触发 ${state.text} 加载状态`)
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<SvgSpinners180Ring size="16" />
|
||||
<span>{state.text}中...</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-500">
|
||||
点击按钮查看加载状态的交互效果
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">点击按钮查看加载状态的交互效果</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,9 +33,7 @@ export const BasicToolsCallingIcon: Story = {
|
||||
<div className="flex items-center gap-4">
|
||||
<ToolsCallingIcon />
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
悬停图标查看工具提示,显示"函数调用"文本
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-gray-600">悬停图标查看工具提示,显示"函数调用"文本</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -123,9 +121,7 @@ export const ModelFeaturesContext: Story = {
|
||||
<h4 className="font-medium">GPT-4 Turbo</h4>
|
||||
<ToolsCallingIcon />
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
支持函数调用的先进模型,可以调用外部工具和API
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">支持函数调用的先进模型,可以调用外部工具和API</p>
|
||||
<div className="mt-2 flex gap-2">
|
||||
<span className="rounded bg-green-100 px-2 py-1 text-xs text-green-800">函数调用</span>
|
||||
<span className="rounded bg-blue-100 px-2 py-1 text-xs text-blue-800">多模态</span>
|
||||
@ -137,9 +133,7 @@ export const ModelFeaturesContext: Story = {
|
||||
<h4 className="font-medium">Claude 3.5 Sonnet</h4>
|
||||
<ToolsCallingIcon />
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
Anthropic的高性能模型,具备强大的工具使用能力
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">Anthropic的高性能模型,具备强大的工具使用能力</p>
|
||||
<div className="mt-2 flex gap-2">
|
||||
<span className="rounded bg-green-100 px-2 py-1 text-xs text-green-800">函数调用</span>
|
||||
<span className="rounded bg-orange-100 px-2 py-1 text-xs text-orange-800">推理</span>
|
||||
@ -151,9 +145,7 @@ export const ModelFeaturesContext: Story = {
|
||||
<h4 className="font-medium">Llama 3.1 8B</h4>
|
||||
{/* 不支持函数调用 */}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
Meta的开源模型,适用于基础对话任务
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">Meta的开源模型,适用于基础对话任务</p>
|
||||
<div className="mt-2 flex gap-2">
|
||||
<span className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-800">文本生成</span>
|
||||
</div>
|
||||
@ -175,9 +167,7 @@ export const ChatMessageContext: Story = {
|
||||
<ToolsCallingIcon iconClassName="w-3.5 h-3.5 mr-1 text-[#00b96b]" />
|
||||
<span className="font-medium">调用工具: weather_api</span>
|
||||
</div>
|
||||
<p className="text-sm text-blue-700">
|
||||
正在获取北京的天气信息...
|
||||
</p>
|
||||
<p className="text-sm text-blue-700">正在获取北京的天气信息...</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-green-50 p-3">
|
||||
@ -185,9 +175,7 @@ export const ChatMessageContext: Story = {
|
||||
<ToolsCallingIcon iconClassName="w-3.5 h-3.5 mr-1 text-[#00b96b]" />
|
||||
<span className="font-medium">调用工具: search_web</span>
|
||||
</div>
|
||||
<p className="text-sm text-green-700">
|
||||
正在搜索最新的AI新闻...
|
||||
</p>
|
||||
<p className="text-sm text-green-700">正在搜索最新的AI新闻...</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-orange-50 p-3">
|
||||
@ -195,9 +183,7 @@ export const ChatMessageContext: Story = {
|
||||
<ToolsCallingIcon iconClassName="w-3.5 h-3.5 mr-1 text-[#00b96b]" />
|
||||
<span className="font-medium">调用工具: code_interpreter</span>
|
||||
</div>
|
||||
<p className="text-sm text-orange-700">
|
||||
正在执行Python代码计算结果...
|
||||
</p>
|
||||
<p className="text-sm text-orange-700">正在执行Python代码计算结果...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -278,12 +264,9 @@ export const InteractiveToolSelection: Story = {
|
||||
? 'border-gray-200 hover:border-blue-500'
|
||||
: 'border-gray-200 opacity-60 cursor-not-allowed'
|
||||
}`}
|
||||
disabled={!tool.available}
|
||||
>
|
||||
disabled={!tool.available}>
|
||||
<ToolsCallingIcon
|
||||
iconClassName={`w-4 h-4 mr-1.5 ${
|
||||
tool.available ? 'text-[#00b96b]' : 'text-gray-400'
|
||||
}`}
|
||||
iconClassName={`w-4 h-4 mr-1.5 ${tool.available ? 'text-[#00b96b]' : 'text-gray-400'}`}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{tool.name}</div>
|
||||
@ -326,9 +309,7 @@ export const LoadingToolCalls: Story = {
|
||||
<span className="font-medium text-green-800">工具调用完成</span>
|
||||
<span className="text-green-600">✓</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-green-700">
|
||||
已获取北京天气信息:晴天,温度 22°C
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-green-700">已获取北京天气信息:晴天,温度 22°C</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
||||
@ -337,9 +318,7 @@ export const LoadingToolCalls: Story = {
|
||||
<span className="font-medium text-red-800">工具调用失败</span>
|
||||
<span className="text-red-600">✗</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-red-700">
|
||||
API密钥无效,请检查配置
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-red-700">API密钥无效,请检查配置</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -386,4 +365,4 @@ export const SettingsPanel: Story = {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,8 +36,13 @@ const ModelListGroup: React.FC<ModelListGroupProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const listRef = useRef<DynamicVirtualListRef>(null)
|
||||
|
||||
const handleCollapseChange = useCallback((activeKeys: string[] | string) => {
|
||||
const isNowExpanded = Array.isArray(activeKeys) ? activeKeys.length > 0 : !!activeKeys
|
||||
const handleCollapseChange = useCallback((keys: 'all' | Set<React.Key>) => {
|
||||
if (keys === 'all') {
|
||||
return
|
||||
}
|
||||
const stringKeys = Array.from(keys)
|
||||
|
||||
const isNowExpanded = Array.isArray(stringKeys) ? stringKeys.length > 0 : !!stringKeys
|
||||
if (isNowExpanded) {
|
||||
// 延迟到 DOM 可见后测量
|
||||
requestAnimationFrame(() => listRef.current?.measure())
|
||||
@ -47,30 +52,34 @@ const ModelListGroup: React.FC<ModelListGroupProps> = ({
|
||||
return (
|
||||
<CustomCollapseWrapper>
|
||||
<CustomCollapse
|
||||
variant="shadow"
|
||||
defaultActiveKey={defaultOpen ? ['1'] : []}
|
||||
onChange={handleCollapseChange}
|
||||
label={
|
||||
<Flex align="center" gap={10}>
|
||||
<span style={{ fontWeight: 'bold' }}>{groupName}</span>
|
||||
</Flex>
|
||||
}
|
||||
extra={
|
||||
<Tooltip title={t('settings.models.manage.remove_whole_group')} mouseLeaveDelay={0}>
|
||||
<Button
|
||||
type="text"
|
||||
className="toolbar-item"
|
||||
icon={<Minus size={14} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onRemoveGroup()
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
styles={{
|
||||
trigger: 'p-[3px_calc(6px_+_var(--scrollbar-width))_3px_16px]'
|
||||
accordionProps={{
|
||||
variant: 'shadow',
|
||||
defaultExpandedKeys: defaultOpen ? ['1'] : [],
|
||||
onSelectionChange: handleCollapseChange
|
||||
}}
|
||||
accordionItemProps={{
|
||||
startContent: (
|
||||
<Tooltip title={t('settings.models.manage.remove_whole_group')} mouseLeaveDelay={0}>
|
||||
<Button
|
||||
type="text"
|
||||
className="toolbar-item"
|
||||
icon={<Minus size={14} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onRemoveGroup()
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
classNames: {
|
||||
trigger: 'p-[3px_calc(6px_+_var(--scrollbar-width))_3px_16px]'
|
||||
},
|
||||
title: (
|
||||
<Flex align="center" gap={10}>
|
||||
<span style={{ fontWeight: 'bold' }}>{groupName}</span>
|
||||
</Flex>
|
||||
)
|
||||
}}>
|
||||
<DynamicVirtualList
|
||||
ref={listRef}
|
||||
@ -114,7 +123,7 @@ const CustomCollapseWrapper = styled.div`
|
||||
/* 移除 collapse 的 padding,转而在 scroller 内部调整 */
|
||||
.ant-collapse-content-box {
|
||||
padding: 0 !important;
|
||||
}import { classNames } from '../../../../utils/style';
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user