Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
b9dc2cc0
Unverified
提交
b9dc2cc0
authored
5月 02, 2025
作者:
Will Chen
提交者:
GitHub
5月 02, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Undo button (#76)
上级
a6eaaa6a
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
135 行增加
和
55 行删除
+135
-55
MessagesList.tsx
src/components/chat/MessagesList.tsx
+125
-50
app_handlers.ts
src/ipc/handlers/app_handlers.ts
+0
-1
ipc_client.ts
src/ipc/ipc_client.ts
+10
-4
没有找到文件。
src/components/chat/MessagesList.tsx
浏览文件 @
b9dc2cc0
import
type
React
from
"react"
;
import
type
{
Message
}
from
"@/ipc/ipc_types"
;
import
{
forwardRef
}
from
"react"
;
import
{
forwardRef
,
useState
}
from
"react"
;
import
ChatMessage
from
"./ChatMessage"
;
import
{
SetupBanner
}
from
"../SetupBanner"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
useStreamChat
}
from
"@/hooks/useStreamChat"
;
import
{
selectedChatIdAtom
}
from
"@/atoms/chatAtoms"
;
import
{
useAtom
,
useAtomValue
}
from
"jotai"
;
import
{
RefreshCw
}
from
"lucide-react"
;
import
{
useAtom
,
useAtomValue
,
useSetAtom
}
from
"jotai"
;
import
{
Loader2
,
RefreshCw
,
Undo
}
from
"lucide-react"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
useVersions
}
from
"@/hooks/useVersions"
;
import
{
selectedAppIdAtom
}
from
"@/atoms/appAtoms"
;
import
{
showError
}
from
"@/lib/toast"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
chatMessagesAtom
}
from
"@/atoms/chatAtoms"
;
interface
MessagesListProps
{
messages
:
Message
[];
...
...
@@ -25,6 +27,10 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
const
{
streamMessage
,
isStreaming
,
error
,
setError
}
=
useStreamChat
();
const
{
isAnyProviderSetup
}
=
useSettings
();
const
selectedChatId
=
useAtomValue
(
selectedChatIdAtom
);
const
setMessages
=
useSetAtom
(
chatMessagesAtom
);
const
[
isUndoLoading
,
setIsUndoLoading
]
=
useState
(
false
);
const
[
isRetryLoading
,
setIsRetryLoading
]
=
useState
(
false
);
return
(
<
div
className=
"flex-1 overflow-y-auto p-4"
ref=
{
ref
}
>
{
messages
.
length
>
0
?
(
...
...
@@ -40,67 +46,136 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
</
div
>
)
}
{
messages
.
length
>
0
&&
!
isStreaming
&&
(
<
div
className=
"flex max-w-3xl mx-auto"
>
<
div
className=
"flex max-w-3xl mx-auto gap-2"
>
{
messages
[
messages
.
length
-
1
].
role
===
"assistant"
&&
(
<
Button
variant=
"outline"
size=
"sm"
disabled=
{
isUndoLoading
}
onClick=
{
async
()
=>
{
if
(
!
selectedChatId
||
!
appId
)
{
console
.
error
(
"No chat selected or app ID not available"
);
return
;
}
if
(
versions
.
length
<
2
)
{
showError
(
"Cannot undo; no previous version"
);
return
;
}
setIsUndoLoading
(
true
);
try
{
const
previousAssistantMessage
=
messages
[
messages
.
length
-
3
];
if
(
previousAssistantMessage
?.
role
===
"assistant"
&&
previousAssistantMessage
?.
commitHash
)
{
console
.
debug
(
"Reverting to previous assistant version"
);
await
revertVersion
({
versionId
:
previousAssistantMessage
.
commitHash
,
});
}
else
{
// Revert to the previous version
await
revertVersion
({
versionId
:
versions
[
1
].
oid
,
});
}
if
(
selectedChatId
)
{
const
chat
=
await
IpcClient
.
getInstance
().
getChat
(
selectedChatId
);
setMessages
(
chat
.
messages
);
}
}
catch
(
error
)
{
console
.
error
(
"Error during undo operation:"
,
error
);
showError
(
"Failed to undo changes"
);
}
finally
{
setIsUndoLoading
(
false
);
}
}
}
>
{
isUndoLoading
?
(
<
Loader2
size=
{
16
}
className=
"mr-1 animate-spin"
/>
)
:
(
<
Undo
size=
{
16
}
/>
)
}
Undo
</
Button
>
)
}
<
Button
variant=
"
ghost
"
variant=
"
outline
"
size=
"sm"
disabled=
{
isRetryLoading
}
onClick=
{
async
()
=>
{
if
(
!
selectedChatId
)
{
console
.
error
(
"No chat selected"
);
return
;
}
// The last message is usually an assistant, but it might not be.
const
lastVersion
=
versions
[
0
];
const
lastMessage
=
messages
[
messages
.
length
-
1
];
let
reverted
=
false
;
if
(
lastVersion
.
oid
===
lastMessage
.
commitHash
&&
lastMessage
.
role
===
"assistant"
)
{
if
(
versions
.
length
<
2
)
{
showError
(
"Cannot retry message; no previous version"
);
return
;
}
const
previousAssistantMessage
=
messages
[
messages
.
length
-
3
];
setIsRetryLoading
(
true
);
try
{
// The last message is usually an assistant, but it might not be.
const
lastVersion
=
versions
[
0
];
const
lastMessage
=
messages
[
messages
.
length
-
1
];
let
reverted
=
false
;
if
(
previousAssistantMessage
?.
role
===
"assistant"
&&
previousAssistantMessage
?.
commitHash
lastVersion
.
oid
===
lastMessage
.
commitHash
&&
lastMessage
.
role
===
"assistant"
)
{
console
.
debug
(
"Reverting to previous assistant version"
);
await
revertVersion
({
versionId
:
previousAssistantMessage
.
commitHash
,
});
reverted
=
true
;
}
else
{
console
.
debug
(
"Reverting to previous version"
);
await
revertVersion
({
versionId
:
versions
[
1
].
oid
,
});
if
(
versions
.
length
<
2
)
{
showError
(
"Cannot retry message; no previous version"
);
return
;
}
const
previousAssistantMessage
=
messages
[
messages
.
length
-
3
];
if
(
previousAssistantMessage
?.
role
===
"assistant"
&&
previousAssistantMessage
?.
commitHash
)
{
console
.
debug
(
"Reverting to previous assistant version"
);
await
revertVersion
({
versionId
:
previousAssistantMessage
.
commitHash
,
});
reverted
=
true
;
}
else
{
console
.
debug
(
"Reverting to previous version"
);
await
revertVersion
({
versionId
:
versions
[
1
].
oid
,
});
}
}
}
// Find the last user message
const
lastUserMessage
=
[...
messages
]
.
reverse
()
.
find
((
message
)
=>
message
.
role
===
"user"
);
if
(
!
lastUserMessage
)
{
console
.
error
(
"No user message found"
);
return
;
}
// If we reverted, we don't need to mark "redo" because
// the old message has already been deleted.
const
redo
=
!
reverted
;
console
.
debug
(
"Streaming message with redo"
,
redo
);
// Find the last user message
const
lastUserMessage
=
[...
messages
]
.
reverse
()
.
find
((
message
)
=>
message
.
role
===
"user"
);
if
(
!
lastUserMessage
)
{
console
.
error
(
"No user message found"
);
return
;
}
// If we reverted, we don't need to mark "redo" because
// the old message has already been deleted.
const
redo
=
!
reverted
;
console
.
debug
(
"Streaming message with redo"
,
redo
);
streamMessage
({
prompt
:
lastUserMessage
.
content
,
chatId
:
selectedChatId
,
redo
,
});
streamMessage
({
prompt
:
lastUserMessage
.
content
,
chatId
:
selectedChatId
,
redo
,
});
}
catch
(
error
)
{
console
.
error
(
"Error during retry operation:"
,
error
);
showError
(
"Failed to retry message"
);
}
finally
{
setIsRetryLoading
(
false
);
}
}
}
>
<
RefreshCw
size=
{
16
}
/>
{
isRetryLoading
?
(
<
Loader2
size=
{
16
}
className=
"mr-1 animate-spin"
/>
)
:
(
<
RefreshCw
size=
{
16
}
/>
)
}
Retry
</
Button
>
</
div
>
...
...
src/ipc/handlers/app_handlers.ts
浏览文件 @
b9dc2cc0
...
...
@@ -494,7 +494,6 @@ export function registerAppHandlers() {
_
,
{
appId
,
previousVersionId
}:
{
appId
:
number
;
previousVersionId
:
string
}
)
=>
{
logger
.
log
(
`Reverting to version
${
previousVersionId
}
for app
${
appId
}
`
);
return
withLock
(
appId
,
async
()
=>
{
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
...
...
src/ipc/ipc_client.ts
浏览文件 @
b9dc2cc0
...
...
@@ -787,25 +787,31 @@ export class IpcClient {
public
async
listLocalOllamaModels
():
Promise
<
LocalModel
[]
>
{
try
{
const
response
=
await
this
.
ipcRenderer
.
invoke
(
"local-models:list-ollama"
);
const
response
=
await
this
.
ipcRenderer
.
invoke
(
"local-models:list-ollama"
);
return
response
?.
models
||
[];
}
catch
(
error
)
{
if
(
error
instanceof
Error
)
{
throw
new
Error
(
`Failed to fetch Ollama models:
${
error
.
message
}
`
);
}
throw
new
Error
(
'Failed to fetch Ollama models: Unknown error occurred'
);
throw
new
Error
(
"Failed to fetch Ollama models: Unknown error occurred"
);
}
}
public
async
listLocalLMStudioModels
():
Promise
<
LocalModel
[]
>
{
try
{
const
response
=
await
this
.
ipcRenderer
.
invoke
(
"local-models:list-lmstudio"
);
const
response
=
await
this
.
ipcRenderer
.
invoke
(
"local-models:list-lmstudio"
);
return
response
?.
models
||
[];
}
catch
(
error
)
{
if
(
error
instanceof
Error
)
{
throw
new
Error
(
`Failed to fetch LM Studio models:
${
error
.
message
}
`
);
}
throw
new
Error
(
'Failed to fetch LM Studio models: Unknown error occurred'
);
throw
new
Error
(
"Failed to fetch LM Studio models: Unknown error occurred"
);
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论