'use client'; import React, { useState, useEffect, useRef } from 'react'; import type { Meta, StoryObj } from "@storybook/react"; import { getPublicClient } from "../api"; const client = getPublicClient('f5d86791-d513-48de-8fd2-b6dbdd4abe9b'); const DEFAULT_PROMPT = `Your job is to predict the next 5-10 words after the current text. Current text: {{inputText}}`; export const AutocompleteTextField = ({ prompt = DEFAULT_PROMPT, placeholder = "", }: any) => { const [text, setText] = React.useState(''); const [dirtyCount, setDirtyCount] = React.useState(0); const [prediction, setPrediction] = React.useState(''); const inputRef = React.useRef(null) as any; const [isFocused, setIsFocused] = useState(false); const timeoutRef = useRef(null) as any; // Event handler for focus const handleFocus = () => { setIsFocused(true); }; // Event handler for blur const handleBlur = () => { setIsFocused(false); }; useEffect(() => { if (!inputRef.current) { return; } const currentElement = inputRef.current as HTMLElement; // Attach the event listeners currentElement.addEventListener('focus', handleFocus); currentElement.addEventListener('blur', handleBlur); // Cleanup function to remove event listeners return () => { currentElement.removeEventListener('focus', handleFocus); currentElement.removeEventListener('blur', handleBlur); }; }, []); // Empty dependency array ensures this effect runs only once React.useEffect(() => { if (dirtyCount > 0) { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } if (text !== '') { timeoutRef.current = setTimeout(() => { getPredictions(text); setDirtyCount(0); }, 500); } } }, [dirtyCount, text]); const handleInput = (e: any) => { e.preventDefault(); console.log(e.target.innerHTML); setText(() => { let newText = e.target.innerHTML; newText = newText.replace(/(){2,}/gi, '
'); newText = newText.replace(//gi, '\n'); newText = newText.replace(/ /gi, ' '); const lastChar = newText[newText.length - 1] === ' ' ? ' ' : newText[newText.length - 1]; if (lastChar === prediction[0]) { setPrediction((prevPrediction) => { const newPrediction = prevPrediction.slice(1); return newPrediction; }); } else { setDirtyCount((prevCount) => prevCount + 1); setPrediction(''); } return newText; }); }; const handleKeyDown = (e: any) => { if (e.key === 'Tab' && prediction) { e.preventDefault(); // Append prediction and reset prediction state const newText = text + prediction; setText(newText); setPrediction(''); const html = newText.split('\n').join('
'); inputRef.current.innerHTML = html; // Update contentEditable text const range = document.createRange(); const sel = window.getSelection() as any; range.selectNodeContents(inputRef.current); // Select the entire content of the div range.collapse(false); // false collapses the range to the end, true to the start sel.removeAllRanges(); // Remove any existing selections sel.addRange(range); // Add the new range } else if (e.key === 'Escape') { setPrediction(''); } }; const getPredictions = async (inputText: any) => { if (!client) { console.error("OpenAI client is not defined."); return; } try { const response = await client.getCompletion({ model: 'gpt-3.5-turbo', messages: [{ role: 'system', content: prompt.replace("{{inputText}}", inputText), }], }); if (response && response.choices && response.choices.length > 0) { const predictedText = response.choices[0].message.content; console.log("Predicted text:", predictedText); setPrediction(predictedText); } } catch (error) { console.error("Failed to fetch predictions:", error); } }; return (
{ inputRef.current.focus(); }} > { inputRef.current.focus(); }} >{prediction.split('\n').join('
')}
{ text ? '' : placeholder }
); }; function TextCompleterStory() { return (

Autocomplete Text Field

); } //👇 This default export determines where your story goes in the story list const meta: Meta = { component: TextCompleterStory, }; export default meta; export const DefaultStory: StoryObj = { // harded code values for now 😬 args: {}, };