11import os
22import pathlib
3+ import tempfile
4+ import urllib .parse
35
46import chatlas
57import faicons
1214 ui .input_action_link ("info_link" , label = None , icon = faicons .icon_svg ("circle-info" )),
1315 ui .output_text ("cost" , inline = True ),
1416 ),
17+ ui .output_ui ("new_gh_issue" , inline = True ),
1518 ui .chat_ui ("chat" , placeholder = "Ask your posit-SDK questions here..." ),
1619 ui .tags .style (
1720 """
3033 border-radius: 0.5em;
3134 display: list-item;
3235 }
36+ .external-link {
37+ cursor: alias;
38+ }
39+ #new_gh_issue {
40+ position: absolute;
41+ right: 15px;
42+ top: 15px;
43+ height: 25px;
44+ }
3345 """
3446 ),
3547 ui .tags .script (
3648 """
3749 $(() => {
3850 $("body").click(function(e) {
39- console.log("click", e, e.target, $(e.target).hasClass("sdk_suggested_prompt"));
4051 if (!$(e.target).hasClass("sdk_suggested_prompt")) {
4152 return;
4253 }
4354 window.Shiny.setInputValue("new_sdk_prompt", $(e.target).text());
4455 });
4556 })
46- Shiny.addCustomMessageHandler("submit-chat", function(message) {
57+ window. Shiny.addCustomMessageHandler("submit-chat", function(message) {
4758 const enterEvent = new KeyboardEvent('keydown', {
4859 key: 'Enter',
4960 code: 'Enter',
5263 });
5364
5465 // Dispatch the 'Enter' event on the input element
55- console.log("Dispatching Enter event");
56- document.querySelector("#chat textarea#chat_user_input").dispatchEvent(enterEvent);
66+ console.log("Dispatching Enter event", message );
67+ document.querySelector("#" + message['id'] + " textarea#chat_user_input").dispatchEvent(enterEvent);
5768 });
5869
5970 """
@@ -66,27 +77,122 @@ def server(input: Inputs): # noqa: A002
6677 aws_model = os .getenv ("AWS_MODEL" , "us.anthropic.claude-3-5-sonnet-20241022-v2:0" )
6778 aws_region = os .getenv ("AWS_REGION" , "us-east-1" )
6879 chat = chatlas .ChatBedrockAnthropic (model = aws_model , aws_region = aws_region )
69- with open (pathlib .Path (__file__ ).parent / "prompt.xml" , "r" ) as f :
80+ prompt_file = pathlib .Path (__file__ ).parent / "_prompt.xml"
81+ if not os .path .exists (prompt_file ):
82+ raise FileNotFoundError (
83+ f"Prompt file not found: { prompt_file } ; Please run `make shiny` to generate it."
84+ )
85+ with open (prompt_file , "r" ) as f :
7086 chat .system_prompt = f .read ()
7187
7288 chat_ui = ui .Chat (
7389 "chat" ,
7490 # messages=[{"role": turn.role, "content": turn.text} for turn in chat.get_turns()],
7591 )
7692
93+ async def submit_chat (new_value : str ):
94+ chat_ui .update_user_input (value = new_value )
95+
96+ local_session = session .require_active_session (None )
97+ await local_session .send_custom_message ("submit-chat" , {"id" : "chat" })
98+
7799 @render .text
78100 def cost ():
79101 _ = chat_ui .messages ()
80102
103+ tokens = chat .tokens ("cumulative" )
104+ if len (tokens ) == 0 :
105+ return None
106+
81107 cost = sum (
82108 [
83109 # Input + Output
84110 (token [0 ] * 0.003 / 1000.0 ) + (token [1 ] * 0.015 / 1000.0 )
85- for token in chat . tokens ( "cumulative" )
111+ for token in tokens
86112 if token is not None
87113 ]
88114 )
89- return "$%s" % float ("%.3g" % cost )
115+ ans = "$%s" % float ("%.3g" % cost )
116+ while len (ans ) < 5 :
117+ ans = ans + "0"
118+ return ans
119+
120+ @render .ui
121+ def new_gh_issue ():
122+ messages = chat_ui .messages ()
123+ for message in messages :
124+ if message ["role" ] == "assistant" :
125+ break
126+ else :
127+ # No LLM response found. Return
128+ return
129+
130+ first_message_content : str = str (messages [0 ].get ("content" , "" ))
131+
132+ with tempfile .TemporaryDirectory () as tmpdirname :
133+ export_path = tmpdirname + "/chat_export.md"
134+ chat .export (export_path , include = "all" , include_system_prompt = False )
135+
136+ with open (export_path , "r" ) as f :
137+ exported_content = f .read ()
138+
139+ body = f"""
140+ **First message:**
141+ ```
142+ { first_message_content }
143+ ```
144+
145+ **Desired outcome:**
146+
147+ Please describe what you would like to achieve in `posit-sdk`. Any additional context, code, or examples are welcome!
148+
149+ ```python
150+ from posit.connect import Client
151+ client = Client()
152+
153+ # Your code here
154+ ```
155+
156+ -----------------------------------------------
157+
158+ <details>
159+ <summary>Chat Log</summary>
160+
161+ ````markdown
162+ { exported_content }
163+ ````
164+ </details>
165+ """
166+
167+ title = (
168+ "SDK Assistant: `"
169+ + (
170+ first_message_content
171+ if len (first_message_content ) <= 50
172+ else (first_message_content [:50 ] + "..." )
173+ )
174+ + "`"
175+ )
176+
177+ new_issue_url = (
178+ "https://github.com/posit-dev/posit-sdk-py/issues/new?"
179+ + urllib .parse .urlencode (
180+ {
181+ "title" : title ,
182+ "labels" : ["template idea" ],
183+ "body" : body ,
184+ }
185+ )
186+ )
187+
188+ # if chat_ui.messages(format="anthropic")
189+ return ui .a (
190+ ui .img (src = "new_gh_issue.svg" , alt = "New GitHub Issue" , height = "100%" ),
191+ title = "Submit script example to Posit SDK" ,
192+ class_ = "external-link" ,
193+ href = new_issue_url ,
194+ target = "_blank" ,
195+ )
90196
91197 @chat_ui .on_user_submit
92198 async def _ ():
@@ -103,20 +209,14 @@ async def _():
103209 @reactive .effect
104210 @reactive .event (input .new_sdk_prompt )
105211 async def _ ():
106- chat_ui .update_user_input (value = input .new_sdk_prompt ())
107- local_session = session .require_active_session (None )
108- await local_session .send_custom_message ("submit-chat" , {})
212+ await submit_chat (input .new_sdk_prompt ())
109213
110214 @reactive .effect
111- async def _init_chat ():
112- chat_ui .update_user_input (
113- value = "What are the pieces of Posit connect and how do they fit together?"
114- )
115-
116- local_session = session .require_active_session (None )
117- await local_session .send_custom_message ("submit-chat" , {})
215+ async def _init_chat_on_load ():
216+ await submit_chat ("What are the pieces of Posit connect and how do they fit together?" )
118217
119- _init_chat .destroy ()
218+ # Remove the effect after the first run
219+ _init_chat_on_load .destroy ()
120220
121221 @reactive .effect
122222 @reactive .event (input .info_link )
@@ -135,4 +235,8 @@ async def _():
135235 ui .modal_show (modal )
136236
137237
138- app = App (app_ui , server )
238+ app = App (
239+ app_ui ,
240+ server ,
241+ static_assets = pathlib .Path (__file__ ).parent / "www" ,
242+ )
0 commit comments