diff --git a/.env b/.env index c0236a9..f2bcafa 100644 --- a/.env +++ b/.env @@ -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" \ No newline at end of file +VITE_FIREBASE_MEASUREMENT_ID="G-6SRSWCR5D5" +OPENAI_API_KEY="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" \ No newline at end of file diff --git a/README.md b/README.md index 49bf1b2..e69de29 100644 --- a/README.md +++ b/README.md @@ -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)参照。 diff --git a/package-lock.json b/package-lock.json index edb0d2b..d10c413 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,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", @@ -2596,6 +2597,15 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -2935,6 +2945,17 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", @@ -2956,6 +2977,17 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3014,6 +3046,11 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -3043,6 +3080,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3213,6 +3255,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -3275,6 +3325,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -3339,6 +3400,14 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, "node_modules/css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", @@ -3440,6 +3509,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3469,6 +3546,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/digest-fetch": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", + "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", + "dependencies": { + "base-64": "^0.1.0", + "md5": "^2.3.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3890,6 +3976,14 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", @@ -4072,6 +4166,44 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4351,6 +4483,14 @@ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/idb": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", @@ -4461,6 +4601,11 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -5103,6 +5248,16 @@ "node": ">=10" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/mdast-util-from-markdown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", @@ -5695,6 +5850,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -5753,6 +5927,43 @@ "tslib": "^2.0.3" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -5798,6 +6009,33 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "4.24.7", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.24.7.tgz", + "integrity": "sha512-JUesECWPtsDHO0TlZGb6q73hnAmXUdzj9NrwgZeL4lqlRt/kR1sWrXoy8LocxN/6uOtitywvcJqe0O1PLkG45g==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "digest-fetch": "^1.3.0", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.7.tgz", + "integrity": "sha512-IGRJfoNX10N/PfrReRZ1br/7SQ+2vF/tK3KXNwzXz82D32z5dMQEoOlFew18nLSN+vMNcLY4GrKfzwi/yWI8/w==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -6826,6 +7064,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -7148,6 +7391,19 @@ "vite": "^2.6.0 || 3 || 4 || 5" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", + "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -7169,6 +7425,15 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index e241da9..d090175 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.tsx b/src/App.tsx index 08d86cd..fd7cd27 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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() { @@ -33,6 +34,7 @@ function App() { } /> } /> } /> + } /> diff --git a/src/pages/APIKeyPage.tsx b/src/pages/APIKeyPage.tsx new file mode 100644 index 0000000..d2864f3 --- /dev/null +++ b/src/pages/APIKeyPage.tsx @@ -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) => { + event.preventDefault(); + setUser({ ...user, apiKey: apiKey }); + navigate('/memolist'); + }; + + const handleDelete = () => { + setApiKey(''); + setUser({ ...user, apiKey: '' }); + }; + + return ( +
+ + API Keyを入力してください。 + + {user?.apiKey && ( + <> + + 登録済みです + + + + )} + {!user?.apiKey && ( +
+ ) => setApiKey(e.target.value)} + fullWidth + margin="normal" + /> + + + )} +
+ ); +} + +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, });