biftekpatates commited on
Commit
3cb5f2b
·
verified ·
1 Parent(s): 5353257

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +175 -96
app.py CHANGED
@@ -1,108 +1,187 @@
 
1
  import os
2
- import gradio as gr
3
- from dotenv import load_dotenv
 
 
 
4
  from openai import OpenAI
 
 
5
  from prompts.initial_prompt import INITIAL_PROMPT
6
  from prompts.main_prompt import MAIN_PROMPT
7
 
8
- # .env 파일에서 OPENAI_API_KEY 로드
9
- if os.path.exists(".env"):
10
- load_dotenv(".env")
11
-
12
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
 
 
13
  client = OpenAI(api_key=OPENAI_API_KEY)
14
 
15
-
16
-
17
- def gpt_call(history, user_message,
18
- model="gpt-4o-mini",
19
- max_tokens=512,
20
- temperature=0.7,
21
- top_p=0.95):
22
- """
23
- OpenAI ChatCompletion API를 통해 답변을 생성하는 함수.
24
- - history: [(user_text, assistant_text), ...]
25
- - user_message: 사용자가 방금 입력한 메시지
26
- """
27
- # 1) 시스템 메시지(=MAIN_PROMPT)를 가장 앞에 추가
28
- messages = [{"role": "system", "content": MAIN_PROMPT}]
29
-
30
- # 2) 기존 대화 기록(history)을 OpenAI 형식으로 변환
31
- # user_text -> 'user' / assistant_text -> 'assistant'
32
- for user_text, assistant_text in history:
33
- if user_text:
34
- messages.append({"role": "user", "content": user_text})
35
- if assistant_text:
36
- messages.append({"role": "assistant", "content": assistant_text})
37
-
38
- # 3) 마지막에 이번 사용자의 입력을 추가
39
- messages.append({"role": "user", "content": user_message})
40
-
41
- # 4) OpenAI API 호출
42
- completion = client.chat.completions.create(
43
- model=model,
44
- messages=messages,
45
- max_tokens=max_tokens,
46
- temperature=temperature,
47
- top_p=top_p
48
  )
49
- return completion.choices[0].message.content
50
-
51
- def respond(user_message, history):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  """
53
- Gradio 상에서 submit할 때 호출되는 함수
54
- - user_message: 사용자가 방금 친 메시지
55
- - history: 기존 (user, assistant) 튜플 리스트
 
 
56
  """
57
- # 사용자가 빈 문자열을 보냈다면 아무 일도 하지 않음
58
- if not user_message:
59
- return "", history
60
-
61
- # GPT 모델로부터 응답을 받음
62
- assistant_reply = gpt_call(history, user_message)
63
-
64
- # history에 (user, assistant) 쌍 추가
65
- history.append((user_message, assistant_reply))
66
-
67
- # Gradio에서는 (새로 비워질 입력창, 갱신된 history)를 반환
68
- return "", history
69
-
70
- ##############################
71
- # Gradio Blocks UI
72
- ##############################
73
- with gr.Blocks() as demo:
74
- gr.Markdown("## Simple Chat Interface")
75
-
76
- # Chatbot 초기 상태를 설정
77
- # 첫 번째 메시지는 (user="", assistant=INITIAL_PROMPT) 형태로 넣어
78
- # 화면상에서 'assistant'가 INITIAL_PROMPT를 말한 것처럼 보이게 함
79
- chatbot = gr.Chatbot(
80
- value=[("", INITIAL_PROMPT)], # (user, assistant)
81
- height=500
82
- )
83
-
84
- # (user, assistant) 쌍을 저장할 히스토리 상태
85
- # 여기서도 동일한 초기 상태를 넣어줌
86
- state_history = gr.State([("", INITIAL_PROMPT)])
87
-
88
- # 사용자 입력
89
- user_input = gr.Textbox(
90
- placeholder="Type your message here...",
91
- label="Your Input"
 
 
 
 
 
 
 
 
92
  )
