1
0

useChatTextarea.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { useEffect, useRef, useState, useCallback } from 'react';
  2. // Media Query for detecting "large" screens (matching Tailwind's lg: breakpoint)
  3. const LARGE_SCREEN_MQ = '(min-width: 1024px)';
  4. // Calculates and sets the textarea height based on its scrollHeight
  5. const adjustTextareaHeight = (textarea: HTMLTextAreaElement | null) => {
  6. if (!textarea) return;
  7. // Only perform auto-sizing on large screens
  8. if (!window.matchMedia(LARGE_SCREEN_MQ).matches) {
  9. // On small screens, reset inline height and max-height styles.
  10. // This allows CSS (e.g., `rows` attribute or classes) to control the height,
  11. // and enables manual resizing if `resize-vertical` is set.
  12. textarea.style.height = ''; // Use 'auto' or '' to reset
  13. textarea.style.maxHeight = '';
  14. return; // Do not adjust height programmatically on small screens
  15. }
  16. const computedStyle = window.getComputedStyle(textarea);
  17. // Get the max-height specified by CSS (e.g., from `lg:max-h-48`)
  18. const currentMaxHeight = computedStyle.maxHeight;
  19. // Temporarily remove max-height to allow scrollHeight to be calculated correctly
  20. textarea.style.maxHeight = 'none';
  21. // Reset height to 'auto' to measure the actual scrollHeight needed
  22. textarea.style.height = 'auto';
  23. // Set the height to the calculated scrollHeight
  24. textarea.style.height = `${textarea.scrollHeight}px`;
  25. // Re-apply the original max-height from CSS to enforce the limit
  26. textarea.style.maxHeight = currentMaxHeight;
  27. };
  28. // Interface describing the API returned by the hook
  29. export interface ChatTextareaApi {
  30. value: () => string;
  31. setValue: (value: string) => void;
  32. focus: () => void;
  33. ref: React.RefObject<HTMLTextAreaElement>;
  34. onInput: (event: React.FormEvent<HTMLTextAreaElement>) => void; // Input handler
  35. }
  36. // This is a workaround to prevent the textarea from re-rendering when the inner content changes
  37. // See https://github.com/ggml-org/llama.cpp/pull/12299
  38. // combined now with auto-sizing logic.
  39. export function useChatTextarea(initValue: string): ChatTextareaApi {
  40. const [savedInitValue, setSavedInitValue] = useState<string>(initValue);
  41. const textareaRef = useRef<HTMLTextAreaElement>(null);
  42. // Effect to set initial value and height on mount or when initValue changes
  43. useEffect(() => {
  44. const textarea = textareaRef.current;
  45. if (textarea) {
  46. if (typeof savedInitValue === 'string' && savedInitValue.length > 0) {
  47. textarea.value = savedInitValue;
  48. // Call adjustTextareaHeight - it will check screen size internally
  49. setTimeout(() => adjustTextareaHeight(textarea), 0);
  50. setSavedInitValue(''); // Reset after applying
  51. } else {
  52. // Adjust height even if there's no initial value (for initial render)
  53. setTimeout(() => adjustTextareaHeight(textarea), 0);
  54. }
  55. }
  56. }, [textareaRef, savedInitValue]); // Depend on ref and savedInitValue
  57. const handleInput = useCallback(
  58. (event: React.FormEvent<HTMLTextAreaElement>) => {
  59. // Call adjustTextareaHeight on every input - it will decide whether to act
  60. adjustTextareaHeight(event.currentTarget);
  61. },
  62. []
  63. );
  64. return {
  65. // Method to get the current value directly from the textarea
  66. value: () => {
  67. return textareaRef.current?.value ?? '';
  68. },
  69. // Method to programmatically set the value and trigger height adjustment
  70. setValue: (value: string) => {
  71. const textarea = textareaRef.current;
  72. if (textarea) {
  73. textarea.value = value;
  74. // Call adjustTextareaHeight - it will check screen size internally
  75. setTimeout(() => adjustTextareaHeight(textarea), 0);
  76. }
  77. },
  78. focus: () => {
  79. if (textareaRef.current) {
  80. textareaRef.current.focus();
  81. }
  82. },
  83. ref: textareaRef,
  84. onInput: handleInput,
  85. };
  86. }