openfree commited on
Commit
4fb7e13
ยท
verified ยท
1 Parent(s): 6e59e9e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -0
app.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # cycles_chat_app.py
2
+ import os, math, numpy as np, matplotlib.pyplot as plt, gradio as gr
3
+ import openai
4
+
5
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
6
+ # 0. OpenAI API key
7
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
+ if "OPENAI_API_KEY" not in os.environ:
9
+ os.environ["OPENAI_API_KEY"] = input("๐Ÿ”‘ Enter your OpenAI API key: ").strip()
10
+ openai.api_key = os.environ["OPENAI_API_KEY"]
11
+
12
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
13
+ # 1. Wave-style chart utilities
14
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
15
+ CYCLES = {
16
+ "Tech Cycle (50 yr)": 50,
17
+ "Finance Cycle (80 yr)": 80,
18
+ "Hegemony Cycle (250 yr)": 250,
19
+ }
20
+ COLOR_MAP = {50: "#ff3333", 80: "#ffcc00", 250: "#66ccff"}
21
+ AMPLITUDE_MAP = {50: 1.0, 80: 1.6, 250: 4.0}
22
+ CENTER = 2025 # alignment reference
23
+
24
+ def _half_sine(xs, period, amp):
25
+ """positive half-sine wave centred on CENTER"""
26
+ phase = np.mod(xs - CENTER, period)
27
+ y = amp * np.sin(np.pi * phase / period)
28
+ y[y < 0] = 0
29
+ return y
30
+
31
+ def build_wave_chart_and_summary(start: int, end: int):
32
+ xs = np.linspace(start, end, (end - start) * 4) # 4 pts per year
33
+ fig, ax = plt.subplots(figsize=(14, 6))
34
+ fig.subplots_adjust(top=0.9) # spare margin for labels
35
+
36
+ summaries, align_years, all_year_labels = [], None, set()
37
+
38
+ # draw half-sine โ€œtowersโ€
39
+ for period in sorted(CYCLES.values()):
40
+ col, amp = COLOR_MAP[period], AMPLITUDE_MAP[period]
41
+ for frac in np.linspace(amp / 30, amp, 30):
42
+ ax.plot(xs, _half_sine(xs, period, frac),
43
+ color=col, alpha=0.85, lw=0.6)
44
+
45
+ years = [CENTER + n * period for n in range(
46
+ math.ceil((start - CENTER) / period),
47
+ math.floor((end - CENTER) / period) + 1)]
48
+ summaries.append(f"{period}-yr cycle peaks: {years}")
49
+ align_years = set(years) if align_years is None else align_years & set(years)
50
+ all_year_labels.update(years)
51
+
52
+ # baseline small year labels (duplicates removed)
53
+ for y in sorted(all_year_labels):
54
+ if start <= y <= end:
55
+ ax.text(y, -0.1, str(y), ha="center", va="top",
56
+ fontsize=6, color="white", rotation=90)
57
+
58
+ # styling
59
+ ax.set_facecolor("black"); fig.patch.set_facecolor("black")
60
+ ax.set_xlim(start, end)
61
+ ax.set_ylim(-0.2, max(AMPLITUDE_MAP.values()) + 0.2)
62
+ ax.set_xlabel("Year", color="white")
63
+ ax.set_ylabel("Relative Amplitude", color="white")
64
+ # (no chart title per request)
65
+ ax.tick_params(colors="white")
66
+ for spine in ax.spines.values():
67
+ spine.set_color("white")
68
+ ax.grid(axis="y", color="white", ls="--", lw=.3, alpha=.3)
69
+
70
+ # alignment marker (CENTER) and 250-yr arrow
71
+ ax.axvline(CENTER, color="white", ls="--", lw=1, alpha=.6)
72
+ arrow_y = AMPLITUDE_MAP[250] * 1.05
73
+ ax.annotate("", xy=(CENTER - 125, arrow_y), xytext=(CENTER + 125, arrow_y),
74
+ arrowprops=dict(arrowstyle="<|-|>", color="white", lw=1.2))
75
+ ax.text(CENTER, arrow_y + .15, "250 yr", color="white",
76
+ ha="center", va="bottom", fontsize=10, fontweight="bold")
77
+
78
+ summary = (f"Range {start}-{end}\n"
79
+ + "\n".join(summaries)
80
+ + "\nAlignment year inside range: "
81
+ + (", ".join(map(str, sorted(align_years))) if align_years else "None"))
82
+ return fig, summary
83
+
84
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
85
+ # 2. GPT chat helper
86
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
87
+ BASE_PROMPT = (
88
+ "You are a concise and accurate Korean assistant. "
89
+ "Always incorporate the provided [Chart summary] when replying."
90
+ )
91
+
92
+ def chat_with_gpt(history, user_msg, chart_summary):
93
+ msgs = [{"role": "system", "content": BASE_PROMPT}]
94
+ if chart_summary != "No chart yet.":
95
+ msgs.append({"role": "system", "content": f"[Chart summary]\n{chart_summary}"})
96
+
97
+ for u, a in history:
98
+ msgs += [{"role": "user", "content": u}, {"role": "assistant", "content": a}]
99
+ msgs.append({"role": "user", "content": user_msg})
100
+
101
+ reply = openai.chat.completions.create(
102
+ model="gpt-3.5-turbo",
103
+ messages=msgs,
104
+ max_tokens=600,
105
+ temperature=0.7
106
+ ).choices[0].message.content.strip()
107
+ return reply
108
+
109
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
110
+ # 3. Gradio UI
111
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
112
+ custom_css = """
113
+ #wave_plot {width: 100% !important;}
114
+ #wave_plot canvas {width: 100% !important; height: auto !important;}
115
+ """
116
+
117
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
118
+ # Service title and descriptions
119
+ gr.Markdown("## ๐Ÿš€ **TriPulse Navigator**")
120
+ gr.Markdown(
121
+ """
122
+ **Tech Cycle (50 yr)** โ€“ Innovation booms and busts about every half-century.
123
+ **Finance Cycle (80 yr)** โ€“ Credit expansions and crises roughly once per lifetime.
124
+ **Hegemony Cycle (250 yr)** โ€“ Rise and decline of world-leading powers across centuries.
125
+ """
126
+ )
127
+
128
+ chart_summary_state = gr.State(value="No chart yet.")
129
+
130
+ with gr.Tabs():
131
+ # โ–ธ Tab 1 โ€” Timeline Chart
132
+ with gr.TabItem("Timeline Chart"):
133
+ # initial chart
134
+ fig0, summ0 = build_wave_chart_and_summary(1500, 2500)
135
+ plot_out = gr.Plot(value=fig0, elem_id="wave_plot")
136
+
137
+ # inputs and zoom buttons
138
+ with gr.Row():
139
+ start_year = gr.Number(label="Start Year", value=1500)
140
+ end_year = gr.Number(label="End Year", value=2500)
141
+ zoom_in_btn = gr.Button("๐Ÿ” Zoom In")
142
+ zoom_out_btn = gr.Button("๐Ÿ”Ž Zoom Out")
143
+
144
+ # refresh on manual year change
145
+ def refresh_chart(s, e):
146
+ fig, summ = build_wave_chart_and_summary(int(s), int(e))
147
+ return fig, summ
148
+
149
+ start_year.change(refresh_chart, [start_year, end_year],
150
+ [plot_out, chart_summary_state])
151
+ end_year.change(refresh_chart, [start_year, end_year],
152
+ [plot_out, chart_summary_state])
153
+
154
+ # zoom helpers
155
+ def zoom(s, e, factor):
156
+ mid = (s + e) / 2
157
+ span = (e - s) * factor / 2
158
+ new_s, new_e = int(mid - span), int(mid + span)
159
+ fig, summ = build_wave_chart_and_summary(new_s, new_e)
160
+ return new_s, new_e, fig, summ
161
+
162
+ zoom_in_btn.click(
163
+ lambda s, e: zoom(s, e, 0.5),
164
+ inputs=[start_year, end_year],
165
+ outputs=[start_year, end_year, plot_out, chart_summary_state],
166
+ )
167
+ zoom_out_btn.click(
168
+ lambda s, e: zoom(s, e, 2.0),
169
+ inputs=[start_year, end_year],
170
+ outputs=[start_year, end_year, plot_out, chart_summary_state],
171
+ )
172
+
173
+ # โ–ธ Tab 2 โ€” GPT Chat
174
+ with gr.TabItem("GPT Chat"):
175
+ chatbot = gr.Chatbot(label="Assistant")
176
+ user_in = gr.Textbox(lines=3, placeholder="๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”โ€ฆ")
177
+ send_btn = gr.Button("Send", variant="primary")
178
+
179
+ def respond(chat_hist, user_msg, summary):
180
+ ans = chat_with_gpt(chat_hist, user_msg, summary)
181
+ chat_hist.append((user_msg, ans))
182
+ return chat_hist, gr.Textbox(value="", interactive=True)
183
+
184
+ send_btn.click(respond, [chatbot, user_in, chart_summary_state],
185
+ [chatbot, user_in])
186
+ user_in.submit(respond, [chatbot, user_in, chart_summary_state],
187
+ [chatbot, user_in])
188
+
189
+ if __name__ == "__main__":
190
+ demo.launch()