1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! 思维链(对标 AntDX ThoughtChain)。
//!
//! 推理步骤折叠列表:状态圆点 + 标题,点击展开看详情。
use crate::{prelude::*, *};
use std::sync::Arc;
/// 步骤状态。
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ThoughtStatus {
/// 等待。
#[default]
Pending,
/// 执行中。
Running,
/// 完成。
Done,
/// 失败。
Failed,
}
/// 思维链步骤。
#[derive(Clone)]
pub struct ThoughtStep {
/// 标题。
pub title: SharedString,
/// 详情(展开可见)。
pub detail: SharedString,
/// 状态。
pub status: ThoughtStatus,
}
impl ThoughtStep {
/// 创建步骤。
pub fn new(
title: impl Into<SharedString>,
detail: impl Into<SharedString>,
status: ThoughtStatus,
) -> Self {
Self {
title: title.into(),
detail: detail.into(),
status,
}
}
}
/// 思维链。
#[derive(IntoElement)]
pub struct ThoughtChain {
/// 是否展开全部详情。
expanded: bool,
/// 步骤列表。
steps: Vec<ThoughtStep>,
/// 展开切换回调(状态由父持有)。
on_toggle: Option<Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync + 'static>>,
/// 用户样式。
style: StyleRefinement,
}
impl ThoughtChain {
/// 创建思维链。
pub fn new(steps: Vec<ThoughtStep>) -> Self {
Self {
expanded: false,
steps,
on_toggle: None,
style: StyleRefinement::default(),
}
}
/// 设置是否展开。
pub fn expanded(mut self, expanded: bool) -> Self {
self.expanded = expanded;
self
}
/// 设置展开切换回调。
pub fn on_toggle<F>(mut self, f: F) -> Self
where
F: Fn(bool, &mut Window, &mut App) + Send + Sync + 'static,
{
self.on_toggle = Some(Arc::new(f));
self
}
}
impl Styled for ThoughtChain {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for ThoughtChain {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let accent = theme.tokens.accent.color;
let muted_foreground = theme.tokens.muted_foreground.color;
let user_style = self.style;
let expanded = self.expanded;
let on_toggle = self.on_toggle;
div()
.flex()
.flex_col()
.w_full()
.gap(px(4.0))
.child(
div()
.id("thought-chain-toggle")
.flex()
.flex_row()
.items_center()
.gap(px(6.0))
.cursor_pointer()
.text_sm()
.text_color(muted_foreground)
.child(if expanded {
IconName::ChevronDown
} else {
IconName::ChevronRight
})
.child("思考过程")
.on_click(move |_, window, cx| {
if let Some(ref cb) = on_toggle {
cb(!expanded, window, cx);
}
}),
)
.when(expanded, |this| {
this.children(self.steps.into_iter().enumerate().map(|(ix, step)| {
let dot = match step.status {
ThoughtStatus::Pending => muted_foreground,
ThoughtStatus::Running => accent,
ThoughtStatus::Done => green(),
ThoughtStatus::Failed => red(),
};
div()
.flex()
.flex_row()
.gap(px(8.0))
.items_start()
.child(
div()
.w(px(8.0))
.h(px(8.0))
.mt(px(5.0))
.rounded_full()
.bg(dot)
.flex_shrink_0(),
)
.child(
div()
.flex()
.flex_col()
.flex_1()
.gap(px(2.0))
.child(div().text_sm().text_color(muted_foreground).child(format!(
"{}. {}",
ix + 1,
step.title
)))
.child(
div()
.text_xs()
.text_color(muted_foreground)
.opacity(0.8)
.child(step.detail),
),
)
.into_any_element()
}))
})
.map(|mut this| {
this.style().refine(&user_style);
this
})
}
}