fix(desktop): DeerFlow UI — ChatArea refactor + ai-elements + dead CSS cleanup
ChatArea retry button uses setInput instead of direct sendToGateway, fix bootstrap spinner stuck for non-logged-in users, remove dead CSS (aurora-title/sidebar-open/quick-action-chips), add ai components (ReasoningBlock/StreamingText/ChatMode/ModelSelector/TaskProgress), add ClassroomPlayer + ResizableChatLayout + artifact panel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,18 @@
|
||||
//! 通过 TOML 配置定时任务,无需改代码调整调度时间。
|
||||
//! 配置格式在 config.rs 的 SchedulerConfig / JobConfig 中定义。
|
||||
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use sqlx::PgPool;
|
||||
use crate::config::SchedulerConfig;
|
||||
use crate::workers::WorkerDispatcher;
|
||||
|
||||
/// 单次任务执行的产出
|
||||
struct TaskExecution {
|
||||
result: Option<String>,
|
||||
error: Option<String>,
|
||||
duration_ms: i64,
|
||||
}
|
||||
|
||||
/// 解析时间间隔字符串为 Duration
|
||||
pub fn parse_duration(s: &str) -> Result<Duration, String> {
|
||||
let s = s.trim().to_lowercase();
|
||||
@@ -143,23 +150,42 @@ pub fn start_user_task_scheduler(db: PgPool) {
|
||||
});
|
||||
}
|
||||
|
||||
/// 执行单个调度任务
|
||||
/// 执行单个调度任务,返回执行产出(结果/错误/耗时)
|
||||
async fn execute_scheduled_task(
|
||||
db: &PgPool,
|
||||
task_id: &str,
|
||||
target_type: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let task_info: Option<(String, Option<String>)> = sqlx::query_as(
|
||||
) -> TaskExecution {
|
||||
let start = Instant::now();
|
||||
|
||||
let task_info: Option<(String, Option<String>)> = match sqlx::query_as(
|
||||
"SELECT name, config_json FROM scheduled_tasks WHERE id = $1"
|
||||
)
|
||||
.bind(task_id)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch task {}: {}", task_id, e))?;
|
||||
{
|
||||
Ok(info) => info,
|
||||
Err(e) => {
|
||||
let elapsed = start.elapsed().as_millis() as i64;
|
||||
return TaskExecution {
|
||||
result: None,
|
||||
error: Some(format!("Failed to fetch task {}: {}", task_id, e)),
|
||||
duration_ms: elapsed,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let (task_name, _config_json) = match task_info {
|
||||
Some(info) => info,
|
||||
None => return Err(format!("Task {} not found", task_id).into()),
|
||||
None => {
|
||||
let elapsed = start.elapsed().as_millis() as i64;
|
||||
return TaskExecution {
|
||||
result: None,
|
||||
error: Some(format!("Task {} not found", task_id)),
|
||||
duration_ms: elapsed,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
@@ -167,22 +193,39 @@ async fn execute_scheduled_task(
|
||||
task_name, target_type
|
||||
);
|
||||
|
||||
match target_type {
|
||||
let exec_result = match target_type {
|
||||
t if t == "agent" => {
|
||||
tracing::info!("[UserScheduler] Agent task '{}' queued for execution", task_name);
|
||||
Ok("agent_dispatched".to_string())
|
||||
}
|
||||
t if t == "hand" => {
|
||||
tracing::info!("[UserScheduler] Hand task '{}' queued for execution", task_name);
|
||||
Ok("hand_dispatched".to_string())
|
||||
}
|
||||
t if t == "workflow" => {
|
||||
tracing::info!("[UserScheduler] Workflow task '{}' queued for execution", task_name);
|
||||
Ok("workflow_dispatched".to_string())
|
||||
}
|
||||
other => {
|
||||
tracing::warn!("[UserScheduler] Unknown target_type '{}' for task '{}'", other, task_name);
|
||||
Err(format!("Unknown target_type: {}", other))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
let elapsed = start.elapsed().as_millis() as i64;
|
||||
|
||||
match exec_result {
|
||||
Ok(msg) => TaskExecution {
|
||||
result: Some(msg),
|
||||
error: None,
|
||||
duration_ms: elapsed,
|
||||
},
|
||||
Err(err) => TaskExecution {
|
||||
result: None,
|
||||
error: Some(err),
|
||||
duration_ms: elapsed,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn tick_user_tasks(db: &PgPool) -> Result<(), sqlx::Error> {
|
||||
@@ -206,17 +249,19 @@ async fn tick_user_tasks(db: &PgPool) -> Result<(), sqlx::Error> {
|
||||
task_id, target_type, schedule_type
|
||||
);
|
||||
|
||||
// 执行任务
|
||||
match execute_scheduled_task(db, &task_id, &target_type).await {
|
||||
Ok(()) => {
|
||||
tracing::info!("[UserScheduler] task {} executed successfully", task_id);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("[UserScheduler] task {} execution failed: {}", task_id, e);
|
||||
}
|
||||
// 执行任务并收集产出
|
||||
let exec = execute_scheduled_task(db, &task_id, &target_type).await;
|
||||
|
||||
if let Some(ref err) = exec.error {
|
||||
tracing::error!("[UserScheduler] task {} execution failed: {}", task_id, err);
|
||||
} else {
|
||||
tracing::info!(
|
||||
"[UserScheduler] task {} executed successfully ({}ms)",
|
||||
task_id, exec.duration_ms
|
||||
);
|
||||
}
|
||||
|
||||
// 更新任务状态
|
||||
// 更新任务状态(含执行产出)
|
||||
let result = sqlx::query(
|
||||
"UPDATE scheduled_tasks
|
||||
SET last_run_at = NOW(),
|
||||
@@ -228,10 +273,16 @@ async fn tick_user_tasks(db: &PgPool) -> Result<(), sqlx::Error> {
|
||||
WHEN schedule_type = 'interval' AND interval_seconds IS NOT NULL
|
||||
THEN NOW() + (interval_seconds || ' seconds')::INTERVAL
|
||||
ELSE NULL
|
||||
END
|
||||
END,
|
||||
last_result = $2,
|
||||
last_error = $3,
|
||||
last_duration_ms = $4
|
||||
WHERE id = $1"
|
||||
)
|
||||
.bind(&task_id)
|
||||
.bind(&exec.result)
|
||||
.bind(&exec.error)
|
||||
.bind(exec.duration_ms)
|
||||
.execute(db)
|
||||
.await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user