mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
polish ui
This commit is contained in:
@@ -10,7 +10,8 @@ import { recipeBuilderRecipe } from './recipeBuilderRecipe';
|
||||
import { extractYamlFromMessage, parseYamlToRecipe } from './recipeExtractor';
|
||||
import { getInitialWorkingDir } from '../../../utils/workingDir';
|
||||
import { UserInput } from '../../../types/message';
|
||||
import { Send } from 'lucide-react';
|
||||
import { Send } from '../../icons';
|
||||
import Stop from '../../ui/Stop';
|
||||
import { Button } from '../../ui/button';
|
||||
import { RecipeBuilderChatProps } from './types';
|
||||
|
||||
@@ -156,69 +157,89 @@ export default function RecipeBuilderChat({ recipe, onRecipeChange }: RecipeBuil
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<LoadingGoose />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const showLoadingIndicator = isLoading || isStreaming;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 min-h-0 bg-background-default">
|
||||
<ScrollArea ref={scrollRef} className="flex-1 min-h-0 px-4" autoScroll>
|
||||
<div className="py-6">
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-center text-textSubtle py-8">
|
||||
<p className="text-lg mb-2">Recipe Builder</p>
|
||||
<p className="text-sm">
|
||||
Tell me what kind of recipe you'd like to create, and I'll help you build it.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<ProgressiveMessageList
|
||||
messages={messages}
|
||||
chat={{ sessionId: sessionId! }}
|
||||
isUserMessage={isUserMessage}
|
||||
/>
|
||||
)}
|
||||
{isStreaming && (
|
||||
<div className="flex justify-center py-4">
|
||||
<LoadingGoose chatState={chatState} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<div className="p-4 border-t border-borderSubtle">
|
||||
<div className="flex gap-2">
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Describe the recipe you want to create..."
|
||||
className="flex-1 p-3 border border-borderSubtle rounded-lg bg-background-default text-textStandard resize-none focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
rows={2}
|
||||
disabled={isStreaming}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
{isStreaming ? (
|
||||
<Button onClick={stopStreaming} variant="outline" size="sm" className="h-full">
|
||||
Stop
|
||||
</Button>
|
||||
<div className="relative flex-1 min-h-0">
|
||||
<ScrollArea ref={scrollRef} className="h-full px-4" autoScroll>
|
||||
<div className="py-6">
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-center text-textSubtle py-8">
|
||||
<p className="text-lg mb-2">Recipe Builder</p>
|
||||
<p className="text-sm">
|
||||
Tell me what kind of recipe you'd like to create, and I'll help you build it.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={!inputValue.trim()}
|
||||
size="sm"
|
||||
className="h-full"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</Button>
|
||||
<ProgressiveMessageList
|
||||
messages={messages}
|
||||
chat={{ sessionId: sessionId! }}
|
||||
isUserMessage={isUserMessage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{showLoadingIndicator && (
|
||||
<div className="absolute bottom-1 left-4 z-20 pointer-events-none">
|
||||
<LoadingGoose chatState={isLoading ? ChatState.LoadingConversation : chatState} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col relative h-auto p-4 bg-background-default z-10 rounded-t-2xl border-t border-borderSubtle">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}}
|
||||
className="relative"
|
||||
>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Describe the recipe you want to create..."
|
||||
className="w-full outline-none border-none focus:ring-0 bg-transparent px-3 pt-3 pb-1.5 text-sm resize-none text-textStandard placeholder:text-textPlaceholder"
|
||||
rows={2}
|
||||
disabled={isLoading || isStreaming}
|
||||
style={{ paddingRight: '120px' }}
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
||||
{isStreaming ? (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={stopStreaming}
|
||||
size="sm"
|
||||
shape="round"
|
||||
variant="outline"
|
||||
className="bg-slate-600 text-white hover:bg-slate-700 border-slate-600 rounded-full px-6 py-2"
|
||||
>
|
||||
<Stop />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
shape="round"
|
||||
variant="outline"
|
||||
disabled={isLoading || !inputValue.trim()}
|
||||
className={`rounded-full px-10 py-2 flex items-center gap-2 ${
|
||||
isLoading || !inputValue.trim()
|
||||
? 'bg-slate-600 text-white cursor-not-allowed opacity-50 border-slate-600'
|
||||
: 'bg-slate-600 text-white hover:bg-slate-700 border-slate-600 hover:cursor-pointer'
|
||||
}`}
|
||||
>
|
||||
<Send />
|
||||
<span className="text-sm">Send</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function RecipeBuilderHeader({
|
||||
onClose,
|
||||
}: RecipeBuilderHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-borderSubtle bg-background-default">
|
||||
<div className="flex items-center justify-between px-4 pt-[36px] pb-3 border-b border-borderSubtle bg-background-default">
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant={currentView === 'chat' ? 'default' : 'ghost'}
|
||||
|
||||
@@ -8,7 +8,9 @@ import ProgressiveMessageList from '../../ProgressiveMessageList';
|
||||
import LoadingGoose from '../../LoadingGoose';
|
||||
import { getInitialWorkingDir } from '../../../utils/workingDir';
|
||||
import { UserInput } from '../../../types/message';
|
||||
import { Send, RefreshCw, AlertTriangle } from 'lucide-react';
|
||||
import { RefreshCw, AlertTriangle } from 'lucide-react';
|
||||
import { Send } from '../../icons';
|
||||
import Stop from '../../ui/Stop';
|
||||
import { Button } from '../../ui/button';
|
||||
import { RecipeBuilderTestProps } from './types';
|
||||
import ParameterInputModal from '../../ParameterInputModal';
|
||||
@@ -176,15 +178,6 @@ export default function RecipeBuilderTest({
|
||||
[handleSubmit]
|
||||
);
|
||||
|
||||
// Show loading state
|
||||
if (isStarting) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<LoadingGoose />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show error state
|
||||
if (sessionError || sessionLoadError) {
|
||||
return (
|
||||
@@ -225,58 +218,82 @@ export default function RecipeBuilderTest({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chat area */}
|
||||
<ScrollArea ref={scrollRef} className="flex-1 min-h-0 px-4" autoScroll>
|
||||
<div className="py-6">
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-center text-textSubtle py-8">
|
||||
<p className="text-sm">Send a message to test your recipe.</p>
|
||||
</div>
|
||||
) : (
|
||||
<ProgressiveMessageList
|
||||
messages={messages}
|
||||
chat={{ sessionId: sessionId! }}
|
||||
isUserMessage={isUserMessage}
|
||||
/>
|
||||
)}
|
||||
{isStreaming && (
|
||||
<div className="flex justify-center py-4">
|
||||
<LoadingGoose chatState={chatState} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{/* Input area */}
|
||||
<div className="p-4 border-t border-borderSubtle">
|
||||
<div className="flex gap-2">
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Test your recipe..."
|
||||
className="flex-1 p-3 border border-borderSubtle rounded-lg bg-background-default text-textStandard resize-none focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
rows={2}
|
||||
disabled={isStreaming}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
{isStreaming ? (
|
||||
<Button onClick={stopStreaming} variant="outline" size="sm" className="h-full">
|
||||
Stop
|
||||
</Button>
|
||||
<div className="relative flex-1 min-h-0">
|
||||
<ScrollArea ref={scrollRef} className="h-full px-4" autoScroll>
|
||||
<div className="py-6">
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-center text-textSubtle py-8">
|
||||
<p className="text-sm">Send a message to test your recipe.</p>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={!inputValue.trim()}
|
||||
size="sm"
|
||||
className="h-full"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</Button>
|
||||
<ProgressiveMessageList
|
||||
messages={messages}
|
||||
chat={{ sessionId: sessionId! }}
|
||||
isUserMessage={isUserMessage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{(isStarting || isStreaming) && (
|
||||
<div className="absolute bottom-1 left-4 z-20 pointer-events-none">
|
||||
<LoadingGoose chatState={isStarting ? ChatState.LoadingConversation : chatState} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col relative h-auto p-4 bg-background-default z-10 rounded-t-2xl border-t border-borderSubtle">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}}
|
||||
className="relative"
|
||||
>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Test your recipe..."
|
||||
className="w-full outline-none border-none focus:ring-0 bg-transparent px-3 pt-3 pb-1.5 text-sm resize-none text-textStandard placeholder:text-textPlaceholder"
|
||||
rows={2}
|
||||
disabled={isStarting || isStreaming}
|
||||
style={{ paddingRight: '120px' }}
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
||||
{isStreaming ? (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={stopStreaming}
|
||||
size="sm"
|
||||
shape="round"
|
||||
variant="outline"
|
||||
className="bg-slate-600 text-white hover:bg-slate-700 border-slate-600 rounded-full px-6 py-2"
|
||||
>
|
||||
<Stop />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
shape="round"
|
||||
variant="outline"
|
||||
disabled={isStarting || !inputValue.trim()}
|
||||
className={`rounded-full px-10 py-2 flex items-center gap-2 ${
|
||||
isStarting || !inputValue.trim()
|
||||
? 'bg-slate-600 text-white cursor-not-allowed opacity-50 border-slate-600'
|
||||
: 'bg-slate-600 text-white hover:bg-slate-700 border-slate-600 hover:cursor-pointer'
|
||||
}`}
|
||||
>
|
||||
<Send />
|
||||
<span className="text-sm">Send</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user