@lobehub/chat 1.117.0 → 1.118.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/AGENTS.md +133 -0
  2. package/CHANGELOG.md +50 -0
  3. package/changelog/v1.json +18 -0
  4. package/package.json +1 -2
  5. package/packages/model-runtime/src/akashchat/index.ts +43 -0
  6. package/packages/model-runtime/src/index.ts +1 -0
  7. package/packages/model-runtime/src/openai/__snapshots__/index.test.ts.snap +1 -1
  8. package/packages/model-runtime/src/runtimeMap.ts +2 -0
  9. package/packages/model-runtime/src/types/type.ts +1 -0
  10. package/packages/model-runtime/src/utils/modelParse.ts +14 -1
  11. package/packages/types/src/user/settings/keyVaults.ts +1 -0
  12. package/src/app/[variants]/(main)/settings/llm/ProviderList/providers.tsx +2 -0
  13. package/src/components/ChatItem/ChatItem.tsx +183 -0
  14. package/src/components/ChatItem/components/Actions.tsx +25 -0
  15. package/src/components/ChatItem/components/Avatar.tsx +50 -0
  16. package/src/components/ChatItem/components/BorderSpacing.tsx +13 -0
  17. package/src/components/ChatItem/components/ErrorContent.tsx +24 -0
  18. package/src/components/ChatItem/components/Loading.tsx +26 -0
  19. package/src/components/ChatItem/components/MessageContent.tsx +76 -0
  20. package/src/components/ChatItem/components/Title.tsx +43 -0
  21. package/src/components/ChatItem/index.ts +2 -0
  22. package/src/components/ChatItem/style.ts +208 -0
  23. package/src/components/ChatItem/type.ts +80 -0
  24. package/src/config/aiModels/akashchat.ts +84 -0
  25. package/src/config/aiModels/index.ts +3 -0
  26. package/src/config/llm.ts +6 -0
  27. package/src/config/modelProviders/akashchat.ts +17 -0
  28. package/src/config/modelProviders/index.ts +3 -0
  29. package/src/features/ChatItem/index.tsx +1 -1
package/AGENTS.md ADDED
@@ -0,0 +1,133 @@
1
+ # LobeChat Development Guidelines
2
+
3
+ This document serves as a comprehensive guide for all team members when developing LobeChat.
4
+
5
+ ## Tech Stack
6
+
7
+ Built with modern technologies:
8
+
9
+ - **Frontend**: Next.js 15, React 19, TypeScript
10
+ - **UI Components**: Ant Design, @lobehub/ui, antd-style
11
+ - **State Management**: Zustand, SWR
12
+ - **Database**: PostgreSQL, PGLite, Drizzle ORM
13
+ - **Testing**: Vitest, Testing Library
14
+ - **Package Manager**: pnpm (monorepo structure)
15
+ - **Build Tools**: Next.js (Turbopack in dev, Webpack in prod), Vitest
16
+
17
+ ## Directory Structure
18
+
19
+ The project follows a well-organized monorepo structure:
20
+
21
+ - `apps/` - Main applications
22
+ - `packages/` - Shared packages and libraries
23
+ - `src/` - Main source code
24
+ - `docs/` - Documentation
25
+ - `.cursor/rules/` - Development rules and guidelines
26
+
27
+ ## Development Workflow
28
+
29
+ ### Git Workflow
30
+
31
+ - Use rebase for git pull: `git pull --rebase`
32
+ - Git commit messages should prefix with gitmoji
33
+ - Git branch name format: `username/feat/feature-name`
34
+ - Use `.github/PULL_REQUEST_TEMPLATE.md` for PR descriptions
35
+
36
+ ### Package Management
37
+
38
+ - Use `pnpm` as the primary package manager
39
+ - Use `bun` to run npm scripts
40
+ - Use `bunx` to run executable npm packages
41
+ - Navigate to specific packages using `cd packages/<package-name>`
42
+
43
+ ### Code Style Guidelines
44
+
45
+ #### TypeScript
46
+
47
+ - Follow strict TypeScript practices for type safety and code quality
48
+ - Use proper type annotations
49
+ - Prefer interfaces over types for object shapes
50
+ - Use generics for reusable components
51
+
52
+ #### React Components
53
+
54
+ - Use functional components with hooks
55
+ - Follow the component structure guidelines
56
+ - Use antd-style & @lobehub/ui for styling
57
+ - Implement proper error boundaries
58
+
59
+ #### Database Schema
60
+
61
+ - Follow Drizzle ORM naming conventions
62
+ - Use plural snake_case for table names
63
+ - Implement proper foreign key relationships
64
+ - Follow the schema style guide
65
+
66
+ ### Testing Strategy
67
+
68
+ **Required Rule**: `testing-guide/testing-guide.mdc`
69
+
70
+ **Commands**:
71
+
72
+ - Web: `bunx vitest run --silent='passed-only' '[file-path-pattern]'`
73
+ - Packages: `cd packages/[package-name] && bunx vitest run --silent='passed-only' '[file-path-pattern]'`
74
+
75
+ **Important Notes**:
76
+
77
+ - Wrap file paths in single quotes to avoid shell expansion
78
+ - Never run `bun run test` - this runs all tests and takes ~10 minutes
79
+ - If a test fails twice, stop and ask for help
80
+ - Always add tests for new code
81
+
82
+ ### Type Checking
83
+
84
+ - Use `bun run type-check` to check for type errors
85
+ - Ensure all TypeScript errors are resolved before committing
86
+
87
+ ### Internationalization
88
+
89
+ - Add new keys to `src/locales/default/namespace.ts`
90
+ - Translate at least `zh-CN` files for development preview
91
+ - Use hierarchical nested objects, not flat keys
92
+ - Don't run `pnpm i18n` manually (handled by CI)
93
+
94
+ ## Available Development Rules
95
+
96
+ The project provides comprehensive rules in `.cursor/rules/` directory:
97
+
98
+ ### Core Development
99
+
100
+ - `backend-architecture.mdc` - Three-layer architecture and data flow
101
+ - `react-component.mdc` - Component patterns and UI library usage
102
+ - `drizzle-schema-style-guide.mdc` - Database schema conventions
103
+ - `define-database-model.mdc` - Model templates and CRUD patterns
104
+ - `i18n.mdc` - Internationalization workflow
105
+
106
+ ### State Management & UI
107
+
108
+ - `zustand-slice-organization.mdc` - Store organization patterns
109
+ - `zustand-action-patterns.mdc` - Action implementation patterns
110
+ - `packages/react-layout-kit.mdc` - Flex layout component usage
111
+
112
+ ### Testing & Quality
113
+
114
+ - `testing-guide/testing-guide.mdc` - Comprehensive testing strategy
115
+ - `code-review.mdc` - Code review process and standards
116
+
117
+ ### Desktop (Electron)
118
+
119
+ - `desktop-feature-implementation.mdc` - Main/renderer process patterns
120
+ - `desktop-local-tools-implement.mdc` - Tool integration workflow
121
+ - `desktop-menu-configuration.mdc` - Menu system configuration
122
+ - `desktop-window-management.mdc` - Window management patterns
123
+ - `desktop-controller-tests.mdc` - Controller testing guide
124
+
125
+ ## Best Practices
126
+
127
+ - **Conservative for existing code, modern approaches for new features**
128
+ - **Code Language**: Use Chinese for files with existing Chinese comments, American English for new files
129
+ - Always add tests for new functionality
130
+ - Follow the established patterns in the codebase
131
+ - Use proper error handling and logging
132
+ - Implement proper accessibility features
133
+ - Consider internationalization from the start
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 1.118.0](https://github.com/lobehub/lobe-chat/compare/v1.117.1...v1.118.0)
6
+
7
+ <sup>Released on **2025-08-29**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Add new provider AkashChat.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Add new provider AkashChat, closes [#8923](https://github.com/lobehub/lobe-chat/issues/8923) ([2f3bf0f](https://github.com/lobehub/lobe-chat/commit/2f3bf0f))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.117.1](https://github.com/lobehub/lobe-chat/compare/v1.117.0...v1.117.1)
31
+
32
+ <sup>Released on **2025-08-29**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Move chat item into chat.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **misc**: Move chat item into chat, closes [#8970](https://github.com/lobehub/lobe-chat/issues/8970) ([e09817e](https://github.com/lobehub/lobe-chat/commit/e09817e))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 1.117.0](https://github.com/lobehub/lobe-chat/compare/v1.116.4...v1.117.0)
6
56
 
7
57
  <sup>Released on **2025-08-29**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Add new provider AkashChat."
6
+ ]
7
+ },
8
+ "date": "2025-08-29",
9
+ "version": "1.118.0"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Move chat item into chat."
15
+ ]
16
+ },
17
+ "date": "2025-08-29",
18
+ "version": "1.117.1"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.117.0",
3
+ "version": "1.118.0",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -300,7 +300,6 @@
300
300
  "@types/chroma-js": "^3.1.1",
