feat: add evidence search and stabilize pdf previews
- add document evidence parsing/search/open routes, Hermes tool wiring, local index settings/status, and the document-evidence skill plus design notes - fix PDF resource tabs by rendering PDFs inline with pdf.js canvases instead of iframe preview pages, release PDF documents on close, and document the fourth-PDF stall bug - keep PDF preview at 2x rendering while removing the previous lazy-load/placeholder direction, and make dev:hot bind loopback defaults externally reachable Verification: - node --check rust/crates/mnote-web/browser/document-resource-tab-runtime.js - node scripts/task-dev-hot-plan-test.js - cargo test -p mnote-web --manifest-path rust/Cargo.toml pdf_preview_page_does_not_render_visible_toolbar - cargo test -p mnote-web --manifest-path rust/Cargo.toml document_shell_returns_page_aggregate_snapshot - cargo build -p mnote-web --manifest-path rust/Cargo.toml - browser smoke: sequentially opened the four tea_seed_oil_cosmetic PDFs; fourth PDF rendered 15/15 canvases, iframeCount=0, browser errors=0
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
pub const EVIDENCE_LOCATOR_SCHEMA: &str = "mnote.evidence_locator.v1";
|
||||
pub const RESOURCE_SOURCE_MAP_SCHEMA: &str = "mnote.resource_source_map.v1";
|
||||
pub const PARSED_RESOURCE_ARTIFACT_SCHEMA: &str = "mnote.parsed_resource_artifact.v1";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EvidenceResourceKind {
|
||||
Markdown,
|
||||
Pdf,
|
||||
Image,
|
||||
Office,
|
||||
Mindmap,
|
||||
RawFile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceBBox {
|
||||
pub x0: f64,
|
||||
pub y0: f64,
|
||||
pub x1: f64,
|
||||
pub y1: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceRange {
|
||||
pub start: u64,
|
||||
pub end: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceOpenAction {
|
||||
pub action_type: String,
|
||||
pub url: String,
|
||||
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||
pub params: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceLocator {
|
||||
pub schema: String,
|
||||
pub root_uri: String,
|
||||
pub owner_document_id: String,
|
||||
pub owner_document_path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resource_path: Option<String>,
|
||||
pub resource_kind: EvidenceResourceKind,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub page: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bbox: Option<EvidenceBBox>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub section_path: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub line_range: Option<EvidenceRange>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub char_range: Option<EvidenceRange>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub block_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_map_path: Option<String>,
|
||||
pub open_action: EvidenceOpenAction,
|
||||
}
|
||||
|
||||
impl EvidenceLocator {
|
||||
pub fn new(
|
||||
root_uri: impl Into<String>,
|
||||
owner_document_id: impl Into<String>,
|
||||
owner_document_path: impl Into<String>,
|
||||
resource_kind: EvidenceResourceKind,
|
||||
open_action: EvidenceOpenAction,
|
||||
) -> Self {
|
||||
Self {
|
||||
schema: EVIDENCE_LOCATOR_SCHEMA.into(),
|
||||
root_uri: root_uri.into(),
|
||||
owner_document_id: owner_document_id.into(),
|
||||
owner_document_path: owner_document_path.into(),
|
||||
resource_path: None,
|
||||
resource_kind,
|
||||
page: None,
|
||||
bbox: None,
|
||||
section_path: Vec::new(),
|
||||
line_range: None,
|
||||
char_range: None,
|
||||
block_id: None,
|
||||
source_map_path: None,
|
||||
open_action,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SourceMapTextItem {
|
||||
pub id: String,
|
||||
pub text: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bbox: Option<EvidenceBBox>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub char_range: Option<EvidenceRange>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SourceMapBlockKind {
|
||||
Text,
|
||||
Heading,
|
||||
Paragraph,
|
||||
Table,
|
||||
Figure,
|
||||
Image,
|
||||
List,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SourceMapBlock {
|
||||
pub id: String,
|
||||
pub block_type: SourceMapBlockKind,
|
||||
pub text: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bbox: Option<EvidenceBBox>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub char_range: Option<EvidenceRange>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SourceMapPage {
|
||||
pub page: u32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub width: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub height: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub text_items: Vec<SourceMapTextItem>,
|
||||
#[serde(default)]
|
||||
pub blocks: Vec<SourceMapBlock>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SourceMapSection {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
#[serde(default)]
|
||||
pub path: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub page_start: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub page_end: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub block_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResourceSourceMap {
|
||||
pub schema: String,
|
||||
pub provider: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub model_version: Option<String>,
|
||||
pub owner_document_path: String,
|
||||
pub source_root_relative_path: String,
|
||||
pub source_hash: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub page_count: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub pages: Vec<SourceMapPage>,
|
||||
#[serde(default)]
|
||||
pub sections: Vec<SourceMapSection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ParsedResourceArtifact {
|
||||
pub schema: String,
|
||||
pub provider: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub model_version: Option<String>,
|
||||
pub owner_document_id: String,
|
||||
pub owner_document_path: String,
|
||||
pub source_root_relative_path: String,
|
||||
pub source_hash: String,
|
||||
pub artifact_root_relative_path: String,
|
||||
pub source_map_root_relative_path: String,
|
||||
pub updated_at_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EvidenceSearchMode {
|
||||
Keyword,
|
||||
Tree,
|
||||
Hybrid,
|
||||
Graph,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceSearchScope {
|
||||
pub workspace_id: String,
|
||||
pub root_uri: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub target_document_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub include_resources: bool,
|
||||
#[serde(default)]
|
||||
pub include_ocr: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceSearchRequest {
|
||||
pub query: String,
|
||||
pub scope: EvidenceSearchScope,
|
||||
#[serde(default = "default_evidence_search_mode")]
|
||||
pub mode: EvidenceSearchMode,
|
||||
#[serde(default = "default_evidence_top_k")]
|
||||
pub top_k: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceSearchResult {
|
||||
pub evidence_id: String,
|
||||
pub quote: String,
|
||||
pub score: f64,
|
||||
pub source: EvidenceLocator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceSearchResponse {
|
||||
pub ok: bool,
|
||||
#[serde(default)]
|
||||
pub results: Vec<EvidenceSearchResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceReadContext {
|
||||
pub before_blocks: u32,
|
||||
pub after_blocks: u32,
|
||||
pub include_section_summary: bool,
|
||||
}
|
||||
|
||||
impl Default for EvidenceReadContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
before_blocks: 3,
|
||||
after_blocks: 3,
|
||||
include_section_summary: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceReadRequest {
|
||||
pub locator: EvidenceLocator,
|
||||
#[serde(default)]
|
||||
pub context: EvidenceReadContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceReadResponse {
|
||||
pub ok: bool,
|
||||
pub locator: EvidenceLocator,
|
||||
pub quote: String,
|
||||
#[serde(default)]
|
||||
pub context_blocks: Vec<EvidenceSearchResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceOpenRequest {
|
||||
pub locator: EvidenceLocator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EvidenceEdge {
|
||||
pub edge_id: String,
|
||||
pub from_id: String,
|
||||
pub to_id: String,
|
||||
pub edge_type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_evidence_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_locator: Option<EvidenceLocator>,
|
||||
pub confidence: f64,
|
||||
pub created_by: String,
|
||||
}
|
||||
|
||||
fn default_evidence_search_mode() -> EvidenceSearchMode {
|
||||
EvidenceSearchMode::Hybrid
|
||||
}
|
||||
|
||||
fn default_evidence_top_k() -> u32 {
|
||||
8
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn evidence_locator_serializes_canonical_schema() {
|
||||
let locator = EvidenceLocator::new(
|
||||
"file:///workspace",
|
||||
"local-md:docs~2FPage.md",
|
||||
"docs/Page.md",
|
||||
EvidenceResourceKind::Pdf,
|
||||
EvidenceOpenAction {
|
||||
action_type: "mnote.open_resource_locator".into(),
|
||||
url: "/documents/local-md:docs~2FPage.md?resource=docs%2Fspec.pdf".into(),
|
||||
params: Value::Null,
|
||||
},
|
||||
);
|
||||
|
||||
let value = serde_json::to_value(locator).expect("locator should serialize");
|
||||
assert_eq!(value["schema"], EVIDENCE_LOCATOR_SCHEMA);
|
||||
assert_eq!(value["resourceKind"], "pdf");
|
||||
assert!(value.get("bbox").is_none());
|
||||
assert!(value["openAction"]["params"].is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_map_keeps_single_provider_contract() {
|
||||
let source_map = ResourceSourceMap {
|
||||
schema: RESOURCE_SOURCE_MAP_SCHEMA.into(),
|
||||
provider: "liteparse".into(),
|
||||
model_version: Some("2.0.5".into()),
|
||||
owner_document_path: "docs/Page.md".into(),
|
||||
source_root_relative_path: "docs/Page.assets/spec.pdf".into(),
|
||||
source_hash: "sha256:demo".into(),
|
||||
page_count: Some(1),
|
||||
pages: vec![SourceMapPage {
|
||||
page: 1,
|
||||
width: Some(595.0),
|
||||
height: Some(842.0),
|
||||
text_items: vec![SourceMapTextItem {
|
||||
id: "p1_t1".into(),
|
||||
text: "Revenue".into(),
|
||||
bbox: Some(EvidenceBBox {
|
||||
x0: 72.0,
|
||||
y0: 124.0,
|
||||
x1: 160.0,
|
||||
y1: 140.0,
|
||||
}),
|
||||
char_range: Some(EvidenceRange { start: 0, end: 7 }),
|
||||
}],
|
||||
blocks: vec![],
|
||||
}],
|
||||
sections: vec![],
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(source_map).expect("source map should serialize");
|
||||
assert_eq!(value["schema"], RESOURCE_SOURCE_MAP_SCHEMA);
|
||||
assert_eq!(value["pages"][0]["textItems"][0]["bbox"]["x0"], 72.0);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub mod ai;
|
||||
pub mod command;
|
||||
pub mod common;
|
||||
pub mod editor;
|
||||
pub mod evidence;
|
||||
pub mod governance;
|
||||
pub mod kernel;
|
||||
pub mod mindmap;
|
||||
@@ -37,6 +38,14 @@ pub use editor::{
|
||||
MarkdownImportMode, MarkdownImportOptions, MarkdownImportRequest, MarkdownImportResult,
|
||||
ReferenceToken, ReferenceTokenKind, ReferenceTokenStrategy, TextMark,
|
||||
};
|
||||
pub use evidence::{
|
||||
EvidenceBBox, EvidenceEdge, EvidenceLocator, EvidenceOpenAction, EvidenceOpenRequest,
|
||||
EvidenceRange, EvidenceReadContext, EvidenceReadRequest, EvidenceReadResponse,
|
||||
EvidenceResourceKind, EvidenceSearchMode, EvidenceSearchRequest, EvidenceSearchResponse,
|
||||
EvidenceSearchResult, EvidenceSearchScope, ParsedResourceArtifact, ResourceSourceMap,
|
||||
SourceMapBlock, SourceMapBlockKind, SourceMapPage, SourceMapSection, SourceMapTextItem,
|
||||
EVIDENCE_LOCATOR_SCHEMA, PARSED_RESOURCE_ARTIFACT_SCHEMA, RESOURCE_SOURCE_MAP_SCHEMA,
|
||||
};
|
||||
pub use kernel::{
|
||||
DocBufferDirtyState, DocumentBuffer, DocumentContentResult, DocumentReadEvidenceItem,
|
||||
DocumentReadEvidenceKind, DocumentReadNode, DocumentReadNodeMeta, DocumentReadNodeType,
|
||||
@@ -78,6 +87,7 @@ pub use tool::{
|
||||
ToolSpec, BRIDGE_TOOL_COMMAND_GET, BRIDGE_TOOL_REQUEST_GET, BRIDGE_TOOL_TRACE_GET,
|
||||
DOCS_TOOLSET_READ, DOCS_TOOL_READ, DOCS_TOOL_SEARCH, DOC_TOOLSET_READ, DOC_TOOLSET_WRITE,
|
||||
DOC_TOOL_FIND, DOC_TOOL_GET, DOC_TOOL_INSERT_BLOCKS, DOC_TOOL_REPLACE_RANGE,
|
||||
EVIDENCE_TOOLSET_READ, EVIDENCE_TOOL_OPEN, EVIDENCE_TOOL_READ, EVIDENCE_TOOL_SEARCH,
|
||||
INDEX_TOOL_REBUILD, MINDMAP_TOOLSET_READ, MINDMAP_TOOLSET_WRITE, MINDMAP_TOOL_APPLY_OPS,
|
||||
MINDMAP_TOOL_EMPTY_TRASH, MINDMAP_TOOL_EXPAND_NODE, MINDMAP_TOOL_GET, MINDMAP_TOOL_GET_SUBTREE,
|
||||
MINDMAP_TOOL_OUTLINE_TO_MINDMAP, MINDMAP_TOOL_PUT, OBSERVE_TOOLSET_READ,
|
||||
@@ -131,6 +141,9 @@ mod tests {
|
||||
"doc_find",
|
||||
"docs_search",
|
||||
"docs_read",
|
||||
"mnote.evidence.search",
|
||||
"mnote.evidence.read",
|
||||
"mnote.evidence.open",
|
||||
"image_read",
|
||||
"doc_insert_blocks",
|
||||
"doc_replace_range",
|
||||
@@ -195,6 +208,18 @@ mod tests {
|
||||
.expect("docs_read toolset should exist");
|
||||
assert!(!docs_read.write_toolset);
|
||||
assert_eq!(docs_read.tool_names, &["docs_search", "docs_read"]);
|
||||
let evidence_read = registry
|
||||
.toolset("toolset.evidence_read")
|
||||
.expect("evidence toolset should exist");
|
||||
assert!(!evidence_read.write_toolset);
|
||||
assert_eq!(
|
||||
evidence_read.tool_names,
|
||||
&[
|
||||
"mnote.evidence.search",
|
||||
"mnote.evidence.read",
|
||||
"mnote.evidence.open",
|
||||
]
|
||||
);
|
||||
let doc_write = registry
|
||||
.toolset("toolset.doc_write")
|
||||
.expect("doc_write toolset should exist");
|
||||
|
||||
@@ -140,6 +140,39 @@ pub const DOCS_TOOL_READ: ToolSpec = ToolSpec {
|
||||
input_schema_json: r#"{"type":"object","required":["documentId"],"properties":{"documentId":{"type":"string"},"maxChars":{"type":"integer","minimum":200,"maximum":20000},"includeContent":{"type":"boolean"}}}"#,
|
||||
};
|
||||
|
||||
pub const EVIDENCE_TOOL_SEARCH: ToolSpec = ToolSpec {
|
||||
name: "mnote.evidence.search",
|
||||
display_name: "证据搜索",
|
||||
description: "在工作区内搜索可回跳原文的证据块,返回 quote、locator 与 openAction。",
|
||||
toolset_id: "toolset.evidence_read",
|
||||
invocation_kind: InvocationKind::Query,
|
||||
effect: ToolEffect::Read,
|
||||
requires_confirmation: false,
|
||||
input_schema_json: r#"{"type":"object","required":["query","scope"],"properties":{"query":{"type":"string"},"scope":{"type":"object","required":["workspaceId","rootUri"],"properties":{"workspaceId":{"type":"string"},"rootUri":{"type":"string"},"targetDocumentId":{"type":"string"},"includeResources":{"type":"boolean"},"includeOcr":{"type":"boolean"}}},"mode":{"enum":["keyword","tree","hybrid","graph"]},"topK":{"type":"integer","minimum":1,"maximum":30}}}"#,
|
||||
};
|
||||
|
||||
pub const EVIDENCE_TOOL_READ: ToolSpec = ToolSpec {
|
||||
name: "mnote.evidence.read",
|
||||
display_name: "证据读回",
|
||||
description: "按 EvidenceLocator 读取原文证据及周边上下文,供 AI 回答引用。",
|
||||
toolset_id: "toolset.evidence_read",
|
||||
invocation_kind: InvocationKind::Query,
|
||||
effect: ToolEffect::Read,
|
||||
requires_confirmation: false,
|
||||
input_schema_json: r#"{"type":"object","required":["locator"],"properties":{"locator":{"type":"object"},"context":{"type":"object","properties":{"beforeBlocks":{"type":"integer","minimum":0,"maximum":20},"afterBlocks":{"type":"integer","minimum":0,"maximum":20},"includeSectionSummary":{"type":"boolean"}}}}}"#,
|
||||
};
|
||||
|
||||
pub const EVIDENCE_TOOL_OPEN: ToolSpec = ToolSpec {
|
||||
name: "mnote.evidence.open",
|
||||
display_name: "证据打开",
|
||||
description: "把 EvidenceLocator 归一化为 MNote 可执行的打开/定位动作。",
|
||||
toolset_id: "toolset.evidence_read",
|
||||
invocation_kind: InvocationKind::Query,
|
||||
effect: ToolEffect::Read,
|
||||
requires_confirmation: false,
|
||||
input_schema_json: r#"{"type":"object","required":["locator"],"properties":{"locator":{"type":"object"}}}"#,
|
||||
};
|
||||
|
||||
pub const IMAGE_READ_TOOL: ToolSpec = ToolSpec {
|
||||
name: "image_read",
|
||||
display_name: "读取图片",
|
||||
@@ -387,6 +420,18 @@ pub const DOCS_TOOLSET_READ: ToolSetSpec = ToolSetSpec {
|
||||
tool_names: &["docs_search", "docs_read"],
|
||||
};
|
||||
|
||||
pub const EVIDENCE_TOOLSET_READ: ToolSetSpec = ToolSetSpec {
|
||||
id: "toolset.evidence_read",
|
||||
display_name: "证据读取",
|
||||
description: "搜索、读回和打开可定位原文证据的只读工具集合。",
|
||||
write_toolset: false,
|
||||
tool_names: &[
|
||||
"mnote.evidence.search",
|
||||
"mnote.evidence.read",
|
||||
"mnote.evidence.open",
|
||||
],
|
||||
};
|
||||
|
||||
pub const READONLY_TOOLSET: ToolSetSpec = ToolSetSpec {
|
||||
id: "toolset.readonly",
|
||||
display_name: "只读工具",
|
||||
@@ -481,6 +526,7 @@ pub const DOC_TOOL_REGISTRY: ToolRegistry = ToolRegistry::new(
|
||||
MEDIA_TOOLSET,
|
||||
SLASH_TOOLSET_WRITE,
|
||||
DOCS_TOOLSET_READ,
|
||||
EVIDENCE_TOOLSET_READ,
|
||||
DOC_TOOLSET_READ,
|
||||
DOC_TOOLSET_WRITE,
|
||||
MINDMAP_TOOLSET_READ,
|
||||
@@ -495,6 +541,9 @@ pub const DOC_TOOL_REGISTRY: ToolRegistry = ToolRegistry::new(
|
||||
DOC_TOOL_FIND,
|
||||
DOCS_TOOL_SEARCH,
|
||||
DOCS_TOOL_READ,
|
||||
EVIDENCE_TOOL_SEARCH,
|
||||
EVIDENCE_TOOL_READ,
|
||||
EVIDENCE_TOOL_OPEN,
|
||||
IMAGE_READ_TOOL,
|
||||
DOC_TOOL_INSERT_BLOCKS,
|
||||
DOC_TOOL_REPLACE_RANGE,
|
||||
|
||||
Reference in New Issue
Block a user