AskAIWidget.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useRef } from 'react'
  2. import { Button, ExpandingTextArea } from 'ui'
  3. interface AskAIWidgetProps {
  4. value: string
  5. onChange: (value: string) => void
  6. onSubmit: (prompt: string) => void
  7. onAccept?: () => void
  8. onReject?: () => void
  9. onCancel?: () => void
  10. isDiffVisible: boolean
  11. isLoading?: boolean
  12. }
  13. export const AskAIWidget = ({
  14. value,
  15. onChange,
  16. onSubmit,
  17. onAccept,
  18. onReject,
  19. onCancel,
  20. isDiffVisible,
  21. isLoading = false,
  22. }: AskAIWidgetProps) => {
  23. const textAreaRef = useRef<HTMLTextAreaElement>(null)
  24. useEffect(() => {
  25. setTimeout(() => {
  26. textAreaRef.current?.focus()
  27. textAreaRef.current?.setSelectionRange(value.length, value.length)
  28. }, 100)
  29. }, [])
  30. const handleSubmit = useCallback(() => {
  31. if (value.trim() && !isLoading) {
  32. onSubmit(value)
  33. }
  34. }, [value, isLoading, onSubmit])
  35. const handleChange = useCallback(
  36. (e: ChangeEvent<HTMLTextAreaElement>) => {
  37. onChange(e.target.value)
  38. },
  39. [onChange]
  40. )
  41. const handleKeyDown = useCallback(
  42. (e: KeyboardEvent<HTMLTextAreaElement>) => {
  43. if (e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
  44. e.preventDefault()
  45. handleSubmit()
  46. }
  47. },
  48. [handleSubmit]
  49. )
  50. return (
  51. <div className="overflow-hidden rounded-md p-0 bg-popover border border-foreground/20 focus-within:border-foreground/30 shadow-xl text-sm max-w-xl">
  52. <ExpandingTextArea
  53. ref={textAreaRef}
  54. className="bg-transparent border-0 outline-0 ring-0 ring-offset-0 focus:outline-0 focus:ring-0 focus:ring-offset-0 focus-visible:outline-0 focus-visible:ring-0 focus-visible:ring-offset-0 focus-within:outline-0 focus-within:ring-0 focus-within:ring-offset-0 shadow-none rounded-none gap-4 text-xs md:text-xs py-2 pl-3 leading-[20px]!"
  55. placeholder={isDiffVisible ? 'Make an edit...' : 'Edit via the Assistant...'}
  56. value={value}
  57. onChange={handleChange}
  58. onKeyDown={handleKeyDown}
  59. disabled={isLoading}
  60. />
  61. {isDiffVisible ? (
  62. <div className="flex justify-start p-0 border-t">
  63. <Button
  64. type="text"
  65. onClick={onAccept}
  66. className="text-xs h-auto py-1 rounded-none px-3 border-r-border"
  67. disabled={isLoading}
  68. >
  69. Accept <span className="text-xs text-foreground-light">⌘ + Enter</span>
  70. </Button>
  71. <Button
  72. onClick={onReject}
  73. type="text"
  74. className="text-xs h-auto py-1 rounded-none px-3 border-r-border"
  75. disabled={isLoading}
  76. >
  77. Reject <span className="text-xs text-foreground-light">Esc</span>
  78. </Button>
  79. </div>
  80. ) : (
  81. <div className="flex justify-start p-0 border-t">
  82. <Button
  83. type="text"
  84. onClick={handleSubmit}
  85. loading={isLoading}
  86. className="text-xs h-auto py-1 rounded-none px-3 border-r-border"
  87. disabled={isLoading}
  88. >
  89. {isLoading ? 'Generating...' : 'Generate'}{' '}
  90. {!isLoading && <span className="text-xs text-foreground-light">Enter</span>}
  91. </Button>
  92. <Button
  93. onClick={onCancel}
  94. type="text"
  95. className="text-xs h-auto py-1 rounded-none px-3 border-r-border"
  96. disabled={isLoading}
  97. >
  98. Cancel <span className="text-xs text-foreground-light">Esc</span>
  99. </Button>
  100. </div>
  101. )}
  102. </div>
  103. )
  104. }