Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
b044acb6
Unverified
提交
b044acb6
authored
6月 11, 2025
作者:
Will Chen
提交者:
GitHub
6月 11, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Polish chat input UX (#388)
上级
c1aa6803
显示空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
88 行增加
和
44 行删除
+88
-44
test_helper.ts
e2e-tests/helpers/test_helper.ts
+1
-4
ChatInputControls.tsx
src/components/ChatInputControls.tsx
+9
-2
ContextFilesPicker.tsx
src/components/ContextFilesPicker.tsx
+14
-4
ModelPicker.tsx
src/components/ModelPicker.tsx
+12
-3
ProModeSelector.tsx
src/components/ProModeSelector.tsx
+1
-1
ChatInput.tsx
src/components/chat/ChatInput.tsx
+51
-30
没有找到文件。
e2e-tests/helpers/test_helper.ts
浏览文件 @
b044acb6
...
...
@@ -132,10 +132,7 @@ export class PageObject {
}
async
openContextFilesPicker
()
{
const
contextButton
=
this
.
page
.
getByRole
(
"button"
,
{
name
:
"Context"
,
exact
:
true
,
});
const
contextButton
=
this
.
page
.
getByTestId
(
"codebase-context-button"
);
await
contextButton
.
click
();
return
new
ContextFilesPickerDialog
(
this
.
page
,
async
()
=>
{
await
contextButton
.
click
();
...
...
src/components/ChatInputControls.tsx
浏览文件 @
b044acb6
...
...
@@ -8,10 +8,17 @@ export function ChatInputControls({
showContextFilesPicker
?:
boolean
;
})
{
return
(
<
div
className=
"
pb-2 flex gap-2
"
>
<
div
className=
"
flex
"
>
<
ModelPicker
/>
{
showContextFilesPicker
&&
<
ContextFilesPicker
/>
}
<
div
className=
"w-2"
></
div
>
<
ProModeSelector
/>
<
div
className=
"w-1"
></
div
>
{
showContextFilesPicker
&&
(
<>
<
ContextFilesPicker
/>
<
div
className=
"w-0.5"
></
div
>
</>
)
}
</
div
>
);
}
src/components/ContextFilesPicker.tsx
浏览文件 @
b044acb6
...
...
@@ -6,7 +6,7 @@ import {
PopoverTrigger
,
}
from
"@/components/ui/popover"
;
import
{
FileCode
,
InfoIcon
,
Trash2
}
from
"lucide-react"
;
import
{
InfoIcon
,
Settings2
,
Trash2
}
from
"lucide-react"
;
import
{
useState
}
from
"react"
;
import
{
Tooltip
,
...
...
@@ -84,12 +84,22 @@ export function ContextFilesPicker() {
return
(
<
Popover
open=
{
isOpen
}
onOpenChange=
{
setIsOpen
}
>
<
Tooltip
>
<
TooltipTrigger
asChild
>
<
PopoverTrigger
asChild
>
<
Button
variant=
"ghost"
className=
"gap-2"
>
<
FileCode
className=
"size-4"
/>
<
span
>
Context
</
span
>
<
Button
variant=
"ghost"
className=
"has-[>svg]:px-2"
size=
"sm"
data
-
testid=
"codebase-context-button"
>
<
Settings2
className=
"size-4"
/>
</
Button
>
</
PopoverTrigger
>
</
TooltipTrigger
>
<
TooltipContent
>
Codebase Context
</
TooltipContent
>
</
Tooltip
>
<
PopoverContent
className=
"w-96"
align=
"start"
>
<
div
className=
"relative space-y-4"
>
<
div
>
...
...
src/components/ModelPicker.tsx
浏览文件 @
b044acb6
...
...
@@ -123,13 +123,15 @@ export function ModelPicker() {
return
(
<
DropdownMenu
open=
{
open
}
onOpenChange=
{
setOpen
}
>
<
Tooltip
>
<
TooltipTrigger
asChild
>
<
DropdownMenuTrigger
asChild
>
<
Button
variant=
"outline"
size=
"sm"
className=
"flex items-center gap-2 h-8
"
className=
"flex items-center gap-2 h-8 max-w-[160px] px-2
"
>
<
span
>
<
span
className=
"truncate"
>
{
modelDisplayName
===
"Auto"
&&
(
<>
<
span
className=
"text-xs text-muted-foreground"
>
...
...
@@ -141,7 +143,14 @@ export function ModelPicker() {
</
span
>
</
Button
>
</
DropdownMenuTrigger
>
<
DropdownMenuContent
className=
"w-64"
align=
"start"
>
</
TooltipTrigger
>
<
TooltipContent
>
{
modelDisplayName
}
</
TooltipContent
>
</
Tooltip
>
<
DropdownMenuContent
className=
"w-64"
align=
"start"
onCloseAutoFocus=
{
(
e
)
=>
e
.
preventDefault
()
}
>
<
DropdownMenuLabel
>
Cloud Models
</
DropdownMenuLabel
>
<
DropdownMenuSeparator
/>
...
...
src/components/ProModeSelector.tsx
浏览文件 @
b044acb6
...
...
@@ -40,7 +40,7 @@ export function ProModeSelector() {
<
Button
variant=
"outline"
size=
"sm"
className=
"
flex items-center gap-2 h-8 border-primary/50 bg-primary/10 hover:bg-primary/2
0 font-medium shadow-sm shadow-primary/10 transition-all hover:shadow-md hover:shadow-primary/15"
className=
"
has-[>svg]:px-2 flex items-center gap-1.5 h-8 border-primary/50 hover:bg-primary/1
0 font-medium shadow-sm shadow-primary/10 transition-all hover:shadow-md hover:shadow-primary/15"
>
<
Sparkles
className=
"h-4 w-4 text-primary"
/>
<
span
className=
"text-primary font-medium"
>
Pro
</
span
>
...
...
src/components/chat/ChatInput.tsx
浏览文件 @
b044acb6
import
{
SendIcon
,
StopCircleIcon
,
X
,
ChevronDown
,
...
...
@@ -15,8 +14,9 @@ import {
Database
,
ChevronsUpDown
,
ChevronsDownUp
,
BarChart2
,
Paperclip
,
ChartColumnIncreasing
,
SendHorizontalIcon
,
}
from
"lucide-react"
;
import
type
React
from
"react"
;
import
{
useCallback
,
useEffect
,
useRef
,
useState
}
from
"react"
;
...
...
@@ -313,28 +313,10 @@ export function ChatInput({ chatId }: { chatId?: number }) {
disabled=
{
isStreaming
}
/>
{
/* File attachment button */
}
<
button
onClick=
{
handleAttachmentClick
}
className=
"px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
disabled=
{
isStreaming
}
title=
"Attach files"
>
<
Paperclip
size=
{
20
}
/>
</
button
>
<
input
type=
"file"
ref=
{
fileInputRef
}
onChange=
{
handleFileChange
}
className=
"hidden"
multiple
accept=
".jpg,.jpeg,.png,.gif,.webp,.txt,.md,.js,.ts,.html,.css,.json,.csv"
/>
{
isStreaming
?
(
<
button
onClick=
{
handleCancel
}
className=
"px-2 py-2 mt-1 mr-
2
hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg"
className=
"px-2 py-2 mt-1 mr-
1
hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg"
title=
"Cancel generation"
>
<
StopCircleIcon
size=
{
20
}
/>
...
...
@@ -343,23 +325,62 @@ export function ChatInput({ chatId }: { chatId?: number }) {
<
button
onClick=
{
handleSubmit
}
disabled=
{
!
inputValue
.
trim
()
&&
attachments
.
length
===
0
}
className=
"px-2 py-2 mt-1 mr-
2
hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
className=
"px-2 py-2 mt-1 mr-
1
hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
title=
"Send message"
>
<
SendIcon
size=
{
20
}
/>
<
Send
Horizontal
Icon
size=
{
20
}
/>
</
button
>
)
}
</
div
>
<
div
className=
"pl-2 pr-1 flex items-center justify-between"
>
<
div
className=
"pl-2 pr-1 flex items-center justify-between pb-2"
>
<
div
className=
"flex items-center"
>
<
ChatInputControls
showContextFilesPicker=
{
true
}
/>
<
button
{
/* File attachment button */
}
<
TooltipProvider
>
<
Tooltip
>
<
TooltipTrigger
asChild
>
<
Button
variant=
"ghost"
onClick=
{
handleAttachmentClick
}
disabled=
{
isStreaming
}
title=
"Attach files"
size=
"sm"
>
<
Paperclip
size=
{
20
}
/>
</
Button
>
</
TooltipTrigger
>
<
TooltipContent
>
Attach files
</
TooltipContent
>
</
Tooltip
>
</
TooltipProvider
>
<
input
type=
"file"
ref=
{
fileInputRef
}
onChange=
{
handleFileChange
}
className=
"hidden"
multiple
accept=
".jpg,.jpeg,.png,.gif,.webp,.txt,.md,.js,.ts,.html,.css,.json,.csv"
/>
</
div
>
<
TooltipProvider
>
<
Tooltip
>
<
TooltipTrigger
asChild
>
<
Button
onClick=
{
()
=>
setShowTokenBar
(
!
showTokenBar
)
}
className=
"flex items-center px-2 py-1 text-xs text-muted-foreground hover:bg-muted rounded"
title=
{
showTokenBar
?
"Hide token usage"
:
"Show token usage"
}
variant=
"ghost"
className=
{
`has-[>svg]:px-2 ${
showTokenBar ? "text-purple-500 bg-purple-100" : ""
}`
}
size=
"sm"
>
<
BarChart2
size=
{
14
}
className=
"mr-1"
/>
{
showTokenBar
?
"Hide tokens"
:
"Tokens"
}
</
button
>
<
ChartColumnIncreasing
size=
{
14
}
/>
</
Button
>
</
TooltipTrigger
>
<
TooltipContent
>
{
showTokenBar
?
"Hide token usage"
:
"Show token usage"
}
</
TooltipContent
>
</
Tooltip
>
</
TooltipProvider
>
</
div
>
{
/* TokenBar is only displayed when showTokenBar is true */
}
{
showTokenBar
&&
<
TokenBar
chatId=
{
chatId
}
/>
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论