-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from sora32127/add_ai_completion_textbox
AI入力補助機能を追加
- Loading branch information
Showing
8 changed files
with
449 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { FormEvent, useCallback, useEffect, useRef, useState } from "react"; | ||
|
||
interface ComponentProps { | ||
className?: string; | ||
parentComponentStateValue: string; | ||
onInputChange: (value: string) => void; | ||
placeholder?: string; | ||
prompt?: string; | ||
} | ||
|
||
export default function TextInputBoxAI({ | ||
className = "", | ||
parentComponentStateValue, | ||
onInputChange, | ||
placeholder = "", | ||
prompt="", | ||
}: ComponentProps) { | ||
const [suggestions, setSuggestions] = useState<string | null>(null); | ||
const textarea = useRef<HTMLTextAreaElement>(null); | ||
const timer = useRef<NodeJS.Timeout | null>(null); | ||
|
||
const resetSuggestions = () => { | ||
setSuggestions(null); | ||
}; | ||
|
||
const handleInputValue = useCallback( | ||
(e: FormEvent<HTMLTextAreaElement>) => { | ||
if (timer.current) { | ||
clearTimeout(timer.current); | ||
} | ||
|
||
if (e.currentTarget.value) { | ||
const text = e.currentTarget.value; | ||
|
||
timer.current = setTimeout(async () => { | ||
try { | ||
const formData = new FormData(); | ||
formData.append("text", text); | ||
formData.append("context", createContextSentense()); | ||
formData.append("prompt", prompt); | ||
const response = await fetch("/api/ai/getCompletion", { | ||
method: "POST", | ||
body: formData, | ||
}); | ||
const suggestion = await response.json(); | ||
setSuggestions(suggestion); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
}, 1000); | ||
} else { | ||
setSuggestions(""); | ||
} | ||
onInputChange(e.currentTarget.value); | ||
}, | ||
[onInputChange] | ||
); | ||
|
||
const commitSuggestions = () => { | ||
const textareaElement = textarea.current; | ||
if (textareaElement && suggestions) { | ||
const newValue = textareaElement.value + suggestions; | ||
textareaElement.value = newValue; | ||
textareaElement.focus(); | ||
resetSuggestions(); | ||
onInputChange(newValue); | ||
} | ||
}; | ||
|
||
const handleSuggestions = (e: KeyboardEvent) => { | ||
if (e.key === "Shift") { | ||
e.preventDefault(); | ||
commitSuggestions(); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
if (suggestions) { | ||
addEventListener("keydown", handleSuggestions); | ||
addEventListener("click", resetSuggestions); | ||
} | ||
return () => { | ||
removeEventListener("keydown", handleSuggestions); | ||
removeEventListener("click", resetSuggestions); | ||
}; | ||
}, [suggestions]); | ||
|
||
const getContextFromLocalStorage = () => { | ||
const situationValue = window.localStorage.getItem("situationValue"); | ||
const reflectionValue = window.localStorage.getItem("reflectionValue"); | ||
const counterReflectionValue = window.localStorage.getItem("counterReflectionValue"); | ||
const noteValue = window.localStorage.getItem("noteValue"); | ||
return { | ||
situationValue, | ||
reflectionValue, | ||
counterReflectionValue, | ||
noteValue, | ||
}; | ||
} | ||
|
||
const createContextSentense = () => { | ||
const contextValues = getContextFromLocalStorage(); | ||
const contextSetense = `以下のテキストはコンテキストを表すものです。文章を補完する際の参考にしてください。状況: ${contextValues.situationValue}。反省: ${contextValues.reflectionValue}。反省に対する反省: ${contextValues.counterReflectionValue}。メモ: ${contextValues.noteValue}。`; | ||
return contextSetense; | ||
} | ||
|
||
return ( | ||
<div className={className}> | ||
<textarea | ||
ref={textarea} | ||
value={parentComponentStateValue} | ||
onChange={handleInputValue} | ||
placeholder={placeholder} | ||
className="w-full border-2 border-base-content rounded-lg p-2 placeholder-slate-500" | ||
/> | ||
{suggestions && ( | ||
<> | ||
<p className="text-base-content mt-2">[補完候補]: {suggestions}</p> | ||
<p className="text-info mt-2"> | ||
Shiftキーまたはボタンを押して補完できます。 | ||
</p> | ||
<button | ||
onClick={commitSuggestions} | ||
className="bg-primary text-white font-bold py-2 px-4 rounded mt-2" | ||
> | ||
補完する | ||
</button> | ||
</> | ||
)} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { ActionFunctionArgs } from '@remix-run/node'; | ||
import { OpenAI } from 'openai'; | ||
|
||
|
||
const OPENAI_API_KEY = process.env.OPENAI_API_KEY | ||
|
||
export async function action ({ request }: ActionFunctionArgs) { | ||
const formData = await request.formData(); | ||
const text = formData.get("text") as string | null; | ||
const context = formData.get("context") as string | ""; | ||
const prompt = formData.get("prompt") as string | ""; | ||
if (!text) { | ||
return new Response("Bad Request", { status: 400 }); | ||
} | ||
const result = await getCompletion(text, context, prompt); | ||
return new Response(JSON.stringify(result), { | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
} | ||
|
||
function createPrompt(prompt: string) { | ||
if (prompt == "reflection") { | ||
return "自分の行動に何が問題があったのか気にしています。あなたは、何が問題だったのかを考える補佐をしてください。" | ||
} else if(prompt== "counterReflection") { | ||
return "もし自分がその行動をしなかったらどうなっていたのかや、本当はどうするべきだったのか反実仮想をしようとしています。あなたは反実仮想の補佐をしてください。" | ||
} else if (prompt == "note") { | ||
return "書ききれなかった何かを補足したいと思っています。あなたは、ユーザーの補足を補佐してください。" | ||
} else if (prompt == "title") { | ||
return "タイトルを考えるのに困っています。あなたは、タイトルを考える補佐をしてください。" | ||
} else { | ||
return "" | ||
} | ||
} | ||
|
||
export async function getCompletion(text:string, context:string, prompt:string) { | ||
const openAI = new OpenAI({ | ||
apiKey: OPENAI_API_KEY, | ||
}) | ||
|
||
const promptSentence = createPrompt(prompt) | ||
const result = await openAI.chat.completions.create({ | ||
messages: [ | ||
{ | ||
role: "system", | ||
content: `あなたは優秀な日本語の作文アシスタントです。ユーザーが入力した文章の続きを、自然で文法的に正しい日本語で簡潔に生成してください。与えられた文脈を考慮し、ユーザーの意図を汲み取って適切な文章を生成することを心がけてください。${promptSentence}` | ||
}, | ||
{ | ||
role: "assistant", | ||
content: `承知しました。ユーザーの入力文に続く自然な日本語の文章を簡潔に生成いたします。以下の文脈情報を参考にします。\n文脈情報: ${context}`, | ||
}, | ||
{ | ||
role: "user", | ||
content: `次の文章の続きを、文脈を考慮して自然な日本語で簡潔に生成してください。40文字程度でお願いします。\n「${text}」`, | ||
} | ||
], | ||
model: 'gpt-3.5-turbo' | ||
}); | ||
const completion = result.choices[0].message.content | ||
return completion | ||
} |
Oops, something went wrong.
7d7cb16
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
healthy-person-emulator-dotorg-kmxg – ./
healthy-person-emulator-dotorg.vercel.app
healthy-person-emulator-dotorg-kmxg-sora32127s-projects.vercel.app
healthy-person-emulator.org
healthy-person-emulator-dotorg-k-git-12b613-sora32127s-projects.vercel.app