mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-16 18:25:17 +02:00
added prompt suggestions
This commit is contained in:
parent
90f0f9d05a
commit
9ca143aa46
3 changed files with 112 additions and 0 deletions
|
|
@ -38,6 +38,7 @@ import { Shimmer } from '@/components/ai-elements/shimmer';
|
|||
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool';
|
||||
import { PermissionRequest } from '@/components/ai-elements/permission-request';
|
||||
import { AskHumanRequest } from '@/components/ai-elements/ask-human-request';
|
||||
import { Suggestions } from '@/components/ai-elements/suggestions';
|
||||
import { ToolPermissionRequestEvent, AskHumanRequestEvent } from '@x/shared/src/runs.js';
|
||||
import {
|
||||
SidebarInset,
|
||||
|
|
@ -278,16 +279,28 @@ const collectFilePaths = (nodes: TreeNode[]): string[] =>
|
|||
interface ChatInputInnerProps {
|
||||
onSubmit: (message: PromptInputMessage, mentions?: FileMention[]) => void
|
||||
isProcessing: boolean
|
||||
presetMessage?: string
|
||||
onPresetMessageConsumed?: () => void
|
||||
}
|
||||
|
||||
function ChatInputInner({
|
||||
onSubmit,
|
||||
isProcessing,
|
||||
presetMessage,
|
||||
onPresetMessageConsumed,
|
||||
}: ChatInputInnerProps) {
|
||||
const controller = usePromptInputController()
|
||||
const message = controller.textInput.value
|
||||
const canSubmit = Boolean(message.trim()) && !isProcessing
|
||||
|
||||
// Handle preset message from suggestions
|
||||
useEffect(() => {
|
||||
if (presetMessage) {
|
||||
controller.textInput.setInput(presetMessage)
|
||||
onPresetMessageConsumed?.()
|
||||
}
|
||||
}, [presetMessage, controller.textInput, onPresetMessageConsumed])
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (!canSubmit) return
|
||||
onSubmit({ text: message.trim(), files: [] }, controller.mentions.mentions)
|
||||
|
|
@ -334,6 +347,8 @@ interface ChatInputWithMentionsProps {
|
|||
visibleFiles: string[]
|
||||
onSubmit: (message: PromptInputMessage, mentions?: FileMention[]) => void
|
||||
isProcessing: boolean
|
||||
presetMessage?: string
|
||||
onPresetMessageConsumed?: () => void
|
||||
}
|
||||
|
||||
function ChatInputWithMentions({
|
||||
|
|
@ -342,12 +357,16 @@ function ChatInputWithMentions({
|
|||
visibleFiles,
|
||||
onSubmit,
|
||||
isProcessing,
|
||||
presetMessage,
|
||||
onPresetMessageConsumed,
|
||||
}: ChatInputWithMentionsProps) {
|
||||
return (
|
||||
<PromptInputProvider knowledgeFiles={knowledgeFiles} recentFiles={recentFiles} visibleFiles={visibleFiles}>
|
||||
<ChatInputInner
|
||||
onSubmit={onSubmit}
|
||||
isProcessing={isProcessing}
|
||||
presetMessage={presetMessage}
|
||||
onPresetMessageConsumed={onPresetMessageConsumed}
|
||||
/>
|
||||
</PromptInputProvider>
|
||||
)
|
||||
|
|
@ -386,6 +405,7 @@ function App() {
|
|||
const [runId, setRunId] = useState<string | null>(null)
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
const [agentId] = useState<string>('copilot')
|
||||
const [presetMessage, setPresetMessage] = useState<string | undefined>(undefined)
|
||||
|
||||
// Runs history state
|
||||
type RunListItem = { id: string; title?: string; createdAt: string; agentId: string }
|
||||
|
|
@ -1598,12 +1618,17 @@ function App() {
|
|||
<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="mx-auto w-full max-w-4xl px-4">
|
||||
{!hasConversation && (
|
||||
<Suggestions onSelect={setPresetMessage} className="mb-3 justify-center" />
|
||||
)}
|
||||
<ChatInputWithMentions
|
||||
knowledgeFiles={knowledgeFiles}
|
||||
recentFiles={recentWikiFiles}
|
||||
visibleFiles={visibleKnowledgeFiles}
|
||||
onSubmit={handlePromptSubmit}
|
||||
isProcessing={isProcessing}
|
||||
presetMessage={presetMessage}
|
||||
onPresetMessageConsumed={() => setPresetMessage(undefined)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
import { Mail, Calendar, FolderOpen, FileText } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface Suggestion {
|
||||
id: string
|
||||
label: string
|
||||
prompt: string
|
||||
icon: React.ReactNode
|
||||
}
|
||||
|
||||
const defaultSuggestions: Suggestion[] = [
|
||||
{
|
||||
id: 'email-draft',
|
||||
label: 'Draft an email',
|
||||
prompt: "Let's draft an email response to [name]",
|
||||
icon: <Mail className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
id: 'meeting-prep',
|
||||
label: 'Prep for a meeting',
|
||||
prompt: 'Help me prep for my next meeting with [name]',
|
||||
icon: <Calendar className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
id: 'doc-collab',
|
||||
label: 'Work on a document',
|
||||
prompt: "Let's work on [document name]",
|
||||
icon: <FileText className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
id: 'organize-files',
|
||||
label: 'Organize files',
|
||||
prompt: 'Help me organize [folder or files]',
|
||||
icon: <FolderOpen className="h-4 w-4" />,
|
||||
},
|
||||
]
|
||||
|
||||
interface SuggestionsProps {
|
||||
suggestions?: Suggestion[]
|
||||
onSelect: (prompt: string) => void
|
||||
className?: string
|
||||
vertical?: boolean
|
||||
}
|
||||
|
||||
export function Suggestions({
|
||||
suggestions = defaultSuggestions,
|
||||
onSelect,
|
||||
className,
|
||||
vertical = false,
|
||||
}: SuggestionsProps) {
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex gap-2',
|
||||
vertical ? 'flex-col items-start' : 'flex-wrap justify-center',
|
||||
className
|
||||
)}>
|
||||
{suggestions.map((suggestion) => (
|
||||
<button
|
||||
key={suggestion.id}
|
||||
onClick={() => onSelect(suggestion.prompt)}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 px-3 py-1.5 rounded-full',
|
||||
'text-sm text-muted-foreground',
|
||||
'border border-border bg-background',
|
||||
'hover:bg-muted hover:text-foreground hover:border-muted-foreground/30',
|
||||
'transition-colors duration-150',
|
||||
'focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
|
||||
)}
|
||||
>
|
||||
{suggestion.icon}
|
||||
<span>{suggestion.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import { Shimmer } from '@/components/ai-elements/shimmer'
|
|||
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool'
|
||||
import { PermissionRequest } from '@/components/ai-elements/permission-request'
|
||||
import { AskHumanRequest } from '@/components/ai-elements/ask-human-request'
|
||||
import { Suggestions } from '@/components/ai-elements/suggestions'
|
||||
import { type PromptInputMessage, type FileMention } from '@/components/ai-elements/prompt-input'
|
||||
import { useMentionDetection } from '@/hooks/use-mention-detection'
|
||||
import { MentionPopover } from '@/components/mention-popover'
|
||||
|
|
@ -544,6 +545,16 @@ export function ChatSidebar({
|
|||
|
||||
{/* Input area - responsive to sidebar width, matches floating bar position exactly */}
|
||||
<div className="absolute bottom-6 left-14 right-6 z-10" ref={containerRef}>
|
||||
{!hasConversation && (
|
||||
<Suggestions
|
||||
onSelect={(prompt) => {
|
||||
onMessageChange(prompt)
|
||||
setTimeout(() => textareaRef.current?.focus(), 0)
|
||||
}}
|
||||
vertical
|
||||
className="mb-3"
|
||||
/>
|
||||
)}
|
||||
<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">
|
||||
{mentionHighlights.hasHighlights && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue