✨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:
parent
bea806c26c
commit
da2f97c227
13
web-app/src/components/ui/skeleton.tsx
Normal file
13
web-app/src/components/ui/skeleton.tsx
Normal 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 }
|
||||
@ -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 ? (
|
||||
|
||||
61
web-app/src/containers/ChatWidthSwitcher.tsx
Normal file
61
web-app/src/containers/ChatWidthSwitcher.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
)}
|
||||
tokens/sec
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user