Task attempt ed00b339-eaec-4c8f-b4f5-79504e6105c6 - Final changes

This commit is contained in:
Louis Knight-Webb
2025-06-24 17:39:51 +01:00
parent bca39efbfc
commit b4b2890573
2 changed files with 80 additions and 31 deletions

View File

@@ -1224,8 +1224,8 @@ export function TaskDetailsPanel({
{/* Footer - Follow-up section */} {/* Footer - Follow-up section */}
{selectedAttempt && ( {selectedAttempt && (
<div className="border-t p-4"> <div className="border-t p-6">
<div className="space-y-2"> <div className="space-y-3">
<Label className="text-sm font-medium"> <Label className="text-sm font-medium">
Follow-up question Follow-up question
</Label> </Label>
@@ -1235,7 +1235,7 @@ export function TaskDetailsPanel({
<AlertDescription>{followUpError}</AlertDescription> <AlertDescription>{followUpError}</AlertDescription>
</Alert> </Alert>
)} )}
<div className="flex gap-2"> <div className="flex gap-3">
<FileSearchTextarea <FileSearchTextarea
placeholder="Ask a follow-up question about this task... Type @ to search files." placeholder="Ask a follow-up question about this task... Type @ to search files."
value={followUpMessage} value={followUpMessage}
@@ -1255,26 +1255,28 @@ export function TaskDetailsPanel({
} }
} }
}} }}
className="flex-1 min-h-[60px] resize-none" className="flex-1 min-h-[80px] resize-none"
disabled={!canSendFollowUp} disabled={!canSendFollowUp}
projectId={projectId} projectId={projectId}
rows={3} rows={4}
/> />
<Button <div className="flex flex-col justify-end">
onClick={handleSendFollowUp} <Button
disabled={ onClick={handleSendFollowUp}
!canSendFollowUp || disabled={
!followUpMessage.trim() || !canSendFollowUp ||
isSendingFollowUp !followUpMessage.trim() ||
} isSendingFollowUp
className="self-end" }
> size="sm"
{isSendingFollowUp ? ( >
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current" /> {isSendingFollowUp ? (
) : ( <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current" />
<Send className="h-4 w-4" /> ) : (
)} <Send className="h-4 w-4" />
</Button> )}
</Button>
</div>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
{!canSendFollowUp {!canSendFollowUp

View File

@@ -1,4 +1,5 @@
import { useState, useRef, useEffect, KeyboardEvent } from 'react' import { useState, useRef, useEffect, KeyboardEvent } from 'react'
import { createPortal } from 'react-dom'
import { Textarea } from '@/components/ui/textarea' import { Textarea } from '@/components/ui/textarea'
import { makeRequest } from '@/lib/api' import { makeRequest } from '@/lib/api'
@@ -161,22 +162,66 @@ export function FileSearchTextarea({
}, 0) }, 0)
} }
// Calculate dropdown position // Calculate dropdown position relative to viewport
const getDropdownPosition = () => { const getDropdownPosition = () => {
if (!textareaRef.current || atSymbolPosition === -1) return { top: 0, left: 0 } if (!textareaRef.current || atSymbolPosition === -1) return { top: 0, left: 0, maxHeight: 240 }
const textareaRect = textareaRef.current.getBoundingClientRect()
const textBeforeAt = value.slice(0, atSymbolPosition) const textBeforeAt = value.slice(0, atSymbolPosition)
const lines = textBeforeAt.split('\n') const lines = textBeforeAt.split('\n')
const currentLine = lines.length - 1 const currentLine = lines.length - 1
const charInLine = lines[lines.length - 1].length const charInLine = lines[lines.length - 1].length
// Rough calculation - this is an approximation // More accurate calculation using computed styles
const lineHeight = 20 const computedStyle = window.getComputedStyle(textareaRef.current)
const charWidth = 8 const lineHeight = parseInt(computedStyle.lineHeight) || 20
const top = (currentLine + 1) * lineHeight + 10 const fontSize = parseInt(computedStyle.fontSize) || 14
const left = charWidth * charInLine const charWidth = fontSize * 0.6 // Approximate character width
return { top, left } // Position relative to textarea
const relativeTop = (currentLine + 1) * lineHeight + 8
const relativeLeft = charWidth * charInLine
// Convert to viewport coordinates
const viewportTop = textareaRect.top + relativeTop
const viewportLeft = textareaRect.left + relativeLeft
// Adjust for viewport boundaries
const dropdownHeight = 240 // max-h-60 = 240px
const dropdownWidth = 256 // min-w-64 = 256px
let finalTop = viewportTop
let finalLeft = viewportLeft
let maxHeight = dropdownHeight
// Prevent going off the right edge
if (viewportLeft + dropdownWidth > window.innerWidth) {
finalLeft = window.innerWidth - dropdownWidth - 16
}
// Prevent going off the left edge
if (finalLeft < 16) {
finalLeft = 16
}
// Prevent going off the bottom edge
if (viewportTop + dropdownHeight > window.innerHeight) {
// Try positioning above the line instead
const aboveTop = textareaRect.top + (currentLine * lineHeight) - dropdownHeight - 8
if (aboveTop > 16) {
finalTop = aboveTop
} else {
// If can't fit above either, limit height and scroll
maxHeight = window.innerHeight - viewportTop - 16
if (maxHeight < 120) {
// If still too small, position at bottom with limited height
finalTop = window.innerHeight - 120 - 16
maxHeight = 120
}
}
}
return { top: finalTop, left: finalLeft, maxHeight }
} }
const dropdownPosition = getDropdownPosition() const dropdownPosition = getDropdownPosition()
@@ -194,13 +239,14 @@ export function FileSearchTextarea({
className={className} className={className}
/> />
{showDropdown && ( {showDropdown && createPortal(
<div <div
ref={dropdownRef} ref={dropdownRef}
className="absolute z-50 bg-background border border-border rounded-md shadow-lg max-h-60 overflow-y-auto min-w-64" className="fixed z-50 bg-background border border-border rounded-md shadow-lg overflow-y-auto min-w-64"
style={{ style={{
top: dropdownPosition.top, top: dropdownPosition.top,
left: dropdownPosition.left, left: dropdownPosition.left,
maxHeight: dropdownPosition.maxHeight,
}} }}
> >
{isLoading ? ( {isLoading ? (
@@ -225,7 +271,8 @@ export function FileSearchTextarea({
))} ))}
</div> </div>
)} )}
</div> </div>,
document.body
)} )}
</div> </div>
) )