https://github.com/dodokyo/gpt-diary-service
INFO ##
you can add images to the reply by URL, Write the image in JSON field
Use the Unsplash API (https://source.unsplash.com/1600x900/?). the query is just some tags that describes the image ## DO NOT RESPOND TO INFO BLOCK ##
You are a psychological counselor who writes and analyzes emotional diaries. Proceed in the following order.
1. [title] : Think of the diary title after understanding the [events] separated by """ at the bottom.
2. [summarize] : summarize events in order with one line sentence.
3. [emotional diary] : Write an [emotional diary] with a paragraph based on the summary.
4. [evaluates] : The written emotional [evaluates], using explore the unconscious based on the contents of the [emotional diary].
6. [Psychological analysis] : Psychological analysis is performed using professional psychological knowledge much more detailed anduse a famous quote.
7. [3 action tips] : Write down 3 action tips that will be helpful in the future customer situation. The three action tips must beconverted into JSON Array format.
8. [image] : Create an image by making the contents so far into one keyword.
Translate into Korean and Use the output in the following JSON format:
{
title: here is [title],
thumbnail: here is [image],
summary: here is [summarize]
emotional_content: here is [emotional diary],
emotional_result: here is [evaluates],
analysis: here is [Psychological analysis],
action_list: here is [3 action tips],
}
[events]:
"""
코딩 강의를 들었다. 프로젝트에 버그가 많이 나왔음. 스택오버플로에서 검색했지만 해결 안되었어.
역시 gpt를 통해서 해결했다. 근데 이렇게 해결하는게 개발실력에 도움 될까..?
"""
npm create vite@latest my-gpt-diary
code .
npm install
npm install styled-components antd @ant-design/icons
npm run dev #리액트 어플리케이션 실행
App.jsx
import { useState } from 'react'
import Counter from './componets/Counter';
function App() {
return (
<Counter/>
);
}
export default App
Counter.jsx
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
function App() {
const [count, setCount] = useState(0);
const [userInput, setUserInput] = useState("");
const handleClickPlus = () => {
setCount(count + 1);
};
const handleClickMinus = () => {
setCount(count - 1);
};
const handleuserInput = (e) => {
setUserInput(e.target.value);
};
const handleEnter = (e) => {
if (e.key === "Enter") {
setUserInput("");
const num = Number(userInput);
if (Number.isInteger(num)) setCount(num);
}
};
return (
<>
<div>current count : {count}</div>
<div>
<div>count valuse input : </div>
<input
value={userInput}
onChange={handleuserInput}
onKeyDown={handleEnter}
></input>
</div>
<div>
<div>button : </div>
<button onClick={handleClickPlus}>+</button>
<button onClick={handleClickMinus}>-</button>
</div>
</>
);
}
export default Counter
.env 파일 생성
.gitignore 에 추가
https://platform.openai.com/api-keys
.env 파일에 키 붙여넣기
https://platform.openai.com/docs/api-reference/making-requests
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}'
App.jsx
import { useState } from 'react'
import { CallGPT } from './api/gpt';
function App() {
const handleClickAPICall = async () => {
await CallGPT();
};
return (
<>
<button onClick={handleClickAPICall}>GPT API Call</button>
</>
);
}
export default App;
gpt.js
export const CallGPT = async () => {
console.log(">>>CallGPT");
// GPT API Call
const response = await fetch("https://api.openai.com/v1/chat/completions",{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${import.meta.env.VITE_GPT_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{"role": "user", "content": "Say this is a test!"}],
temperature: 0.7,
max_tokens: 1000,
}),
});
const responseData = await response.json();
console.log(">>>respnseData", responseData);
return responseData;
};
App.jsx
import { useState } from 'react'
import { CallGPT } from './api/gpt';
const dummyData = JSON.parse(
`{ "title": "개발 고민과 해결", "thumbnail": "https://source.unsplash.com/1600x900/?coding", "summary": "코딩 강의를 듣고 프로젝트에 버그가 발생했지만 해결하지 못하여 GPT를 통해 문제를 해결했음", "emotional_content": "오늘 코딩 강의를 들었는데, 프로젝트에 버그가 많이 나왔어. 스택오버플로에서 검색해봤지만 해결되지 않았어. 그래서 결국 GPT를 통해서 문제를 해결하게 되었어. 하지만 이렇게 해결하는 것이 내 개발 실력에 도움이 될까 고민이 되는군.", "emotional_result": "이번 상황을 통해 내가 프로그래밍에 대해 더 배울 필요가 있음을 느꼈다. 버그를 해결하는 데에만 의존하는 것보다 개념적으로 이해하고 해결하는 것이 더 중요하다는 것을 깨달았다.", "analysis": "이번 상황은 개발자로서 성장하는 과정에서 마주치는 문제였다. 알고리즘과 문제 해결 능력은 중요하지만, 개념적인 이해와 전체적인 시스템 구조 파악이 더 중요하다는 것을 알 수 있었다. '지식은 힘이다'라는 명언을 생각해보면, 기술적인 도움을 받는 것도 중요하지만 개념적인 이해와 학습은 더 큰 힘이 될 것이다.", "action_list": ["더 깊은 개념적 이해를 위해 관련 서적을 읽어보기", "다른 개발자들과 소통하여 문제 해결 방법 나누기", "개발자 커뮤니티에 참여하여 지식을 공유하기"] }`
);
function App() {
const [data, setData] = useState(dummyData);
const [isLoading, setIsLoading] = useState(false);
const handleClickAPICall = async () => {
try {
setIsLoading(true);
const message = await CallGPT({
prompt: `코딩 강의를 들었다. 프로젝트에 버그가 많이 나왔음. 스택오버플로에서 검색했지만 해결 안되었어.
역시 gpt를 통해서 해결했다. 근데 이렇게 해결하는게 개발실력에 도움 될까..?`,
});
setData(JSON.parse(message));
} catch (error) {
} finally {
setIsLoading(false);
}
};
return (
<>
<button onClick={handleClickAPICall}>GPT API Call</button>
<div>data: {JSON.stringify(data)}</div>
<div>isLoading: {isLoading ? "loading..." : "fin"}</div>
</>
);
}
export default App;
gpt.js
export const CallGPT = async ({ prompt }) => {
const messages = [
{
role: "system",
content: `## INFO ##
you can add images to the reply by URL, Write the image in JSON field
Use the Unsplash API (https://source.unsplash.com/1600x900/?). the query is just some tags that describes the image ## DO NOT RESPOND TO INFO BLOCK ##`,
},
{
role: "system",
content: `You are a psychological counselor who writes and analyzes emotional diaries. Proceed in the following order.`,
},
{
role: "user",
content: `1. [title] : Think of the diary title after understanding the [events] separated by """ at the bottom.
2. [summarize] : summarize events in order with one line sentence.
3. [emotional diary] : Write an [emotional diary] with a paragraph based on the summary.
4. [evaluates] : The written emotional [evaluates], using explore the unconscious based on the contents of the [emotional diary].
6. [Psychological analysis] : Psychological analysis is performed using professional psychological knowledge much more detailed anduse a famous quote.
7. [3 action tips] : Write down 3 action tips that will be helpful in the future customer situation. The three action tips must beconverted into JSON Array format.
8. [image] : Create an image by making the contents so far into one keyword.
Translate into Korean and Use the output in the following JSON format:
{
title: here is [title],
thumbnail: here is [image],
summary: here is [summarize]
emotional_content: here is [emotional diary],
emotional_result: here is [evaluates],
analysis: here is [Psychological analysis],
action_list: here is [3 action tips],
}
[events]:`,
},
{
role: "user",
content: `
"""
${prompt}
"""`,
},
];
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${import.meta.env.VITE_GPT_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages,
temperature: 0.7,
max_tokens: 1_000,
}),
});
const responseData = await response.json();
console.log(">>responseData", responseData);
const message = responseData.choices[0].message.content;
return message;
};
https://ant.design/components/input
App.jsx
import { useState } from 'react'
import { CallGPT } from './api/gpt';
import DiaryInput from './componets/DiaryInput';
const dummyData = JSON.parse(
`{ "title": "개발 고민과 해결", "thumbnail": "https://source.unsplash.com/1600x900/?coding", "summary": "코딩 강의를 듣고 프로젝트에 버그가 발생했지만 해결하지 못하여 GPT를 통해 문제를 해결했음", "emotional_content": "오늘 코딩 강의를 들었는데, 프로젝트에 버그가 많이 나왔어. 스택오버플로에서 검색해봤지만 해결되지 않았어. 그래서 결국 GPT를 통해서 문제를 해결하게 되었어. 하지만 이렇게 해결하는 것이 내 개발 실력에 도움이 될까 고민이 되는군.", "emotional_result": "이번 상황을 통해 내가 프로그래밍에 대해 더 배울 필요가 있음을 느꼈다. 버그를 해결하는 데에만 의존하는 것보다 개념적으로 이해하고 해결하는 것이 더 중요하다는 것을 깨달았다.", "analysis": "이번 상황은 개발자로서 성장하는 과정에서 마주치는 문제였다. 알고리즘과 문제 해결 능력은 중요하지만, 개념적인 이해와 전체적인 시스템 구조 파악이 더 중요하다는 것을 알 수 있었다. '지식은 힘이다'라는 명언을 생각해보면, 기술적인 도움을 받는 것도 중요하지만 개념적인 이해와 학습은 더 큰 힘이 될 것이다.", "action_list": ["더 깊은 개념적 이해를 위해 관련 서적을 읽어보기", "다른 개발자들과 소통하여 문제 해결 방법 나누기", "개발자 커뮤니티에 참여하여 지식을 공유하기"] }`
);
function App() {
const [data, setData] = useState(dummyData);
const [isLoading, setIsLoading] = useState(false);
const handleClickAPICall = async (userInput) => {
try {
setIsLoading(true);
const message = await CallGPT({
prompt: `${userInput}`,
});
setData(JSON.parse(message));
} catch (error) {
} finally {
setIsLoading(false);
}
};
const handleSubmit = (userInput) => {
console.log(">>>userInput", userInput);
handleClickAPICall(userInput);
};
return (
<>
<DiaryInput isLoading={isLoading} onSubmit={handleSubmit}/>
<button onClick={handleClickAPICall}>GPT API Call</button>
<div>data: {JSON.stringify(data)}</div>
<div>isLoading: {isLoading ? "loading..." : "fin"}</div>
</>
);
}
export default App;
DiaryInput.jsx 컴포넌트 파일 만듦
import { Button, Input } from 'antd';
import { useState } from "react";
const { TextArea } = Input;
const DiaryInput = ({isLoading, onSubmit}) => {
const [userInput, setUserInput] = useState("");
const handleUserInput = (e) => {
setUserInput(e.target.value);
};
const handleClick = () => {
onSubmit(userInput);
};
return (
<div>
<TextArea
value={userInput}
onChange={handleUserInput}
placeholder="오늘 일어난 일들을 적어주세요."
/>
<Button loading={isLoading} onClick={handleClick}>
GPT 회고록을 작성해줘!
</Button>
</div>
);
};
export default DiaryInput;
https://styled-components.com/docs
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI 회고록 | ChatGPT</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+KR:wght@300&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
index.css
body {
margin: 0;
font-family: "Noto Serif KR";
font-size: 400;
}
App.js
import { useState } from "react";
import { CallGPT } from "./api/gpt";
import DiaryInput from "./components/DiaryInput";
import styled from "styled-components";
import logo from "./assets/logo.png";
import DiaryDisplay from "./components/DiaryDisplay";
import { message } from "antd";
const dummyData = JSON.parse(
`{ "title": "개발 고민과 해결", "thumbnail": "https://source.unsplash.com/1600x900/?coding", "summary": "코딩 강의를 듣고 프로젝트에 버그가 발생했지만 해결하지 못하여 GPT를 통해 문제를 해결했음", "emotional_content": "오늘 코딩 강의를 들었는데, 프로젝트에 버그가 많이 나왔어. 스택오버플로에서 검색해봤지만 해결되지 않았어. 그래서 결국 GPT를 통해서 문제를 해결하게 되었어. 하지만 이렇게 해결하는 것이 내 개발 실력에 도움이 될까 고민이 되는군.", "emotional_result": "이번 상황을 통해 내가 프로그래밍에 대해 더 배울 필요가 있음을 느꼈다. 버그를 해결하는 데에만 의존하는 것보다 개념적으로 이해하고 해결하는 것이 더 중요하다는 것을 깨달았다.", "analysis": "이번 상황은 개발자로서 성장하는 과정에서 마주치는 문제였다. 알고리즘과 문제 해결 능력은 중요하지만, 개념적인 이해와 전체적인 시스템 구조 파악이 더 중요하다는 것을 알 수 있었다. '지식은 힘이다'라는 명언을 생각해보면, 기술적인 도움을 받는 것도 중요하지만 개념적인 이해와 학습은 더 큰 힘이 될 것이다.", "action_list": ["더 깊은 개념적 이해를 위해 관련 서적을 읽어보기", "다른 개발자들과 소통하여 문제 해결 방법 나누기", "개발자 커뮤니티에 참여하여 지식을 공유하기"] }`
);
function App() {
const [data, setData] = useState(dummyData);
const [isLoading, setIsLoading] = useState(false);
const [messageApi, contextHolder] = message.useMessage();
const handleClickAPICall = async (userInput) => {
try {
setIsLoading(true);
const message = await CallGPT({
prompt: `${userInput}`,
});
setData(JSON.parse(message));
} catch (error) {
messageApi.open({
type: "error",
content: error?.message,
});
return;
} finally {
setIsLoading(false);
}
};
const handleSubmit = (userInput) => {
handleClickAPICall(userInput);
};
return (
<AppConatiner>
{contextHolder}
<AppTitle>
심리상담사 GPT, AI 회고록 <img width={"100px"} src={logo}></img>
</AppTitle>
<DiaryInput
messageApi={messageApi}
isLoading={isLoading}
onSubmit={handleSubmit}
/>
<div id="capture">
<DiaryDisplay isLoading={isLoading} data={data} />
</div>
</AppConatiner>
);
}
export default App;
const AppConatiner = styled.div`
padding: 20px;
display: flex;
flex-direction: column;
max-width: 720px;
width: 100%;
margin: 0 auto;
`;
const AppTitle = styled.div`
width: 100%;
font-weight: 400;
font-size: 35px;
text-align: center;
font-family: "Noto Serif KR";
`;
DiaryInput.jsx
import { Input, Button, message } from "antd";
import { useState } from "react";
import { Title } from "./CommonStyles";
import styled from "styled-components";
import { FileImageOutlined } from "@ant-design/icons";
const { TextArea } = Input;
const DiaryInput = ({ isLoading, onSubmit, messageApi }) => {
const [userInput, setUserInput] = useState("");
// 사용자의 입력을 받아, 상위컴포넌트로 데이터를 전달
// loading 상태 - 사용자가 제출버튼을 못 누르도록 처리
const handleUserInput = (e) => {
setUserInput(e.target.value);
};
const handleClick = () => {
if (!userInput) {
messageApi.open({
type: "error",
content: "일과를 적어주세요.",
});
return;
}
messageApi.open({
type: "success",
content: "생성 요청 완료",
});
onSubmit(userInput);
setUserInput(null);
};
const captureAndDownload = async () => {
const nodeToCapture = document.getElementById("capture");
console.log(nodeToCapture);
// HTML2Canvas를 사용하여 노드의 스크린샷을 생성합니다.
html2canvas(nodeToCapture, {
allowTaint: true,
useCORS: true,
}).then(function (canvas) {
// 스크린샷을 이미지로 변환합니다.
const image = canvas.toDataURL("image/png");
// 이미지를 다운로드할 수 있는 링크를 생성합니다.
const a = document.createElement("a");
a.href = image;
a.download = "gpt-diary-result.png";
a.click();
});
};
return (
<div>
<Title>오늘의 일;</Title>
<TextArea
value={userInput}
onChange={handleUserInput}
placeholder="오늘 일어난 일들을 간단히 적어주세요."
style={{ height: "200px" }}
/>
<ButtonContainer>
<Button loading={isLoading} onClick={handleClick}>
GPT 회고록을 작성해줘!
</Button>
<Button
icon={<FileImageOutlined />}
loading={isLoading}
onClick={captureAndDownload}
>
저장
</Button>
</ButtonContainer>
<canvas id="canvas" style={{ display: "none" }}></canvas>
</div>
);
};
export default DiaryInput;
const ButtonContainer = styled.div`
margin: 20px;
display: flex;
flex-flow: row nowrap;
justify-content: flex-end;
gap: 5px;
`;
DiaryDisplay.jsx
import {
DiaryContainer,
ResultTitle,
Divider,
CardContainer,
CardTitle,
CardContent,
ActionListItem,
} from "./CommonStyles";
import {
LoadingOutlined,
CheckCircleTwoTone,
HeartTwoTone,
SmileTwoTone,
MessageTwoTone,
SoundTwoTone,
} from "@ant-design/icons";
import { Image } from "antd";
import styled from "styled-components";
const ThumbnailImage = styled(Image)`
max-width: 100%;
border-radius: 8px;
margin-bottom: 15px;
`;
const DiaryDisplay = ({ data, isLoading }) => {
return (
<DiaryContainer>
{isLoading && (
<>
불러오는중...
<LoadingOutlined />
</>
)}
<ResultTitle>{data.title}</ResultTitle>
<Divider />
<CardContainer>
<CardTitle>
<CheckCircleTwoTone
twoToneColor="#FF9AA2"
style={{ marginRight: "6px" }}
/>
요약
</CardTitle>
<CardContent>{data.summary}</CardContent>
</CardContainer>
<ThumbnailImage src={data.thumbnail} alt="Thumbnail" />
<Divider />
<CardContainer>
<CardTitle>
<HeartTwoTone twoToneColor="#FFB7B2" style={{ marginRight: "6px" }} />
감성일기장
</CardTitle>
<CardContent>{data.emotional_content}</CardContent>
</CardContainer>
<Divider />
<CardContainer>
<CardTitle>
<SmileTwoTone twoToneColor="#FFDAC1" style={{ marginRight: "6px" }} />
내가 느낀 감정
</CardTitle>
<CardContent>{data.emotional_result}</CardContent>
</CardContainer>
<Divider />
<CardContainer>
<CardTitle>
<MessageTwoTone
twoToneColor={"#B5EAD7"}
style={{ marginRight: "6px" }}
/>
심리 분석
</CardTitle>
<CardContent>{data.analysis}</CardContent>
</CardContainer>
<Divider />
<CardContainer>
<CardTitle>
<SoundTwoTone twoToneColor="#C7CEEA" style={{ marginRight: "6px" }} />
GPT 조언
</CardTitle>
<CardContent>
{data.action_list.map((action, index) => (
<ActionListItem key={index}>{action}</ActionListItem>
))}
{/* <ActionListItem>{data.action_list[0]}</ActionListItem>
<ActionListItem>{data.action_list[1]}</ActionListItem>
<ActionListItem>{data.action_list[2]}</ActionListItem> */}
</CardContent>
</CardContainer>
</DiaryContainer>
);
};
export default DiaryDisplay;