Ping9gu commited on
Commit
8b7b432
·
0 Parent(s):

Initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ venv/
6
+ .env
7
+
8
+ # IDE
9
+ .vscode/
10
+ .idea/
11
+
12
+ # Logs
13
+ *.log
14
+
15
+ # Environment variables
16
+ .env
README.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: My Diary AI
3
+ emoji: 📝
4
+ colorFrom: blue
5
+ colorTo: pink
6
+ sdk: gradio
7
+ sdk_version: 4.8.0
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ AI를 활용한 일기 생성 프로젝트입니다.
13
+
14
+ ## 특징
15
+ - 키워드를 기반으로 자세한 일기 생성
16
+ - 감정과 상황을 생생하게 표현
17
+ - Hugging Face의 한국어 언어 모델 활용
18
+
19
+ ## 설치 방법
20
+
21
+ ```bash
22
+ git clone https://huggingface.co/Ping9gu/my-diary
23
+ cd my-diary
24
+ ```
25
+
26
+ ## 저장소 클론
27
+ ```bash
28
+ git clone https://github.com/Ping9gu/my-diary.git
29
+ ```
30
+
31
+ ## 가상환경 생성 및 활성화
32
+ ```bash
33
+ python -m venv venv
34
+ source venv/bin/activate
35
+ ```
36
+
37
+ ## 의존성 설치
38
+ ```bash
39
+ pip install -r requirements.txt
40
+ ```
41
+
42
+ ## 환경 설정
43
+ 1. .env 파일 생성하고 다음 내용 추가:
44
+ ```
45
+ HUGGINGFACE_API_KEY=your_token_here
46
+ MODEL_ID=nlpai-lab/kullm-polyglot-5.8b-v2
47
+ ```
48
+
49
+ ## 서버 실행
50
+ ```bash
51
+ cd backend
52
+ ./run_server.sh
53
+ ```
54
+
55
+ ## 사용 방법
56
+ 1. 웹 브라우저에서 http://localhost:5000 접속
57
+ 2. 키워드 입력
58
+ 3. "일기 생성하기" 버튼 클릭
59
+
60
+ ## 라이선스
61
+ MIT License
app.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from backend.app import create_interface
3
+
4
+ demo = create_interface()
5
+
6
+ if __name__ == "__main__":
7
+ demo.launch()
backend/app.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, send_from_directory
2
+ from flask_cors import CORS
3
+ from huggingface_hub import InferenceClient
4
+ import os
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
8
+
9
+ app = Flask(__name__)
10
+ CORS(app, resources={
11
+ r"/*": {
12
+ "origins": ["http://your-frontend-domain"],
13
+ "methods": ["GET", "POST", "OPTIONS"],
14
+ "allow_headers": ["Content-Type", "Accept"]
15
+ }
16
+ })
17
+
18
+ # Hugging Face 클라이언트 초기화
19
+ client = InferenceClient(
20
+ model=os.getenv("MODEL_ID"),
21
+ token=os.getenv("HUGGINGFACE_API_KEY")
22
+ )
23
+
24
+ # 더 큰 한국어 모델로 변경
25
+ model_name = "nlpai-lab/kullm-polyglot-5.8b-v2" # 더 나은 품질의 모델
26
+
27
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
28
+ model = AutoModelForCausalLM.from_pretrained(
29
+ model_name,
30
+ device_map="auto",
31
+ torch_dtype=torch.float16,
32
+ low_cpu_mem_usage=True
33
+ )
34
+
35
+ # device_map="auto"를 사용하므로 .to(device) 호출 제거
36
+ # device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
37
+ # model = model.to(device) # 이 줄 제거
38
+
39
+ # 루트 경로 핸들러 추가
40
+ @app.route('/')
41
+ def serve_frontend():
42
+ return send_from_directory('../frontend', 'index.html')
43
+
44
+ # 정적 파일 서빙을 위한 라우트 추가
45
+ @app.route('/<path:path>')
46
+ def serve_static(path):
47
+ return send_from_directory('../frontend', path)
48
+
49
+ # favicon.ico 핸들러 추가
50
+ @app.route('/favicon.ico')
51
+ def favicon():
52
+ return '', 204 # No Content 응답 반환
53
+
54
+ @app.route('/api/generate-diary', methods=['POST'])
55
+ def generate_diary():
56
+ try:
57
+ data = request.json
58
+ if not data or 'keywords' not in data:
59
+ return jsonify({"error": "키워드가 필요합니다"}), 400
60
+
61
+ keywords = data.get('keywords', '').strip()
62
+ if not keywords:
63
+ return jsonify({"error": "키워드가 비어있습니다"}), 400
64
+
65
+ prompt = f"""다음은 오늘 있었던 일의 요약입니다. 이것을 바탕으로 생생하고 감동적인 일기를 작성해주세요.
66
+
67
+ [상세 요구사항]
68
+ 1. 도입부:
69
+ - 그날의 날씨나 분위기로 시작
70
+ - 상황과 등장인물 소개
71
+
72
+ 2. 전개:
73
+ - 구체적인 대화와 행동 묘사
74
+ - 오감을 사용한 장면 묘사
75
+ - 등장인물들의 표정과 감정 변화
76
+
77
+ 3. 감정과 생각:
78
+ - 내면의 감정을 섬세하게 표현
79
+ - 사건에 대한 나의 생각과 깨달음
80
+ - 다른 사람들의 감정에 대한 공감
81
+
82
+ 4. 문체:
83
+ - 문어체와 구어체를 적절히 혼용
84
+ - 비유와 은유를 활용한 표현
85
+ - 반복을 피하고 다양한 어휘 사용
86
+
87
+ 5. 마무리:
88
+ - 그날의 경험이 주는 의미
89
+ - 앞으로의 기대나 다짐
90
+
91
+ 요약:
92
+ {keywords}
93
+
94
+ ===
95
+ 오늘의 일기:
96
+ 오늘은 """
97
+
98
+ # Hugging Face API를 통한 텍스트 생성
99
+ parameters = {
100
+ "max_new_tokens": 768,
101
+ "temperature": 0.88,
102
+ "top_p": 0.95,
103
+ "repetition_penalty": 1.35,
104
+ "top_k": 50,
105
+ "do_sample": True,
106
+ "num_return_sequences": 1
107
+ }
108
+
109
+ response = client.text_generation(
110
+ prompt,
111
+ **parameters
112
+ )
113
+
114
+ if not response:
115
+ return jsonify({"error": "일기 생성에 실패했습니다"}), 500
116
+
117
+ # 프롬프트 제거하고 생성된 텍스트만 반환
118
+ diary_content = response.split("오늘은 ")[-1].strip()
119
+ diary_content = "오늘은 " + diary_content
120
+
121
+ return jsonify({"diary": diary_content})
122
+
123
+ except Exception as e:
124
+ print(f"Error generating diary: {str(e)}")
125
+ return jsonify({"error": f"일기 생성 중 오류가 발생했습니다: {str(e)}"}), 500
126
+
127
+ if __name__ == '__main__':
128
+ app.run(debug=True)
backend/config.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ load_dotenv()
5
+
6
+ class Config:
7
+ SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key')
8
+ DEEPSEEK_API_KEY = os.getenv('DEEPSEEK_API_KEY')
backend/models/diary.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+
3
+ class Diary:
4
+ def __init__(self, content, keywords):
5
+ self.content = content
6
+ self.keywords = keywords
7
+ self.created_at = datetime.now()
8
+
9
+ def to_dict(self):
10
+ return {
11
+ 'content': self.content,
12
+ 'keywords': self.keywords,
13
+ 'created_at': self.created_at.isoformat()
14
+ }
backend/requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ flask==2.3.3
2
+ flask-cors==4.0.0
3
+ requests==2.31.0
4
+ python-dotenv==1.0.0
5
+ werkzeug==2.3.7
6
+ bitsandbytes>=0.41.1
7
+ huggingface-hub>=0.17.0
backend/run_server.sh ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ screen -S diary-server -dm bash -c '
3
+ source venv/bin/activate
4
+ python app.py
5
+ '
frontend/index.html ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI 일기 도우미</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
9
+ </head>
10
+ <body>
11
+ <h1>AI 일기 도우미</h1>
12
+ <form>
13
+ <textarea placeholder="오늘은 무슨 일이 있었어?"></textarea>
14
+ <button type="submit">일기 생성하기</button>
15
+ </form>
16
+ <div id="loading-container" style="display: none;">
17
+ <div class="loading-spinner"></div>
18
+ <div class="loading-text">일기를 생성하는 중입니다...</div>
19
+ <div class="loading-progress">
20
+ <div class="progress-bar"></div>
21
+ </div>
22
+ </div>
23
+ <div id="result"></div>
24
+ <script src="script.js"></script>
25
+ </body>
26
+ </html>
frontend/script.js ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ const diaryForm = document.querySelector('form');
3
+ const keywordsInput = document.querySelector('textarea');
4
+ const resultDiv = document.querySelector('#result');
5
+ const generateButton = document.querySelector('button');
6
+ const loadingContainer = document.querySelector('#loading-container');
7
+ const progressBar = document.querySelector('.progress-bar');
8
+
9
+ diaryForm.addEventListener('submit', async function(e) {
10
+ e.preventDefault();
11
+
12
+ const keywords = keywordsInput.value.trim();
13
+ if (!keywords) {
14
+ resultDiv.textContent = "키워드를 입력해주세요";
15
+ return;
16
+ }
17
+
18
+ try {
19
+ generateButton.disabled = true;
20
+ resultDiv.textContent = "";
21
+ loadingContainer.style.display = 'block';
22
+ progressBar.style.width = '0%';
23
+
24
+ // 프로그레스 바 애니메이션 시작 (60초에서 30초로 변경)
25
+ progressBar.style.animation = 'progress 30s linear';
26
+
27
+ const API_URL = 'http://your-ec2-instance-ip:5000';
28
+
29
+ const response = await fetch(`${API_URL}/api/generate-diary`, {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ 'Accept': 'application/json'
34
+ },
35
+ body: JSON.stringify({
36
+ keywords: keywords
37
+ })
38
+ });
39
+
40
+ if (!response.ok) {
41
+ const errorText = await response.text();
42
+ throw new Error(`서버 응답 오류: ${response.status}`);
43
+ }
44
+
45
+ const data = await response.json();
46
+
47
+ if (data.error) {
48
+ resultDiv.textContent = `오류: ${data.error}`;
49
+ } else {
50
+ resultDiv.textContent = data.diary || '일기 생성에 실패했습니다.';
51
+ }
52
+ } catch (error) {
53
+ resultDiv.textContent = `오류가 발생했습니다: ${error.message}`;
54
+ } finally {
55
+ generateButton.disabled = false;
56
+ loadingContainer.style.display = 'none';
57
+ progressBar.style.animation = 'none';
58
+ }
59
+ });
60
+ });
frontend/style.css ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: 'Noto Sans KR', sans-serif;
3
+ margin: 0;
4
+ padding: 20px;
5
+ background-color: #f5f5f5;
6
+ }
7
+
8
+ .container {
9
+ max-width: 800px;
10
+ margin: 0 auto;
11
+ background-color: white;
12
+ padding: 20px;
13
+ border-radius: 10px;
14
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
15
+ }
16
+
17
+ .input-section {
18
+ margin: 20px 0;
19
+ }
20
+
21
+ textarea {
22
+ width: 100%;
23
+ height: 100px;
24
+ padding: 10px;
25
+ margin-bottom: 10px;
26
+ border: 1px solid #ddd;
27
+ border-radius: 5px;
28
+ }
29
+
30
+ button {
31
+ background-color: #007bff;
32
+ color: white;
33
+ border: none;
34
+ padding: 10px 20px;
35
+ border-radius: 5px;
36
+ cursor: pointer;
37
+ }
38
+
39
+ button:hover {
40
+ background-color: #0056b3;
41
+ }
42
+
43
+ .diary-section {
44
+ margin-top: 20px;
45
+ padding: 20px;
46
+ border: 1px solid #ddd;
47
+ border-radius: 5px;
48
+ }
49
+
50
+ #loading-container {
51
+ text-align: center;
52
+ margin: 20px 0;
53
+ }
54
+
55
+ .loading-spinner {
56
+ width: 50px;
57
+ height: 50px;
58
+ border: 5px solid #f3f3f3;
59
+ border-top: 5px solid #3498db;
60
+ border-radius: 50%;
61
+ animation: spin 1s linear infinite;
62
+ margin: 0 auto;
63
+ }
64
+
65
+ .loading-text {
66
+ margin: 10px 0;
67
+ color: #666;
68
+ }
69
+
70
+ .loading-progress {
71
+ width: 100%;
72
+ max-width: 300px;
73
+ height: 4px;
74
+ background-color: #f3f3f3;
75
+ margin: 10px auto;
76
+ border-radius: 2px;
77
+ overflow: hidden;
78
+ }
79
+
80
+ .progress-bar {
81
+ width: 0%;
82
+ height: 100%;
83
+ background-color: #3498db;
84
+ animation: progress 60s linear;
85
+ }
86
+
87
+ @keyframes spin {
88
+ 0% { transform: rotate(0deg); }
89
+ 100% { transform: rotate(360deg); }
90
+ }
91
+
92
+ @keyframes progress {
93
+ 0% { width: 0%; }
94
+ 100% { width: 100%; }
95
+ }