Skip to content

✨ feat: Transition animation switch #7981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions docs/self-hosting/advanced/model-list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ You can use `+` to add a model, `-` to hide a model, and use `model name->deploy
```text
id->deploymentName=displayName<maxToken:vision:reasoning:search:fc:file:imageOutput>,model2,model3
```

The deploymentName `->deploymentName` can be omitted, and it defaults to the latest model version. Currently, the model service providers that support `->deploymentName` are: Azure, Volcengine and Qwen.

For example: `+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-0125-preview=gpt-4-turbo`
Expand Down Expand Up @@ -47,11 +48,11 @@ Examples are as follows:

Currently supported extension capabilities are:

| --- | Description |
| ----------- | -------------------------------------------------------- |
| `fc` | Function Calling |
| `vision` | Image Recognition |
| `imageOutput` | Image Generation |
| `reasoning` | Support Reasoning |
| `search` | Support Web Search |
| `file` | File Upload (a bit hacky, not recommended for daily use) |
| --- | Description |
| ------------- | -------------------------------------------------------- |
| `fc` | Function Calling |
| `vision` | Image Recognition |
| `imageOutput` | Image Generation |
| `reasoning` | Support Reasoning |
| `search` | Support Web Search |
| `file` | File Upload (a bit hacky, not recommended for daily use) |
19 changes: 10 additions & 9 deletions docs/self-hosting/advanced/model-list.zh-CN.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ LobeChat 支持在部署时自定义模型列表,详情请参考 [模型提供
```text
id->deploymentName=displayName<maxToken:vision:reasoning:search:fc:file:imageOutput>,model2,model3
```
部署名`->deploymentName`可以省略,默认为最新版本的模型。当前支持`->deploymentName`的模型服务商有:Azure、Volcengine和Qwen。

部署名`->deploymentName`可以省略,默认为最新版本的模型。当前支持`->deploymentName`的模型服务商有:Azure、Volcengine 和 Qwen。

例如: `+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-0125-preview=gpt-4-turbo`

Expand Down Expand Up @@ -46,11 +47,11 @@ id->deploymentName=displayName<maxToken:vision:reasoning:search:fc:file:imageOut

目前支持的扩展能力有:

| --- | 描述 |
| ----------- | ---------------------- |
| `fc` | 函数调用(function calling) |
| `vision` | 视觉识别 |
| `imageOutput` | 图像生成 |
| `reasoning` | 支持推理 |
| `search` | 支持联网搜索 |
| `file` | 文件上传(比较 hack,不建议日常使用) |
| --- | 描述 |
| ------------- | ---------------------- |
| `fc` | 函数调用(function calling) |
| `vision` | 视觉识别 |
| `imageOutput` | 图像生成 |
| `reasoning` | 支持推理 |
| `search` | 支持联网搜索 |
| `file` | 文件上传(比较 hack,不建议日常使用) |
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { ActionIconGroup, Block } from '@lobehub/ui';
import { ChatItem } from '@lobehub/ui/chat';
import { useTheme } from 'antd-style';
import { RotateCwIcon } from 'lucide-react';
import { memo, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
import { UserGeneralConfig } from '@/types/user/settings';

const data = `
### Features

**Key Highlights**
- 🌐 Multi-model: GPT-4/Gemini/Ollama
- 🖼️ Vision: \`gpt-4-vision\` integration
- 🛠️ Plugins: Function Calling & real-time data
`;

const streamingSpeed = 25; // ms per character

interface ChatTransitionPreviewProps {
mode: UserGeneralConfig['transitionMode'];
}

const randomInlRange = (min = 0, max = min + 10) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};

const ChatTransitionPreview = memo<ChatTransitionPreviewProps>(({ mode }) => {
const [streamedContent, setStreamedContent] = useState(() => {
if (mode === 'none') {
return data.slice(0, Math.max(0, randomInlRange(10, 100)));
}
return '';
});

const chunkStep = useMemo(() => {
if (mode === 'none') {
return Math.ceil(data.length / randomInlRange(3, 5));
}
return 3;
}, [mode]);

const [isStreaming, setIsStreaming] = useState(true);
const { t } = useTranslation('common');
const token = useTheme();

useEffect(() => {
if (!isStreaming) return;

let currentPosition = 0;
if (streamedContent.length > 0) {
currentPosition = streamedContent.length;
}

const intervalId = setInterval(() => {
if (currentPosition < data.length) {
// Stream character by character
const nextChunkSize = Math.min(chunkStep, data.length - currentPosition);
const nextContent = data.slice(0, Math.max(0, currentPosition + nextChunkSize));
setStreamedContent(nextContent);
currentPosition += nextChunkSize;
} else {
clearInterval(intervalId);
setIsStreaming(false);
}
}, streamingSpeed);

return () => clearInterval(intervalId);
}, [isStreaming, streamedContent.length, chunkStep]);

const handleReset = () => {
setStreamedContent('');
setIsStreaming(true);
};

return (
<Block
style={{
background: token.colorBgContainerSecondary,
marginBlock: 16,
minHeight: 280,
paddingBottom: 16,
}}
>
<ChatItem
actions={
<ActionIconGroup
items={[
{
icon: RotateCwIcon,
key: 'reset',
onClick: handleReset,
title: t('retry'),
},
]}
size="small"
/>
}
avatar={{ avatar: DEFAULT_INBOX_AVATAR }}
markdownProps={{ animated: mode === 'fadeIn' }}
message={streamedContent}
variant="bubble"
width={'100%'}
/>
</Block>
);
});

export default ChatTransitionPreview;
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import {
Form,
type FormGroupItemType,
Icon,
Segmented,
Select,
SliderWithInput,
highlighterThemes,
mermaidThemes,
} from '@lobehub/ui';
import { Skeleton } from 'antd';
import { useTheme } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { Loader2Icon } from 'lucide-react';
import { Loader2Icon, TriangleAlert } from 'lucide-react';
import { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -20,19 +22,64 @@ import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';

import ChatPreview from './ChatPreview';
import ChatTransitionPreview from './ChatTransitionPreview';
import HighlighterPreview from './HighlighterPreview';
import MermaidPreview from './MermaidPreview';

const ChatAppearance = memo(() => {
const { t } = useTranslation('setting');
const { general } = useUserStore(settingsSelectors.currentSettings, isEqual);
const theme = useTheme();
const [setSettings, isUserStateInit] = useUserStore((s) => [s.setSettings, s.isUserStateInit]);
const [loading, setLoading] = useState(false);

if (!isUserStateInit) return <Skeleton active paragraph={{ rows: 5 }} title={false} />;

const theme: FormGroupItemType = {
const themeItems: FormGroupItemType = {
children: [
{
children: (
<ChatTransitionPreview key={general.transitionMode} mode={general.transitionMode} />
),
noStyle: true,
},
{
children: (
<Segmented
block
options={[
{
label: t('settingChatAppearance.transitionMode.options.none.value'),
value: 'none',
},
{
label: t('settingChatAppearance.transitionMode.options.fadeIn'),
value: 'fadeIn',
},
{
label: t('settingChatAppearance.transitionMode.options.smooth'),
value: 'smooth',
},
]}
/>
),
desc: t('settingChatAppearance.transitionMode.desc'),
label: t('settingChatAppearance.transitionMode.title'),
name: 'transitionMode',
tooltip:
general.transitionMode === 'none'
? {
icon: (
<TriangleAlert
color={theme.colorWarning}
size={14}
style={{ alignSelf: 'flex-end', marginBlockEnd: 2, marginInlineStart: 8 }}
/>
),
title: t('settingChatAppearance.transitionMode.options.none.desc'),
}
: undefined,
},
{
children: <ChatPreview fontSize={general.fontSize} />,
noStyle: true,
Expand Down Expand Up @@ -112,7 +159,7 @@ const ChatAppearance = memo(() => {
return (
<Form
initialValues={general}
items={[theme]}
items={[themeItems]}
itemsType={'group'}
onValuesChange={async (value) => {
setLoading(true);
Expand Down
6 changes: 4 additions & 2 deletions src/components/Thinking/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ interface ThinkingProps {
duration?: number;
style?: CSSProperties;
thinking?: boolean;
thinkingAnimated?: boolean;
}

const Thinking = memo<ThinkingProps>(({ content, duration, thinking, style, citations }) => {
const Thinking = memo<ThinkingProps>((props) => {
const { content, duration, thinking, style, citations, thinkingAnimated } = props;
const { t } = useTranslation(['components', 'common']);
const { styles, cx, theme } = useStyles();

Expand Down Expand Up @@ -154,7 +156,7 @@ const Thinking = memo<ThinkingProps>(({ content, duration, thinking, style, cita
}}
>
{typeof content === 'string' ? (
<Markdown animated={thinking} citations={citations} variant={'chat'}>
<Markdown animated={thinkingAnimated} citations={citations} variant={'chat'}>
{content}
</Markdown>
) : (
Expand Down
1 change: 1 addition & 0 deletions src/config/aiModels/deepseek.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AIChatModelCard } from '@/types/aiModel';

// https://api-docs.deepseek.com/zh-cn/quick_start/pricing
const deepseekChatModels: AIChatModelCard[] = [
{
Expand Down
1 change: 1 addition & 0 deletions src/config/aiModels/hunyuan.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AIChatModelCard } from '@/types/aiModel';

// https://cloud.tencent.com/document/product/1729/104753
const hunyuanChatModels: AIChatModelCard[] = [
{
Expand Down
Loading