Fix tiptap selection sync and toolbar event loop
This commit is contained in:
@@ -3,6 +3,7 @@ use bridge_runtime::{
|
||||
execute_runtime_query, RuntimeActorWire, RuntimeBridgeContextWire, RuntimeInput,
|
||||
RuntimeQueryEnvelopeWire, RuntimeSourceWire, RuntimeTargetWire, RuntimeToolInvocationWire,
|
||||
};
|
||||
use clap::ValueEnum;
|
||||
use core_protocol::{
|
||||
default_tool_registry, query::Pagination, tool_effect_label, ActorPayload, CommandEnvelope,
|
||||
CreateDocumentPage, DeleteDocumentPage, GetMindmap, GetPageContent, InvocationKind,
|
||||
@@ -10,6 +11,11 @@ use core_protocol::{
|
||||
QueryEnvelope, RestoreDocumentPage, SavePageContent, SearchBlocks, SearchDocuments,
|
||||
SourcePayload, TargetRef, ToolExecutionMode, ToolInvocation, UpdatePageTitle,
|
||||
};
|
||||
use mnote_editor_core::{
|
||||
apply_ai_pipeline, export_markdown, import_markdown, BlockType, DocumentBlock,
|
||||
EditorAiScenario, EditorCommand, EditorInputKind, EditorPipelineRequest, EditorSession,
|
||||
VisibilitySnapshot,
|
||||
};
|
||||
use reqwest::blocking::Client;
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
@@ -130,6 +136,80 @@ pub struct CliTransportPlan {
|
||||
pub args_json: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorCliOutput {
|
||||
pub ok: bool,
|
||||
pub entrypoint: &'static str,
|
||||
pub operation: &'static str,
|
||||
pub block_count: usize,
|
||||
pub output_markdown: String,
|
||||
pub visible_block_ids: Vec<String>,
|
||||
pub outline: Vec<EditorOutlineItem>,
|
||||
pub audit: Vec<EditorAuditItem>,
|
||||
pub change_report: EditorChangeReportOutput,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub undo_applied: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub redo_applied: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorOutlineItem {
|
||||
pub block_id: String,
|
||||
pub level: u8,
|
||||
pub title: String,
|
||||
pub depth: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAuditItem {
|
||||
pub command: String,
|
||||
pub target_block_id: Option<String>,
|
||||
pub before: Option<EditorBlockSnapshot>,
|
||||
pub after: Option<EditorBlockSnapshot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorBlockSnapshot {
|
||||
pub block_id: String,
|
||||
pub block_type: String,
|
||||
pub text: String,
|
||||
pub parent_id: Option<String>,
|
||||
pub indent: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorChangeReportOutput {
|
||||
#[serde(default)]
|
||||
pub created_blocks: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub updated_blocks: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub moved_blocks: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub removed_blocks: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub notes: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
|
||||
pub enum EditorInputKindArg {
|
||||
PlainText,
|
||||
Markdown,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
|
||||
pub enum EditorAiScenarioArg {
|
||||
MeetingNotesToTodos,
|
||||
LongParagraphToTitle,
|
||||
PageReorder,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PageCreateArgs<'a> {
|
||||
pub page_id: &'a str,
|
||||
@@ -194,6 +274,8 @@ pub fn supported_command_surface() -> Vec<&'static str> {
|
||||
"search documents",
|
||||
"search blocks",
|
||||
"sidebar dataset",
|
||||
"editor markdown-roundtrip",
|
||||
"editor session-demo",
|
||||
"tool run",
|
||||
];
|
||||
for tool_name in default_tool_registry().tool_names() {
|
||||
@@ -223,6 +305,8 @@ pub fn supported_json_contracts() -> Vec<&'static str> {
|
||||
"search.documents",
|
||||
"search.blocks",
|
||||
"sidebar.dataset",
|
||||
"editor.markdown_roundtrip",
|
||||
"editor.session_demo",
|
||||
"tool.run",
|
||||
];
|
||||
for tool_name in default_tool_registry().tool_names() {
|
||||
@@ -231,6 +315,179 @@ pub fn supported_json_contracts() -> Vec<&'static str> {
|
||||
contracts
|
||||
}
|
||||
|
||||
pub fn editor_markdown_roundtrip(markdown: &str) -> CliResult<EditorCliOutput> {
|
||||
let document = import_markdown(markdown).map_err(map_editor_error)?;
|
||||
let projection = VisibilitySnapshot::derive(&document);
|
||||
Ok(build_editor_output(
|
||||
"markdown_roundtrip",
|
||||
document.blocks().len(),
|
||||
export_markdown(&document),
|
||||
&projection,
|
||||
Vec::new(),
|
||||
EditorChangeReportOutput::default(),
|
||||
None,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn editor_ai_pipeline(
|
||||
kind: EditorInputKind,
|
||||
scenario: EditorAiScenario,
|
||||
input: &str,
|
||||
) -> CliResult<EditorCliOutput> {
|
||||
let result = apply_ai_pipeline(EditorPipelineRequest {
|
||||
kind,
|
||||
scenario,
|
||||
input: input.to_string(),
|
||||
})
|
||||
.map_err(map_editor_error)?;
|
||||
let projection = VisibilitySnapshot::derive(&result.document);
|
||||
Ok(build_editor_output(
|
||||
"ai_pipeline",
|
||||
result.document.blocks().len(),
|
||||
result.markdown,
|
||||
&projection,
|
||||
result
|
||||
.audit
|
||||
.into_iter()
|
||||
.map(|item| EditorAuditItem {
|
||||
command: item.command,
|
||||
target_block_id: item.target_block_id,
|
||||
before: item.before.map(|snapshot| EditorBlockSnapshot {
|
||||
block_id: snapshot.block_id,
|
||||
block_type: snapshot.block_type,
|
||||
text: snapshot.text,
|
||||
parent_id: snapshot.parent_id,
|
||||
indent: snapshot.indent,
|
||||
}),
|
||||
after: item.after.map(|snapshot| EditorBlockSnapshot {
|
||||
block_id: snapshot.block_id,
|
||||
block_type: snapshot.block_type,
|
||||
text: snapshot.text,
|
||||
parent_id: snapshot.parent_id,
|
||||
indent: snapshot.indent,
|
||||
}),
|
||||
})
|
||||
.collect(),
|
||||
EditorChangeReportOutput {
|
||||
created_blocks: result.change_report.created_blocks,
|
||||
updated_blocks: result.change_report.updated_blocks,
|
||||
moved_blocks: result.change_report.moved_blocks,
|
||||
removed_blocks: result.change_report.removed_blocks,
|
||||
notes: result.change_report.notes,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn editor_session_demo(markdown: &str) -> CliResult<EditorCliOutput> {
|
||||
let document = import_markdown(markdown).map_err(map_editor_error)?;
|
||||
let bootstrap = if document.blocks().is_empty() {
|
||||
mnote_editor_core::DocumentModel::new(vec![DocumentBlock::new(
|
||||
"demo_root",
|
||||
BlockType::Paragraph,
|
||||
)
|
||||
.with_text("demo root")])
|
||||
} else {
|
||||
document
|
||||
};
|
||||
|
||||
let mut session = EditorSession::new(bootstrap);
|
||||
let primary_block_id = session
|
||||
.document()
|
||||
.blocks()
|
||||
.first()
|
||||
.map(|block| block.id.clone())
|
||||
.ok_or_else(|| CliError::validation("session demo 缺少可编辑块"))?;
|
||||
let original_text = session
|
||||
.document()
|
||||
.block(&primary_block_id)
|
||||
.map(|block| block.content.text.clone())
|
||||
.unwrap_or_default();
|
||||
let next_text = if original_text.trim().is_empty() {
|
||||
"demo edited".to_string()
|
||||
} else {
|
||||
format!("{original_text} [edited]")
|
||||
};
|
||||
|
||||
session
|
||||
.apply_command(EditorCommand::ReplaceBlock {
|
||||
block_id: primary_block_id.clone(),
|
||||
text: next_text,
|
||||
})
|
||||
.map_err(map_editor_error)?;
|
||||
session
|
||||
.apply_command(EditorCommand::InsertBlockAfter {
|
||||
after_block_id: Some(primary_block_id),
|
||||
block: DocumentBlock::new("cli_demo_block", BlockType::Paragraph)
|
||||
.with_text("CLI demo block"),
|
||||
})
|
||||
.map_err(map_editor_error)?;
|
||||
|
||||
let undo_applied = session.undo();
|
||||
let redo_applied = session.redo();
|
||||
let projection = VisibilitySnapshot::derive(session.document());
|
||||
Ok(build_editor_output(
|
||||
"session_demo",
|
||||
session.document().blocks().len(),
|
||||
export_markdown(session.document()),
|
||||
&projection,
|
||||
Vec::new(),
|
||||
EditorChangeReportOutput::default(),
|
||||
Some(undo_applied),
|
||||
Some(redo_applied),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn render_editor_plain_output(output: &EditorCliOutput) -> String {
|
||||
format!(
|
||||
"entrypoint={} operation={} block_count={} visible_blocks={} audit_count={} updated_blocks={} undo_applied={} redo_applied={}",
|
||||
output.entrypoint,
|
||||
output.operation,
|
||||
output.block_count,
|
||||
output.visible_block_ids.join(","),
|
||||
output.audit.len(),
|
||||
output.change_report.updated_blocks.join(","),
|
||||
output.undo_applied.unwrap_or(false),
|
||||
output.redo_applied.unwrap_or(false),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_editor_output(
|
||||
operation: &'static str,
|
||||
block_count: usize,
|
||||
output_markdown: String,
|
||||
projection: &VisibilitySnapshot,
|
||||
audit: Vec<EditorAuditItem>,
|
||||
change_report: EditorChangeReportOutput,
|
||||
undo_applied: Option<bool>,
|
||||
redo_applied: Option<bool>,
|
||||
) -> EditorCliOutput {
|
||||
EditorCliOutput {
|
||||
ok: true,
|
||||
entrypoint: "mnote-cli",
|
||||
operation,
|
||||
block_count,
|
||||
output_markdown,
|
||||
visible_block_ids: projection.visible_block_ids.clone(),
|
||||
outline: projection
|
||||
.outline
|
||||
.iter()
|
||||
.map(|entry| EditorOutlineItem {
|
||||
block_id: entry.block_id.clone(),
|
||||
level: entry.level,
|
||||
title: entry.title.clone(),
|
||||
depth: entry.depth,
|
||||
})
|
||||
.collect(),
|
||||
audit,
|
||||
change_report,
|
||||
undo_applied,
|
||||
redo_applied,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_plain_output(output: &CliJsonOutput) -> String {
|
||||
match &output.operation {
|
||||
CliOperationOutput::Command {
|
||||
@@ -2359,6 +2616,10 @@ fn map_bridge_error(error: storage_convex_bridge::BridgeError) -> CliError {
|
||||
CliError::validation(error.message)
|
||||
}
|
||||
|
||||
fn map_editor_error(error: mnote_editor_core::CoreError) -> CliError {
|
||||
CliError::validation(error.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -2374,9 +2635,121 @@ mod tests {
|
||||
assert!(commands.contains(&"mindmap get"));
|
||||
assert!(commands.contains(&"search documents"));
|
||||
assert!(commands.contains(&"sidebar dataset"));
|
||||
assert!(commands.contains(&"editor markdown-roundtrip"));
|
||||
assert!(commands.contains(&"editor session-demo"));
|
||||
assert!(commands.contains(&"tool run"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_markdown_roundtrip_returns_outline_and_markdown() {
|
||||
let output =
|
||||
editor_markdown_roundtrip("# 标题\n普通段落").expect("roundtrip should succeed");
|
||||
|
||||
assert_eq!(output.operation, "markdown_roundtrip");
|
||||
assert_eq!(output.block_count, 2);
|
||||
assert!(output.output_markdown.contains("# 标题"));
|
||||
assert_eq!(
|
||||
output.visible_block_ids,
|
||||
vec!["imported_1".to_string(), "imported_2".to_string()]
|
||||
);
|
||||
assert_eq!(output.outline.len(), 1);
|
||||
assert_eq!(output.outline[0].title, "标题");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_session_demo_applies_insert_and_redo() {
|
||||
let output = editor_session_demo("原始段落").expect("session demo should succeed");
|
||||
|
||||
assert_eq!(output.operation, "session_demo");
|
||||
assert_eq!(output.block_count, 2);
|
||||
assert_eq!(output.undo_applied, Some(true));
|
||||
assert_eq!(output.redo_applied, Some(true));
|
||||
assert!(output.output_markdown.contains("原始段落 [edited]"));
|
||||
assert!(output.output_markdown.contains("CLI demo block"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_ai_pipeline_exposes_audit_and_change_report() {
|
||||
let output = editor_ai_pipeline(
|
||||
EditorInputKind::PlainText,
|
||||
EditorAiScenario::MeetingNotesToTodos,
|
||||
"待办:整理会议纪要\n普通段落",
|
||||
)
|
||||
.expect("ai pipeline should succeed");
|
||||
|
||||
assert_eq!(output.operation, "ai_pipeline");
|
||||
assert_eq!(output.audit.len(), 1);
|
||||
assert_eq!(
|
||||
output.change_report.updated_blocks,
|
||||
vec!["imported_1".to_string()]
|
||||
);
|
||||
assert!(output.output_markdown.contains("待办:整理会议纪要"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_ai_pipeline_reorders_pages() {
|
||||
let output = editor_ai_pipeline(
|
||||
EditorInputKind::PlainText,
|
||||
EditorAiScenario::PageReorder,
|
||||
"第一页\n\n第二页",
|
||||
)
|
||||
.expect("ai pipeline should succeed");
|
||||
|
||||
assert_eq!(output.output_markdown.lines().next(), Some("第二页"));
|
||||
assert_eq!(
|
||||
output.change_report.moved_blocks,
|
||||
vec!["imported_1".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_regression_meeting_notes_to_todos() {
|
||||
let output = editor_ai_pipeline(
|
||||
EditorInputKind::PlainText,
|
||||
EditorAiScenario::MeetingNotesToTodos,
|
||||
"待办:整理会议纪要\n普通段落",
|
||||
)
|
||||
.expect("ai regression meeting notes should succeed");
|
||||
|
||||
assert!(output.output_markdown.contains("- [ ] 待办:整理会议纪要"));
|
||||
assert_eq!(
|
||||
output.change_report.updated_blocks,
|
||||
vec!["imported_1".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_regression_long_paragraph_to_title() {
|
||||
let output = editor_ai_pipeline(
|
||||
EditorInputKind::PlainText,
|
||||
EditorAiScenario::LongParagraphToTitle,
|
||||
"这是一个很长的段落,用来验证 AI 回归样例会把首块提炼成标题,并保留后续结构化内容。",
|
||||
)
|
||||
.expect("ai regression long paragraph should succeed");
|
||||
|
||||
assert!(output.output_markdown.starts_with("# 这是一个很长的段落"));
|
||||
assert_eq!(
|
||||
output.change_report.updated_blocks,
|
||||
vec!["imported_1".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_regression_page_reorder() {
|
||||
let output = editor_ai_pipeline(
|
||||
EditorInputKind::PlainText,
|
||||
EditorAiScenario::PageReorder,
|
||||
"第一页\n\n第二页",
|
||||
)
|
||||
.expect("ai regression reorder should succeed");
|
||||
|
||||
assert_eq!(output.output_markdown.lines().next(), Some("第二页"));
|
||||
assert_eq!(
|
||||
output.change_report.moved_blocks,
|
||||
vec!["imported_1".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_save_json_contract_uses_documents_save() {
|
||||
let output = plan_page_save(
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use core_protocol::{InvocationKind, ToolExecutionMode};
|
||||
use mnote_cli::{
|
||||
execute_output, plan_block_embed, plan_block_insert, plan_block_move, plan_block_patch,
|
||||
plan_mindmap_get, plan_mindmap_op, plan_mindmap_put, plan_page_create, plan_page_delete,
|
||||
plan_page_get, plan_page_move, plan_page_restore, plan_page_save, plan_page_title,
|
||||
plan_search_blocks, plan_search_documents, plan_sidebar_dataset, plan_tool_run,
|
||||
editor_ai_pipeline, editor_markdown_roundtrip, editor_session_demo, execute_output,
|
||||
plan_block_embed, plan_block_insert, plan_block_move, plan_block_patch, plan_mindmap_get,
|
||||
plan_mindmap_op, plan_mindmap_put, plan_page_create, plan_page_delete, plan_page_get,
|
||||
plan_page_move, plan_page_restore, plan_page_save, plan_page_title, plan_search_blocks,
|
||||
plan_search_documents, plan_sidebar_dataset, plan_tool_run, render_editor_plain_output,
|
||||
render_plain_output, BlockEmbedArgs, BlockMoveArgs, CliContext, CliError, CliJsonOutput,
|
||||
PageCreateArgs, PageDeleteArgs, PageMoveArgs, PageRestoreArgs,
|
||||
EditorAiScenarioArg, EditorCliOutput, EditorInputKindArg, PageCreateArgs, PageDeleteArgs,
|
||||
PageMoveArgs, PageRestoreArgs,
|
||||
};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -57,6 +59,7 @@ enum Commands {
|
||||
Mindmap(MindmapCommand),
|
||||
Search(SearchCommand),
|
||||
Sidebar(SidebarCommand),
|
||||
Editor(EditorCliCommand),
|
||||
Tool(ToolCommand),
|
||||
}
|
||||
|
||||
@@ -370,6 +373,43 @@ struct SidebarDatasetArgs {
|
||||
workspace_id: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct EditorCliCommand {
|
||||
#[command(subcommand)]
|
||||
action: EditorAction,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum EditorAction {
|
||||
MarkdownRoundtrip(EditorMarkdownRoundtripArgs),
|
||||
SessionDemo(EditorSessionDemoArgs),
|
||||
AiPipeline(EditorAiPipelineArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct EditorMarkdownRoundtripArgs {
|
||||
#[arg(long)]
|
||||
markdown: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct EditorSessionDemoArgs {
|
||||
#[arg(long)]
|
||||
markdown: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct EditorAiPipelineArgs {
|
||||
#[arg(long, value_enum, default_value_t = EditorInputKindArg::PlainText)]
|
||||
kind: EditorInputKindArg,
|
||||
|
||||
#[arg(long, value_enum, default_value_t = EditorAiScenarioArg::MeetingNotesToTodos)]
|
||||
scenario: EditorAiScenarioArg,
|
||||
|
||||
#[arg(long)]
|
||||
input: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct ToolCommand {
|
||||
#[command(subcommand)]
|
||||
@@ -422,6 +462,11 @@ fn main() {
|
||||
dry_run: cli.global.dry_run,
|
||||
};
|
||||
|
||||
enum CommandOutcome {
|
||||
Bridge(CliJsonOutput),
|
||||
Editor(EditorCliOutput),
|
||||
}
|
||||
|
||||
let result = match cli.command {
|
||||
Commands::Page(command) => match command.action {
|
||||
PageAction::Get(args) => {
|
||||
@@ -475,7 +520,8 @@ fn main() {
|
||||
workspace_id: args.workspace_id.as_deref(),
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
.map(CommandOutcome::Bridge),
|
||||
Commands::Block(command) => match command.action {
|
||||
BlockAction::Insert(args) => plan_block_insert(
|
||||
&context,
|
||||
@@ -512,7 +558,8 @@ fn main() {
|
||||
target_block_id: args.target_block_id.as_deref(),
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
.map(CommandOutcome::Bridge),
|
||||
Commands::Mindmap(command) => match command.action {
|
||||
MindmapAction::Get(args) => plan_mindmap_get(
|
||||
&context,
|
||||
@@ -535,7 +582,8 @@ fn main() {
|
||||
&args.mindmap_id,
|
||||
&args.ops_json,
|
||||
),
|
||||
},
|
||||
}
|
||||
.map(CommandOutcome::Bridge),
|
||||
Commands::Search(command) => match command.action {
|
||||
SearchAction::Documents(args) => plan_search_documents(
|
||||
&context,
|
||||
@@ -551,10 +599,22 @@ fn main() {
|
||||
args.limit,
|
||||
args.cursor.as_deref(),
|
||||
),
|
||||
},
|
||||
}
|
||||
.map(CommandOutcome::Bridge),
|
||||
Commands::Sidebar(command) => match command.action {
|
||||
SidebarAction::Dataset(args) => plan_sidebar_dataset(&context, &args.workspace_id),
|
||||
},
|
||||
}
|
||||
.map(CommandOutcome::Bridge),
|
||||
Commands::Editor(command) => match command.action {
|
||||
EditorAction::MarkdownRoundtrip(args) => editor_markdown_roundtrip(&args.markdown),
|
||||
EditorAction::SessionDemo(args) => editor_session_demo(&args.markdown),
|
||||
EditorAction::AiPipeline(args) => editor_ai_pipeline(
|
||||
to_editor_input_kind(args.kind),
|
||||
to_editor_ai_scenario(args.scenario),
|
||||
&args.input,
|
||||
),
|
||||
}
|
||||
.map(CommandOutcome::Editor),
|
||||
Commands::Tool(command) => match command.action {
|
||||
ToolAction::Run(args) => plan_tool_run(
|
||||
&context,
|
||||
@@ -563,18 +623,23 @@ fn main() {
|
||||
to_execution_mode(args.mode),
|
||||
&args.args_json,
|
||||
),
|
||||
},
|
||||
}
|
||||
.and_then(|output| {
|
||||
if cli.global.execute {
|
||||
execute_output(&output, &context)
|
||||
} else {
|
||||
Ok(output)
|
||||
}
|
||||
.map(CommandOutcome::Bridge),
|
||||
}
|
||||
.and_then(|output| match output {
|
||||
CommandOutcome::Bridge(output) => {
|
||||
if cli.global.execute {
|
||||
execute_output(&output, &context).map(CommandOutcome::Bridge)
|
||||
} else {
|
||||
Ok(CommandOutcome::Bridge(output))
|
||||
}
|
||||
}
|
||||
CommandOutcome::Editor(output) => Ok(CommandOutcome::Editor(output)),
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(output) => emit_success(&output, cli.global.json),
|
||||
Ok(CommandOutcome::Bridge(output)) => emit_success(&output, cli.global.json),
|
||||
Ok(CommandOutcome::Editor(output)) => emit_editor_success(&output, cli.global.json),
|
||||
Err(error) => emit_error(&error, cli.global.json),
|
||||
}
|
||||
}
|
||||
@@ -590,6 +655,17 @@ fn emit_success(output: &CliJsonOutput, json_mode: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_editor_success(output: &EditorCliOutput, json_mode: bool) {
|
||||
if json_mode {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(output).expect("editor CLI JSON 输出必须可序列化")
|
||||
);
|
||||
} else {
|
||||
println!("{}", render_editor_plain_output(output));
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_error(error: &CliError, json_mode: bool) -> ! {
|
||||
if json_mode {
|
||||
println!(
|
||||
@@ -625,3 +701,22 @@ fn to_execution_mode(mode: ToolModeArg) -> ToolExecutionMode {
|
||||
ToolModeArg::ExplainPlan => ToolExecutionMode::ExplainPlan,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_editor_input_kind(kind: EditorInputKindArg) -> mnote_editor_core::EditorInputKind {
|
||||
match kind {
|
||||
EditorInputKindArg::PlainText => mnote_editor_core::EditorInputKind::PlainText,
|
||||
EditorInputKindArg::Markdown => mnote_editor_core::EditorInputKind::Markdown,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_editor_ai_scenario(scenario: EditorAiScenarioArg) -> mnote_editor_core::EditorAiScenario {
|
||||
match scenario {
|
||||
EditorAiScenarioArg::MeetingNotesToTodos => {
|
||||
mnote_editor_core::EditorAiScenario::MeetingNotesToTodos
|
||||
}
|
||||
EditorAiScenarioArg::LongParagraphToTitle => {
|
||||
mnote_editor_core::EditorAiScenario::LongParagraphToTitle
|
||||
}
|
||||
EditorAiScenarioArg::PageReorder => mnote_editor_core::EditorAiScenario::PageReorder,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user