1+
2+ """
3+ code from https://github.com/hyperdiv/hyperdiv-apps/tree/main/gpt-chatbot
4+
5+ python >= 3.9
6+
7+ pip3 install -r requirements.txt
8+
9+ """
10+
11+ import sys
12+ import os
13+ import openai
14+ import hyperdiv as hd
15+
16+
17+
18+ api_key = os .environ .get ("OPENAI_API_KEY" ,"<your OpenAI API key if not set as env var>" )
19+
20+
21+
22+ def add_message (role ,content ,state ,gpt_model ):
23+ state .messages += (
24+ dict (role = role ,content = content ,id = state .message_id ,gpt_model = gpt_model ),
25+ )
26+ state .message_id += 1
27+
28+
29+ def request (gpt_model ,state ):
30+ response = openai .chat .completions .create (
31+ model = gpt_model ,
32+ messages = [dict (role = m ["role" ],content = m ["content" ])for m in state .messages ],
33+ temperature = 0 ,
34+ stream = True ,
35+ )
36+
37+ for chunk in response :
38+ message = chunk .choices [0 ].delta .content
39+ state .current_reply += str (message )
40+
41+ add_message ("assistant" ,state .current_reply ,state ,gpt_model )
42+ state .current_reply = ""
43+
44+
45+ def render_user_message (content ,gpt_model ):
46+ with hd .hbox (
47+ align = "center" ,
48+ padding = 0.5 ,
49+ border_radius = "medium" ,
50+ background_color = "neutral-50" ,
51+ font_color = "neutral-600" ,
52+ justify = "space-between" ,
53+ ):
54+ with hd .hbox (gap = 0.5 ,align = "center" ):
55+ hd .icon ("chevron-right" ,shrink = 0 )
56+ hd .text (content )
57+ hd .badge (gpt_model )
58+
59+
60+ def main ():
61+ state = hd .state (messages = (),current_reply = "" ,gpt_model = "gpt-4" ,message_id = 0 )
62+
63+ task = hd .task ()
64+
65+ template = hd .template (title = "GPT Chatbot" ,sidebar = False )
66+
67+ with template .body :
68+ # Render the messages so far, if any.
69+ if len (state .messages )> 0 :
70+ # We use a vertical-reverse box to render the messages, so
71+ # they naturally stay 'stuck' to the bottom and auto-scroll up.
72+ with hd .box (direction = "vertical-reverse" ,gap = 1.5 ,vertical_scroll = True ):
73+ # The current reply is the most recent message
74+ if state .current_reply :
75+ hd .markdown (state .current_reply )
76+
77+ # Render the rest of the messages in reverse. The
78+ # `vertical-reverse` direction will re-reverse them,
79+ # rendering them in expected order.
80+ for e in reversed (state .messages ):
81+ with hd .scope (e ["id" ]):
82+ if e ["role" ]== "system" :
83+ continue
84+ if e ["role" ]== "user" :
85+ render_user_message (e ["content" ],e ["gpt_model" ])
86+ else :
87+ hd .markdown (e ["content" ])
88+
89+ with hd .box (align = "center" ,gap = 1.5 ):
90+ # Render the input form.
91+ with hd .form (direction = "horizontal" ,width = "100%" )as form :
92+ with hd .box (grow = 1 ):
93+ prompt = form .text_input (
94+ placeholder = "Talk to the AI" ,
95+ autofocus = True ,
96+ disabled = task .running ,
97+ name = "prompt" ,
98+ )
99+
100+ model = form .select (
101+ options = ("gpt-3.5-turbo" ,"gpt-4" ,"gpt-4-1106-preview" ),
102+ value = "gpt-4" ,
103+ name = "gpt-model" ,
104+ )
105+
106+ if form .submitted :
107+ add_message ("user" ,prompt .value ,state ,model .value )
108+ prompt .reset ()
109+ task .rerun (request ,model .value ,state )
110+
111+ # Render a small button that when clicked, resets the message history.
112+ if len (state .messages )> 0 :
113+ if hd .button (
114+ "Start Over" ,size = "small" ,variant = "text" ,disabled = task .running
115+ ).clicked :
116+ state .messages = ()
117+
118+
119+ hd .run (main )