|
import { useEffect, useRef, useState } from 'react'; |
|
import { CONFIG } from '../config'; |
|
import { |
|
getBlogPrompt, |
|
getPromptGeneratePodcastScript, |
|
} from '../utils/prompts'; |
|
|
|
import { EXAMPLES } from '../examples'; |
|
import { HfInference } from '@huggingface/inference'; |
|
import { isBlogMode } from '../utils/utils'; |
|
|
|
interface SplitContent { |
|
thought: string; |
|
codeBlock: string; |
|
} |
|
|
|
const getFromTo = (content: string, from: string, to: string): string => { |
|
const firstSplit = content.split(from, 2); |
|
if (firstSplit[1] !== undefined) { |
|
const secondSplit = firstSplit[1].split(to, 1); |
|
return secondSplit[0]; |
|
} else { |
|
return ''; |
|
} |
|
}; |
|
|
|
const splitContent = (content: string): SplitContent => { |
|
return { |
|
thought: getFromTo(content, '<think>', '</think>').trim(), |
|
codeBlock: getFromTo(content, '```yaml', '```').trim(), |
|
}; |
|
}; |
|
|
|
export const ScriptMaker = ({ |
|
setScript, |
|
setBlogURL, |
|
setBusy, |
|
busy, |
|
hfToken, |
|
}: { |
|
setScript: (script: string) => void; |
|
setBlogURL: (url: string) => void; |
|
setBusy: (busy: boolean) => void; |
|
busy: boolean; |
|
hfToken: string; |
|
}) => { |
|
const [model, setModel] = useState<string>(CONFIG.inferenceProviderModels[0]); |
|
const [customModel, setCustomModel] = useState<string>( |
|
CONFIG.inferenceProviderModels[0] |
|
); |
|
const usingModel = model === 'custom' ? customModel : model; |
|
|
|
const [input, setInput] = useState<string>(''); |
|
const [note, setNote] = useState<string>(isBlogMode ? getBlogPrompt() : ''); |
|
const [thought, setThought] = useState<string>(''); |
|
const [isGenerating, setIsGenerating] = useState<boolean>(false); |
|
|
|
const refThought = useRef<HTMLTextAreaElement | null>(null); |
|
|
|
useEffect(() => { |
|
setBusy(isGenerating); |
|
}, [isGenerating]); |
|
|
|
useEffect(() => { |
|
setTimeout(() => { |
|
|
|
if (refThought.current) { |
|
refThought.current.scrollTop = refThought.current.scrollHeight; |
|
} |
|
}, 10); |
|
}, [thought]); |
|
|
|
const generate = async () => { |
|
setIsGenerating(true); |
|
setThought(''); |
|
try { |
|
let responseContent = ''; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const client = new HfInference(hfToken); |
|
const chunks = client.chatCompletionStream({ |
|
model: usingModel, |
|
messages: [ |
|
{ |
|
role: 'user', |
|
content: getPromptGeneratePodcastScript(input, note), |
|
}, |
|
], |
|
temperature: 0.3, |
|
stream: true, |
|
provider: CONFIG.inferenceProvider, |
|
}); |
|
for await (const chunk of chunks) { |
|
|
|
|
|
|
|
|
|
const addedContent = chunk.choices[0].delta.content; |
|
responseContent += addedContent; |
|
const { thought, codeBlock } = splitContent(responseContent); |
|
setThought(thought); |
|
if (codeBlock.length > 0) { |
|
setScript(codeBlock); |
|
} |
|
} |
|
} catch (error) { |
|
console.error(error); |
|
alert(`ERROR: ${error}`); |
|
} |
|
setIsGenerating(false); |
|
setTimeout(() => { |
|
const generatePodcastBtn = document.getElementById( |
|
'btn-generate-podcast' |
|
); |
|
generatePodcastBtn?.click(); |
|
}, 50); |
|
}; |
|
|
|
return ( |
|
<div className="card bg-base-100 w-full shadow-xl"> |
|
<div className="card-body"> |
|
<h2 className="card-title">Step 1: Input information</h2> |
|
|
|
<select |
|
className="select select-bordered w-full" |
|
disabled={isGenerating || busy} |
|
onChange={(e) => { |
|
const idx = parseInt(e.target.value); |
|
const ex = EXAMPLES[idx]; |
|
if (ex) { |
|
setInput(ex.input); |
|
setNote(ex.note); |
|
} |
|
}} |
|
> |
|
<option selected disabled value={-1}> |
|
Try one of these examples!! |
|
</option> |
|
{EXAMPLES.map((example, index) => ( |
|
<option key={index} value={index}> |
|
{example.name} |
|
</option> |
|
))} |
|
</select> |
|
|
|
{isBlogMode && ( |
|
<> |
|
<input |
|
type="text" |
|
placeholder="Blog URL" |
|
className="input input-bordered w-full" |
|
onChange={(e) => setBlogURL(e.target.value)} |
|
/> |
|
</> |
|
)} |
|
|
|
<textarea |
|
className="textarea textarea-bordered w-full h-72 p-2" |
|
placeholder="Type your input information here (an article, a document, etc)..." |
|
value={input} |
|
onChange={(e) => setInput(e.target.value)} |
|
disabled={isGenerating || busy} |
|
></textarea> |
|
|
|
<textarea |
|
className="textarea textarea-bordered w-full h-24 p-2" |
|
placeholder="Optional note (the theme, tone, etc)..." |
|
value={note} |
|
onChange={(e) => setNote(e.target.value)} |
|
disabled={isGenerating || busy} |
|
></textarea> |
|
|
|
<select |
|
className="select select-bordered" |
|
value={model} |
|
onChange={(e) => setModel(e.target.value)} |
|
disabled={isGenerating || busy} |
|
> |
|
{CONFIG.inferenceProviderModels.map((s) => ( |
|
<option key={s} value={s}> |
|
{s} |
|
</option> |
|
))} |
|
<option value="custom">Custom</option> |
|
</select> |
|
|
|
{model === 'custom' && ( |
|
<input |
|
type="text" |
|
placeholder="Use a custom model from HF Hub (must be supported by Inference Providers)" |
|
className="input input-bordered w-full" |
|
value={customModel} |
|
onChange={(e) => setCustomModel(e.target.value)} |
|
/> |
|
)} |
|
|
|
{thought.length > 0 && ( |
|
<> |
|
<p>Thought process:</p> |
|
<textarea |
|
className="textarea textarea-bordered w-full h-24 p-2" |
|
value={thought} |
|
ref={refThought} |
|
readOnly |
|
></textarea> |
|
</> |
|
)} |
|
<button |
|
className="btn btn-primary mt-2" |
|
onClick={generate} |
|
disabled={isGenerating || busy || input.length < 10} |
|
> |
|
{isGenerating ? ( |
|
<> |
|
<span className="loading loading-spinner loading-sm"></span> |
|
Generating... |
|
</> |
|
) : ( |
|
'Generate script' |
|
)} |
|
</button> |
|
|
|
<div role="alert" className="alert text-sm"> |
|
<svg |
|
xmlns="http://www.w3.org/2000/svg" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
className="stroke-info h-6 w-6 shrink-0" |
|
> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
strokeWidth="2" |
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" |
|
></path> |
|
</svg> |
|
<span> |
|
The LLM may generate an incorrect YAML. If it fails on Step 2, |
|
re-generate the script or adding a note to force it to follow YAML |
|
format. |
|
</span> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
}; |
|
|