Skip to content

Commit f848fe7

Browse files
committed
Create a dynamic link for users to click on that creates a new SDK issue given code
1 parent 661dbae commit f848fe7

File tree

3 files changed

+138
-21
lines changed

3 files changed

+138
-21
lines changed

shiny/assistant/.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
chatlas/
2-
prompt.xml
2+
_prompt.xml
33
rsconnect-python/
44
_swagger.json
55
_swagger_prompt.md
6-
repomix-instruction.md
6+
_repomix-instructions.md

shiny/assistant/app.py

Lines changed: 123 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
22
import pathlib
3+
import tempfile
4+
import urllib.parse
35

46
import chatlas
57
import faicons
@@ -12,6 +14,7 @@
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
"""
@@ -30,20 +33,28 @@
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',
@@ -52,8 +63,8 @@
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+
)
Lines changed: 13 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)