enhancement: add setting chat width container (#5289)

* enhancement: add setting conversation width

* enahncement: cleanup log and change improve accesibility

* enahcement: move const beta version
This commit is contained in:
Faisal Amir 2025-06-16 15:02:43 +07:00 committed by GitHub
parent bea806c26c
commit da2f97c227
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 202 additions and 105 deletions

View File

@ -0,0 +1,13 @@
import { cn } from '@/lib/utils'
function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="skeleton"
className={cn('bg-main-view-fg/10', className)}
{...props}
/>
)
}
export { Skeleton }

View File

@ -21,7 +21,6 @@ import {
IconTool,
IconCodeCircle2,
IconPlayerStopFilled,
IconBrandSpeedtest,
IconX,
} from '@tabler/icons-react'
import { useTranslation } from 'react-i18next'
@ -45,12 +44,7 @@ type ChatInputProps = {
initialMessage?: boolean
}
const ChatInput = ({
model,
className,
showSpeedToken = true,
initialMessage,
}: ChatInputProps) => {
const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
const textareaRef = useRef<HTMLTextAreaElement>(null)
const [isFocused, setIsFocused] = useState(false)
const [rows, setRows] = useState(1)
@ -60,7 +54,7 @@ const ChatInput = ({
const { currentThreadId } = useThreads()
const { t } = useTranslation()
const { spellCheckChatInput } = useGeneralSetting()
const { tokenSpeed } = useAppState()
const { showModal, PromiseModal: OutOfContextModal } =
useOutOfContextPromiseModal()
const maxRows = 10
@ -559,15 +553,6 @@ const ChatInput = ({
</TooltipProvider>
)}
</div>
{showSpeedToken && (
<div className="flex items-center gap-1 text-main-view-fg/60 text-xs">
<IconBrandSpeedtest size={18} />
<span>
{Math.round(tokenSpeed?.tokenSpeed ?? 0)} tokens/sec
</span>
</div>
)}
</div>
{streamingContent ? (

View File

@ -0,0 +1,61 @@
import { Skeleton } from '@/components/ui/skeleton'
import { useAppearance } from '@/hooks/useAppearance'
import { cn } from '@/lib/utils'
import { IconCircleCheckFilled } from '@tabler/icons-react'
export function ChatWidthSwitcher() {
const { chatWidth, setChatWidth } = useAppearance()
return (
<div className="flex gap-4">
<button
className={cn(
'w-full overflow-hidden border border-main-view-fg/10 rounded-md my-2 pb-2 cursor-pointer',
chatWidth === 'compact' && 'border-accent'
)}
onClick={() => setChatWidth('compact')}
>
<div className="flex items-center justify-between px-4 py-2 bg-main-view-fg/10">
<span className="font-medium text-xs font-sans">Compact Width</span>
{chatWidth === 'compact' && (
<IconCircleCheckFilled className="size-4 text-accent" />
)}
</div>
<div className="overflow-auto p-2">
<div className="flex flex-col px-10 gap-2 mt-2">
<Skeleton className="h-2 w-full rounded-full" />
<Skeleton className="h-2 w-full rounded-full" />
<Skeleton className="h-2 w-full rounded-full" />
<div className="bg-main-view-fg/10 h-8 px-4 w-full flex-shrink-0 border-none resize-none outline-0 rounded-2xl flex items-center">
<span className="text-main-view-fg/50">Ask me anything...</span>
</div>
</div>
</div>
</button>
<button
className={cn(
'w-full overflow-hidden border border-main-view-fg/10 rounded-md my-2 pb-2 cursor-pointer',
chatWidth === 'full' && 'border-accent'
)}
onClick={() => setChatWidth('full')}
>
<div className="flex items-center justify-between px-4 py-2 bg-main-view-fg/10">
<span className="font-medium text-xs font-sans">Full Width</span>
{chatWidth === 'full' && (
<IconCircleCheckFilled className="size-4 text-accent" />
)}
</div>
<div className="overflow-auto p-2">
<div className="flex flex-col gap-2 mt-2">
<Skeleton className="h-2 w-full rounded-full" />
<Skeleton className="h-2 w-full rounded-full" />
<Skeleton className="h-2 w-full rounded-full" />
<div className="bg-main-view-fg/10 h-8 px-4 w-full flex-shrink-0 border-none resize-none outline-0 rounded-2xl flex items-center">
<span className="text-main-view-fg/50">Ask me anything...</span>
</div>
</div>
</div>
</button>
</div>
)
}

View File

@ -359,12 +359,11 @@ export const ThreadContent = memo(
{!isToolCalls && (
<div className="flex items-center gap-2 mt-2 text-main-view-fg/60 text-xs">
<div className={cn('flex items-center gap-2')}>
<div
className={cn(
'flex items-center gap-2',
item.isLastMessage &&
streamingContent &&
'opacity-0 visibility-hidden pointer-events-none'
item.isLastMessage && streamingContent && 'hidden'
)}
>
<CopyButton text={item.content?.[0]?.text.value || ''} />
@ -448,8 +447,10 @@ export const ThreadContent = memo(
</TooltipContent>
</Tooltip>
)}
</div>
<TokenSpeedIndicator
streaming={Boolean(item.isLastMessage && streamingContent)}
metadata={item.metadata}
/>
</div>

View File

@ -1,19 +1,28 @@
import { IconBrandSpeedtest } from '@tabler/icons-react'
import { useAppState } from '@/hooks/useAppState'
import { Gauge } from 'lucide-react'
interface TokenSpeedIndicatorProps {
metadata?: Record<string, unknown>
streaming?: boolean
}
export const TokenSpeedIndicator = ({
metadata
metadata,
streaming,
}: TokenSpeedIndicatorProps) => {
const persistedTokenSpeed = (metadata?.tokenSpeed as { tokenSpeed: number })?.tokenSpeed
const { tokenSpeed } = useAppState()
const persistedTokenSpeed = (metadata?.tokenSpeed as { tokenSpeed: number })
?.tokenSpeed
return (
<div className="flex items-center gap-1 text-main-view-fg/60 text-xs">
<IconBrandSpeedtest size={16} />
<Gauge size={16} />
<span>
{Math.round(persistedTokenSpeed)} tokens/sec
{Math.round(
streaming ? Number(tokenSpeed?.tokenSpeed) : persistedTokenSpeed
)}
&nbsp;tokens/sec
</span>
</div>
)

View File

@ -22,8 +22,8 @@ const DialogAppUpdater = () => {
setRemindMeLater(true)
}
const beta = VERSION.includes('beta')
const nightly = VERSION.includes('-')
const beta = VERSION.includes('beta')
const { release, fetchLatestRelease } = useReleaseNotes()

View File

@ -6,8 +6,10 @@ import { rgb, oklch, formatCss } from 'culori'
import { useTheme } from './useTheme'
export type FontSize = '14px' | '15px' | '16px' | '18px'
export type ChatWidth = 'full' | 'compact'
interface AppearanceState {
chatWidth: ChatWidth
fontSize: FontSize
appBgColor: RgbaColor
appMainViewBgColor: RgbaColor
@ -19,6 +21,7 @@ interface AppearanceState {
appAccentTextColor: string
appDestructiveTextColor: string
appLeftPanelTextColor: string
setChatWidth: (size: ChatWidth) => void
setFontSize: (size: FontSize) => void
setAppBgColor: (color: RgbaColor) => void
setAppMainViewBgColor: (color: RgbaColor) => void
@ -129,6 +132,7 @@ export const useAppearance = create<AppearanceState>()(
persist(
(set) => {
return {
chatWidth: 'compact',
fontSize: defaultFontSize,
appBgColor: defaultAppBgColor,
appMainViewBgColor: defaultAppMainViewBgColor,
@ -270,6 +274,10 @@ export const useAppearance = create<AppearanceState>()(
})
},
setChatWidth: (value: ChatWidth) => {
set({ chatWidth: value })
},
setFontSize: (size: FontSize) => {
// Update CSS variable
document.documentElement.style.setProperty('--font-size-base', size)

View File

@ -18,6 +18,7 @@ import CodeBlockStyleSwitcher from '@/containers/CodeBlockStyleSwitcher'
import { LineNumbersSwitcher } from '@/containers/LineNumbersSwitcher'
import { CodeBlockExample } from '@/containers/CodeBlockExample'
import { toast } from 'sonner'
import { ChatWidthSwitcher } from '@/containers/ChatWidthSwitcher'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const Route = createFileRoute(route.settings.appearance as any)({
@ -98,6 +99,15 @@ function Appareances() {
/>
</Card>
{/* Chat Message */}
<Card>
<CardItem
title="Chat Width"
description="Choose the width of the chat area to customize your conversation view."
/>
<ChatWidthSwitcher />
</Card>
{/* Codeblock */}
<Card>
<CardItem

View File

@ -35,7 +35,7 @@ function ThreadDetail() {
const { setCurrentAssistant, assistants } = useAssistant()
const { setMessages } = useMessages()
const { streamingContent } = useAppState()
const { appMainViewBgColor } = useAppearance()
const { appMainViewBgColor, chatWidth } = useAppearance()
const { messages } = useMessages(
useShallow((state) => ({
@ -213,7 +213,12 @@ function ThreadDetail() {
'flex flex-col h-full w-full overflow-auto px-4 pt-4 pb-3'
)}
>
<div className="w-4/6 mx-auto flex max-w-full flex-col grow">
<div
className={cn(
'w-4/6 mx-auto flex max-w-full flex-col grow',
chatWidth === 'compact' ? 'w-4/6' : 'w-full'
)}
>
{messages &&
messages.map((item, index) => {
// Only pass isLastMessage to the last message in the array
@ -247,7 +252,12 @@ function ThreadDetail() {
<StreamingContent threadId={threadId} />
</div>
</div>
<div className="w-4/6 mx-auto pt-2 pb-3 shrink-0 relative">
<div
className={cn(
' mx-auto pt-2 pb-3 shrink-0 relative',
chatWidth === 'compact' ? 'w-4/6' : 'w-full px-3'
)}
>
<div
className={cn(
'absolute z-0 -top-6 h-8 py-1 flex w-full justify-center pointer-events-none opacity-0 visibility-hidden',