Skip to content

Commit

Permalink
自動タグ付けを追加
Browse files Browse the repository at this point in the history
  • Loading branch information
Nah0012 committed Jan 17, 2024
1 parent 9d8a0e5 commit 05b1b92
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ VITE_FIREBASE_PROJECT_ID="infox-dc968"
VITE_FIREBASE_STORAGE_BUCKET="infox-dc968.appspot.com"
VITE_FIREBASE_MESSAGING_SENDER_ID="82111709736"
VITE_FIREBASE_APP_ID="1:82111709736:web:0c353713d0508816a16b1d"
VITE_FIREBASE_MEASUREMENT_ID="G-6SRSWCR5D5"
VITE_FIREBASE_MEASUREMENT_ID="G-6SRSWCR5D5"
OPENAI_API_KEY="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"
16 changes: 0 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +0,0 @@
# 実現したこと

Vite + React(TypeScript) + Firebase の構成で以下の機能を持つアプリを作成し、Firebase にホスティングした。GitHub へ Push 時に Firebase にホスティングするようワークフローを設定している。

- サインイン、サインアウト
- メモの一覧表示
- メモの登録、更新
- メモの削除

# 注意事項

- .env ファイルは[Vite + React + Firebase のハンズオン](https://qiita.com/Inp/items/906100b46fcbda6fb2ee)等を参考に別途作成する必要あり

# 詳細

[React(TypeScript) + Firebase でメモアプリ開発](https://zenn.dev/shoji9x9/articles/eb185b3d66567b)参照。
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"create-vite": "^5.0.0",
"dompurify": "^3.0.8",
"firebase": "^10.4.0",
"openai": "^4.0.0",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dnd": "^14.0.2",
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import MenuIcon from "@mui/icons-material/Menu";
import { IconButton } from "@mui/material";
import { useState } from "react";
import { ViewMemo } from "./services/ViewMemo";
import {APIKeyPage} from "./pages/APIKeyPage";


function App() {
Expand All @@ -33,6 +34,7 @@ function App() {
<Route path="/memolist" element={<MemoList />} />
<Route path="/memo/:id?" element={<Memo />} />
<Route path="/view/:id" element={<ViewMemo />} />
<Route path="/api-key" element={<APIKeyPage />} />
</Routes>
</div>
<IconButton className="fixedIconButton" onClick={handleSidebarOpen}>
Expand Down
62 changes: 62 additions & 0 deletions src/pages/APIKeyPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useState, ChangeEvent, FormEvent, useEffect } from 'react';
import { Button, TextField, Typography } from "@mui/material";
import { useRecoilState } from 'recoil';
import { userAtom } from "../states/userAtom";
import { useNavigate } from 'react-router-dom';

export function APIKeyPage(): JSX.Element {
const [apiKey, setApiKey] = useState('');
const [user, setUser] = useRecoilState(userAtom);
const navigate = useNavigate();

useEffect(() => {
if (user?.apiKey) {
setApiKey(user.apiKey);
}
}, [user]);

const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setUser({ ...user, apiKey: apiKey });
navigate('/memolist');
};

const handleDelete = () => {
setApiKey('');
setUser({ ...user, apiKey: '' });
};

return (
<div>
<Typography variant="h4" gutterBottom>
API Keyを入力してください。
</Typography>
{user?.apiKey && (
<>
<Typography variant="h6" gutterBottom>
登録済みです
</Typography>
<Button onClick={handleDelete} variant="contained" color="secondary">
API Keyを削除する
</Button>
</>
)}
{!user?.apiKey && (
<form onSubmit={handleSubmit}>
<TextField
label="API Key"
value={apiKey}
onChange={(e: ChangeEvent<HTMLInputElement>) => setApiKey(e.target.value)}
fullWidth
margin="normal"
/>
<Button type="submit" variant="contained" color="primary">
Save
</Button>
</form>
)}
</div>
);
}

export default APIKeyPage;
1 change: 1 addition & 0 deletions src/pages/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export function Header(): JSX.Element {
return {
userId: null,
userName: null,
apiKey: null,
};
});
clearUserInLocalStorage();
Expand Down
58 changes: 47 additions & 11 deletions src/pages/Memo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -21,11 +23,15 @@ export function Memo(): JSX.Element {
const [titleError, setTitleError] = useState(false);
const [content, setContent] = useState("");


// タグ関連の状態とイベントハンドラー
const [tags, setTags] = useState<Tag[]>([
{ id: '1', text: 'タグなし' }
]);
let openai: OpenAI;
if (loginUser?.apiKey) {
openai = new OpenAI({
apiKey: loginUser.apiKey,
dangerouslyAllowBrowser: true
});
}

const [tags, setTags] = useState<Tag[]>([]);

interface Tag {
id: string;
Expand Down Expand Up @@ -58,36 +64,65 @@ export function Memo(): JSX.Element {
navigate("/memolist");
};

const generateTags = async (content: string): Promise<Tag[]> => {
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(),
}));
}
} else {
// createdAt が null の場合のエラーハンドリング
console.error("createdAt is null");
setCreatedAt(new Date());
}
};

Expand Down Expand Up @@ -116,6 +151,7 @@ export function Memo(): JSX.Element {
get();
}, [id, loginUser, setMessageAtom]);


return (
<>
<Box sx={{ display: "flex" }}>
Expand Down
6 changes: 5 additions & 1 deletion src/pages/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onClose }) => {
setUserAtom({
userId: null,
userName: null,
apiKey: null,
});
clearUserInLocalStorage();
navigate("/");
Expand Down Expand Up @@ -66,6 +67,10 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onClose }) => {
<AddCircleIcon />
<ListItemText primary="新しいメモ" />
</ListItem>
<ListItem component={Link} to="/api-key">
<AddCircleIcon />
<ListItemText primary="API Keyを登録する" />
</ListItem>
</>
) : (
<>
Expand All @@ -77,7 +82,6 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onClose }) => {
アプリの機能を利用するにはサインインしてください。
</Typography>
</ListItem>
{/* 他のヘルプメニューアイテムを追加する場合はここに追加 */}
</>
)}
</List>
Expand Down
2 changes: 2 additions & 0 deletions src/states/userAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { atom } from "recoil";
export type LoginUser = {
userId: string | null;
userName: string | null;
apiKey: string | null;
};

export const userAtom = atom({
key: "userAtom",
default: {
userId: localStorage.getItem("userId") || null,
userName: localStorage.getItem("userName") || null,
apiKey: localStorage.getItem("apiKey") || null,
} as LoginUser,
});
2 changes: 2 additions & 0 deletions src/utils/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ export function setUserToLocalStorage(user?: User) {
if (user?.uid) {
localStorage.setItem("userId", user.uid);
localStorage.setItem("userName", user.displayName || "");
localStorage.setItem("apiKey", user.apiKey || "");
}
}

export function clearUserInLocalStorage() {
localStorage.removeItem("userId");
localStorage.removeItem("userName");
localStorage.removeItem("apiKey");
}

0 comments on commit 05b1b92

Please sign in to comment.