@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.
- package/AGENTS.md +133 -0
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +1 -2
- package/packages/model-runtime/src/akashchat/index.ts +43 -0
- package/packages/model-runtime/src/index.ts +1 -0
- package/packages/model-runtime/src/openai/__snapshots__/index.test.ts.snap +1 -1
- package/packages/model-runtime/src/runtimeMap.ts +2 -0
- package/packages/model-runtime/src/types/type.ts +1 -0
- package/packages/model-runtime/src/utils/modelParse.ts +14 -1
- package/packages/types/src/user/settings/keyVaults.ts +1 -0
- package/src/app/[variants]/(main)/settings/llm/ProviderList/providers.tsx +2 -0
- package/src/components/ChatItem/ChatItem.tsx +183 -0
- package/src/components/ChatItem/components/Actions.tsx +25 -0
- package/src/components/ChatItem/components/Avatar.tsx +50 -0
- package/src/components/ChatItem/components/BorderSpacing.tsx +13 -0
- package/src/components/ChatItem/components/ErrorContent.tsx +24 -0
- package/src/components/ChatItem/components/Loading.tsx +26 -0
- package/src/components/ChatItem/components/MessageContent.tsx +76 -0
- package/src/components/ChatItem/components/Title.tsx +43 -0
- package/src/components/ChatItem/index.ts +2 -0
- package/src/components/ChatItem/style.ts +208 -0
- package/src/components/ChatItem/type.ts +80 -0
- package/src/config/aiModels/akashchat.ts +84 -0
- package/src/config/aiModels/index.ts +3 -0
- package/src/config/llm.ts +6 -0
- package/src/config/modelProviders/akashchat.ts +17 -0
- package/src/config/modelProviders/index.ts +3 -0
- 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
|
+
[](#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
|
+
[](#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.
|
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
|
+
});
|
@@ -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,
|
@@ -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
|
-
:
|
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,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';
|