Skip to content

Commit 8145015

Browse files
✨ feat: Add AiMessageDisplay component for streaming and static messages
1 parent cb8c92d commit 8145015

File tree

9 files changed

+120
-39
lines changed

9 files changed

+120
-39
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"ink-testing-library": "^3.0.0",
7575
"lerna": "^8.2.2",
7676
"lint-staged": "^15.5.2",
77+
"lodash-es": "^4.17.21",
7778
"multi-semantic-release": "^3.0.2",
7879
"openai": "^4.103.0",
7980
"pkgroll": "^2.12.2",

packages/lobe-commit/src/commands/Ai/index.tsx

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,23 @@
1-
import { Spinner } from '@inkjs/ui';
21
import { Panel, useTheme } from '@lobehub/cli-ui';
32
import { Text } from 'ink';
43
import { memo, useEffect, useState } from 'react';
54

5+
import AiMessageDisplay from '@/components/AiMessageDisplay';
66
import { useCommits } from '@/hooks/useCommits';
77
import { selectors } from '@/store';
88

9-
const StreamingCursor = memo(() => {
10-
const [visible, setVisible] = useState(true);
11-
const theme = useTheme();
12-
13-
useEffect(() => {
14-
const interval = setInterval(() => {
15-
setVisible((prev) => !prev);
16-
}, 500);
17-
18-
return () => clearInterval(interval);
19-
}, []);
20-
21-
return <Text color={theme.colorTextDescription}>{visible ? '▊' : ' '}</Text>;
22-
});
23-
249
const Ai = memo(() => {
2510
const [message, setMessage] = useState<string>('');
2611
const theme = useTheme();
2712
const commitConfig = selectors.getCommitConfig();
2813

29-
const { summary, start, loadingInfo, loading } = useCommits({ setMessage });
14+
const {
15+
summary,
16+
start,
17+
loadingInfo,
18+
loading,
19+
message: streamingMessage,
20+
} = useCommits({ setMessage });
3021

3122
useEffect(() => {
3223
start();
@@ -45,16 +36,12 @@ const Ai = memo(() => {
4536
reverse
4637
title={`🤯 AI Commit Generator ${commitConfig.stream ? '(Streaming)' : ''}`}
4738
>
48-
{!loading && message ? (
49-
<Text>{message}</Text>
50-
) : loading && message && commitConfig.stream ? (
51-
<Text>
52-
{message}
53-
<StreamingCursor />
54-
</Text>
55-
) : (
56-
<Spinner label={loadingInfo} />
57-
)}
39+
<AiMessageDisplay
40+
loading={loading}
41+
loadingInfo={loadingInfo}
42+
message={message}
43+
streamingMessage={streamingMessage}
44+
/>
5845
</Panel>
5946
);
6047
});

packages/lobe-commit/src/commands/Commit/AiCommit.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Spinner } from '@inkjs/ui';
21
import { Panel, SelectInput, type SelectInputItem, SplitView, useTheme } from '@lobehub/cli-ui';
32
import { Text, useInput } from 'ink';
43
import { memo, useCallback, useEffect, useMemo } from 'react';
54

5+
import AiMessageDisplay from '@/components/AiMessageDisplay';
66
import { useCommits } from '@/hooks/useCommits';
7+
import { selectors } from '@/store';
78
import { useCommitStore } from '@/store/commitStore';
89

910
const AiCommit = memo(() => {
@@ -14,8 +15,16 @@ const AiCommit = memo(() => {
1415
}));
1516
useInput(useCallback((_, key) => key.tab && setStep('type'), []));
1617
const theme = useTheme();
18+
const commitConfig = selectors.getCommitConfig();
1719

18-
const { summary, start, loadingInfo, loading, restart } = useCommits({ setMessage });
20+
const {
21+
summary,
22+
start,
23+
loadingInfo,
24+
loading,
25+
restart,
26+
message: streamingMessage,
27+
} = useCommits({ setMessage });
1928

2029
const handleSelect = useCallback(
2130
(item: any) => {
@@ -62,7 +71,7 @@ const AiCommit = memo(() => {
6271
return (
6372
<Panel
6473
footer={!loading && message && <SelectInput items={items} onSelect={handleSelect} />}
65-
title={`🤯 AI Commit Generator`}
74+
title={`🤯 AI Commit Generator ${commitConfig.stream ? '(Streaming)' : ''}`}
6675
>
6776
{summary && (
6877
<SplitView direction={'bottom'}>
@@ -72,7 +81,12 @@ const AiCommit = memo(() => {
7281
</Text>
7382
</SplitView>
7483
)}
75-
{!loading && message ? <Text>{message}</Text> : <Spinner label={loadingInfo} />}
84+
<AiMessageDisplay
85+
loading={loading}
86+
loadingInfo={loadingInfo}
87+
message={message}
88+
streamingMessage={streamingMessage}
89+
/>
7690
</Panel>
7791
);
7892
});

packages/lobe-commit/src/commands/Config/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ const Config = memo(() => {
135135
desc: 'Enable streaming output for AI responses, default as enabled',
136136
key: 'stream',
137137
label: 'Streaming output',
138+
showValue: false,
138139
value: store.stream,
139140
},
140141
{
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Spinner } from '@inkjs/ui';
2+
import { Text } from 'ink';
3+
import { memo } from 'react';
4+
5+
import { selectors } from '@/store';
6+
7+
import StreamingCursor from './StreamingCursor';
8+
9+
interface AiMessageDisplayProps {
10+
loading: boolean;
11+
loadingInfo: string;
12+
message?: string;
13+
streamingMessage?: string;
14+
}
15+
16+
const AiMessageDisplay = memo<AiMessageDisplayProps>(
17+
({ loading, loadingInfo, message, streamingMessage }) => {
18+
const commitConfig = selectors.getCommitConfig();
19+
20+
if (commitConfig.stream) {
21+
if (loading) {
22+
if (streamingMessage) {
23+
return (
24+
<Text>
25+
{streamingMessage}
26+
<StreamingCursor />
27+
</Text>
28+
);
29+
} else {
30+
return (
31+
<Text>
32+
<StreamingCursor />
33+
</Text>
34+
);
35+
}
36+
} else {
37+
return <Text>{message}</Text>;
38+
}
39+
} else {
40+
if (!loading && message) {
41+
return <Text>{message}</Text>;
42+
} else {
43+
return <Spinner label={loadingInfo} />;
44+
}
45+
}
46+
},
47+
);
48+
49+
export default AiMessageDisplay;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useTheme } from '@lobehub/cli-ui';
2+
import { Text } from 'ink';
3+
import { memo, useEffect, useState } from 'react';
4+
5+
const StreamingCursor = memo(() => {
6+
const [visible, setVisible] = useState(true);
7+
const theme = useTheme();
8+
9+
useEffect(() => {
10+
const interval = setInterval(() => {
11+
setVisible((prev) => !prev);
12+
}, 500);
13+
14+
return () => clearInterval(interval);
15+
}, []);
16+
17+
return <Text color={theme.colorTextDescription}>{visible ? '▊' : ' '}</Text>;
18+
});
19+
20+
export default StreamingCursor;

packages/lobe-commit/src/core/Commits.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export class Commits {
7171
messages: any[],
7272
onStreamMessage: (message: string) => void,
7373
): Promise<string> {
74+
// 开始流式输出,先调用一次回调来切换UI状态
75+
onStreamMessage('');
76+
7477
const stream = await this.client.chat.completions.create({
7578
messages: messages as OpenAI.Chat.Completions.ChatCompletionMessageParam[],
7679
model: this.config.modelName,
@@ -84,11 +87,11 @@ export class Commits {
8487
const content = chunk.choices[0]?.delta?.content || '';
8588
if (content) {
8689
fullMessage += content;
87-
onStreamMessage(
88-
addEmojiToMessage(
89-
fullMessage.replace(/\((.*?)\):/, (match, p1) => match && `(${p1.toLowerCase()}):`),
90-
),
90+
// 实时更新显示的消息
91+
const processedMessage = addEmojiToMessage(
92+
fullMessage.replace(/\((.*?)\):/, (match, p1) => match && `(${p1.toLowerCase()}):`),
9193
);
94+
onStreamMessage(processedMessage);
9295
}
9396
}
9497

packages/lobe-commit/src/hooks/useCommits.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,24 @@ export const useCommits = ({ setMessage, onSuccess, onError, ...config }: Commit
2222
(message: string) => {
2323
setStreamingMessage(message);
2424
setMessage?.(message);
25+
setIsGlobalLoading(false);
2526
},
2627
[setMessage],
2728
);
2829

2930
const { data, isLoading } = useSWR(
3031
shouldFetch ? key : null,
31-
async () =>
32-
commits.current.genCommit({
32+
async () => {
33+
if (commitConfig.stream) {
34+
setIsGlobalLoading(false);
35+
}
36+
return commits.current.genCommit({
3337
cacheSummary: summary,
3438
onStreamMessage: commitConfig.stream ? handleStreamMessage : undefined,
3539
setLoadingInfo,
3640
setSummary,
37-
}),
41+
});
42+
},
3843
{
3944
onError: (err, ...rest) => {
4045
onError?.(err, ...rest);

packages/lobe-commit/src/store/selectors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ const getDiffChunkSize = () => {
2020
const diffChunkSize = getConfig('diffChunkSize');
2121
return defaultDiffChunkSize > diffChunkSize ? defaultDiffChunkSize : diffChunkSize;
2222
};
23-
const getCommitConfig = () => ({
23+
const getCommitConfig = (): Config => ({
2424
...(config.store as Config),
2525
apiBaseUrl: getOpenAIProxyUrl(),
2626
diffChunkSize: getDiffChunkSize(),
2727
githubToken: getGithubToken(),
2828
openaiToken: getOpenAIApiKey(),
29+
stream: getConfig('stream'),
2930
});
3031

3132
export default {

0 commit comments

Comments
 (0)