113 lines
3.4 KiB
Rust
113 lines
3.4 KiB
Rust
use reqwest::Url;
|
|
use serde_json::{json, Value};
|
|
use thiserror::Error;
|
|
|
|
use crate::pipe::Action;
|
|
|
|
const BAIDU_URL: &str = "https://www.baidu.com";
|
|
const BAIDU_DOMAIN: &str = "www.baidu.com";
|
|
const BAIDU_INPUT_SELECTOR: &str = "#kw";
|
|
const BAIDU_SEARCH_BUTTON_SELECTOR: &str = "#su";
|
|
const ZHIHU_URL: &str = "https://www.zhihu.com/search";
|
|
const ZHIHU_DOMAIN: &str = "www.zhihu.com";
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct PlannedStep {
|
|
pub action: Action,
|
|
pub params: Value,
|
|
pub expected_domain: String,
|
|
pub log_message: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct TaskPlan {
|
|
pub summary: String,
|
|
pub steps: Vec<PlannedStep>,
|
|
}
|
|
|
|
#[derive(Debug, Error, Clone, PartialEq, Eq)]
|
|
pub enum PlannerError {
|
|
#[error("unsupported instruction: {0}")]
|
|
UnsupportedInstruction(String),
|
|
#[error("missing search query in instruction")]
|
|
MissingQuery,
|
|
}
|
|
|
|
pub fn plan_instruction(instruction: &str) -> Result<TaskPlan, PlannerError> {
|
|
let trimmed = instruction.trim();
|
|
if let Some(query) = extract_query(trimmed, &["打开百度搜索", "打开百度并搜索"])? {
|
|
return Ok(plan_baidu_search(query));
|
|
}
|
|
|
|
if let Some(query) = extract_query(trimmed, &["打开知乎搜索", "打开知乎并搜索"])? {
|
|
return Ok(plan_zhihu_search(query));
|
|
}
|
|
|
|
Err(PlannerError::UnsupportedInstruction(trimmed.to_string()))
|
|
}
|
|
|
|
fn extract_query<'a>(
|
|
instruction: &'a str,
|
|
prefixes: &[&str],
|
|
) -> Result<Option<&'a str>, PlannerError> {
|
|
let Some(query) = prefixes
|
|
.iter()
|
|
.find_map(|prefix| instruction.strip_prefix(prefix))
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
|
|
let query = query.trim();
|
|
if query.is_empty() {
|
|
return Err(PlannerError::MissingQuery);
|
|
}
|
|
|
|
Ok(Some(query))
|
|
}
|
|
|
|
fn plan_baidu_search(query: &str) -> TaskPlan {
|
|
TaskPlan {
|
|
summary: format!("已在百度搜索{query}"),
|
|
steps: vec![
|
|
PlannedStep {
|
|
action: Action::Navigate,
|
|
params: json!({ "url": BAIDU_URL }),
|
|
expected_domain: BAIDU_DOMAIN.to_string(),
|
|
log_message: "navigate https://www.baidu.com".to_string(),
|
|
},
|
|
PlannedStep {
|
|
action: Action::Type,
|
|
params: json!({
|
|
"selector": BAIDU_INPUT_SELECTOR,
|
|
"text": query,
|
|
"clear_first": true
|
|
}),
|
|
expected_domain: BAIDU_DOMAIN.to_string(),
|
|
log_message: format!("type {query} into {BAIDU_INPUT_SELECTOR}"),
|
|
},
|
|
PlannedStep {
|
|
action: Action::Click,
|
|
params: json!({ "selector": BAIDU_SEARCH_BUTTON_SELECTOR }),
|
|
expected_domain: BAIDU_DOMAIN.to_string(),
|
|
log_message: format!("click {BAIDU_SEARCH_BUTTON_SELECTOR}"),
|
|
},
|
|
],
|
|
}
|
|
}
|
|
|
|
fn plan_zhihu_search(query: &str) -> TaskPlan {
|
|
let url = Url::parse_with_params(ZHIHU_URL, &[("type", "content"), ("q", query)])
|
|
.expect("valid Zhihu search URL");
|
|
let url: String = url.into();
|
|
|
|
TaskPlan {
|
|
summary: format!("已在知乎搜索{query}"),
|
|
steps: vec![PlannedStep {
|
|
action: Action::Navigate,
|
|
params: json!({ "url": url }),
|
|
expected_domain: ZHIHU_DOMAIN.to_string(),
|
|
log_message: format!("navigate {url}"),
|
|
}],
|
|
}
|
|
}
|