File size: 7,296 Bytes
cd5d77c
 
 
 
d403426
 
 
cd5d77c
 
 
 
d403426
184d70e
d403426
cd5d77c
d403426
 
 
cd5d77c
184d70e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd5d77c
 
184d70e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d403426
 
 
184d70e
d403426
 
184d70e
 
312f42b
 
184d70e
 
 
 
d403426
cd5d77c
184d70e
 
d403426
184d70e
 
 
 
cd5d77c
d403426
184d70e
d403426
cd5d77c
184d70e
d403426
 
cd5d77c
 
184d70e
d403426
 
 
f6b850a
184d70e
d403426
cd5d77c
184d70e
 
d403426
f6b850a
184d70e
d403426
184d70e
d403426
cd5d77c
184d70e
d403426
 
184d70e
cd5d77c
 
184d70e
 
 
 
 
 
 
 
 
312f42b
 
cd5d77c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# cycles_chat_app.py
import os, math, numpy as np, matplotlib.pyplot as plt, gradio as gr
import openai

# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 0. OpenAI key
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if "OPENAI_API_KEY" not in os.environ:
    os.environ["OPENAI_API_KEY"] = input("๐Ÿ”‘ Enter your OpenAI API key: ").strip()
openai.api_key = os.environ["OPENAI_API_KEY"]

# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 1.  Wave-style chart utilities  (โ† NEW)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
CYCLES = {
    "Tech Cycle (50 yr)": 50,
    "Finance Cycle (80 yr)": 80,
    "Hegemony Cycle (250 yr)": 250,
}
COLOR_MAP = {50: "#ff3333", 80: "#ffcc00", 250: "#66ccff"}        # red / yellow / blue
AMPLITUDE_MAP = {50: 1.0, 80: 1.6, 250: 4.0}                     # taller tower for longer cycle
CENTER = 2025                                                    # alignment reference

def _half_sine(xs, period, amp):
    """positive half-sine wave, aligned so peak at CENTER, 0 elsewhere"""
    phase = np.mod(xs - CENTER, period)
    y = amp * np.sin(np.pi * phase / period)
    y[y < 0] = 0
    return y

def build_wave_chart_and_summary(start: int, end: int):
    """return (matplotlib figure, text summary)"""
    xs = np.linspace(start, end, (end - start) * 4)              # 4 pts/yr โ‡’ smooth
    fig, ax = plt.subplots(figsize=(10, 3))
    summaries, align_years = [], None

    # draw many stacked half-sine lines for each cycle โ†’ โ€œtowerโ€ ๋А๋‚Œ
    for period in sorted(CYCLES.values()):
        col, amp = COLOR_MAP[period], AMPLITUDE_MAP[period]
        for frac in np.linspace(amp / 30, amp, 30):              # 30 concentric lines
            ax.plot(xs, _half_sine(xs, period, frac),
                    color=col, alpha=0.85, lw=0.6)

        years = [CENTER + n * period for n in range(
                 math.ceil((start - CENTER) / period),
                 math.floor((end   - CENTER) / period) + 1)]
        summaries.append(f"{period}-yr cycle peaks: {years}")
        align_years = set(years) if align_years is None else align_years & set(years)

    # cosmeticโ€Šโ€”โ€Šblack bg & white grid
    ax.set_facecolor("black"); fig.patch.set_facecolor("black")
    ax.set_xlim(start, end); ax.set_ylim(0, max(AMPLITUDE_MAP.values()) + .2)
    ax.set_xlabel("Year", color="white"); ax.set_ylabel("Relative Amplitude", color="white")
    ax.set_title("Technology ยท Finance ยท Hegemony Cycles", color="white", pad=20)
    ax.tick_params(colors="white");  [s.set_color("white") for s in ax.spines.values()]
    ax.grid(color="white", ls="--", lw=.3, alpha=.3)

    # vertical dashed line at alignment year + 250-yr span arrow
    ax.axvline(CENTER, color="white", ls="--", lw=1, alpha=.6)
    arrow_y = AMPLITUDE_MAP[250] * 1.05
    ax.annotate("", xy=(CENTER - 125, arrow_y), xytext=(CENTER + 125, arrow_y),
                arrowprops=dict(arrowstyle="<|-|>", color="white", lw=1.2))
    ax.text(CENTER, arrow_y + .15, "250 yr", color="white",
            ha="center", va="bottom", fontsize=10, fontweight="bold")

    summary = (f"Range {start}-{end}\n" +
               "\n".join(summaries) +
               f"\nAlignment year inside range: {', '.join(map(str, sorted(align_years))) if align_years else 'None'}")
    return fig, summary

# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 2.  GPT chat (์ฐจํŠธ ์š”์•ฝ โ†’ system ํ”„๋กฌํ”„ํŠธ์— ์ž๋™ ์‚ฝ์ž…)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
BASE_PROMPT = (
    "๋‹น์‹ ์€ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ •ํ™•ํ•œ ํ•œ๊ตญ์–ด ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค. "
    "์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๋‹ตํ•  ๋•Œ, ์ œ๊ณต๋œ [Chart summary] ๋‚ด์šฉ์„ ๋ฐ˜๋“œ์‹œ ๋ฐ˜์˜ํ•˜์„ธ์š”."
)

def chat_with_gpt(history, user_msg, chart_summary):
    msgs = [{"role": "system", "content": BASE_PROMPT}]
    if chart_summary != "No chart yet.":        # ์ฐจํŠธ๊ฐ€ ์ด๋ฏธ ๊ทธ๋ ค์กŒ๋‹ค๋ฉด
        msgs.append({"role": "system", "content": f"[Chart summary]\n{chart_summary}"})

    for u, a in history:
        msgs += [{"role": "user", "content": u}, {"role": "assistant", "content": a}]
    msgs.append({"role": "user", "content": user_msg})

    answer = openai.chat.completions.create(
        model="gpt-3.5-turbo", messages=msgs,
        max_tokens=600, temperature=0.7).choices[0].message.content.strip()
    return answer

# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 3.  Gradio UI
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.Markdown("## ๐Ÿ“Š Wave-style Cycle Timeline & ๐Ÿ’ฌ GPT Chat")

    chart_summary_state = gr.State(value="No chart yet.")

    with gr.Tabs():
        # โ–ธ Tab 1 โ€” Chart
        with gr.TabItem("Timeline Chart"):
            start_year = gr.Number(label="Start Year", value=1500)
            end_year   = gr.Number(label="End Year",   value=2500)

            fig0, summ0 = build_wave_chart_and_summary(1500, 2500)
            plot_out = gr.Plot(value=fig0)

            def refresh_chart(s, e):
                fig, summ = build_wave_chart_and_summary(int(s), int(e))
                return fig, summ

            start_year.change(refresh_chart, [start_year, end_year],
                              [plot_out, chart_summary_state])
            end_year.change(refresh_chart,   [start_year, end_year],
                              [plot_out, chart_summary_state])

        # โ–ธ Tab 2 โ€” GPT Chat
        with gr.TabItem("GPT Chat"):
            chatbot = gr.Chatbot(label="Assistant")
            user_in = gr.Textbox(lines=3, placeholder="๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”โ€ฆ")
            send_btn = gr.Button("Send", variant="primary")

            def respond(chat_hist, user_msg, summary):
                reply = chat_with_gpt(chat_hist, user_msg, summary)
                chat_hist.append((user_msg, reply))
                return chat_hist, gr.Textbox(value="", interactive=True)

            send_btn.click(respond, [chatbot, user_in, chart_summary_state],
                                       [chatbot, user_in])
            user_in.submit(respond, [chatbot, user_in, chart_summary_state],
                                       [chatbot, user_in])

if __name__ == "__main__":
    demo.launch()