93
-
94
- # 입력이 submit되면 respond() 호출 → 출력은 (새 입력창, 갱신된 chatbot)
95
- user_input.submit(
96
- respond,
97
- inputs=[user_input, state_history],
98
- outputs=[user_input, chatbot]
99
- ).then(
100
- # respond 끝난 뒤, 최신 history를 state_history에 반영
101
- fn=lambda _, h: h,
102
- inputs=[user_input, chatbot],
103
- outputs=[state_history]
104
- )
105
-
106
- # 메인 실행
107
- if __name__ == "__main__":
108
- demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py — FastAPI-only (no WebSockets/SSE)
2
  import os
3
+ from typing import List, Optional, Tuple
4
+
5
+ from fastapi import FastAPI
6
+ from fastapi.responses import HTMLResponse, JSONResponse
7
+ from pydantic import BaseModel, Field
8
  from openai import OpenAI
9
+
10
+ # --- Your prompts (edit these files in /prompts) ---
11
  from prompts.initial_prompt import INITIAL_PROMPT
12
  from prompts.main_prompt import MAIN_PROMPT
13
 
14
+ # Read key from Hugging Face Secret (Settings → Variables and secrets)
 
 
 
15
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
16
+ if not OPENAI_API_KEY:
17
+ print("WARNING: OPENAI_API_KEY secret is not set. Set it in the Space settings.")
18
  client = OpenAI(api_key=OPENAI_API_KEY)
19
 
20
+ # ---------- Models for API I/O ----------
21
+ class ChatIn(BaseModel):
22
+ message: str = Field("", description="The latest user message.")
23
+ # Optional: carry conversation history as a list of [user, assistant] turns
24
+ history: Optional[List[Tuple[str, str]]] = Field(
25
+ default=None,
26
+ description="Optional history as list of (user, assistant) tuples."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  )
28
+ model: str = Field(default="gpt-4o-mini")
29
+ temperature: float = Field(default=0.7)
30
+ top_p: float = Field(default=0.95)
31
+ max_tokens: int = Field(default=600)
32
+
33
+ class ChatOut(BaseModel):
34
+ reply: str
35
+
36
+ # ---------- Core non-streaming OpenAI call (server-side) ----------
37
+ def generate_reply(
38
+ user_message: str,
39
+ history: Optional[List[Tuple[str, str]]] = None,
40
+ model: str = "gpt-4o-mini",
41
+ temperature: float = 0.7,
42
+ top_p: float = 0.95,
43
+ max_tokens: int = 600,
44
+ ) -> str:
45
  """
46
+ Builds messages as:
47
+ system: MAIN_PROMPT
48
+ assistant: INITIAL_PROMPT (so users see your intro)
49
+ ... then optional history [(user, assistant), ...]
50
+ user: latest message
51
  """
52
+ messages = [
53
+ {"role": "system", "content": MAIN_PROMPT},
54
+ {"role": "assistant", "content": INITIAL_PROMPT},
55
+ ]
56
+
57
+ if history:
58
+ for u, a in history:
59
+ if u:
60
+ messages.append({"role": "user", "content": u})
61
+ if a:
62
+ messages.append({"role": "assistant", "content": a})
63
+
64
+ messages.append({"role": "user", "content": user_message or ""})
65
+
66
+ try:
67
+ resp = client.chat.completions.create(
68
+ model=model,
69
+ messages=messages,
70
+ temperature=temperature,
71
+ top_p=top_p,
72
+ max_tokens=max_tokens,
73
+ )
74
+ return resp.choices[0].message.content
75
+ except Exception as e:
76
+ # Return the error to the UI
77
+ return f"Error: {e}"
78
+
79
+ # ---------- FastAPI app + routes ----------
80
+ app = FastAPI(title="Module Chat Basic Mode")
81
+
82
+ @app.get("/health")
83
+ async def health():
84
+ return {"status": "ok"}
85
+
86
+ @app.post("/api/chat", response_model=ChatOut)
87
+ async def api_chat(payload: ChatIn):
88
+ reply = generate_reply(
89
+ user_message=payload.message,
90
+ history=payload.history,
91
+ model=payload.model,
92
+ temperature=payload.temperature,
93
+ top_p=payload.top_p,
94
+ max_tokens=payload.max_tokens,
95
  )