301
301
  "@types/crypto-js": "^4.2.2",
302
302
  "@types/debug": "^4.1.12",
303
- "@types/diff": "^8.0.0",
304
303
  "@types/fs-extra": "^11.0.4",
305
304
  "@types/ip": "^1.1.3",
306
305
  "@types/json-schema": "^7.0.15",
@@ -0,0 +1,43 @@
1
+ import { ModelProvider } from '../types';
2
+ import { processMultiProviderModelList } from '../utils/modelParse';
3
+ import { createOpenAICompatibleRuntime } from '../utils/openaiCompatibleFactory';
4
+
5
+ export interface AkashChatModelCard {
6
+ id: string;
7
+ }
8
+
9
+ export const LobeAkashChatAI = createOpenAICompatibleRuntime({
10
+ baseURL: 'https://chatapi.akash.network/api/v1',
11
+ chatCompletion: {
12
+ handlePayload: (payload) => {
13
+ const { model, ...rest } = payload;
14
+
15
+ return {
16
+ ...rest,
17
+ model,
18
+ stream: true,
19
+ } as any;
20
+ },
21
+ },
22
+ debug: {
23
+ chatCompletion: () => process.env.DEBUG_AKASH_CHAT_COMPLETION === '1',
24
+ },
25
+ models: async ({ client }) => {
26
+ try {
27
+ const modelsPage = (await client.models.list()) as any;
28
+ const rawList: any[] = modelsPage.data || [];
29
+
30
+ // Remove `created` field from each model item
31
+ const modelList: AkashChatModelCard[] = rawList.map(({ created, ...rest }) => rest);
32
+
33
+ return await processMultiProviderModelList(modelList, 'akashchat');
34
+ } catch (error) {
35
+ console.warn(
36
+ 'Failed to fetch AkashChat models. Please ensure your AkashChat API key is valid:',
37
+ error,
38
+ );
39
+ return [];
40
+ }
41
+ },
42
+ provider: ModelProvider.AkashChat,
43
+ });
@@ -1,3 +1,4 @@
1
+ export { LobeAkashChatAI } from './akashchat';
1
2
  export { LobeAnthropicAI } from './anthropic';
2
3
  export { LobeAzureAI } from './azureai';
3
4
  export { LobeAzureOpenAI } from './azureOpenai';
