1use crate::messages::chatbot::ChatbotMessage;
4use crate::transport::http::HttpClient;
5use crate::transport::token::TokenManager;
6use serde_repr::{Deserialize_repr, Serialize_repr};
7use sha2::{Digest, Sha256};
8use std::sync::Arc;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
12#[repr(u8)]
13#[non_exhaustive]
14pub enum AICardStatus {
15 Processing = 1,
17 Inputing = 2,
19 Finished = 3,
21 Executing = 4,
23 Failed = 5,
25}
26
27#[derive(Clone)]
29pub struct CardReplier {
30 pub(crate) http_client: HttpClient,
31 pub(crate) token_manager: Arc<TokenManager>,
32 pub(crate) client_id: String,
33 pub(crate) incoming_message: ChatbotMessage,
34}
35
36impl CardReplier {
37 pub fn new(
39 http_client: HttpClient,
40 token_manager: Arc<TokenManager>,
41 client_id: String,
42 incoming_message: ChatbotMessage,
43 ) -> Self {
44 Self {
45 http_client,
46 token_manager,
47 client_id,
48 incoming_message,
49 }
50 }
51
52 pub fn gen_card_id(msg: &ChatbotMessage) -> String {
54 let factor = format!(
55 "{}_{}_{}_{}_{}",
56 msg.sender_id.as_deref().unwrap_or(""),
57 msg.sender_corp_id.as_deref().unwrap_or(""),
58 msg.conversation_id.as_deref().unwrap_or(""),
59 msg.message_id.as_deref().unwrap_or(""),
60 uuid::Uuid::new_v4()
61 );
62 let mut hasher = Sha256::new();
63 hasher.update(factor.as_bytes());
64 format!("{:x}", hasher.finalize())
65 }
66
67 fn stringify_card_param_map(card_data: &serde_json::Value) -> serde_json::Value {
69 match card_data {
70 serde_json::Value::Object(map) => {
71 let mut out = serde_json::Map::new();
72 for (k, v) in map {
73 out.insert(
74 k.clone(),
75 match v {
76 serde_json::Value::String(_) => v.clone(),
77 _ => serde_json::Value::String(v.to_string()),
78 },
79 );
80 }
81 serde_json::Value::Object(out)
82 }
83 other => other.clone(),
84 }
85 }
86
87 #[allow(clippy::too_many_arguments)]
89 pub async fn create_and_send_card(
90 &self,
91 card_template_id: &str,
92 card_data: &serde_json::Value,
93 callback_type: &str,
94 callback_route_key: &str,
95 at_sender: bool,
96 at_all: bool,
97 recipients: Option<&[String]>,
98 support_forward: bool,
99 ) -> crate::Result<String> {
100 let access_token = self.token_manager.get_access_token().await?;
101 let card_instance_id = Self::gen_card_id(&self.incoming_message);
102 let param_map = Self::stringify_card_param_map(card_data);
103
104 let mut create_body = serde_json::json!({
105 "cardTemplateId": card_template_id,
106 "outTrackId": card_instance_id,
107 "cardData": {"cardParamMap": param_map},
108 "callbackType": callback_type,
109 "imGroupOpenSpaceModel": {"supportForward": support_forward},
110 "imRobotOpenSpaceModel": {"supportForward": support_forward},
111 });
112
113 if callback_type == "HTTP" {
114 create_body["callbackRouteKey"] = serde_json::json!(callback_route_key);
115 }
116
117 let url = format!(
118 "{}/v1.0/card/instances",
119 self.http_client.openapi_endpoint()
120 );
121 self.http_client
122 .post_json(&url, &create_body, Some(&access_token))
123 .await?;
124
125 let mut deliver_body = serde_json::json!({
126 "outTrackId": card_instance_id,
127 "userIdType": 1,
128 });
129
130 self.build_deliver_body(&mut deliver_body, at_sender, at_all, recipients);
131
132 let url = format!(
133 "{}/v1.0/card/instances/deliver",
134 self.http_client.openapi_endpoint()
135 );
136 self.http_client
137 .post_json(&url, &deliver_body, Some(&access_token))
138 .await?;
139
140 Ok(card_instance_id)
141 }
142
143 #[allow(clippy::too_many_arguments)]
145 pub async fn create_and_deliver_card(
146 &self,
147 card_template_id: &str,
148 card_data: &serde_json::Value,
149 callback_type: &str,
150 callback_route_key: &str,
151 at_sender: bool,
152 at_all: bool,
153 recipients: Option<&[String]>,
154 support_forward: bool,
155 extra: Option<&serde_json::Value>,
156 ) -> crate::Result<String> {
157 let access_token = self.token_manager.get_access_token().await?;
158 let card_instance_id = Self::gen_card_id(&self.incoming_message);
159 let param_map = Self::stringify_card_param_map(card_data);
160
161 let mut body = serde_json::json!({
162 "cardTemplateId": card_template_id,
163 "outTrackId": card_instance_id,
164 "cardData": {"cardParamMap": param_map},
165 "callbackType": callback_type,
166 "imGroupOpenSpaceModel": {"supportForward": support_forward},
167 "imRobotOpenSpaceModel": {"supportForward": support_forward},
168 });
169
170 if callback_type == "HTTP" {
171 body["callbackRouteKey"] = serde_json::json!(callback_route_key);
172 }
173
174 self.build_deliver_body(&mut body, at_sender, at_all, recipients);
175
176 if let Some(extra) = extra {
177 if let (Some(body_obj), Some(extra_obj)) = (body.as_object_mut(), extra.as_object()) {
178 for (k, v) in extra_obj {
179 body_obj.insert(k.clone(), v.clone());
180 }
181 }
182 }
183
184 let url = format!(
185 "{}/v1.0/card/instances/createAndDeliver",
186 self.http_client.openapi_endpoint()
187 );
188 self.http_client
189 .post_json(&url, &body, Some(&access_token))
190 .await?;
191
192 Ok(card_instance_id)
193 }
194
195 pub async fn put_card_data(
197 &self,
198 card_instance_id: &str,
199 card_data: &serde_json::Value,
200 extra: Option<&serde_json::Value>,
201 ) -> crate::Result<()> {
202 let access_token = self.token_manager.get_access_token().await?;
203 let param_map = Self::stringify_card_param_map(card_data);
204
205 let mut body = serde_json::json!({
206 "outTrackId": card_instance_id,
207 "cardData": {"cardParamMap": param_map},
208 });
209
210 if let Some(extra) = extra {
211 if let (Some(body_obj), Some(extra_obj)) = (body.as_object_mut(), extra.as_object()) {
212 for (k, v) in extra_obj {
213 body_obj.insert(k.clone(), v.clone());
214 }
215 }
216 }
217
218 let url = format!(
219 "{}/v1.0/card/instances",
220 self.http_client.openapi_endpoint()
221 );
222 self.http_client
223 .put_json(&url, &body, Some(&access_token))
224 .await?;
225
226 Ok(())
227 }
228
229 fn build_deliver_body(
231 &self,
232 body: &mut serde_json::Value,
233 at_sender: bool,
234 at_all: bool,
235 recipients: Option<&[String]>,
236 ) {
237 let msg = &self.incoming_message;
238 let Some(body_obj) = body.as_object_mut() else {
239 return;
240 };
241
242 if msg.conversation_type.as_deref() == Some("2") {
243 body_obj.insert(
244 "openSpaceId".to_owned(),
245 serde_json::json!(format!(
246 "dtv1.card//IM_GROUP.{}",
247 msg.conversation_id.as_deref().unwrap_or("")
248 )),
249 );
250
251 let mut group_model = serde_json::json!({
252 "robotCode": self.client_id,
253 });
254
255 if at_all {
256 group_model["atUserIds"] = serde_json::json!({"@ALL": "@ALL"});
257 } else if at_sender {
258 let staff_id = msg.sender_staff_id.as_deref().unwrap_or("");
259 let nick = msg.sender_nick.as_deref().unwrap_or("");
260 group_model["atUserIds"] = serde_json::json!({staff_id: nick});
261 }
262
263 if let Some(recipients) = recipients {
264 group_model["recipients"] = serde_json::json!(recipients);
265 }
266
267 if let Some(ref hosting) = msg.hosting_context {
268 group_model["extension"] = serde_json::json!({
269 "hostingRepliedContext": serde_json::to_string(
270 &serde_json::json!({"userId": hosting.user_id})
271 ).unwrap_or_default()
272 });
273 }
274
275 body_obj.insert("imGroupOpenDeliverModel".to_owned(), group_model);
276 } else if msg.conversation_type.as_deref() == Some("1") {
277 body_obj.insert(
278 "openSpaceId".to_owned(),
279 serde_json::json!(format!(
280 "dtv1.card//IM_ROBOT.{}",
281 msg.sender_staff_id.as_deref().unwrap_or("")
282 )),
283 );
284
285 let mut robot_model = serde_json::json!({"spaceType": "IM_ROBOT"});
286
287 if let Some(ref hosting) = msg.hosting_context {
288 robot_model["extension"] = serde_json::json!({
289 "hostingRepliedContext": serde_json::to_string(
290 &serde_json::json!({"userId": hosting.user_id})
291 ).unwrap_or_default()
292 });
293 }
294
295 body_obj.insert("imRobotOpenDeliverModel".to_owned(), robot_model);
296 }
297 }
298}
299
300#[derive(Clone)]
302pub struct AICardReplier {
303 inner: CardReplier,
304}
305
306impl AICardReplier {
307 pub fn new(inner: CardReplier) -> Self {
309 Self { inner }
310 }
311
312 pub fn inner(&self) -> &CardReplier {
314 &self.inner
315 }
316
317 pub async fn start(
319 &self,
320 card_template_id: &str,
321 card_data: &serde_json::Value,
322 recipients: Option<&[String]>,
323 support_forward: bool,
324 ) -> crate::Result<String> {
325 let mut data = card_data.clone();
326 if let Some(obj) = data.as_object_mut() {
327 obj.insert(
328 "flowStatus".to_owned(),
329 serde_json::json!(AICardStatus::Processing as u8),
330 );
331 }
332
333 self.inner
334 .create_and_send_card(
335 card_template_id,
336 &data,
337 "STREAM",
338 "",
339 false,
340 false,
341 recipients,
342 support_forward,
343 )
344 .await
345 }
346
347 pub async fn finish(
349 &self,
350 card_instance_id: &str,
351 card_data: &serde_json::Value,
352 ) -> crate::Result<()> {
353 let mut data = card_data.clone();
354 if let Some(obj) = data.as_object_mut() {
355 obj.insert(
356 "flowStatus".to_owned(),
357 serde_json::json!(AICardStatus::Finished as u8),
358 );
359 }
360
361 self.inner
362 .put_card_data(card_instance_id, &data, None)
363 .await
364 }
365
366 pub async fn fail(
368 &self,
369 card_instance_id: &str,
370 card_data: &serde_json::Value,
371 ) -> crate::Result<()> {
372 let mut data = card_data.clone();
373 if let Some(obj) = data.as_object_mut() {
374 obj.insert(
375 "flowStatus".to_owned(),
376 serde_json::json!(AICardStatus::Failed as u8),
377 );
378 }
379
380 self.inner
381 .put_card_data(card_instance_id, &data, None)
382 .await
383 }
384
385 pub async fn streaming(
387 &self,
388 card_instance_id: &str,
389 content_key: &str,
390 content_value: &str,
391 append: bool,
392 finished: bool,
393 failed: bool,
394 ) -> crate::Result<()> {
395 let access_token = self.inner.token_manager.get_access_token().await?;
396
397 let body = serde_json::json!({
398 "outTrackId": card_instance_id,
399 "guid": uuid::Uuid::new_v4().to_string(),
400 "key": content_key,
401 "content": content_value,
402 "isFull": !append,
403 "isFinalize": finished,
404 "isError": failed,
405 });
406
407 let url = format!(
408 "{}/v1.0/card/streaming",
409 self.inner.http_client.openapi_endpoint()
410 );
411 self.inner
412 .http_client
413 .put_json(&url, &body, Some(&access_token))
414 .await?;
415
416 Ok(())
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn test_gen_card_id() {
426 let msg = ChatbotMessage {
427 sender_id: Some("user_001".to_owned()),
428 sender_corp_id: Some("corp_001".to_owned()),
429 conversation_id: Some("conv_001".to_owned()),
430 message_id: Some("msg_001".to_owned()),
431 ..Default::default()
432 };
433 let id = CardReplier::gen_card_id(&msg);
434 assert_eq!(id.len(), 64);
435 }
436
437 #[test]
438 fn test_ai_card_status_values() {
439 assert_eq!(AICardStatus::Processing as u8, 1);
440 assert_eq!(AICardStatus::Inputing as u8, 2);
441 assert_eq!(AICardStatus::Finished as u8, 3);
442 assert_eq!(AICardStatus::Executing as u8, 4);
443 assert_eq!(AICardStatus::Failed as u8, 5);
444 }
445
446 #[test]
447 fn test_ai_card_status_serialize() {
448 let status = AICardStatus::Processing;
449 let json = serde_json::to_value(status).unwrap();
450 assert_eq!(json, 1);
451 }
452}