) => {
+ event.preventDefault();
+ setUser({ ...user, apiKey: apiKey });
+ navigate('/memolist');
+ };
+
+ const handleDelete = () => {
+ setApiKey('');
+ setUser({ ...user, apiKey: '' });
+ };
+
+ return (
+
+
+ API Keyを入力してください。
+
+ {user?.apiKey && (
+ <>
+
+ 登録済みです
+
+
+ >
+ )}
+ {!user?.apiKey && (
+
+ )}
+
+ );
+}
+
+export default APIKeyPage;
\ No newline at end of file
diff --git a/src/pages/Header.tsx b/src/pages/Header.tsx
index 98b97bf..9519a0c 100644
--- a/src/pages/Header.tsx
+++ b/src/pages/Header.tsx
@@ -17,6 +17,7 @@ export function Header(): JSX.Element {
return {
userId: null,
userName: null,
+ apiKey: null,
};
});
clearUserInLocalStorage();
diff --git a/src/pages/Memo.tsx b/src/pages/Memo.tsx
index 0fecc43..3233f6e 100644
--- a/src/pages/Memo.tsx
+++ b/src/pages/Memo.tsx
@@ -11,6 +11,8 @@ import 'react-quill/dist/quill.snow.css';
import './toolbar.css';
import { useEffect, useState } from 'react';//suzu
import { WithContext as ReactTags } from 'react-tag-input';//suzu
+import { OpenAI } from "openai";
+
export function Memo(): JSX.Element {
@@ -21,11 +23,15 @@ export function Memo(): JSX.Element {
const [titleError, setTitleError] = useState(false);
const [content, setContent] = useState("");
-
- // タグ関連の状態とイベントハンドラー
- const [tags, setTags] = useState([
- { id: '1', text: 'タグなし' }
- ]);
+ let openai: OpenAI;
+ if (loginUser?.apiKey) {
+ openai = new OpenAI({
+ apiKey: loginUser.apiKey,
+ dangerouslyAllowBrowser: true
+ });
+ }
+
+ const [tags, setTags] = useState([]);
interface Tag {
id: string;
@@ -58,28 +64,57 @@ export function Memo(): JSX.Element {
navigate("/memolist");
};
+ const generateTags = async (content: string): Promise => {
+ const gptResponse = await openai.chat.completions.create({
+ model: 'gpt-3.5-turbo',
+ messages: [{"role": "user", "content": "与えられたテキストから適切なハッシュタグを生成してください。テキストの主要なトピックやキーワードを考慮し、関連性の高いタグを提案してください。"}, {"role": "user", "content": content}],
+ temperature: 0.5,
+ max_tokens: 60,
+ });
+
+ const messageContent = gptResponse.choices[0].message.content;
+ if (messageContent === null) {
+ return [];
+ }
+ const tags = messageContent.split(" ").map((tag, index) => {
+ return { id: index.toString(), text: tag };
+ });
+ return tags;
+ };
+
const save = async () => {
if (!title) {
setTitleError(true);
return;
}
const updatedAt = new Date();
- let memoCreatedAt = createdAt;
+ const memoCreatedAt = createdAt;
if (!id && !createdAt) {
setCreatedAt(updatedAt);
}
if (memoCreatedAt) {
try {
- //await saveMemo({ id, title, content, updatedAt, createdAt: createdAt || updatedAt }, loginUser);
- await saveMemo({ id, title, content, tags, updatedAt, createdAt: memoCreatedAt }, loginUser);
+ if (!loginUser.apiKey) {
+ await saveMemo({ id, title, content, tags:[], updatedAt, createdAt: memoCreatedAt }, loginUser);
+ setMessageAtom((prev) => ({
+ ...prev,
+ ...successMessage("Saved"),
+ }));
+ navigate("/memolist");
+ return;
+ }
+ else {
+ const generatedTags = await generateTags(content);
+ setTags(generatedTags);
+
+ await saveMemo({ id, title, content, tags: generatedTags, updatedAt, createdAt: memoCreatedAt }, loginUser);
setMessageAtom((prev) => ({
...prev,
...successMessage("Saved"),
}));
navigate("/memolist");
- //backToMemoList();
- } catch (e) {
+ }} catch (e) {
setMessageAtom((prev) => ({
...prev,
...exceptionMessage(),
@@ -87,7 +122,7 @@ export function Memo(): JSX.Element {
}
} else {
// createdAt が null の場合のエラーハンドリング
- console.error("createdAt is null");
+ setCreatedAt(new Date());
}
};
@@ -116,6 +151,7 @@ export function Memo(): JSX.Element {
get();
}, [id, loginUser, setMessageAtom]);
+
return (
<>
diff --git a/src/pages/MemoList.tsx b/src/pages/MemoList.tsx
index d35509f..24305e1 100644
--- a/src/pages/MemoList.tsx
+++ b/src/pages/MemoList.tsx
@@ -100,6 +100,16 @@ export function MemoList(): JSX.Element {
}
};
+ const removeHtmlTags = (htmlString: string): string => {
+ // 新しい行で
タグを置き換える
+ const newlineRegex = /
/gi;
+ htmlString = htmlString.replace(newlineRegex, ' ');
+
+ // DOMParserを使用してHTML文字列からHTMLタグを取り除く
+ const doc = new DOMParser().parseFromString(htmlString, 'text/html');
+ return doc.body.textContent || "";
+ };
+
useEffect(() => {
getMemoList();
}, [loginUser, getMemoList]);
@@ -189,7 +199,7 @@ export function MemoList(): JSX.Element {
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
- setMemoList(items);
+ setMemoList(items);
};
@@ -336,7 +346,7 @@ export function MemoList(): JSX.Element {
width: '80%',
display: 'inline-block' // インライン要素でも幅を適用させる
}}>
- {truncateText(memo.content, 100)}
+ {truncateText(removeHtmlTags(memo.content), 100)}
>
}
diff --git a/src/pages/Sidebar.tsx b/src/pages/Sidebar.tsx
index acd3bcf..48b2722 100644
--- a/src/pages/Sidebar.tsx
+++ b/src/pages/Sidebar.tsx
@@ -30,6 +30,7 @@ const Sidebar: React.FC = ({ isOpen, onClose }) => {
setUserAtom({
userId: null,
userName: null,
+ apiKey: null,
});
clearUserInLocalStorage();
navigate("/");
@@ -66,6 +67,10 @@ const Sidebar: React.FC = ({ isOpen, onClose }) => {
+
+
+
+
>
) : (
<>
@@ -77,7 +82,6 @@ const Sidebar: React.FC = ({ isOpen, onClose }) => {
アプリの機能を利用するにはサインインしてください。
- {/* 他のヘルプメニューアイテムを追加する場合はここに追加 */}
>
)}
diff --git a/src/states/userAtom.ts b/src/states/userAtom.ts
index 1519472..8ff543a 100644
--- a/src/states/userAtom.ts
+++ b/src/states/userAtom.ts
@@ -3,6 +3,7 @@ import { atom } from "recoil";
export type LoginUser = {
userId: string | null;
userName: string | null;
+ apiKey: string | null;
};
export const userAtom = atom({
@@ -10,5 +11,6 @@ export const userAtom = atom({
default: {
userId: localStorage.getItem("userId") || null,
userName: localStorage.getItem("userName") || null,
+ apiKey: localStorage.getItem("apiKey") || null,
} as LoginUser,
});