96
+ return ChatOut(reply=reply)
97
+
98
+ # Single-page minimal UI that uses ONLY plain HTTPS (no WebSockets)
99
+ INDEX_HTML = f"""
100
+ <!doctype html>
101
+ <html>
102
+ <head>
103
+ <meta charset="utf-8"/>
104
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
105
+ <title>Module Chat — Basic Mode</title>
106
+ <style>
107
+ :root {{ color-scheme: light dark; }}
108
+ body {{ font-family: system-ui, Arial, sans-serif; max-width: 820px; margin: 24px auto; padding: 0 12px; }}
109
+ textarea {{ width: 100%; height: 140px; }}
110
+ #reply {{ white-space: pre-wrap; border: 1px solid #ddd; padding: 12px; border-radius: 10px; min-height: 120px; }}
111
+ button {{ padding: 10px 16px; border-radius: 10px; cursor: pointer; }}
112
+ .tip {{ color: #555; font-size: 0.95rem; }}
113
+ .row {{ display: flex; gap: 8px; align-items: center; }}
114
+ .row > * {{ flex: 1; }}
115
+ .small {{ font-size: 12px; color: #666; }}
116
+ </style>
117
+ </head>
118
+ <body>
119
+ <h1>Module Chat — Basic Mode</h1>
120
+ <p class="tip">
121
+ This version uses only normal HTTPS requests (works even on strict campus Wi-Fi).<br/>
122
+ <b>Assistant:</b> {INITIAL_PROMPT}
123
+ </p>
124
+
125
+ <textarea id="msg" placeholder="Type your message..."></textarea>
126
+ <div class="row">
127
+ <button id="send">Send</button>
128
+ <label style="flex:0 0 auto">
129
+ <input type="checkbox" id="keepHistory" checked /> keep history
130
+ </label>
131
+ </div>
132
+
133
+ <h3>Reply</h3>
134
+ <div id="reply"></div>
135
+
136
+ <p class="small">Tip: Press Ctrl/Cmd+Enter to send.</p>
137
+
138
+ <script>
139
+ const btn = document.getElementById("send");
140
+ const msg = document.getElementById("msg");
141
+ const out = document.getElementById("reply");
142
+ const keep = document.getElementById("keepHistory");
143
+ let history = [];
144
+
145
+ async function sendOnce() {{
146
+ out.textContent = "Thinking...";
147
+ const payload = {{
148
+ message: msg.value || "",
149
+ history: keep.checked ? history : null
150
+ }};
151
+ try {{
152
+ const res = await fetch("/api/chat", {{
153
+ method: "POST",
154
+ headers: {{ "Content-Type": "application/json" }},
155
+ body: JSON.stringify(payload)
156
+ }});
157
+ if (!res.ok) {{
158
+ const txt = await res.text();
159
+ out.textContent = "Server error: " + res.status + " " + txt;
160
+ return;
161
+ }}
162
+ const data = await res.json();
163
+ const reply = data.reply || "(no reply)";
164
+ out.textContent = reply;
165
+ if (keep.checked) {{
166
+ history.push([msg.value || "", reply]);
167
+ }}
168
+ }} catch (e) {{
169
+ out.textContent = "Network error: " + e;
170
+ }}
171
+ }}
172
+
173
+ btn.onclick = sendOnce;
174
+ msg.addEventListener("keydown", (ev) => {{
175
+ if (ev.key === "Enter" && (ev.ctrlKey || ev.metaKey)) {{
176
+ ev.preventDefault();
177
+ sendOnce();
178
+ }}
179
+ }});
180
+ </script>
181
+ </body>
182
+ </html>
183
+ """
184
+
185
+ @app.get("/", response_class=HTMLResponse)
186
+ async def root():
187
+ return INDEX_HTML