@@ -366,7 +366,7 @@ exports[`LobeOpenAI > models > should get models 1`] = `
366
366
  "maxOutput": undefined,
367
367
  "reasoning": false,
368
368
  "releasedAt": "2022-12-16",
369
- "type": "chat",
369
+ "type": "embedding",
370
370
  "vision": false,
371
371
  },
372
372
  {
@@ -2,6 +2,7 @@ import { LobeAi21AI } from './ai21';
2
2
  import { Lobe302AI } from './ai302';
3
3
  import { LobeAi360AI } from './ai360';
4
4
  import { LobeAiHubMixAI } from './aihubmix';
5
+ import { LobeAkashChatAI } from './akashchat';
5
6
  import { LobeAnthropicAI } from './anthropic';
6
7
  import { LobeAzureOpenAI } from './azureOpenai';
7
8
  import { LobeAzureAI } from './azureai';
@@ -61,6 +62,7 @@ export const providerRuntimeMap = {
61
62
  ai302: Lobe302AI,
62
63
  ai360: LobeAi360AI,
63
64
  aihubmix: LobeAiHubMixAI,
65
+ akashchat: LobeAkashChatAI,
64
66
  anthropic: LobeAnthropicAI,
65
67
  azure: LobeAzureOpenAI,
66
68
  azureai: LobeAzureAI,
@@ -32,6 +32,7 @@ export enum ModelProvider {
32
32
  Ai302 = 'ai302',
33
33
  Ai360 = 'ai360',
34
34
  AiHubMix = 'aihubmix',
35
+ AkashChat = 'akashchat',
35
36
  Anthropic = 'anthropic',
36
37
  Azure = 'azure',
37
38
  AzureAI = 'azureai',
@@ -118,6 +118,14 @@ export const IMAGE_MODEL_KEYWORDS = [
118
118
  '^V_1',
119
119
  ] as const;
120
120
 
121
+ // 嵌入模型关键词配置
122
+ export const EMBEDDING_MODEL_KEYWORDS = [
123
+ 'embedding',
124
+ 'embed',
125
+ 'bge',
126
+ 'm3e',
127
+ ] as const;
128
+
121
129
  /**
122
130
  * 检测关键词列表是否匹配模型ID(支持多种匹配模式)
123
131
  * @param modelId 模型ID(小写)
@@ -278,7 +286,12 @@ const processModelCard = (
278
286
  IMAGE_MODEL_KEYWORDS.map((k) => k.toLowerCase()),
279
287
  )
280
288
  ? 'image'
281
- : 'chat');
289
+ : isKeywordListMatch(
290
+ model.id.toLowerCase(),
291
+ EMBEDDING_MODEL_KEYWORDS.map((k) => k.toLowerCase()),
292
+ )
293
+ ? 'embedding'
294
+ : 'chat');
282
295
 
283
296
  // image model can't find parameters
284
297
  if (modelType === 'image' && !model.parameters && !knownModel?.parameters) {
@@ -41,6 +41,7 @@ export interface UserKeyVaults extends SearchEngineKeyVaults {
41
41
  ai302?: OpenAICompatibleKeyVault;
42
42
  ai360?: OpenAICompatibleKeyVault;
43
43
  aihubmix?: OpenAICompatibleKeyVault;
44
+ akashchat?: OpenAICompatibleKeyVault;
44
45
  anthropic?: OpenAICompatibleKeyVault;
45
46
  azure?: AzureOpenAIKeyVault;
46
47
  azureai?: AzureOpenAIKeyVault;
@@ -4,6 +4,7 @@ import {
4
4
  Ai21ProviderCard,
5
5
  Ai302ProviderCard,
6
6
  Ai360ProviderCard,
7
+ AkashChatProviderCard,
7
8
  AnthropicProviderCard,
8
9
  BaichuanProviderCard,
9
10
  CohereProviderCard,
@@ -113,6 +114,7 @@ export const useProviderList = (): ProviderItem[] => {
113
114
  GiteeAIProviderCard,
114
115
  PPIOProviderCard,
115
116
  InfiniAIProviderCard,
117
+ AkashChatProviderCard,
116
118
  Ai302ProviderCard,
117
119
  ],
118
120
  [
@@ -0,0 +1,183 @@
1
+ 'use client';
2
+
3
+ import { useResponsive } from 'antd-style';
4
+ import { memo, useEffect, useRef, useState } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import Actions from './components/Actions';
8
+ import Avatar from './components/Avatar';
9
+ import BorderSpacing from './components/BorderSpacing';
10
+ import ErrorContent from './components/ErrorContent';
11
+ import MessageContent from './components/MessageContent';
12
+ import Title from './components/Title';
13
+ import { useStyles } from './style';
14
+ import type { ChatItemProps } from './type';
15
+
16
+ const MOBILE_AVATAR_SIZE = 32;
17
+
18
+ const ChatItem = memo<ChatItemProps>(
19
+ ({
20
+ avatarAddon,
21
+ onAvatarClick,
22
+ avatarProps,
23
+ actions,
24
+ className,
25
+ primary,
26
+ loading,
27
+ message,
28
+ placeholderMessage = '...',
29
+ placement = 'left',
30
+ variant = 'bubble',
31
+ avatar,
32
+ error,
33
+ showTitle,
34
+ time,
35
+ editing,
36
+ onChange,
37
+ onEditingChange,
38
+ messageExtra,
39
+ renderMessage,
40
+ text,
41
+ errorMessage,
42
+ onDoubleClick,
43
+ fontSize,
44
+ aboveMessage,
45
+ belowMessage,
46
+ markdownProps,
47
+ actionsWrapWidth = 54,
48
+ ...rest
49
+ }) => {
50
+ const { mobile } = useResponsive();
51
+ const { cx, styles } = useStyles({
52
+ editing,
53
+ placement,
54
+ primary,
55
+ showTitle,
56
+ time,
57
+ title: avatar.title,
58
+ variant,
59
+ });
60
+
61
+ // 在 ChatItem 组件中添加
62
+ const contentRef = useRef<HTMLDivElement>(null);
63
+ const containerRef = useRef<HTMLDivElement>(null);
64
+ const [layoutMode, setLayoutMode] = useState<'horizontal' | 'vertical'>(
65
+ variant === 'bubble' ? 'horizontal' : 'vertical',
66
+ );
67
+
68
+ // 使用 ResizeObserver 监控内容和容器尺寸
69
+ useEffect(() => {
70
+ if (variant === 'docs') {
71
+ setLayoutMode('vertical');
72
+ return;
73
+ }
74
+
75
+ if (!contentRef.current || !containerRef.current) return;
76
+
77
+ const observer = new ResizeObserver(() => {
78
+ if (!contentRef.current || !containerRef.current) return;
79
+
80
+ const containerWidth = containerRef.current.clientWidth;
81
+ const contentWidth = contentRef.current.scrollWidth; // 使用scrollWidth获取实际内容宽度
82
+
83
+ // 预留给Actions的最小空间 (根据实际Actions大小调整)
84
+
85
+ // 只有当内容宽度 + Actions最小宽度 > 容器宽度时才切换布局
86
+ setLayoutMode(contentWidth + actionsWrapWidth > containerWidth ? 'vertical' : 'horizontal');
87
+ });
88
+
89
+ observer.observe(contentRef.current);
90
+ observer.observe(containerRef.current);
91
+
92
+ return () => observer.disconnect();
93
+ }, [variant, actionsWrapWidth]);
94
+
95
+ return (
96
+ <Flexbox
97
+ className={cx(styles.container, className)}
98
+ direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
99
+ gap={mobile ? 6 : 12}
100
+ {...rest}
101
+ >
102
+ <Avatar
103
+ {...avatarProps}
104
+ addon={avatarAddon}
105
+ alt={avatarProps?.alt || avatar.title || 'avatar'}
106
+ avatar={avatar}
107
+ loading={loading}
108
+ onClick={onAvatarClick}
109
+ placement={placement}
110
+ size={mobile ? MOBILE_AVATAR_SIZE : undefined}
111
+ style={{
112
+ marginTop: 6,
113
+ ...avatarProps?.style,
114
+ }}
115
+ />
116
+ <Flexbox
117
+ align={placement === 'left' ? 'flex-start' : 'flex-end'}
118
+ className={styles.messageContainer}
119
+ ref={containerRef}
120
+ >
121
+ <Title avatar={avatar} placement={placement} showTitle={showTitle} time={time} />
122
+ {aboveMessage}
123
+ <Flexbox
124
+ align={placement === 'left' ? 'flex-start' : 'flex-end'}
125
+ className={styles.messageContent}
126
+ data-layout={layoutMode} // 添加数据属性以方便样式选择
127
+ direction={
128
+ layoutMode === 'horizontal'
129
+ ? placement === 'left'
130
+ ? 'horizontal'
131
+ : 'horizontal-reverse'
132
+ : 'vertical'
133
+ }
134
+ gap={8}
135
+ >
136
+ <Flexbox ref={contentRef} width={'100%'}>
137
+ {error && (message === placeholderMessage || !message) ? (
138
+ <ErrorContent error={error} message={errorMessage} placement={placement} />
139
+ ) : (
140
+ <MessageContent
141
+ editing={editing}
142
+ fontSize={fontSize}
143
+ markdownProps={markdownProps}
144
+ message={message}
145
+ messageExtra={
146
+ <>
147
+ {error && (
148
+ <ErrorContent error={error} message={errorMessage} placement={placement} />
149
+ )}
150
+ {messageExtra}
151
+ </>
152
+ }
153
+ onChange={onChange}
154
+ onDoubleClick={onDoubleClick}
155
+ onEditingChange={onEditingChange}
156
+ placement={placement}
157
+ primary={primary}
158
+ renderMessage={renderMessage}
159
+ text={text}
160
+ variant={variant}
161
+ />
162
+ )}
163
+ </Flexbox>
164
+ {actions && (
165
+ <Actions
166
+ actions={actions}
167
+ editing={editing}
168
+ placement={placement}
169
+ variant={variant}
170
+ />
171
+ )}
172
+ </Flexbox>
173
+ {belowMessage}
174
+ </Flexbox>
175
+ {mobile && variant === 'bubble' && <BorderSpacing borderSpacing={MOBILE_AVATAR_SIZE} />}
176
+ </Flexbox>
177
+ );
178
+ },
179
+ );
180
+
181
+ export default ChatItem;
182
+
183
+ export type { ChatItemProps } from './type';
@@ -0,0 +1,25 @@
1
+ import { type Ref, memo } from 'react';
2
+ import { Flexbox } from 'react-layout-kit';
3
+
4
+ import { useStyles } from '../style';
5
+ import { ChatItemProps } from '../type';
6
+
7
+ export interface ActionsProps {
8
+ actions: ChatItemProps['actions'];
9
+ editing?: boolean;
10
+ placement?: ChatItemProps['placement'];
11
+ ref?: Ref<HTMLDivElement>;
12
+ variant?: ChatItemProps['variant'];
13
+ }
14
+
15
+ const Actions = memo<ActionsProps>(({ actions, placement, variant, editing, ref }) => {
16
+ const { styles } = useStyles({ editing, placement, variant });
17
+
18
+ return (
19
+ <Flexbox align={'flex-start'} className={styles.actions} ref={ref} role="menubar">
20
+ {actions}
21
+ </Flexbox>
22
+ );
23
+ });
24
+
25
+ export default Actions;
@@ -0,0 +1,50 @@
1
+ import { Avatar as A } from '@lobehub/ui';
2
+ import { type CSSProperties, memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import { useStyles } from '../style';
6
+ import type { ChatItemProps } from '../type';
7
+ import Loading from './Loading';
8
+
9
+ export interface AvatarProps {
10
+ addon?: ChatItemProps['avatarAddon'];
11
+ alt?: string;
12
+ avatar: ChatItemProps['avatar'];
13
+ loading?: ChatItemProps['loading'];
14
+ onClick?: ChatItemProps['onAvatarClick'];
15
+ placement?: ChatItemProps['placement'];
16
+ size?: number;
17
+ style?: CSSProperties;
18
+ unoptimized?: boolean;
19
+ }
20
+
21
+ const Avatar = memo<AvatarProps>(
22
+ ({ loading, avatar, placement, unoptimized, addon, onClick, size = 40, style, alt }) => {
23
+ const { styles } = useStyles({ avatarSize: size });
24
+ const avatarContent = (
25
+ <div className={styles.avatarContainer} style={style}>
26
+ <A
27
+ alt={alt || avatar.title}
28
+ animation={loading}
29
+ avatar={avatar.avatar}
30
+ background={avatar.backgroundColor}
31
+ onClick={onClick}
32
+ size={size}
33
+ title={avatar.title}
34
+ unoptimized={unoptimized}
35
+ />
36
+ <Loading loading={loading} placement={placement} />
37
+ </div>
38
+ );
39
+
40
+ if (!addon) return avatarContent;
41
+ return (
42
+ <Flexbox align={'center'} className={styles.avatarGroupContainer} gap={8}>
43
+ {avatarContent}
44
+ {addon}
45
+ </Flexbox>
46
+ );
47
+ },
48
+ );
49
+
50
+ export default Avatar;
@@ -0,0 +1,13 @@
1
+ import { memo } from 'react';
2
+
3
+ export interface BorderSpacingProps {
4
+ borderSpacing?: number;
5
+ }
6
+
7
+ const BorderSpacing = memo<BorderSpacingProps>(({ borderSpacing }) => {
8
+ if (!borderSpacing) return null;
9
+
10
+ return <div style={{ flex: 'none', width: borderSpacing }} />;
11
+ });
12
+
13
+ export default BorderSpacing;
@@ -0,0 +1,24 @@
1
+ import { Alert } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import { useStyles } from '../style';
6
+ import { ChatItemProps } from '../type';
7
+
8
+ export interface ErrorContentProps {
9
+ error?: ChatItemProps['error'];
10
+ message?: ChatItemProps['errorMessage'];
11
+ placement?: ChatItemProps['placement'];
12
+ }
13
+
14
+ const ErrorContent = memo<ErrorContentProps>(({ message, error, placement }) => {
15
+ const { styles } = useStyles({ placement });
16
+
17
+ return (
18
+ <Flexbox className={styles.errorContainer}>
19
+ <Alert closable={false} extra={message} showIcon type={'error'} {...error} />
20
+ </Flexbox>
21
+ );
22
+ });
23
+
24
+ export default ErrorContent;
@@ -0,0 +1,26 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { Loader2 } from 'lucide-react';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useStyles } from '../style';
7
+ import { ChatItemProps } from '../type';
8
+
9
+ export interface LoadingProps {
10
+ loading?: ChatItemProps['loading'];
11
+ placement?: ChatItemProps['placement'];
12
+ }
13
+
14
+ const Loading = memo<LoadingProps>(({ loading, placement }) => {
15
+ const { styles } = useStyles({ placement });
16
+
17
+ if (!loading) return null;
18
+
19
+ return (
20
+ <Flexbox align={'center'} className={styles.loading} justify={'center'}>
21
+ <Icon icon={Loader2} size={{ size: 12, strokeWidth: 3 }} spin />
22
+ </Flexbox>
23
+ );
24
+ });
25
+
26
+ export default Loading;
@@ -0,0 +1,76 @@
1
+ import { MarkdownProps } from '@lobehub/ui';
2
+ import { EditableMessage } from '@lobehub/ui/chat';
3
+ import { useResponsive } from 'antd-style';
4
+ import { type ReactNode, memo } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { useStyles } from '../style';
8
+ import { ChatItemProps } from '../type';
9
+
10
+ export interface MessageContentProps {
11
+ editing?: ChatItemProps['editing'];
12
+ fontSize?: number;
13
+ markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
14
+ message?: ReactNode;
15
+ messageExtra?: ChatItemProps['messageExtra'];
16
+ onChange?: ChatItemProps['onChange'];
17
+ onDoubleClick?: ChatItemProps['onDoubleClick'];
18
+ onEditingChange?: ChatItemProps['onEditingChange'];
19
+ placement?: ChatItemProps['placement'];
20
+ primary?: ChatItemProps['primary'];
21
+ renderMessage?: ChatItemProps['renderMessage'];
22
+ text?: ChatItemProps['text'];
23
+ variant?: ChatItemProps['variant'];
24
+ }
25
+
26
+ const MessageContent = memo<MessageContentProps>(
27
+ ({
28
+ editing,
29
+ onChange,
30
+ onEditingChange,
31
+ text,
32
+ message,
33
+ placement,
34
+ messageExtra,
35
+ renderMessage,
36
+ variant,
37
+ primary,
38
+ onDoubleClick,
39
+ fontSize,
40
+ markdownProps,
41
+ }) => {
42
+ const { cx, styles } = useStyles({ editing, placement, primary, variant });
43
+ const { mobile } = useResponsive();
44
+
45
+ const content = (
46
+ <EditableMessage
47
+ classNames={{ input: styles.editingInput }}
48
+ editButtonSize={'small'}
49
+ editing={editing}
50
+ fontSize={fontSize}
51
+ fullFeaturedCodeBlock
52
+ markdownProps={markdownProps}
53
+ onChange={onChange}
54
+ onEditingChange={onEditingChange}
55
+ openModal={mobile ? editing : undefined}
56
+ text={text}
57
+ value={message ? String(message) : ''}
58
+ />
59
+ );
60
+ const messageContent = renderMessage ? renderMessage(content) : content;
61
+
62
+ return (
63
+ <Flexbox
64
+ className={cx(styles.message, editing && styles.editingContainer)}
65
+ onDoubleClick={onDoubleClick}
66
+ >
67
+ {messageContent}
68
+ {messageExtra && !editing ? (
69
+ <div className={styles.messageExtra}>{messageExtra}</div>
70
+ ) : null}
71
+ </Flexbox>
72
+ );
73
+ },
74
+ );
75
+
76
+ export default MessageContent;
@@ -0,0 +1,43 @@
1
+ import dayjs from 'dayjs';
2
+ import { memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import { useStyles } from '../style';
6
+ import { ChatItemProps } from '../type';
7
+
8
+ export interface TitleProps {
9
+ avatar: ChatItemProps['avatar'];
10
+ placement?: ChatItemProps['placement'];
11
+ showTitle?: ChatItemProps['showTitle'];
12
+ time?: ChatItemProps['time'];
13
+ }
14
+
15
+ const formatTime = (time: number): string => {
16
+ const now = dayjs();
17
+ const target = dayjs(time);
18
+
19
+ if (target.isSame(now, 'day')) {
20
+ return target.format('HH:mm:ss');
21
+ } else if (target.isSame(now, 'year')) {
22
+ return target.format('MM-DD HH:mm:ss');
23
+ } else {
24
+ return target.format('YYYY-MM-DD HH:mm:ss');
25
+ }
26
+ };
27
+
28
+ const Title = memo<TitleProps>(({ showTitle, placement, time, avatar }) => {
29
+ const { styles } = useStyles({ placement, showTitle, time });
30
+
31
+ return (
32
+ <Flexbox
33
+ className={styles.name}
34
+ direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
35
+ gap={4}
36
+ >
37
+ {showTitle ? avatar.title || 'untitled' : undefined}
38
+ {time && <time>{formatTime(time)}</time>}
39
+ </Flexbox>
40
+ );
41
+ });
42
+
43
+ export default Title;
@@ -0,0 +1,2 @@
1
+ export { default as ChatItem } from './ChatItem';
2
+ export type * from './type';
@@ -0,0 +1,208 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { rgba } from 'polished';
3
+
4
+ export const useStyles = createStyles(
5
+ (
6
+ { cx, css, token, responsive },
7
+ {
8
+ placement,
9
+ variant,
10
+ title,
11
+ avatarSize,
12
+ editing,
13
+ time,
14
+ }: {
15
+ avatarSize?: number;
16
+ editing?: boolean;
17
+ placement?: 'left' | 'right';
18
+ primary?: boolean;
19
+ showTitle?: boolean;
20
+ time?: number;
21
+ title?: string;
22
+ variant?: 'bubble' | 'docs';
23
+ },
24
+ ) => {
25
+ const blockStylish = css`
26
+ padding-block: 8px;
27
+ padding-inline: 12px;
28
+ border: 1px solid ${rgba(token.colorBorderSecondary, 0.66)};
29
+ border-radius: ${token.borderRadiusLG}px;
30
+
31
+ background-color: ${token.colorBgContainer};
32
+ `;
33
+
34
+ const rawStylish = css`
35
+ padding-block-start: ${title ? 0 : '6px'};
36
+ `;
37
+
38
+ const rawContainerStylish = css`
39
+ margin-block-end: -16px;
40
+ transition: background-color 100ms ${token.motionEaseOut};
41
+ `;
42
+
43
+ const typeStylish = variant === 'bubble' ? blockStylish : rawStylish;
44
+
45
+ const editingStylish =
46
+ editing &&
47
+ css`
48
+ width: 100%;
49
+ `;
50
+
51
+ return {
52
+ actions: cx(
53
+ css`
54
+ flex: none;
55
+ align-self: ${variant === 'bubble'
56
+ ? 'flex-end'
57
+ : placement === 'left'
58
+ ? 'flex-start'
59
+ : 'flex-end'};
60
+ justify-content: ${placement === 'left' ? 'flex-end' : 'flex-start'};
61
+ `,
62
+ editing &&
63
+ css`
64
+ pointer-events: none !important;
65
+ opacity: 0 !important;
66
+ `,
67
+ ),
68
+ avatarContainer: css`
69
+ position: relative;
70
+ flex: none;
71
+ width: ${avatarSize}px;
72
+ height: ${avatarSize}px;
73
+ `,
74
+ avatarGroupContainer: css`
75
+ width: ${avatarSize}px;
76
+ `,
77
+ container: cx(
78
+ variant === 'docs' && rawContainerStylish,
79
+ css`
80
+ position: relative;
81
+
82
+ width: 100%;
83
+ max-width: 100vw;
84
+ padding-block: 24px 12px;
85
+ padding-inline: 12px;
86
+
87
+ time {
88
+ display: inline-block;
89
+ white-space: nowrap;
90
+ }
91
+
92
+ div[role='menubar'] {
93
+ display: flex;
94
+ }
95
+
96
+ time,
97
+ div[role='menubar'] {
98
+ pointer-events: none;
99
+ opacity: 0;
100
+ transition: opacity 200ms ${token.motionEaseOut};
101
+ }
102
+
103
+ &:hover {
104
+ time,
105
+ div[role='menubar'] {
106
+ pointer-events: unset;
107
+ opacity: 1;
108
+ }
109
+ }
110
+
111
+ ${responsive.mobile} {
112
+ padding-block-start: ${variant === 'docs' ? '16px' : '12px'};
113
+ padding-inline: 8px;
114
+ }
115
+ `,
116
+ ),
117
+ editingContainer: cx(
118
+ editingStylish,
119
+ css`
120
+ padding-block: 8px 12px;
121
+ padding-inline: 12px;
122
+ border: 1px solid ${token.colorBorderSecondary};
123
+
124
+ &:active,
125
+ &:hover {
126
+ border-color: ${token.colorBorder};
127
+ }
128
+ `,
129
+ variant === 'docs' &&
130
+ css`
131
+ border-radius: ${token.borderRadius}px;
132
+ background: ${token.colorFillQuaternary};
133
+ `,
134
+ ),
135
+ editingInput: css`
136
+ width: 100%;
137
+ `,
138
+ errorContainer: css`
139
+ position: relative;
140
+ overflow: hidden;
141
+ width: 100%;
142
+ `,
143
+
144
+ loading: css`
145
+ position: absolute;
146
+ inset-block-end: 0;
147
+ inset-inline: ${placement === 'right' ? '-4px' : 'unset'}
148
+ ${placement === 'left' ? '-4px' : 'unset'};
149
+
150
+ width: 16px;
151
+ height: 16px;
152
+ border-radius: 50%;
153
+
154
+ color: ${token.colorBgLayout};
155
+
156
+ background: ${token.colorPrimary};
157
+ `,
158
+ message: cx(
159
+ typeStylish,
160
+ css`
161
+ position: relative;
162
+ overflow: hidden;
163
+ max-width: 100%;
164
+
165
+ ${responsive.mobile} {
166
+ width: 100%;
167
+ }
168
+ `,
169
+ ),
170
+ messageContainer: cx(
171
+ editingStylish,
172
+ css`
173
+ position: relative;
174
+ overflow: hidden;
175
+ max-width: 100%;
176
+ margin-block-start: ${time ? -16 : 0}px;
177
+
178
+ ${responsive.mobile} {
179
+ overflow-x: auto;
180
+ }
181
+ `,
182
+ ),
183
+ messageContent: cx(
184
+ editingStylish,
185
+ css`
186
+ position: relative;
187
+ overflow: hidden;
188
+ max-width: 100%;
189
+
190
+ ${responsive.mobile} {
191
+ flex-direction: column !important;
192
+ }
193
+ `,
194
+ ),
195
+ messageExtra: cx('message-extra'),
196
+ name: css`
197
+ pointer-events: none;
198
+
199
+ margin-block-end: 6px;
200
+
201
+ font-size: 12px;
202
+ line-height: 1;
203
+ color: ${token.colorTextDescription};
204
+ text-align: ${placement === 'left' ? 'left' : 'right'};
205
+ `,
206
+ };
207
+ },
208
+ );
@@ -0,0 +1,80 @@
1
+ import { AlertProps, AvatarProps, DivProps, MarkdownProps } from '@lobehub/ui';
2
+ import { EditableMessageProps, MetaData } from '@lobehub/ui/chat';
3
+ import { ReactNode } from 'react';
4
+ import { FlexboxProps } from 'react-layout-kit';
5
+
6
+ export interface ChatItemProps extends Omit<FlexboxProps, 'children' | 'onChange'> {
7
+ aboveMessage?: ReactNode;
8
+ /**
9
+ * @description Actions to be displayed in the chat item
10
+ */
11
+ actions?: ReactNode;
12
+ actionsWrapWidth?: number;
13
+ /**
14
+ * @description Metadata for the avatar
15
+ */
16
+ avatar: MetaData;
17
+ avatarAddon?: ReactNode;
18
+ avatarProps?: AvatarProps;
19
+ belowMessage?: ReactNode;
20
+ /**
21
+ * @description Whether the chat item is in editing mode
22
+ */
23
+ editing?: boolean;
24
+ /**
25
+ * @description Props for Error render
26
+ */
27
+ error?: AlertProps;
28
+ errorMessage?: ReactNode;
29
+ fontSize?: number;
30
+ /**
31
+ * @description Whether the chat item is in loading state
32
+ */
33
+ loading?: boolean;
34
+ markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
35
+ /**
36
+ * @description The message content of the chat item
37
+ */
38
+ message?: ReactNode;
39
+ messageExtra?: ReactNode;
40
+ onAvatarClick?: () => void;
41
+ /**
42
+ * @description Callback when the message content changes
43
+ * @param value - The new message content
44
+ */
45
+ onChange?: (value: string) => void;
46
+ onDoubleClick?: DivProps['onDoubleClick'];
47
+ /**
48
+ * @description Callback when the editing mode changes
49
+ * @param editing - The new editing mode
50
+ */
51
+ onEditingChange?: (editing: boolean) => void;
52
+ /**
53
+ * @default "..."
54
+ */
55
+ placeholderMessage?: string;
56
+ /**
57
+ * @description The placement of the chat item
58
+ * @default 'left'
59
+ */
60
+ placement?: 'left' | 'right';
61
+ /**
62
+ * @description Whether the chat item is primary
63
+ */
64
+ primary?: boolean;
65
+ renderMessage?: (content: ReactNode) => ReactNode;
66
+ /**
67
+ * @description Whether to show the title of the chat item
68
+ */
69
+ showTitle?: boolean;
70
+ text?: EditableMessageProps['text'];
71
+ /**
72
+ * @description The timestamp of the chat item
73
+ */
74
+ time?: number;
75
+ /**
76
+ * @description The type of the chat item
77
+ * @default 'bubble'
78
+ */
79
+ variant?: 'bubble' | 'docs';
80
+ }
@@ -0,0 +1,84 @@
1
+ import { AIChatModelCard } from '@/types/aiModel';
2
+
3
+ const akashChatModels: AIChatModelCard[] = [
4
+ {
5
+ abilities: {
6
+ functionCall: true,
7
+ reasoning: true,
8
+ },
9
+ contextWindowTokens: 65_536,
10
+ description:
11
+ 'DeepSeek V3.1:下一代推理模型,提升了复杂推理与链路思考能力,适合需要深入分析的任务。',
12
+ displayName: 'DeepSeek V3.1',
13
+ enabled: true,
14
+ id: 'DeepSeek-V3-1',
15
+ type: 'chat',
16
+ },
17
+ {
18
+ abilities: {
19
+ functionCall: true,
20
+ reasoning: true,
21
+ },
22
+ contextWindowTokens: 65_536,
23
+ displayName: 'DeepSeek R1 Distill Qwen 32B',
24
+ id: 'DeepSeek-R1-Distill-Qwen-32B',
25
+ type: 'chat',
26
+ },
27
+ {
28
+ abilities: {
29
+ functionCall: true,
30
+ reasoning: true,
31
+ },
32
+ contextWindowTokens: 131_072,
33
+ description: 'GPT-OSS-120B MXFP4 量化的 Transformer 结构,在资源受限时仍能保持强劲性能。',
34
+ displayName: 'GPT-OSS-120B',
35
+ enabled: true,
36
+ id: 'gpt-oss-120b',
37
+ type: 'chat',
38
+ },
39
+ {
40
+ abilities: {
41
+ functionCall: true,
42
+ },
43
+ contextWindowTokens: 262_144,
44
+ description:
45
+ 'Qwen3 235B A22B Instruct 2507:面向高级推理与对话指令优化的模型,混合专家架构以在大规模参数下保持推理效率。',
46
+ displayName: 'Qwen3 235B A22B Instruct 2507',
47
+ id: 'Qwen3-235B-A22B-Instruct-2507-FP8',
48
+ type: 'chat',
49
+ },
50
+ {
51
+ abilities: {
52
+ functionCall: true,
53
+ vision: true,
54
+ },
55
+ contextWindowTokens: 131_072,
56
+ description:
57
+ 'Llama 4 Maverick:基于 Mixture-of-Experts 的大规模模型,提供高效的专家激活策略以在推理中表现优异。',
58
+ displayName: 'Llama 4 Maverick (17Bx128E)',
59
+ id: 'Meta-Llama-4-Maverick-17B-128E-Instruct-FP8',
60
+ type: 'chat',
61
+ },
62
+ {
63
+ abilities: {
64
+ functionCall: true,
65
+ },
66
+ contextWindowTokens: 131_072,
67
+ description: 'Llama 3.3 70B:通用性强的 Transformer 模型,适用于对话和生成任务。',
68
+ displayName: 'Llama 3.3 70B',
69
+ id: 'Meta-Llama-3-3-70B-Instruct',
70
+ type: 'chat',
71
+ },
72
+ {
73
+ abilities: {
74
+ functionCall: true,
75
+ },
76
+ contextWindowTokens: 131_072,
77
+ displayName: 'Llama 3.1 8B',
78
+ id: 'Meta-Llama-3-1-8B-Instruct-FP8',
79
+ type: 'chat',
80
+ },
81
+ ];
82
+ export const allModels = [...akashChatModels];
83
+
84
+ export default allModels;
@@ -4,6 +4,7 @@ import { default as ai21 } from './ai21';
4
4
  import { default as ai302 } from './ai302';
5
5
  import { default as ai360 } from './ai360';
6
6
  import { default as aihubmix } from './aihubmix';
7
+ import { default as akashchat } from './akashchat';
7
8
  import { default as anthropic } from './anthropic';
8
9
  import { default as azure } from './azure';
9
10
  import { default as azureai } from './azureai';
@@ -83,6 +84,7 @@ export const LOBE_DEFAULT_MODEL_LIST = buildDefaultModelList({
83
84
  ai302,
84
85
  ai360,
85
86
  aihubmix,
87
+ akashchat,
86
88
  anthropic,
87
89
  azure,
88
90
  azureai,
@@ -143,6 +145,7 @@ export { default as ai21 } from './ai21';
143
145
  export { default as ai302 } from './ai302';
144
146
  export { default as ai360 } from './ai360';
145
147
  export { default as aihubmix } from './aihubmix';
148
+ export { default as akashchat } from './akashchat';
146
149
  export { default as anthropic } from './anthropic';
147
150
  export { default as azure } from './azure';
148
151
  export { default as azureai } from './azureai';
package/src/config/llm.ts CHANGED
@@ -178,6 +178,9 @@ export const getLLMConfig = () => {
178
178
  ENABLED_AI302: z.boolean(),
179
179
  AI302_API_KEY: z.string().optional(),
180
180
 
181
+ ENABLED_AKASHCHAT: z.boolean(),
182
+ AKASHCHAT_API_KEY: z.string().optional(),
183
+
181
184
  ENABLED_AIHUBMIX: z.boolean(),
182
185
  AIHUBMIX_API_KEY: z.string().optional(),
183
186
  },
@@ -356,6 +359,9 @@ export const getLLMConfig = () => {
356
359
  ENABLED_AI302: !!process.env.AI302_API_KEY,
357
360
  AI302_API_KEY: process.env.AI302_API_KEY,
358
361
 
362
+ ENABLED_AKASHCHAT: !!process.env.AKASHCHAT_API_KEY,
363
+ AKASHCHAT_API_KEY: process.env.AKASHCHAT_API_KEY,
364
+
359
365
  ENABLED_AIHUBMIX: !!process.env.AIHUBMIX_API_KEY,
360
366
  AIHUBMIX_API_KEY: process.env.AIHUBMIX_API_KEY,
361
367
  },
@@ -0,0 +1,17 @@
1
+ import { ModelProviderCard } from '@/types/llm';
2
+
3
+ const AkashChat: ModelProviderCard = {
4
+ chatModels: [],
5
+ checkModel: 'Meta-Llama-3-1-8B-Instruct-FP8',
6
+ description: 'Akash 是一个无需许可的云资源市场,与传统云提供商相比,其定价具有竞争力。',
7
+ id: 'akashchat',
8
+ modelsUrl: 'https://chatapi.akash.network/documentation',
9
+ name: 'AkashChat',
10
+ settings: {
11
+ sdkType: 'openai',
12
+ showModelFetcher: true,
13
+ },
14
+ url: 'https://chatapi.akash.network/',
15
+ };
16
+
17
+ export default AkashChat;
@@ -4,6 +4,7 @@ import Ai21Provider from './ai21';
4
4
  import Ai302Provider from './ai302';
5
5
  import Ai360Provider from './ai360';
6
6
  import AiHubMixProvider from './aihubmix';
7
+ import AkashChatProvider from './akashchat';
7
8
  import AnthropicProvider from './anthropic';
8
9
  import AzureProvider from './azure';
9
10
  import AzureAIProvider from './azureai';
@@ -172,6 +173,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
172
173
  Ai360Provider,
173
174
  Search1APIProvider,
174
175
  InfiniAIProvider,
176
+ AkashChatProvider,
175
177
  QiniuProvider,
176
178
  ];
177
179
 
@@ -188,6 +190,7 @@ export { default as Ai21ProviderCard } from './ai21';
188
190
  export { default as Ai302ProviderCard } from './ai302';
189
191
  export { default as Ai360ProviderCard } from './ai360';
190
192
  export { default as AiHubMixProviderCard } from './aihubmix';
193
+ export { default as AkashChatProviderCard } from './akashchat';
191
194
  export { default as AnthropicProviderCard } from './anthropic';
192
195
  export { default as AzureProviderCard } from './azure';
193
196
  export { default as AzureAIProviderCard } from './azureai';
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
- import { ChatItemProps, ChatItem as ChatItemRaw } from '@lobehub/ui/chat';
4
3
  import isEqual from 'fast-deep-equal';
5
4
  import { memo, useMemo } from 'react';
6
5
 
6
+ import { ChatItemProps, ChatItem as ChatItemRaw } from '@/components/ChatItem';
7
7
  import { isDesktop } from '@/const/version';
8
8
  import { useElectronStore } from '@/store/electron';
9
9
  import { electronSyncSelectors } from '@/store/electron/selectors';