mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-03 20:41:07 +02:00
fix chat boxes and remove context indicator
This commit is contained in:
parent
80fe2dbf86
commit
5cc8b4bb79
3 changed files with 41 additions and 80 deletions
|
|
@ -2,11 +2,12 @@ import * as React from 'react'
|
||||||
import { useCallback, useEffect, useState, useRef } from 'react'
|
import { useCallback, useEffect, useState, useRef } from 'react'
|
||||||
import { workspace } from '@x/shared';
|
import { workspace } from '@x/shared';
|
||||||
import { RunEvent } from '@x/shared/src/runs.js';
|
import { RunEvent } from '@x/shared/src/runs.js';
|
||||||
import type { ChatStatus, LanguageModelUsage, ToolUIPart } from 'ai';
|
import type { LanguageModelUsage, ToolUIPart } from 'ai';
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
import { Button } from './components/ui/button';
|
import { Button } from './components/ui/button';
|
||||||
import { CheckIcon, LoaderIcon } from 'lucide-react';
|
import { CheckIcon, LoaderIcon, ArrowUp } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
import { MarkdownEditor } from './components/markdown-editor';
|
import { MarkdownEditor } from './components/markdown-editor';
|
||||||
import { ChatInputBar } from './components/chat-button';
|
import { ChatInputBar } from './components/chat-button';
|
||||||
import { ChatSidebar } from './components/chat-sidebar';
|
import { ChatSidebar } from './components/chat-sidebar';
|
||||||
|
|
@ -27,31 +28,15 @@ import {
|
||||||
MessageResponse,
|
MessageResponse,
|
||||||
} from '@/components/ai-elements/message';
|
} from '@/components/ai-elements/message';
|
||||||
import {
|
import {
|
||||||
PromptInput,
|
|
||||||
PromptInputBody,
|
|
||||||
PromptInputFooter,
|
|
||||||
type PromptInputMessage,
|
type PromptInputMessage,
|
||||||
PromptInputProvider,
|
PromptInputProvider,
|
||||||
PromptInputSubmit,
|
|
||||||
PromptInputTextarea,
|
PromptInputTextarea,
|
||||||
PromptInputTools,
|
|
||||||
usePromptInputController,
|
usePromptInputController,
|
||||||
type FileMention,
|
type FileMention,
|
||||||
} from '@/components/ai-elements/prompt-input';
|
} from '@/components/ai-elements/prompt-input';
|
||||||
import { Reasoning, ReasoningContent, ReasoningTrigger } from '@/components/ai-elements/reasoning';
|
import { Reasoning, ReasoningContent, ReasoningTrigger } from '@/components/ai-elements/reasoning';
|
||||||
import { Shimmer } from '@/components/ai-elements/shimmer';
|
import { Shimmer } from '@/components/ai-elements/shimmer';
|
||||||
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool';
|
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool';
|
||||||
import {
|
|
||||||
Context,
|
|
||||||
ContextCacheUsage,
|
|
||||||
ContextContent,
|
|
||||||
ContextContentBody,
|
|
||||||
ContextContentHeader,
|
|
||||||
ContextInputUsage,
|
|
||||||
ContextOutputUsage,
|
|
||||||
ContextReasoningUsage,
|
|
||||||
ContextTrigger,
|
|
||||||
} from '@/components/ai-elements/context';
|
|
||||||
import {
|
import {
|
||||||
SidebarInset,
|
SidebarInset,
|
||||||
SidebarProvider,
|
SidebarProvider,
|
||||||
|
|
@ -259,61 +244,52 @@ const collectFilePaths = (nodes: TreeNode[]): string[] =>
|
||||||
interface ChatInputInnerProps {
|
interface ChatInputInnerProps {
|
||||||
onSubmit: (message: PromptInputMessage, mentions?: FileMention[]) => void
|
onSubmit: (message: PromptInputMessage, mentions?: FileMention[]) => void
|
||||||
isProcessing: boolean
|
isProcessing: boolean
|
||||||
contextUsage: LanguageModelUsage
|
|
||||||
maxTokens: number
|
|
||||||
usedTokens: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatInputInner({
|
function ChatInputInner({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
contextUsage,
|
|
||||||
maxTokens,
|
|
||||||
usedTokens,
|
|
||||||
}: ChatInputInnerProps) {
|
}: ChatInputInnerProps) {
|
||||||
const controller = usePromptInputController()
|
const controller = usePromptInputController()
|
||||||
const message = controller.textInput.value
|
const message = controller.textInput.value
|
||||||
const canSubmit = Boolean(message.trim()) && !isProcessing
|
const canSubmit = Boolean(message.trim()) && !isProcessing
|
||||||
const submitStatus: ChatStatus = isProcessing ? 'streaming' : 'ready'
|
|
||||||
|
|
||||||
const handleSubmit = useCallback((msg: PromptInputMessage) => {
|
const handleSubmit = useCallback(() => {
|
||||||
onSubmit(msg, controller.mentions.mentions)
|
if (!canSubmit) return
|
||||||
|
onSubmit({ text: message.trim(), files: [] }, controller.mentions.mentions)
|
||||||
|
controller.textInput.clear()
|
||||||
controller.mentions.clearMentions()
|
controller.mentions.clearMentions()
|
||||||
}, [onSubmit, controller.mentions])
|
}, [canSubmit, message, onSubmit, controller])
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleSubmit()
|
||||||
|
}
|
||||||
|
}, [handleSubmit])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PromptInput onSubmit={handleSubmit}>
|
<div className="flex items-center gap-2 bg-background border border-border rounded-3xl shadow-xl px-4 py-2.5">
|
||||||
<PromptInputBody>
|
<PromptInputTextarea
|
||||||
<PromptInputTextarea
|
placeholder="Type your message..."
|
||||||
placeholder="Type your message..."
|
disabled={isProcessing}
|
||||||
disabled={isProcessing}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
className="min-h-[1.5rem] py-0 border-0 shadow-none focus-visible:ring-0 rounded-none"
|
||||||
</PromptInputBody>
|
/>
|
||||||
<PromptInputFooter>
|
<Button
|
||||||
<PromptInputTools>
|
size="icon"
|
||||||
<Context
|
onClick={handleSubmit}
|
||||||
maxTokens={maxTokens}
|
disabled={!canSubmit}
|
||||||
usedTokens={usedTokens}
|
className={cn(
|
||||||
usage={contextUsage}
|
"h-7 w-7 rounded-full shrink-0 transition-all",
|
||||||
>
|
canSubmit
|
||||||
<ContextTrigger size="sm" />
|
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
||||||
<ContextContent>
|
: "bg-muted text-muted-foreground"
|
||||||
<ContextContentHeader />
|
)}
|
||||||
<ContextContentBody>
|
>
|
||||||
<ContextInputUsage />
|
<ArrowUp className="h-4 w-4" />
|
||||||
<ContextOutputUsage />
|
</Button>
|
||||||
<ContextReasoningUsage />
|
</div>
|
||||||
<ContextCacheUsage />
|
|
||||||
</ContextContentBody>
|
|
||||||
</ContextContent>
|
|
||||||
</Context>
|
|
||||||
</PromptInputTools>
|
|
||||||
<PromptInputSubmit
|
|
||||||
disabled={!canSubmit}
|
|
||||||
status={submitStatus}
|
|
||||||
/>
|
|
||||||
</PromptInputFooter>
|
|
||||||
</PromptInput>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,9 +300,6 @@ interface ChatInputWithMentionsProps {
|
||||||
visibleFiles: string[]
|
visibleFiles: string[]
|
||||||
onSubmit: (message: PromptInputMessage, mentions?: FileMention[]) => void
|
onSubmit: (message: PromptInputMessage, mentions?: FileMention[]) => void
|
||||||
isProcessing: boolean
|
isProcessing: boolean
|
||||||
contextUsage: LanguageModelUsage
|
|
||||||
maxTokens: number
|
|
||||||
usedTokens: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatInputWithMentions({
|
function ChatInputWithMentions({
|
||||||
|
|
@ -335,18 +308,12 @@ function ChatInputWithMentions({
|
||||||
visibleFiles,
|
visibleFiles,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
contextUsage,
|
|
||||||
maxTokens,
|
|
||||||
usedTokens,
|
|
||||||
}: ChatInputWithMentionsProps) {
|
}: ChatInputWithMentionsProps) {
|
||||||
return (
|
return (
|
||||||
<PromptInputProvider knowledgeFiles={knowledgeFiles} recentFiles={recentFiles} visibleFiles={visibleFiles}>
|
<PromptInputProvider knowledgeFiles={knowledgeFiles} recentFiles={recentFiles} visibleFiles={visibleFiles}>
|
||||||
<ChatInputInner
|
<ChatInputInner
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
isProcessing={isProcessing}
|
isProcessing={isProcessing}
|
||||||
contextUsage={contextUsage}
|
|
||||||
maxTokens={maxTokens}
|
|
||||||
usedTokens={usedTokens}
|
|
||||||
/>
|
/>
|
||||||
</PromptInputProvider>
|
</PromptInputProvider>
|
||||||
)
|
)
|
||||||
|
|
@ -1259,7 +1226,7 @@ function App() {
|
||||||
<ConversationScrollButton className="bottom-24" />
|
<ConversationScrollButton className="bottom-24" />
|
||||||
</Conversation>
|
</Conversation>
|
||||||
|
|
||||||
<div className="sticky bottom-0 z-10 bg-background pb-4 pt-6 shadow-lg">
|
<div className="sticky bottom-0 z-10 bg-background pb-12 pt-0 shadow-lg">
|
||||||
<div className="pointer-events-none absolute inset-x-0 -top-6 h-6 bg-linear-to-t from-background to-transparent" />
|
<div className="pointer-events-none absolute inset-x-0 -top-6 h-6 bg-linear-to-t from-background to-transparent" />
|
||||||
<div className="mx-auto w-full max-w-4xl px-4">
|
<div className="mx-auto w-full max-w-4xl px-4">
|
||||||
<ChatInputWithMentions
|
<ChatInputWithMentions
|
||||||
|
|
@ -1268,9 +1235,6 @@ function App() {
|
||||||
visibleFiles={visibleKnowledgeFiles}
|
visibleFiles={visibleKnowledgeFiles}
|
||||||
onSubmit={handlePromptSubmit}
|
onSubmit={handlePromptSubmit}
|
||||||
isProcessing={isProcessing}
|
isProcessing={isProcessing}
|
||||||
contextUsage={contextUsage}
|
|
||||||
maxTokens={maxTokens}
|
|
||||||
usedTokens={usedTokens}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1123,7 +1123,7 @@ export const PromptInputTextarea = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="relative flex-1 min-w-0">
|
<div ref={containerRef} className="relative contents">
|
||||||
{mentionHighlights.hasHighlights && (
|
{mentionHighlights.hasHighlights && (
|
||||||
<div
|
<div
|
||||||
ref={highlightRef}
|
ref={highlightRef}
|
||||||
|
|
@ -1146,10 +1146,7 @@ export const PromptInputTextarea = ({
|
||||||
)}
|
)}
|
||||||
<InputGroupTextarea
|
<InputGroupTextarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
className={cn(
|
className={cn("field-sizing-content max-h-48 min-h-10", className)}
|
||||||
"field-sizing-content max-h-48 min-h-16 relative z-10",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
name="message"
|
name="message"
|
||||||
onCompositionEnd={() => setIsComposing(false)}
|
onCompositionEnd={() => setIsComposing(false)}
|
||||||
onCompositionStart={() => setIsComposing(true)}
|
onCompositionStart={() => setIsComposing(true)}
|
||||||
|
|
|
||||||
|
|
@ -478,7 +478,7 @@ export function ChatSidebar({
|
||||||
|
|
||||||
{/* Input area - responsive to sidebar width, matches floating bar position exactly */}
|
{/* Input area - responsive to sidebar width, matches floating bar position exactly */}
|
||||||
<div className="absolute bottom-6 left-14 right-6 z-10" ref={containerRef}>
|
<div className="absolute bottom-6 left-14 right-6 z-10" ref={containerRef}>
|
||||||
<div className="flex items-center gap-2 bg-background border border-border rounded-2xl shadow-xl px-4 py-2.5">
|
<div className="flex items-center gap-2 bg-background border border-border rounded-3xl shadow-xl px-4 py-2.5">
|
||||||
<div className="relative flex-1 min-w-0">
|
<div className="relative flex-1 min-w-0">
|
||||||
{mentionHighlights.hasHighlights && (
|
{mentionHighlights.hasHighlights && (
|
||||||
<div
|
<div
|
||||||
|
|
@ -518,7 +518,7 @@ export function ChatSidebar({
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!canSubmit}
|
disabled={!canSubmit}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-7 w-7 rounded-full shrink-0 transition-all self-end",
|
"h-7 w-7 rounded-full shrink-0 transition-all",
|
||||||
canSubmit
|
canSubmit
|
||||||
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
||||||
: "bg-muted text-muted-foreground"
|
: "bg-muted text-muted-foreground"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue