D26(iced 重写)的关键一次性动作:UI 框架无关的宿主层独立成 crate, Slint 版(crates/app)与 iced 版(crates/app-iced)共用,Slint 退役后不再重写一遍。 - 新建 crates/presenter(package `unlucky-presenter` / lib `unlucky_presenter`), workspace 登记 + crate 文档头写清「这层为什么存在」、对外表面、UI 侧允许/禁止、 iced 接线骨架、以及哪些东西留在了 crates/app 及为什么。 - `git mv`(保留文件历史)迁入:host/(25 文件)、dto/(11 文件)、state.rs(+tests)、 prefs.rs、settings_bridge.rs、toast.rs、router.rs、dialogs.rs、wizard.rs、 mock.rs、mock_data.rs、backend.rs。模块路径与公开 API 名称不变(`host::install` 等), 内部实现零改动。 - 新增 `theme`:把原本混在 ui_bind/theme_runtime 里的**纯判据**(CONFIG_POLL_TICKS / SYSTEM_THEME_POLL_TICKS / TILE_REGENERATE_STEP / BackgroundKey / tile_viewport / system_theme_value / 两个 poll 判据)抽出来,附 5 个单测;app 侧改成调用它们。 - crates/app 改为消费者:删掉迁走的文件,`crate::host|state|dto|prefs|settings_bridge| toast|router|dialogs|wizard|mock|mock_data|backend` 全部改写为 `unlucky_presenter::*` (64 处),backend.rs 的 config_path/build_backend 提为 pub。 - crates/app-iced 只登记 unlucky-presenter 依赖,本 mission 不接线。 - prefs.rs 的 2 处 slint 文档引用改写;`grep -rn slint crates/presenter` 现在无输出。
104 lines
3.8 KiB
Rust
104 lines
3.8 KiB
Rust
//! AI 页与覆盖层:会话消息、对话框(一次一条)、Toast。
|
|
|
|
use crate::{ChatRole, DialogKind, UiChatMessage, UiDialog, UiToast};
|
|
use unlucky_presenter::dialogs as dlg;
|
|
use unlucky_presenter::dto::ChatRole as DtoChatRole;
|
|
use unlucky_presenter::state::AppState;
|
|
use unlucky_presenter::toast as toast_mod;
|
|
|
|
/// AI 会话消息。
|
|
pub fn chat(state: &AppState) -> Vec<UiChatMessage> {
|
|
state
|
|
.ai
|
|
.messages
|
|
.iter()
|
|
.map(|message| UiChatMessage {
|
|
role: match message.role {
|
|
DtoChatRole::User => ChatRole::User,
|
|
DtoChatRole::Assistant => ChatRole::Assistant,
|
|
DtoChatRole::Tool => ChatRole::Tool,
|
|
DtoChatRole::System => ChatRole::System,
|
|
},
|
|
text: message.text.clone().into(),
|
|
tool_name: message
|
|
.tool
|
|
.as_ref()
|
|
.map(|tool| tool.name.clone())
|
|
.unwrap_or_default()
|
|
.into(),
|
|
tool_ok: message.tool.as_ref().is_none_or(|tool| tool.ok),
|
|
streaming: message.streaming,
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// 当前对话框(一次一条)+ 是否显示 + 队列剩余条数。
|
|
pub fn dialog(state: &AppState) -> (UiDialog, bool, i32) {
|
|
let queued = i32::try_from(state.dialogs.pending_len()).unwrap_or(0);
|
|
match state.dialogs.current() {
|
|
None => (UiDialog::default(), false, queued),
|
|
Some(request) => {
|
|
let (kind, primary, secondary) = match &request.kind {
|
|
dlg::DialogKind::Confirm { primary, secondary } => {
|
|
(DialogKind::Confirm, primary.clone(), secondary.clone())
|
|
}
|
|
dlg::DialogKind::Input { primary, .. } => {
|
|
(DialogKind::Input, primary.clone(), String::new())
|
|
}
|
|
dlg::DialogKind::Progress { .. } => {
|
|
(DialogKind::Progress, "取消".to_string(), String::new())
|
|
}
|
|
dlg::DialogKind::Error { primary, .. } => {
|
|
(DialogKind::Error, primary.clone(), "忽略".to_string())
|
|
}
|
|
};
|
|
let progress = match &request.kind {
|
|
dlg::DialogKind::Progress { progress, .. } => *progress,
|
|
_ => 0.0,
|
|
};
|
|
let input_label = match &request.kind {
|
|
dlg::DialogKind::Input { label, .. } => label.clone(),
|
|
_ => String::new(),
|
|
};
|
|
let input_value = match &request.kind {
|
|
dlg::DialogKind::Input { value, .. } => value.clone(),
|
|
_ => String::new(),
|
|
};
|
|
(
|
|
UiDialog {
|
|
id: request.id.to_string().into(),
|
|
kind,
|
|
title: request.title.clone().into(),
|
|
body: request.body.clone().into(),
|
|
primary: primary.into(),
|
|
secondary: secondary.into(),
|
|
progress,
|
|
input_label: input_label.into(),
|
|
input_value: input_value.into(),
|
|
},
|
|
true,
|
|
queued,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Toast 列表。
|
|
pub fn toasts(state: &AppState) -> Vec<UiToast> {
|
|
state
|
|
.toasts
|
|
.items()
|
|
.iter()
|
|
.map(|toast| UiToast {
|
|
id: toast.id.to_string().into(),
|
|
kind: match toast.kind {
|
|
toast_mod::ToastKind::Info => crate::ToastKind::Info,
|
|
toast_mod::ToastKind::Success => crate::ToastKind::Success,
|
|
toast_mod::ToastKind::Warn => crate::ToastKind::Warn,
|
|
toast_mod::ToastKind::Error => crate::ToastKind::Error,
|
|
},
|
|
text: toast.text.clone().into(),
|
|
})
|
|
.collect()
|
|
}
|