feat: add folder picker and target_url input to Web UI

- Add /select-folder and /select-file APIs using PowerShell dialogs
- Add --target-url parameter to CLI for explicit target URL override
- Redesign Web UI with folder browse buttons for all path inputs
- Add target_url optional input field for specifying target page URL
- Auto-fill scene-id from selected folder name

🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
木炎
2026-04-17 00:23:09 +08:00
parent ce072c2ebe
commit f268668713
6 changed files with 252 additions and 118 deletions

View File

@@ -18,6 +18,7 @@ fn run() -> Result<(), String> {
scene_id: args.scene_id,
scene_name: args.scene_name,
scene_kind: args.scene_kind,
target_url: args.target_url,
output_root: args.output_root,
lessons_path: args.lessons_path,
})
@@ -32,6 +33,7 @@ struct CliArgs {
scene_id: String,
scene_name: String,
scene_kind: Option<SceneKind>,
target_url: Option<String>,
output_root: PathBuf,
lessons_path: PathBuf,
}
@@ -41,6 +43,7 @@ fn parse_args(args: impl Iterator<Item = String>) -> Result<CliArgs, String> {
let mut scene_id = None;
let mut scene_name = None;
let mut scene_kind = None;
let mut target_url = None;
let mut output_root = None;
let mut lessons_path = None;
let mut pending_flag: Option<String> = None;
@@ -57,6 +60,7 @@ fn parse_args(args: impl Iterator<Item = String>) -> Result<CliArgs, String> {
.ok_or_else(|| format!("invalid scene kind: {}", arg))?,
);
}
"--target-url" => target_url = Some(arg),
"--output-root" => output_root = Some(PathBuf::from(arg)),
"--lessons" => lessons_path = Some(PathBuf::from(arg)),
_ => return Err(format!("unsupported argument {flag}")),
@@ -65,8 +69,8 @@ fn parse_args(args: impl Iterator<Item = String>) -> Result<CliArgs, String> {
}
match arg.as_str() {
"--source-dir" | "--scene-id" | "--scene-name" | "--scene-kind" | "--output-root"
| "--lessons" => {
"--source-dir" | "--scene-id" | "--scene-name" | "--scene-kind" | "--target-url"
| "--output-root" | "--lessons" => {
pending_flag = Some(arg);
}
"--help" | "-h" => return Err(usage()),
@@ -83,11 +87,12 @@ fn parse_args(args: impl Iterator<Item = String>) -> Result<CliArgs, String> {
scene_id: scene_id.ok_or_else(usage)?,
scene_name: scene_name.ok_or_else(usage)?,
scene_kind,
target_url,
output_root: output_root.ok_or_else(usage)?,
lessons_path: lessons_path.ok_or_else(usage)?,
})
}
fn usage() -> String {
"usage: sg_scene_generate --source-dir <scenario-dir> --scene-id <scene-id> --scene-name <display-name> [--scene-kind <report_collection|monitoring>] --output-root <skill-staging-root> --lessons <lessons-toml>".to_string()
"usage: sg_scene_generate --source-dir <scenario-dir> --scene-id <scene-id> --scene-name <display-name> [--scene-kind <report_collection|monitoring>] [--target-url <url>] --output-root <skill-staging-root> --lessons <lessons-toml>".to_string()
}

View File

@@ -13,6 +13,7 @@ pub struct GenerateSceneRequest {
pub scene_id: String,
pub scene_name: String,
pub scene_kind: Option<SceneKind>,
pub target_url: Option<String>,
pub output_root: PathBuf,
pub lessons_path: PathBuf,
}
@@ -153,7 +154,10 @@ fn scene_toml_report_collection(
tool_name: &str,
) -> String {
let expected_domain = analysis.bootstrap.expected_domain.as_deref().unwrap_or_default();
let target_url = analysis.bootstrap.target_url.as_deref().unwrap_or_default();
// Use request.target_url if provided, otherwise fall back to analysis
let target_url = request.target_url.as_deref()
.or(analysis.bootstrap.target_url.as_deref())
.unwrap_or_default();
format!(
"[scene]\nid = \"{}\"\nskill = \"{}\"\ntool = \"{}\"\nkind = \"browser_script\"\nversion = \"0.1.0\"\ncategory = \"report_collection\"\n\n[manifest]\nschema_version = \"1\"\n\n[bootstrap]\nexpected_domain = \"{}\"\ntarget_url = \"{}\"\npage_title_keywords = [\"报表\", \"线损\"]\nrequires_target_page = true\n\n[deterministic]\nsuffix = \"。。。\"\ninclude_keywords = [\"{}\", \"报表\", \"统计\"]\nexclude_keywords = [\"知乎\"]\n\n[[params]]\nname = \"org\"\nresolver = \"dictionary_entity\"\nrequired = true\nprompt_missing = \"已命中{},但缺少供电单位。\"\nprompt_ambiguous = \"已命中{},但供电单位存在歧义。\"\n\n[params.resolver_config]\ndictionary_ref = \"references/org-dictionary.json\"\noutput_label_field = \"org_label\"\noutput_code_field = \"org_code\"\n\n[[params]]\nname = \"period\"\nresolver = \"month_week_period\"\nrequired = true\nprompt_missing = \"已命中{},但缺少统计周期。\"\nprompt_ambiguous = \"已命中{},但统计周期存在歧义。\"\n\n[artifact]\ntype = \"report-artifact\"\nsuccess_status = [\"ok\", \"partial\", \"empty\"]\nfailure_status = [\"blocked\", \"error\"]\n\n[postprocess]\nexporter = \"xlsx_report\"\nauto_open = \"excel\"\n",
request.scene_id,
@@ -175,7 +179,10 @@ fn scene_toml_monitoring(
tool_name: &str,
) -> String {
let expected_domain = analysis.bootstrap.expected_domain.as_deref().unwrap_or_default();
let target_url = analysis.bootstrap.target_url.as_deref().unwrap_or_default();
// Use request.target_url if provided, otherwise fall back to analysis
let target_url = request.target_url.as_deref()
.or(analysis.bootstrap.target_url.as_deref())
.unwrap_or_default();
format!(
"[scene]\nid = \"{}\"\nskill = \"{}\"\ntool = \"{}\"\nkind = \"browser_script\"\nversion = \"0.1.0\"\ncategory = \"monitoring\"\n\n[manifest]\nschema_version = \"1\"\n\n[bootstrap]\nexpected_domain = \"{}\"\ntarget_url = \"{}\"\npage_title_keywords = [\"监测\", \"状态\"]\nrequires_target_page = true\n\n[deterministic]\nsuffix = \"。。。\"\ninclude_keywords = [\"{}\", \"监测\", \"状态\"]\nexclude_keywords = [\"知乎\"]\n\n# 监测类场景参数留空,由用户手动编辑\n\n[artifact]\ntype = \"report-artifact\"\nsuccess_status = [\"ok\", \"partial\", \"empty\"]\nfailure_status = [\"blocked\", \"error\"]\n\n[postprocess]\nexporter = \"xlsx_report\"\nauto_open = \"excel\"\n",
request.scene_id,