Skip to main content

dingtalk_stream/card/
instances.rs

1//! 卡片实例,对齐 Python card_instance.py
2
3use crate::card::replier::{AICardReplier, AICardStatus, CardReplier};
4
5const MARKDOWN_CARD_TEMPLATE_ID: &str = "589420e2-c1e2-46ef-a5ed-b8728e654da9.schema";
6const MARKDOWN_BUTTON_CARD_TEMPLATE_ID: &str = "1366a1eb-bc54-4859-ac88-517c56a9acb1.schema";
7const AI_MARKDOWN_CARD_TEMPLATE_ID: &str = "382e4302-551d-4880-bf29-a30acfab2e71.schema";
8const RPA_PLUGIN_CARD_TEMPLATE_ID: &str = "7f538f6d-ebb7-4533-a9ac-61a32da094cf.schema";
9
10/// Markdown 卡片实例
11pub struct MarkdownCardInstance {
12    replier: CardReplier,
13    /// 卡片实例 ID
14    pub card_instance_id: Option<String>,
15    title: Option<String>,
16    logo: Option<String>,
17}
18
19impl MarkdownCardInstance {
20    /// 创建新的 Markdown 卡片实例
21    pub fn new(replier: CardReplier) -> Self {
22        Self {
23            replier,
24            card_instance_id: None,
25            title: None,
26            logo: None,
27        }
28    }
29
30    /// 设置标题和 logo
31    pub fn set_title_and_logo(&mut self, title: &str, logo: &str) {
32        self.title = Some(title.to_owned());
33        self.logo = Some(logo.to_owned());
34    }
35
36    fn get_card_data(&self, markdown: &str) -> serde_json::Value {
37        let mut data = serde_json::json!({"markdown": markdown});
38        if let Some(ref t) = self.title {
39            if !t.is_empty() {
40                data["title"] = serde_json::json!(t);
41            }
42        }
43        if let Some(ref l) = self.logo {
44            if !l.is_empty() {
45                data["logo"] = serde_json::json!(l);
46            }
47        }
48        data
49    }
50
51    /// 回复 markdown 内容
52    pub async fn reply(
53        &mut self,
54        markdown: &str,
55        at_sender: bool,
56        at_all: bool,
57        recipients: Option<&[String]>,
58        support_forward: bool,
59    ) -> crate::Result<()> {
60        let card_data = self.get_card_data(markdown);
61        self.card_instance_id = Some(
62            self.replier
63                .create_and_send_card(
64                    MARKDOWN_CARD_TEMPLATE_ID,
65                    &card_data,
66                    "STREAM",
67                    "",
68                    at_sender,
69                    at_all,
70                    recipients,
71                    support_forward,
72                )
73                .await?,
74        );
75        Ok(())
76    }
77
78    /// 更新 markdown 内容
79    pub async fn update(&self, markdown: &str) -> crate::Result<()> {
80        let id = self
81            .card_instance_id
82            .as_deref()
83            .ok_or_else(|| crate::Error::Card("card not sent yet".to_owned()))?;
84        self.replier
85            .put_card_data(id, &self.get_card_data(markdown), None)
86            .await
87    }
88}
89
90/// Markdown + Button 卡片实例
91pub struct MarkdownButtonCardInstance {
92    replier: CardReplier,
93    /// 卡片实例 ID
94    pub card_instance_id: Option<String>,
95    title: Option<String>,
96    logo: Option<String>,
97    button_list: Vec<serde_json::Value>,
98}
99
100impl MarkdownButtonCardInstance {
101    /// 创建新实例
102    pub fn new(replier: CardReplier) -> Self {
103        Self {
104            replier,
105            card_instance_id: None,
106            title: None,
107            logo: None,
108            button_list: Vec::new(),
109        }
110    }
111
112    /// 设置标题和 logo
113    pub fn set_title_and_logo(&mut self, title: &str, logo: &str) {
114        self.title = Some(title.to_owned());
115        self.logo = Some(logo.to_owned());
116    }
117
118    fn get_card_data(&self, markdown: &str, tips: &str) -> serde_json::Value {
119        let mut data = serde_json::json!({"markdown": markdown, "tips": tips});
120        if let Some(ref t) = self.title {
121            if !t.is_empty() {
122                data["title"] = serde_json::json!(t);
123            }
124        }
125        if let Some(ref l) = self.logo {
126            if !l.is_empty() {
127                data["logo"] = serde_json::json!(l);
128            }
129        }
130        if !self.button_list.is_empty() {
131            data["sys_full_json_obj"] = serde_json::Value::String(
132                serde_json::to_string(&serde_json::json!({"msgButtons": self.button_list}))
133                    .unwrap_or_default(),
134            );
135        }
136        data
137    }
138
139    /// 回复
140    pub async fn reply(
141        &mut self,
142        markdown: &str,
143        button_list: Vec<serde_json::Value>,
144        tips: &str,
145        recipients: Option<&[String]>,
146        support_forward: bool,
147    ) -> crate::Result<()> {
148        self.button_list = button_list;
149        let card_data = self.get_card_data(markdown, tips);
150        self.card_instance_id = Some(
151            self.replier
152                .create_and_send_card(
153                    MARKDOWN_BUTTON_CARD_TEMPLATE_ID,
154                    &card_data,
155                    "STREAM",
156                    "",
157                    false,
158                    false,
159                    recipients,
160                    support_forward,
161                )
162                .await?,
163        );
164        Ok(())
165    }
166
167    /// 更新
168    pub async fn update(
169        &mut self,
170        markdown: &str,
171        button_list: Vec<serde_json::Value>,
172        tips: &str,
173    ) -> crate::Result<()> {
174        let id = self
175            .card_instance_id
176            .as_deref()
177            .ok_or_else(|| crate::Error::Card("card not sent yet".to_owned()))?;
178        self.button_list = button_list;
179        self.replier
180            .put_card_data(id, &self.get_card_data(markdown, tips), None)
181            .await
182    }
183}
184
185/// AI Markdown 卡片实例
186pub struct AIMarkdownCardInstance {
187    replier: AICardReplier,
188    /// 卡片实例 ID
189    pub card_instance_id: Option<String>,
190    title: Option<String>,
191    logo: Option<String>,
192    markdown: String,
193    static_markdown: String,
194    button_list: Option<Vec<serde_json::Value>>,
195    inputing_status: bool,
196    order: Vec<String>,
197}
198
199impl AIMarkdownCardInstance {
200    /// 创建新实例
201    pub fn new(replier: AICardReplier) -> Self {
202        Self {
203            replier,
204            card_instance_id: None,
205            title: None,
206            logo: None,
207            markdown: String::new(),
208            static_markdown: String::new(),
209            button_list: None,
210            inputing_status: false,
211            order: vec![
212                "msgTitle".to_owned(),
213                "msgContent".to_owned(),
214                "staticMsgContent".to_owned(),
215                "msgTextList".to_owned(),
216                "msgImages".to_owned(),
217                "msgSlider".to_owned(),
218                "msgButtons".to_owned(),
219            ],
220        }
221    }
222
223    /// 设置标题和 logo
224    pub fn set_title_and_logo(&mut self, title: &str, logo: &str) {
225        self.title = Some(title.to_owned());
226        self.logo = Some(logo.to_owned());
227    }
228
229    /// 设置排序
230    pub fn set_order(&mut self, order: Vec<String>) {
231        self.order = order;
232    }
233
234    /// 获取卡片数据
235    pub fn get_card_data(&self, flow_status: Option<AICardStatus>) -> serde_json::Value {
236        let mut data = serde_json::json!({
237            "msgContent": self.markdown,
238            "staticMsgContent": self.static_markdown,
239        });
240        if let Some(status) = flow_status {
241            data["flowStatus"] = serde_json::json!(status as u8);
242        }
243        if let Some(ref t) = self.title {
244            if !t.is_empty() {
245                data["msgTitle"] = serde_json::json!(t);
246            }
247        }
248        if let Some(ref l) = self.logo {
249            if !l.is_empty() {
250                data["logo"] = serde_json::json!(l);
251            }
252        }
253
254        let mut sys_full = serde_json::json!({"order": self.order});
255        if let Some(ref buttons) = self.button_list {
256            if !buttons.is_empty() {
257                sys_full["msgButtons"] = serde_json::json!(buttons);
258            }
259        }
260        if let Some(ref hosting) = self.replier.inner().incoming_message.hosting_context {
261            sys_full["source"] =
262                serde_json::json!({"text": format!("由{}的数字助理回答", hosting.nick)});
263        }
264        data["sys_full_json_obj"] =
265            serde_json::Value::String(serde_json::to_string(&sys_full).unwrap_or_default());
266        data
267    }
268
269    /// 开始 AI 卡片
270    pub async fn ai_start(
271        &mut self,
272        recipients: Option<&[String]>,
273        support_forward: bool,
274    ) -> crate::Result<()> {
275        if self.card_instance_id.is_some() {
276            return Ok(());
277        }
278        self.card_instance_id = Some(
279            self.replier
280                .start(
281                    AI_MARKDOWN_CARD_TEMPLATE_ID,
282                    &serde_json::json!({}),
283                    recipients,
284                    support_forward,
285                )
286                .await?,
287        );
288        self.inputing_status = false;
289        Ok(())
290    }
291
292    /// 流式输出
293    pub async fn ai_streaming(&mut self, markdown: &str, append: bool) -> crate::Result<()> {
294        let id = self
295            .card_instance_id
296            .as_deref()
297            .ok_or_else(|| crate::Error::Card("card not started yet".to_owned()))?
298            .to_owned();
299        if !self.inputing_status {
300            let card_data = self.get_card_data(Some(AICardStatus::Inputing));
301            self.replier
302                .inner()
303                .put_card_data(&id, &card_data, None)
304                .await?;
305            self.inputing_status = true;
306        }
307        if append {
308            self.markdown.push_str(markdown);
309        } else {
310            self.markdown = markdown.to_owned();
311        }
312        self.replier
313            .streaming(&id, "msgContent", &self.markdown, false, false, false)
314            .await
315    }
316
317    /// 完成 AI 卡片
318    pub async fn ai_finish(
319        &mut self,
320        markdown: Option<&str>,
321        button_list: Option<Vec<serde_json::Value>>,
322        _tips: &str,
323    ) -> crate::Result<()> {
324        let id = self
325            .card_instance_id
326            .as_deref()
327            .ok_or_else(|| crate::Error::Card("card not started yet".to_owned()))?
328            .to_owned();
329        if let Some(md) = markdown {
330            self.markdown = md.to_owned();
331        }
332        if let Some(buttons) = button_list {
333            self.button_list = Some(buttons);
334        }
335        self.replier.finish(&id, &self.get_card_data(None)).await
336    }
337
338    /// 更新非流式内容
339    pub async fn update(
340        &mut self,
341        static_markdown: Option<&str>,
342        button_list: Option<Vec<serde_json::Value>>,
343        _tips: &str,
344    ) -> crate::Result<()> {
345        let id = self
346            .card_instance_id
347            .as_deref()
348            .ok_or_else(|| crate::Error::Card("card not started yet".to_owned()))?
349            .to_owned();
350        if let Some(buttons) = button_list {
351            self.button_list = Some(buttons);
352        }
353        if let Some(sm) = static_markdown {
354            self.static_markdown = sm.to_owned();
355        }
356        self.replier.finish(&id, &self.get_card_data(None)).await
357    }
358
359    /// 失败状态
360    pub async fn ai_fail(&mut self) -> crate::Result<()> {
361        let id = self
362            .card_instance_id
363            .as_deref()
364            .ok_or_else(|| crate::Error::Card("card not started yet".to_owned()))?
365            .to_owned();
366        let mut card_data = serde_json::json!({});
367        if let Some(ref t) = self.title {
368            if !t.is_empty() {
369                card_data["msgTitle"] = serde_json::json!(t);
370            }
371        }
372        if let Some(ref l) = self.logo {
373            if !l.is_empty() {
374                card_data["logo"] = serde_json::json!(l);
375            }
376        }
377        self.replier.fail(&id, &card_data).await
378    }
379}
380
381/// 轮播图卡片实例
382pub struct CarouselCardInstance {
383    replier: AICardReplier,
384    /// 卡片实例 ID
385    pub card_instance_id: Option<String>,
386    title: Option<String>,
387    logo: Option<String>,
388}
389
390impl CarouselCardInstance {
391    /// 创建新实例
392    pub fn new(replier: AICardReplier) -> Self {
393        Self {
394            replier,
395            card_instance_id: None,
396            title: None,
397            logo: None,
398        }
399    }
400
401    /// 设置标题和 logo
402    pub fn set_title_and_logo(&mut self, title: &str, logo: &str) {
403        self.title = Some(title.to_owned());
404        self.logo = Some(logo.to_owned());
405    }
406
407    /// 开始 AI 卡片
408    pub async fn ai_start(&mut self) -> crate::Result<()> {
409        self.card_instance_id = Some(
410            self.replier
411                .start(
412                    AI_MARKDOWN_CARD_TEMPLATE_ID,
413                    &serde_json::json!({}),
414                    None,
415                    true,
416                )
417                .await?,
418        );
419        Ok(())
420    }
421
422    /// 回复轮播图卡片
423    pub async fn reply(
424        &mut self,
425        markdown: &str,
426        image_slider_list: &[(String, String)],
427        button_text: &str,
428        recipients: Option<&[String]>,
429        support_forward: bool,
430    ) -> crate::Result<()> {
431        let slider: Vec<serde_json::Value> = image_slider_list
432            .iter()
433            .map(|(t, i)| serde_json::json!({"title": t, "image": i}))
434            .collect();
435        let sys_full = serde_json::json!({
436            "order": ["msgTitle", "staticMsgContent", "msgSlider", "msgImages", "msgTextList", "msgButtons"],
437            "msgSlider": slider,
438            "msgButtons": [{"text": button_text, "color": "blue", "id": "image_slider_select_button", "request": true}]
439        });
440        let mut card_data = serde_json::json!({"staticMsgContent": markdown, "sys_full_json_obj": serde_json::to_string(&sys_full).unwrap_or_default()});
441        if let Some(ref t) = self.title {
442            if !t.is_empty() {
443                card_data["msgTitle"] = serde_json::json!(t);
444            }
445        }
446        if let Some(ref l) = self.logo {
447            if !l.is_empty() {
448                card_data["logo"] = serde_json::json!(l);
449            }
450        }
451
452        self.card_instance_id = Some(
453            self.replier
454                .inner()
455                .create_and_send_card(
456                    AI_MARKDOWN_CARD_TEMPLATE_ID,
457                    &serde_json::json!({"flowStatus": AICardStatus::Processing as u8}),
458                    "STREAM",
459                    "",
460                    false,
461                    false,
462                    recipients,
463                    support_forward,
464                )
465                .await?,
466        );
467        let id = self.card_instance_id.as_deref().unwrap_or_default();
468        self.replier.finish(id, &card_data).await
469    }
470}
471
472/// RPA 插件卡片实例
473pub struct RPAPluginCardInstance {
474    replier: AICardReplier,
475    /// 卡片实例 ID
476    pub card_instance_id: Option<String>,
477    goal: String,
478    corp_id: String,
479}
480
481impl RPAPluginCardInstance {
482    /// 创建新实例
483    pub fn new(replier: AICardReplier) -> Self {
484        Self {
485            replier,
486            card_instance_id: None,
487            goal: String::new(),
488            corp_id: String::new(),
489        }
490    }
491
492    /// 设置目标
493    pub fn set_goal(&mut self, goal: &str) {
494        self.goal = goal.to_owned();
495    }
496
497    /// 设置企业 ID
498    pub fn set_corp_id(&mut self, corp_id: &str) {
499        self.corp_id = corp_id.to_owned();
500    }
501
502    /// 回复 RPA 插件卡片
503    #[allow(clippy::too_many_arguments)]
504    pub async fn reply(
505        &mut self,
506        plugin_id: &str,
507        plugin_version: &str,
508        plugin_name: &str,
509        ability_name: &str,
510        plugin_args: &serde_json::Value,
511        recipients: Option<&[String]>,
512        support_forward: bool,
513    ) -> crate::Result<()> {
514        let plan = serde_json::json!({
515            "corpId": self.corp_id, "goal": self.goal,
516            "plan": format!("(function(){{dd.callPlugin({{'pluginName':'{}','abilityName':'{}','args':{} }});}})())", plugin_name, ability_name, serde_json::to_string(plugin_args).unwrap_or_default()),
517            "planType": "jsCode",
518            "pluginInstances": [{"id": format!("AGI-EXTENSION-{}", plugin_id), "version": plugin_version}]
519        });
520        let card_data = serde_json::json!({"goal": self.goal, "processFlag": "true", "plan": serde_json::to_string(&plan).unwrap_or_default()});
521
522        self.card_instance_id = Some(
523            self.replier
524                .inner()
525                .create_and_send_card(
526                    RPA_PLUGIN_CARD_TEMPLATE_ID,
527                    &serde_json::json!({"flowStatus": AICardStatus::Processing as u8}),
528                    "STREAM",
529                    "",
530                    false,
531                    false,
532                    recipients,
533                    support_forward,
534                )
535                .await?,
536        );
537        let id = self.card_instance_id.as_deref().unwrap_or_default();
538        self.replier.finish(id, &card_data).await
539    }
540}