@lobehub/lobehub 2.0.0-next.281 → 2.0.0-next.283
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/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +2 -2
- package/packages/builtin-tool-local-system/src/client/Inspector/ReadLocalFile/index.tsx +20 -2
- package/packages/observability-otel/src/node.ts +40 -3
- package/src/app/[variants]/(main)/agent/features/Conversation/Header/HeaderActions/useMenu.tsx +4 -4
- package/src/app/[variants]/(main)/community/(detail)/model/features/Details/Overview/ProviderList/index.tsx +6 -3
- package/src/app/[variants]/(main)/community/(detail)/provider/features/Details/Overview/ModelList/index.tsx +6 -3
- package/src/features/Conversation/Messages/components/Extras/Usage/UsageDetail/ModelCard.tsx +1 -1
- package/src/features/EditorModal/EditorCanvas.tsx +62 -0
- package/src/features/EditorModal/TextArea.tsx +30 -0
- package/src/features/EditorModal/Typobar.tsx +139 -0
- package/src/features/EditorModal/index.tsx +18 -8
- package/src/features/PageEditor/Header/useMenu.tsx +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.283](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.282...v2.0.0-next.283)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-14**</sup>
|
|
8
|
+
|
|
9
|
+
#### 💄 Styles
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix UI issues with tooltip wrapping and dropdown type.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Styles
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix UI issues with tooltip wrapping and dropdown type, closes [#11495](https://github.com/lobehub/lobe-chat/issues/11495) ([9d90eba](https://github.com/lobehub/lobe-chat/commit/9d90eba))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 2.0.0-next.282](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.281...v2.0.0-next.282)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2026-01-14**</sup>
|
|
33
|
+
|
|
34
|
+
#### 💄 Styles
|
|
35
|
+
|
|
36
|
+
- **misc**: Update readFile content.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### Styles
|
|
44
|
+
|
|
45
|
+
- **misc**: Update readFile content, closes [#11485](https://github.com/lobehub/lobe-chat/issues/11485) ([050499b](https://github.com/lobehub/lobe-chat/commit/050499b))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
## [Version 2.0.0-next.281](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.280...v2.0.0-next.281)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2026-01-14**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {
|
|
4
|
+
"improvements": [
|
|
5
|
+
"Fix UI issues with tooltip wrapping and dropdown type."
|
|
6
|
+
]
|
|
7
|
+
},
|
|
8
|
+
"date": "2026-01-14",
|
|
9
|
+
"version": "2.0.0-next.283"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"children": {
|
|
13
|
+
"improvements": [
|
|
14
|
+
"Update readFile content."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"date": "2026-01-14",
|
|
18
|
+
"version": "2.0.0-next.282"
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"children": {
|
|
4
22
|
"fixes": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.283",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent 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",
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
"@lobehub/icons": "^4.0.2",
|
|
207
207
|
"@lobehub/market-sdk": "0.28.1",
|
|
208
208
|
"@lobehub/tts": "^4.0.2",
|
|
209
|
-
"@lobehub/ui": "^4.
|
|
209
|
+
"@lobehub/ui": "^4.19.0",
|
|
210
210
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
211
211
|
"@neondatabase/serverless": "^1.0.2",
|
|
212
212
|
"@next/third-parties": "^16.1.1",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { type LocalReadFileParams } from '@lobechat/electron-client-ipc';
|
|
4
4
|
import { type BuiltinInspectorProps } from '@lobechat/types';
|
|
5
|
-
import { cx } from 'antd-style';
|
|
6
|
-
import { memo } from 'react';
|
|
5
|
+
import { createStaticStyles, cx } from 'antd-style';
|
|
6
|
+
import { memo, useMemo } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
9
|
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
|
@@ -11,12 +11,28 @@ import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
|
|
11
11
|
import { type LocalReadFileState } from '../../..';
|
|
12
12
|
import { FilePathDisplay } from '../../components/FilePathDisplay';
|
|
13
13
|
|
|
14
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
15
|
+
lineRange: css`
|
|
16
|
+
flex-shrink: 0;
|
|
17
|
+
margin-inline-start: 4px;
|
|
18
|
+
opacity: 0.7;
|
|
19
|
+
`,
|
|
20
|
+
}));
|
|
21
|
+
|
|
14
22
|
export const ReadLocalFileInspector = memo<
|
|
15
23
|
BuiltinInspectorProps<LocalReadFileParams, LocalReadFileState>
|
|
16
24
|
>(({ args, partialArgs, isArgumentsStreaming, isLoading }) => {
|
|
17
25
|
const { t } = useTranslation('plugin');
|
|
18
26
|
|
|
19
27
|
const filePath = args?.path || partialArgs?.path || '';
|
|
28
|
+
const loc = args?.loc || partialArgs?.loc;
|
|
29
|
+
|
|
30
|
+
// Format line range display, e.g., "L1-L200"
|
|
31
|
+
const lineRangeText = useMemo(() => {
|
|
32
|
+
if (!loc || loc.length !== 2) return null;
|
|
33
|
+
const [start, end] = loc;
|
|
34
|
+
return `L${start + 1}-L${end}`;
|
|
35
|
+
}, [loc]);
|
|
20
36
|
|
|
21
37
|
// During argument streaming
|
|
22
38
|
if (isArgumentsStreaming) {
|
|
@@ -31,6 +47,7 @@ export const ReadLocalFileInspector = memo<
|
|
|
31
47
|
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
|
32
48
|
<span>{t('builtins.lobe-local-system.apiName.readLocalFile')}: </span>
|
|
33
49
|
<FilePathDisplay filePath={filePath} />
|
|
50
|
+
{lineRangeText && <span className={styles.lineRange}>{lineRangeText}</span>}
|
|
34
51
|
</div>
|
|
35
52
|
);
|
|
36
53
|
}
|
|
@@ -39,6 +56,7 @@ export const ReadLocalFileInspector = memo<
|
|
|
39
56
|
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
|
40
57
|
<span>{t('builtins.lobe-local-system.apiName.readLocalFile')}: </span>
|
|
41
58
|
<FilePathDisplay filePath={filePath} />
|
|
59
|
+
{lineRangeText && <span className={styles.lineRange}>{lineRangeText}</span>}
|
|
42
60
|
</div>
|
|
43
61
|
);
|
|
44
62
|
});
|
|
@@ -60,16 +60,53 @@ export function attributesCommon(): DetectedResourceAttributes {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
function debugLogLevelFromString(level?: string | null): DiagLogLevel | undefined {
|
|
64
|
+
if (!level) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
if (typeof level !== 'string') {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
switch (level.toLowerCase()) {
|
|
72
|
+
case 'none':
|
|
73
|
+
return DiagLogLevel.NONE;
|
|
74
|
+
case 'error':
|
|
75
|
+
return DiagLogLevel.ERROR;
|
|
76
|
+
case 'warn':
|
|
77
|
+
return DiagLogLevel.WARN;
|
|
78
|
+
case 'info':
|
|
79
|
+
return DiagLogLevel.INFO;
|
|
80
|
+
case 'debug':
|
|
81
|
+
return DiagLogLevel.DEBUG;
|
|
82
|
+
case 'verbose':
|
|
83
|
+
return DiagLogLevel.VERBOSE;
|
|
84
|
+
case 'all':
|
|
85
|
+
return DiagLogLevel.ALL;
|
|
86
|
+
default:
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function register(options?: { debug?: true | DiagLogLevel; name?: string; version?: string }) {
|
|
64
92
|
const attributes = attributesCommon();
|
|
65
93
|
|
|
94
|
+
if (typeof options?.name !== 'undefined') {
|
|
95
|
+
attributes[ATTR_SERVICE_NAME] = options.name;
|
|
96
|
+
}
|
|
66
97
|
if (typeof options?.version !== 'undefined') {
|
|
67
98
|
attributes[ATTR_SERVICE_VERSION] = options.version;
|
|
68
99
|
}
|
|
69
|
-
if (typeof options?.debug !== 'undefined') {
|
|
100
|
+
if (typeof options?.debug !== 'undefined' || env.OTEL_JS_LOBEHUB_DIAG) {
|
|
101
|
+
const levelFromEnv = debugLogLevelFromString(env.OTEL_JS_LOBEHUB_DIAG);
|
|
102
|
+
|
|
70
103
|
diag.setLogger(
|
|
71
104
|
new DiagConsoleLogger(),
|
|
72
|
-
|
|
105
|
+
!!levelFromEnv
|
|
106
|
+
? levelFromEnv
|
|
107
|
+
: options?.debug === true
|
|
108
|
+
? DiagLogLevel.DEBUG
|
|
109
|
+
: options?.debug,
|
|
73
110
|
);
|
|
74
111
|
}
|
|
75
112
|
|
package/src/app/[variants]/(main)/agent/features/Conversation/Header/HeaderActions/useMenu.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { DropdownItem } from '@lobehub/ui/es/DropdownMenu/type';
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { useGlobalStore } from '@/store/global';
|
|
8
8
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
9
9
|
|
|
10
|
-
export const useMenu = (): { menuItems:
|
|
10
|
+
export const useMenu = (): { menuItems: DropdownItem[] } => {
|
|
11
11
|
const { t } = useTranslation('chat');
|
|
12
12
|
|
|
13
13
|
const [wideScreen, toggleWideScreen] = useGlobalStore((s) => [
|
|
@@ -15,14 +15,14 @@ export const useMenu = (): { menuItems: DropdownMenuCheckboxItem[] } => {
|
|
|
15
15
|
s.toggleWideScreen,
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
|
-
const menuItems = useMemo<
|
|
18
|
+
const menuItems = useMemo<DropdownItem[]>(
|
|
19
19
|
() => [
|
|
20
20
|
{
|
|
21
21
|
checked: wideScreen,
|
|
22
22
|
key: 'full-width',
|
|
23
23
|
label: t('viewMode.fullWidth'),
|
|
24
24
|
onCheckedChange: toggleWideScreen,
|
|
25
|
-
type: '
|
|
25
|
+
type: 'switch',
|
|
26
26
|
},
|
|
27
27
|
],
|
|
28
28
|
[t, wideScreen, toggleWideScreen],
|
|
@@ -74,6 +74,7 @@ const ProviderList = memo(() => {
|
|
|
74
74
|
: record.model?.maxDimension
|
|
75
75
|
? formatTokenNumber(record.model.maxDimension)
|
|
76
76
|
: '--',
|
|
77
|
+
showSorterTooltip: false,
|
|
77
78
|
sorter: (a, b) => {
|
|
78
79
|
const aValue = a.model?.maxOutput || a.model?.maxDimension || 0;
|
|
79
80
|
const bValue = b.model?.maxOutput || b.model?.maxDimension || 0;
|
|
@@ -81,7 +82,7 @@ const ProviderList = memo(() => {
|
|
|
81
82
|
},
|
|
82
83
|
title: (
|
|
83
84
|
<Tooltip title={t('models.providerInfo.maxOutputTooltip')}>
|
|
84
|
-
{t('models.providerInfo.maxOutput')}
|
|
85
|
+
<span>{t('models.providerInfo.maxOutput')}</span>
|
|
85
86
|
</Tooltip>
|
|
86
87
|
),
|
|
87
88
|
width: 120,
|
|
@@ -95,6 +96,7 @@ const ProviderList = memo(() => {
|
|
|
95
96
|
? '$' + formatPriceByCurrency(inputRate, record.model.pricing?.currency)
|
|
96
97
|
: '--';
|
|
97
98
|
},
|
|
99
|
+
showSorterTooltip: false,
|
|
98
100
|
sorter: (a, b) => {
|
|
99
101
|
const aRate = getTextInputUnitRate(a.model?.pricing) || 0;
|
|
100
102
|
const bRate = getTextInputUnitRate(b.model?.pricing) || 0;
|
|
@@ -102,7 +104,7 @@ const ProviderList = memo(() => {
|
|
|
102
104
|
},
|
|
103
105
|
title: (
|
|
104
106
|
<Tooltip title={t('models.providerInfo.inputTooltip')}>
|
|
105
|
-
{t('models.providerInfo.input')}
|
|
107
|
+
<span>{t('models.providerInfo.input')}</span>
|
|
106
108
|
</Tooltip>
|
|
107
109
|
),
|
|
108
110
|
width: 100,
|
|
@@ -116,6 +118,7 @@ const ProviderList = memo(() => {
|
|
|
116
118
|
? '$' + formatPriceByCurrency(outputRate, record.model.pricing?.currency)
|
|
117
119
|
: '--';
|
|
118
120
|
},
|
|
121
|
+
showSorterTooltip: false,
|
|
119
122
|
sorter: (a, b) => {
|
|
120
123
|
const aRate = getTextOutputUnitRate(a.model?.pricing) || 0;
|
|
121
124
|
const bRate = getTextOutputUnitRate(b.model?.pricing) || 0;
|
|
@@ -123,7 +126,7 @@ const ProviderList = memo(() => {
|
|
|
123
126
|
},
|
|
124
127
|
title: (
|
|
125
128
|
<Tooltip title={t('models.providerInfo.outputTooltip')}>
|
|
126
|
-
{t('models.providerInfo.output')}
|
|
129
|
+
<span>{t('models.providerInfo.output')}</span>
|
|
127
130
|
</Tooltip>
|
|
128
131
|
),
|
|
129
132
|
width: 100,
|
|
@@ -72,10 +72,11 @@ const ModelList = memo(() => {
|
|
|
72
72
|
key: 'maxOutput',
|
|
73
73
|
render: (_, record) =>
|
|
74
74
|
record.maxOutput ? formatTokenNumber(record.maxOutput) : '--',
|
|
75
|
+
showSorterTooltip: false,
|
|
75
76
|
sorter: (a, b) => (a.maxOutput || 0) - (b.maxOutput || 0),
|
|
76
77
|
title: (
|
|
77
78
|
<Tooltip title={t('models.providerInfo.maxOutputTooltip')}>
|
|
78
|
-
{t('models.providerInfo.maxOutput')}
|
|
79
|
+
<span>{t('models.providerInfo.maxOutput')}</span>
|
|
79
80
|
</Tooltip>
|
|
80
81
|
),
|
|
81
82
|
width: 120,
|
|
@@ -89,6 +90,7 @@ const ModelList = memo(() => {
|
|
|
89
90
|
? '$' + formatPriceByCurrency(inputRate, record.pricing?.currency)
|
|
90
91
|
: '--';
|
|
91
92
|
},
|
|
93
|
+
showSorterTooltip: false,
|
|
92
94
|
sorter: (a, b) => {
|
|
93
95
|
const aRate = getTextInputUnitRate(a.pricing) || 0;
|
|
94
96
|
const bRate = getTextInputUnitRate(b.pricing) || 0;
|
|
@@ -96,7 +98,7 @@ const ModelList = memo(() => {
|
|
|
96
98
|
},
|
|
97
99
|
title: (
|
|
98
100
|
<Tooltip title={t('models.providerInfo.inputTooltip')}>
|
|
99
|
-
{t('models.providerInfo.input')}
|
|
101
|
+
<span>{t('models.providerInfo.input')}</span>
|
|
100
102
|
</Tooltip>
|
|
101
103
|
),
|
|
102
104
|
width: 100,
|
|
@@ -110,6 +112,7 @@ const ModelList = memo(() => {
|
|
|
110
112
|
? '$' + formatPriceByCurrency(outputRate, record.pricing?.currency)
|
|
111
113
|
: '--';
|
|
112
114
|
},
|
|
115
|
+
showSorterTooltip: false,
|
|
113
116
|
sorter: (a, b) => {
|
|
114
117
|
const aRate = getTextOutputUnitRate(a.pricing) || 0;
|
|
115
118
|
const bRate = getTextOutputUnitRate(b.pricing) || 0;
|
|
@@ -117,7 +120,7 @@ const ModelList = memo(() => {
|
|
|
117
120
|
},
|
|
118
121
|
title: (
|
|
119
122
|
<Tooltip title={t('models.providerInfo.outputTooltip')}>
|
|
120
|
-
{t('models.providerInfo.output')}
|
|
123
|
+
<span>{t('models.providerInfo.output')}</span>
|
|
121
124
|
</Tooltip>
|
|
122
125
|
),
|
|
123
126
|
width: 100,
|
package/src/features/Conversation/Messages/components/Extras/Usage/UsageDetail/ModelCard.tsx
CHANGED
|
@@ -70,7 +70,7 @@ const ModelCard = memo<ModelCardProps>(({ pricing, id, provider, displayName })
|
|
|
70
70
|
{
|
|
71
71
|
label: (
|
|
72
72
|
<Tooltip title={t('messages.modelCard.creditTooltip')}>
|
|
73
|
-
{t('messages.modelCard.credit')}
|
|
73
|
+
<span>{t('messages.modelCard.credit')}</span>
|
|
74
74
|
</Tooltip>
|
|
75
75
|
),
|
|
76
76
|
value: 'credit',
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IEditor,
|
|
3
|
+
ReactCodePlugin,
|
|
4
|
+
ReactCodemirrorPlugin,
|
|
5
|
+
ReactHRPlugin,
|
|
6
|
+
ReactLinkPlugin,
|
|
7
|
+
ReactListPlugin,
|
|
8
|
+
ReactMathPlugin,
|
|
9
|
+
ReactTablePlugin,
|
|
10
|
+
} from '@lobehub/editor';
|
|
11
|
+
import { Editor } from '@lobehub/editor/react';
|
|
12
|
+
import { Flexbox } from '@lobehub/ui';
|
|
13
|
+
import { FC } from 'react';
|
|
14
|
+
|
|
15
|
+
import TypoBar from './Typobar';
|
|
16
|
+
|
|
17
|
+
interface EditorCanvasProps {
|
|
18
|
+
defaultValue?: string;
|
|
19
|
+
editor?: IEditor;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const EditorCanvas: FC<EditorCanvasProps> = ({ defaultValue, editor }) => {
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
<TypoBar editor={editor} />
|
|
26
|
+
<Flexbox
|
|
27
|
+
padding={16}
|
|
28
|
+
style={{ cursor: 'text', maxHeight: '80vh', minHeight: '50vh', overflowY: 'auto' }}
|
|
29
|
+
>
|
|
30
|
+
<Editor
|
|
31
|
+
autoFocus
|
|
32
|
+
content={''}
|
|
33
|
+
editor={editor}
|
|
34
|
+
onInit={(editor) => {
|
|
35
|
+
if (!editor || !defaultValue) return;
|
|
36
|
+
try {
|
|
37
|
+
editor?.setDocument('markdown', defaultValue);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error('setDocument error:', e);
|
|
40
|
+
}
|
|
41
|
+
}}
|
|
42
|
+
plugins={[
|
|
43
|
+
ReactListPlugin,
|
|
44
|
+
ReactCodePlugin,
|
|
45
|
+
ReactCodemirrorPlugin,
|
|
46
|
+
ReactHRPlugin,
|
|
47
|
+
ReactLinkPlugin,
|
|
48
|
+
ReactTablePlugin,
|
|
49
|
+
ReactMathPlugin,
|
|
50
|
+
]}
|
|
51
|
+
style={{
|
|
52
|
+
paddingBottom: 120,
|
|
53
|
+
}}
|
|
54
|
+
type={'text'}
|
|
55
|
+
variant={'chat'}
|
|
56
|
+
/>
|
|
57
|
+
</Flexbox>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default EditorCanvas;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { TextArea } from '@lobehub/ui';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
|
|
4
|
+
interface EditorCanvasProps {
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
onChange?: (value: string) => void;
|
|
7
|
+
value?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const EditorCanvas: FC<EditorCanvasProps> = ({ defaultValue, value, onChange }) => {
|
|
11
|
+
return (
|
|
12
|
+
<TextArea
|
|
13
|
+
defaultValue={defaultValue}
|
|
14
|
+
onChange={(e) => {
|
|
15
|
+
onChange?.(e.target.value);
|
|
16
|
+
}}
|
|
17
|
+
style={{
|
|
18
|
+
cursor: 'text',
|
|
19
|
+
maxHeight: '80vh',
|
|
20
|
+
minHeight: '50vh',
|
|
21
|
+
overflowY: 'auto',
|
|
22
|
+
padding: 16,
|
|
23
|
+
}}
|
|
24
|
+
value={value}
|
|
25
|
+
variant={'borderless'}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default EditorCanvas;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { HotkeyEnum, type IEditor, getHotkeyById } from '@lobehub/editor';
|
|
2
|
+
import { useEditorState } from '@lobehub/editor/react';
|
|
3
|
+
import {
|
|
4
|
+
ChatInputActionBar,
|
|
5
|
+
ChatInputActions,
|
|
6
|
+
type ChatInputActionsProps,
|
|
7
|
+
} from '@lobehub/editor/react';
|
|
8
|
+
import { cssVar } from 'antd-style';
|
|
9
|
+
import {
|
|
10
|
+
BoldIcon,
|
|
11
|
+
CodeXmlIcon,
|
|
12
|
+
ItalicIcon,
|
|
13
|
+
ListIcon,
|
|
14
|
+
ListOrderedIcon,
|
|
15
|
+
ListTodoIcon,
|
|
16
|
+
MessageSquareQuote,
|
|
17
|
+
SigmaIcon,
|
|
18
|
+
SquareDashedBottomCodeIcon,
|
|
19
|
+
StrikethroughIcon,
|
|
20
|
+
UnderlineIcon,
|
|
21
|
+
} from 'lucide-react';
|
|
22
|
+
import { memo, useMemo } from 'react';
|
|
23
|
+
import { useTranslation } from 'react-i18next';
|
|
24
|
+
|
|
25
|
+
const TypoBar = memo<{ editor?: IEditor }>(({ editor }) => {
|
|
26
|
+
const { t } = useTranslation('editor');
|
|
27
|
+
const editorState = useEditorState(editor);
|
|
28
|
+
|
|
29
|
+
const items: ChatInputActionsProps['items'] = useMemo(
|
|
30
|
+
() =>
|
|
31
|
+
[
|
|
32
|
+
{
|
|
33
|
+
active: editorState.isBold,
|
|
34
|
+
icon: BoldIcon,
|
|
35
|
+
key: 'bold',
|
|
36
|
+
label: t('typobar.bold'),
|
|
37
|
+
onClick: editorState.bold,
|
|
38
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Bold).keys },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
active: editorState.isItalic,
|
|
42
|
+
icon: ItalicIcon,
|
|
43
|
+
key: 'italic',
|
|
44
|
+
label: t('typobar.italic'),
|
|
45
|
+
onClick: editorState.italic,
|
|
46
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Italic).keys },
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
active: editorState.isUnderline,
|
|
50
|
+
icon: UnderlineIcon,
|
|
51
|
+
key: 'underline',
|
|
52
|
+
label: t('typobar.underline'),
|
|
53
|
+
onClick: editorState.underline,
|
|
54
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Underline).keys },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
active: editorState.isStrikethrough,
|
|
58
|
+
icon: StrikethroughIcon,
|
|
59
|
+
key: 'strikethrough',
|
|
60
|
+
label: t('typobar.strikethrough'),
|
|
61
|
+
onClick: editorState.strikethrough,
|
|
62
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Strikethrough).keys },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'divider',
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
icon: ListIcon,
|
|
70
|
+
key: 'bulletList',
|
|
71
|
+
label: t('typobar.bulletList'),
|
|
72
|
+
onClick: editorState.bulletList,
|
|
73
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.BulletList).keys },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
icon: ListOrderedIcon,
|
|
77
|
+
key: 'numberlist',
|
|
78
|
+
label: t('typobar.numberList'),
|
|
79
|
+
onClick: editorState.numberList,
|
|
80
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.NumberList).keys },
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
icon: ListTodoIcon,
|
|
84
|
+
key: 'tasklist',
|
|
85
|
+
label: t('typobar.taskList'),
|
|
86
|
+
onClick: editorState.checkList,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'divider',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
active: editorState.isBlockquote,
|
|
93
|
+
icon: MessageSquareQuote,
|
|
94
|
+
key: 'blockquote',
|
|
95
|
+
label: t('typobar.blockquote'),
|
|
96
|
+
onClick: editorState.blockquote,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: 'divider',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
icon: SigmaIcon,
|
|
103
|
+
key: 'math',
|
|
104
|
+
label: t('typobar.tex'),
|
|
105
|
+
onClick: editorState.insertMath,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
active: editorState.isCode,
|
|
109
|
+
icon: CodeXmlIcon,
|
|
110
|
+
key: 'code',
|
|
111
|
+
label: t('typobar.code'),
|
|
112
|
+
onClick: editorState.code,
|
|
113
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.CodeInline).keys },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
icon: SquareDashedBottomCodeIcon,
|
|
117
|
+
key: 'codeblock',
|
|
118
|
+
label: t('typobar.codeblock'),
|
|
119
|
+
onClick: editorState.codeblock,
|
|
120
|
+
},
|
|
121
|
+
].filter(Boolean) as ChatInputActionsProps['items'],
|
|
122
|
+
[editorState],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<ChatInputActionBar
|
|
127
|
+
left={<ChatInputActions items={items} />}
|
|
128
|
+
style={{
|
|
129
|
+
background: cssVar.colorFillQuaternary,
|
|
130
|
+
borderTopLeftRadius: 8,
|
|
131
|
+
borderTopRightRadius: 8,
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
TypoBar.displayName = 'TypoBar';
|
|
138
|
+
|
|
139
|
+
export default TypoBar;
|
|
@@ -3,7 +3,11 @@ import { Modal, ModalProps, createRawModal } from '@lobehub/ui';
|
|
|
3
3
|
import { memo, useState } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { useUserStore } from '@/store/user';
|
|
7
|
+
import { labPreferSelectors } from '@/store/user/selectors';
|
|
8
|
+
|
|
9
|
+
import EditorCanvas from './EditorCanvas';
|
|
10
|
+
import TextareCanvas from './TextArea';
|
|
7
11
|
|
|
8
12
|
interface EditorModalProps extends ModalProps {
|
|
9
13
|
onConfirm?: (value: string) => Promise<void>;
|
|
@@ -13,7 +17,8 @@ interface EditorModalProps extends ModalProps {
|
|
|
13
17
|
export const EditorModal = memo<EditorModalProps>(({ value, onConfirm, ...rest }) => {
|
|
14
18
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
|
15
19
|
const { t } = useTranslation('common');
|
|
16
|
-
|
|
20
|
+
const [v, setV] = useState(value);
|
|
21
|
+
const enableRichRender = useUserStore(labPreferSelectors.enableInputMarkdown);
|
|
17
22
|
const editor = useEditor();
|
|
18
23
|
|
|
19
24
|
return (
|
|
@@ -25,12 +30,13 @@ export const EditorModal = memo<EditorModalProps>(({ value, onConfirm, ...rest }
|
|
|
25
30
|
okText={t('ok')}
|
|
26
31
|
onOk={async () => {
|
|
27
32
|
setConfirmLoading(true);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
let finalValue;
|
|
34
|
+
if (enableRichRender) {
|
|
35
|
+
finalValue = editor?.getDocument('markdown') as unknown as string;
|
|
36
|
+
} else {
|
|
37
|
+
finalValue = v;
|
|
33
38
|
}
|
|
39
|
+
await onConfirm?.(finalValue || '');
|
|
34
40
|
setConfirmLoading(false);
|
|
35
41
|
}}
|
|
36
42
|
styles={{
|
|
@@ -43,7 +49,11 @@ export const EditorModal = memo<EditorModalProps>(({ value, onConfirm, ...rest }
|
|
|
43
49
|
width={'min(90vw, 920px)'}
|
|
44
50
|
{...rest}
|
|
45
51
|
>
|
|
46
|
-
|
|
52
|
+
{enableRichRender ? (
|
|
53
|
+
<EditorCanvas defaultValue={value} editor={editor} />
|
|
54
|
+
) : (
|
|
55
|
+
<TextareCanvas defaultValue={value} onChange={(v) => setV(v)} value={v} />
|
|
56
|
+
)}
|
|
47
57
|
</Modal>
|
|
48
58
|
);
|
|
49
59
|
});
|
|
@@ -84,8 +84,7 @@ export const useMenu = (): { menuItems: any[] } => {
|
|
|
84
84
|
key: 'full-width',
|
|
85
85
|
label: t('viewMode.fullWidth', { ns: 'chat' }),
|
|
86
86
|
onCheckedChange: toggleWideScreen,
|
|
87
|
-
|
|
88
|
-
type: 'checkbox' as const,
|
|
87
|
+
type: 'switch' as const,
|
|
89
88
|
},
|
|
90
89
|
{
|
|
91
90
|
type: 'divider' as const,
|