pikadick/commands/
chat.rs1use crate::ClientDataKey;
2use anyhow::{
3 ensure,
4 Context as _,
5};
6use serenity::builder::{
7 CreateEmbed,
8 EditInteractionResponse,
9};
10use tracing::{
11 error,
12 info,
13};
14
15const R6_TRACKER_PROMPT: &str = "When a user asks for rainbox six siege statistics for a person, respond only with \"!r6tracker <playername>\".";
16
17#[derive(Debug, pikadick_slash_framework::FromOptions)]
19pub struct Options {
20 message: String,
22}
23
24pub fn create_slash_command() -> anyhow::Result<pikadick_slash_framework::Command> {
26 pikadick_slash_framework::CommandBuilder::new()
27 .name("chat")
28 .description("Chat with pikadick")
29 .argument(
30 pikadick_slash_framework::ArgumentParamBuilder::new()
31 .name("message")
32 .description("The message")
33 .kind(pikadick_slash_framework::ArgumentKind::String)
34 .required(true)
35 .build()?,
36 )
37 .on_process(|ctx, interaction, args: Options| async move {
38 let data_lock = ctx.data.read().await;
39 let client_data = data_lock
40 .get::<ClientDataKey>()
41 .expect("missing client data");
42 let client = client_data.open_ai_client.clone();
43 let r6_tracker_client = client_data.r6tracker_client.clone();
44 drop(data_lock);
45
46 info!(
47 "requesting completion for chat message \"{}\"",
48 args.message
49 );
50
51 interaction.defer(&ctx.http).await?;
52
53 let chat_result = client
54 .chat_completion(
55 "gpt-3.5-turbo",
56 &[
57 open_ai::ChatMessage {
58 role: "user".into(),
60 content: R6_TRACKER_PROMPT.into(),
61 },
62 open_ai::ChatMessage {
63 role: "user".into(),
64 content: args.message.into(),
65 },
66 ],
67 Some(500),
68 )
69 .await
70 .context("failed to get search results")
71 .and_then(|mut response| {
72 ensure!(!response.choices.is_empty(), "missing response choice");
73 Ok(response.choices.swap_remove(0))
74 });
75
76 let chat_response = match chat_result {
77 Ok(result) => result.message.content,
78 Err(error) => {
79 error!("{error:?}");
80 let response = EditInteractionResponse::new().content(format!("{error:?}"));
81
82 interaction.edit_response(&ctx.http, response).await?;
83 return Ok(());
84 }
85 };
86
87 #[allow(clippy::collapsible_match)]
89 match chat_response.split_once(' ') {
90 Some((command, rest)) => match command {
91 "!r6tracker" => {
92 let stats = r6_tracker_client
93 .get_stats(rest)
94 .await
95 .context("failed to get r6tracker stats");
96 match stats.as_ref().map(|stats| stats.data()) {
97 Ok(Some(stats)) => {
98 let embed_builder = stats.populate_embed(CreateEmbed::new());
99 let response = EditInteractionResponse::new().embed(embed_builder);
100
101 interaction.edit_response(&ctx.http, response).await?;
102 }
103 Ok(None) => {
104 let response = EditInteractionResponse::new()
105 .content(format!("User \"{rest}\" was not found"));
106
107 interaction.edit_response(&ctx.http, response).await?;
108 }
109 Err(error) => {
110 let response =
111 EditInteractionResponse::new().content(format!("{error:?}"));
112
113 error!("{error:?}");
114 interaction.edit_response(&ctx.http, response).await?;
115 }
116 }
117 }
118 _ => {
119 let response = EditInteractionResponse::new().content(chat_response);
120 interaction.edit_response(&ctx.http, response).await?;
121 }
122 },
123 None => {
124 let response = EditInteractionResponse::new().content(chat_response);
125 interaction.edit_response(&ctx.http, response).await?;
126 }
127 }
128
129 Ok(())
130 })
131 .build()
132 .context("failed to build chat command")
133}