mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-28 03:58:45 -06:00
* M10: enhance camera images: straighten a page from four corners (ocr.camera-perspective) Four corners on a page (any order, a convex quadrilateral) are warped to an upright rectangle that replaces what the page shows, read at the page picture's own resolution (at most 600 dpi) and kept at that physical size. One undoable step through Replace Pages, so comments, links and bookmarks stay. - geom: a pure 3x3 homography solve and corner ordering (no dependencies); collinear, concave and non-finite input gives None. - engine: camera::warp (bilinear, over white, capped at 40 MP) and Session::enhance_camera_page. - automation: page_enhance_camera (corners in top-left-origin points). - parity: ocr.camera-perspective is partial; the UI handles, automatic edge detection and contrast cleanup are still missing. Refs #1008. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M10: Enhance camera image: drag four corners on the page, then Enhance page (ocr.camera-perspective) Scan & OCR ▸ Enhance camera image (ocr.enhance_camera) shows four corner handles on the current page, inset 5% from its edges, with what will be cut away dimmed. Enhance page straightens what they enclose through Session::enhance_camera_page (one undoable step); Cancel and Esc leave the page alone. The buttons float at the top right, clear of a centred page's corners, and the instructions are a notice, as for Crop. A command of its own, beside Enhance scanned file (ocr.enhance), which stays for scan correction (#1012). - ui-egui: camera_ui (handles, dimming, bar), QuickTool::EnhanceCamera, quick_tool "enhance-camera" for ui.state and the shot example. - Catalogs: the three new labels in every language with UI coverage (plus es, pl, zh-hant); ATTRIBUTION.toml hashes updated. - automation: page_enhance_camera is linked to the command. - parity: ocr.camera-perspective lists the command and the UI test. Refs #1008. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M10: Enhance camera image finds the document's edges (ocr.camera-perspective) The corners now start on the photographed document's edges when they can be found, else inset 5% as before. camera_edges::detect_document, pure Rust and deterministic: shrink to 400 px, blur, Sobel edges thinned and thresholded against the 98th percentile; a Hough transform where each edge votes within 6 degrees of its own normal; pairs of near-horizontal and near-vertical lines make convex quadrilaterals (at least 12% of the picture), kept when every side lies on a matching edge along at least 55% of its length and scored by area times the square of the mean support; each side is then refitted through its edge points (total least squares). None when nothing convinces. - Session::detect_camera_corners (user space); page_enhance_camera finds the corners itself when `corners` is omitted and reports the corners it used and whether they were detected; the UI command starts from them. - Tests on synthetic photos: plain table, text on the sheet, wood grain with a shadow, strong perspective, a 30 degree turn, a sheet running off the picture, no document, white on white, bad input; end to end through the engine and the tool. - Parity notes: what is still missing (turning the result upright, rounded corners, clean-up). Refs #1008. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M10: after Enhance page, rotate, crop or organize the page (ocr.camera-perspective) Enhance camera image no longer closes on Enhance page: the bar then offers rotate counterclockwise and clockwise for the enhanced page (one undo step each), Crop pages and Organize pages (which leave the tool for those tools), and Done; Esc leaves too. A photo of a card often comes out a quarter turn off until the page is turned upright automatically. The two icon buttons are named for screen readers. Every label already exists in the catalogs. Refs #1008. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: magdale76 <16279778+magdale76@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
557 lines
22 KiB
Rust
557 lines
22 KiB
Rust
//! The tool catalogue: Acrobat's "All tools" information architecture, as data.
|
|
//!
|
|
//! Every UI (the egui shell today, anything tomorrow) builds its tool panel, palette and menus
|
|
//! from this table; nothing is hard-coded in the toolkit. Each item names the command it will
|
|
//! dispatch and the milestone (plan/execution-plan.md) in which it ships, so the UI can be honest
|
|
//! about what works today.
|
|
//!
|
|
//! Source of the inventory: plan/acrobat/02-ui-ux.md §2 (observed in Acrobat Pro 26.002).
|
|
|
|
/// Where a tool or item stands.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Availability {
|
|
/// Works in this build.
|
|
Ready,
|
|
/// Planned; ships in the named milestone.
|
|
Planned(&'static str),
|
|
/// Cloud-only in Acrobat; PdfCraft offers an optional pluggable provider instead.
|
|
Provider,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct ToolItem {
|
|
pub label: &'static str,
|
|
pub icon: &'static str,
|
|
pub command: &'static str,
|
|
pub availability: Availability,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct ToolSection {
|
|
pub title: &'static str,
|
|
pub items: &'static [ToolItem],
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct ToolGroup {
|
|
pub id: &'static str,
|
|
pub label: &'static str,
|
|
pub icon: &'static str,
|
|
/// Icon tint, as Acrobat colour-codes its tool families.
|
|
pub hue: [u8; 3],
|
|
pub badge: Option<&'static str>,
|
|
pub availability: Availability,
|
|
pub sections: &'static [ToolSection],
|
|
}
|
|
|
|
const fn item(label: &'static str, icon: &'static str, command: &'static str, availability: Availability) -> ToolItem {
|
|
ToolItem { label, icon, command, availability }
|
|
}
|
|
|
|
use Availability::{Planned, Provider, Ready};
|
|
|
|
const RED: [u8; 3] = [0xE0, 0x3E, 0x3E];
|
|
const PURPLE: [u8; 3] = [0x8E, 0x4E, 0xE6];
|
|
const BLUE: [u8; 3] = [0x3A, 0x6F, 0xE8];
|
|
const GREEN: [u8; 3] = [0x2D, 0x9D, 0x5B];
|
|
const ORANGE: [u8; 3] = [0xE8, 0x8A, 0x1A];
|
|
const TEAL: [u8; 3] = [0x14, 0x9C, 0xA8];
|
|
const PINK: [u8; 3] = [0xD6, 0x3B, 0x8F];
|
|
|
|
pub static TOOL_GROUPS: &[ToolGroup] = &[
|
|
ToolGroup {
|
|
id: "export",
|
|
label: "Export a PDF",
|
|
icon: "file-output",
|
|
hue: BLUE,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Export to",
|
|
items: &[
|
|
item("Microsoft Word (.docx)", "file-text", "export.docx", Ready),
|
|
item("Spreadsheet (.xlsx)", "grid-3x3", "export.xlsx", Planned("M10")),
|
|
item("Presentation (.pptx)", "presentation", "export.pptx", Planned("M10")),
|
|
item("Image (PNG)", "image", "export.image", Ready),
|
|
item("Export all images", "image", "export.all_images", Ready),
|
|
item("HTML web page", "file-symlink", "export.html", Ready),
|
|
item("Rich Text Format (.rtf)", "file-text", "export.rtf", Ready),
|
|
item("Text (plain)", "type", "export.text", Ready),
|
|
item("PostScript / EPS", "file-down", "export.ps", Planned("M10")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "fill_sign",
|
|
label: "Fill & Sign",
|
|
icon: "pen-line",
|
|
hue: PURPLE,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Fill",
|
|
items: &[
|
|
item("Add text", "type", "sign.fill.text", Ready),
|
|
item("Checkmark", "check", "sign.fill.check", Ready),
|
|
item("Cross", "x", "sign.fill.cross", Ready),
|
|
item("Dot", "circle-dot", "sign.fill.dot", Ready),
|
|
item("Line", "minus", "sign.fill.line", Ready),
|
|
item("Date", "clock-3", "sign.fill.date", Ready),
|
|
item("Signature", "signature", "sign.fill.signature", Ready),
|
|
item("Initials", "signature", "sign.fill.initials", Ready),
|
|
item("Change signature", "signature", "sign.fill.signature.change", Ready),
|
|
item("Change initials", "signature", "sign.fill.initials.change", Ready),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "edit",
|
|
label: "Edit a PDF",
|
|
icon: "file-pen-line",
|
|
hue: PINK,
|
|
badge: None,
|
|
availability: Planned("M7"),
|
|
sections: &[
|
|
ToolSection {
|
|
title: "Modify page",
|
|
items: &[
|
|
item("Rotate pages", "rotate-cw", "page.rotate", Ready),
|
|
item("Insert pages", "file-plus-2", "page.insert", Ready),
|
|
item("Delete pages", "trash-2", "page.delete", Ready),
|
|
item("Extract pages", "file-output", "page.extract", Ready),
|
|
item("Organize pages", "layout-grid", "page.organize", Ready),
|
|
],
|
|
},
|
|
ToolSection {
|
|
title: "Add content",
|
|
items: &[
|
|
item("Edit text & images", "text-cursor-input", "edit.edit_text", Ready),
|
|
item("Text", "type", "edit.text", Ready),
|
|
item("Image", "image-plus", "edit.image", Ready),
|
|
item("Header and footer", "heading", "edit.header_footer", Ready),
|
|
item("Watermark", "stamp", "edit.watermark", Ready),
|
|
item("Link", "link-2", "edit.link", Ready),
|
|
item("Bates numbering", "hash", "edit.bates", Ready),
|
|
item("Background", "palette", "edit.background", Ready),
|
|
item("Attach file", "paperclip", "edit.attach", Planned("M12")),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
ToolGroup {
|
|
id: "create",
|
|
label: "Create a PDF",
|
|
icon: "file-plus-2",
|
|
hue: RED,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Create from",
|
|
items: &[
|
|
item("Single file", "file-input", "create.file", Ready),
|
|
item("Multiple files", "files", "create.multiple", Ready),
|
|
item("Images", "image", "create.images", Ready),
|
|
item("Clipboard", "copy-plus", "create.clipboard", Ready),
|
|
item("Blank page", "file-plus-2", "create.blank", Ready),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "combine",
|
|
label: "Combine files",
|
|
icon: "files",
|
|
hue: BLUE,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection { title: "Combine", items: &[item("Add files to combine", "files", "page.combine", Ready)] }],
|
|
},
|
|
ToolGroup {
|
|
id: "organize",
|
|
label: "Organize pages",
|
|
icon: "layout-grid",
|
|
hue: GREEN,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[
|
|
ToolSection {
|
|
title: "Page options",
|
|
items: &[
|
|
item("Page grid", "layout-grid", "page.organize", Ready),
|
|
item("Rotate", "rotate-cw", "page.rotate", Ready),
|
|
item("Delete", "trash-2", "page.delete", Ready),
|
|
item("Extract", "file-output", "page.extract", Ready),
|
|
item("Insert", "file-plus-2", "page.insert", Ready),
|
|
item("Replace", "replace", "page.replace", Ready),
|
|
item("Split", "scissors", "page.split", Ready),
|
|
],
|
|
},
|
|
ToolSection {
|
|
title: "More",
|
|
items: &[
|
|
item("Set page boxes", "square-dashed-mouse-pointer", "page.boxes", Ready),
|
|
item("Crop pages", "crop", "page.crop", Ready),
|
|
item("Duplicate pages", "copy-plus", "page.duplicate", Ready),
|
|
item("Page labels", "tag", "page.number", Ready),
|
|
item("Page transitions", "presentation", "page.transitions", Planned("M4")),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
ToolGroup {
|
|
id: "comment",
|
|
label: "Add comments",
|
|
icon: "message-square-text",
|
|
hue: ORANGE,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[
|
|
ToolSection {
|
|
title: "Markup",
|
|
items: &[
|
|
item("Sticky note", "sticky-note", "comment.note", Ready),
|
|
item("Highlight text", "highlighter", "comment.highlight", Ready),
|
|
item("Underline", "underline", "comment.underline", Ready),
|
|
item("Strikethrough", "strikethrough", "comment.strikeout", Ready),
|
|
item("Squiggly underline", "spline", "comment.squiggly", Ready),
|
|
item("Text box", "type", "comment.freetext", Ready),
|
|
],
|
|
},
|
|
ToolSection {
|
|
title: "Drawing",
|
|
items: &[
|
|
item("Draw freehand", "pencil", "comment.ink", Ready),
|
|
item("Rectangle", "square", "comment.square", Ready),
|
|
item("Oval", "circle", "comment.circle", Ready),
|
|
item("Line", "minus", "comment.line", Ready),
|
|
item("Arrow", "move-right", "comment.arrow", Ready),
|
|
item("Cloud", "cloud", "comment.cloud", Planned("M5")),
|
|
item("Stamp", "stamp", "comment.stamp", Planned("M5")),
|
|
],
|
|
},
|
|
ToolSection {
|
|
title: "Review",
|
|
items: &[
|
|
item("Comment list", "message-square-text", "comment.list", Ready),
|
|
item("Flatten comments", "layers", "comment.flatten", Ready),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
ToolGroup {
|
|
id: "scan",
|
|
label: "Scan & OCR",
|
|
icon: "scan-text",
|
|
hue: GREEN,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Recognize text",
|
|
items: &[
|
|
item("In this file", "scan-text", "ocr.recognize", Ready),
|
|
item("In multiple files", "files", "ocr.recognize_batch", Ready),
|
|
item("Enhance scanned file", "sparkles", "ocr.enhance", Planned("M10")),
|
|
item("Enhance camera image", "camera", "ocr.enhance_camera", Ready),
|
|
item("Correct recognized text", "text-select", "ocr.correct", Planned("M10")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "protect",
|
|
label: "Protect a PDF",
|
|
icon: "shield-check",
|
|
hue: BLUE,
|
|
badge: None,
|
|
availability: Planned("M8"),
|
|
sections: &[
|
|
ToolSection {
|
|
title: "Protect",
|
|
items: &[
|
|
item("Protect with password", "lock", "protect.password", Ready),
|
|
item("Remove hidden information", "eye-off", "protect.remove_hidden", Ready),
|
|
],
|
|
},
|
|
ToolSection {
|
|
title: "Advanced options",
|
|
items: &[
|
|
item("Encrypt with certificate", "file-lock-2", "protect.certificate", Planned("M8")),
|
|
item("Security properties", "shield-check", "protect.properties", Ready),
|
|
item("Remove security", "lock-open", "protect.remove", Ready),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
ToolGroup {
|
|
id: "redact",
|
|
label: "Redact a PDF",
|
|
icon: "rectangle-horizontal",
|
|
hue: RED,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Redact",
|
|
items: &[
|
|
item("Redact text and images", "rectangle-horizontal", "redact.mark", Ready),
|
|
item("Redact pages", "file-x", "redact.pages", Ready),
|
|
item("Find text and redact", "file-search", "redact.search", Ready),
|
|
item("Set properties", "settings-2", "redact.properties", Ready),
|
|
item("Sanitize document", "sparkles", "redact.sanitize", Ready),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "compress",
|
|
label: "Compress a PDF",
|
|
icon: "file-down",
|
|
hue: RED,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Optimize",
|
|
items: &[
|
|
item("Reduce file size", "file-down", "optimize.reduce", Ready),
|
|
item("Advanced optimization", "settings-2", "optimize.advanced", Ready),
|
|
item("Audit space usage", "gauge", "optimize.audit", Planned("M11")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "form",
|
|
label: "Prepare a form",
|
|
icon: "text-cursor-input",
|
|
hue: PURPLE,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[
|
|
ToolSection {
|
|
title: "Fields",
|
|
items: &[
|
|
item("Field list", "list", "form.fields", Ready),
|
|
item("Detect form fields", "scan", "form.detect", Ready),
|
|
item("Clear form", "eraser", "form.clear", Ready),
|
|
item("Field properties", "settings-2", "form.field.properties", Ready),
|
|
item("Tab order by rows", "rows-3", "form.tab_order.row", Ready),
|
|
item("Tab order by columns", "columns-3", "form.tab_order.column", Ready),
|
|
item("Flatten form fields", "layers", "form.flatten", Ready),
|
|
item("Import data", "file-input", "form.import_data", Ready),
|
|
item("Export data", "file-output", "form.export_data", Ready),
|
|
],
|
|
},
|
|
ToolSection {
|
|
title: "Add form components",
|
|
items: &[
|
|
item("Text field", "text-cursor-input", "form.add.text", Ready),
|
|
item("Checkbox", "check-circle-2", "form.add.checkbox", Ready),
|
|
item("Radio button", "circle", "form.add.radio", Ready),
|
|
item("Drop-down list", "chevron-down", "form.add.combo", Ready),
|
|
item("List box", "list", "form.add.list", Ready),
|
|
item("Button", "square", "form.add.button", Ready),
|
|
item("Image field", "image", "form.add.image", Ready),
|
|
item("Date field", "clock-3", "form.add.date", Ready),
|
|
item("Digital signature", "signature", "form.add.signature", Ready),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
ToolGroup {
|
|
id: "stamp",
|
|
label: "Add a stamp",
|
|
icon: "stamp",
|
|
hue: PURPLE,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection { title: "Stamps", items: &[item("Stamp palette", "stamp", "comment.stamp", Ready)] }],
|
|
},
|
|
ToolGroup {
|
|
id: "certificate",
|
|
label: "Use a certificate",
|
|
icon: "badge-check",
|
|
hue: TEAL,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Certificates",
|
|
items: &[
|
|
item("Digitally sign", "signature", "sign.digital", Ready),
|
|
item("Timestamp", "clock-3", "sign.timestamp", Planned("M9")),
|
|
item("Validate all signatures", "badge-check", "sign.validate", Ready),
|
|
item("Certify (visible signature)", "badge-check", "sign.certify", Ready),
|
|
item("Certify (invisible signature)", "badge-check", "sign.certify_invisible", Ready),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "print_production",
|
|
label: "Use print production",
|
|
icon: "printer",
|
|
hue: PURPLE,
|
|
badge: None,
|
|
availability: Planned("M11"),
|
|
sections: &[ToolSection {
|
|
title: "Print production",
|
|
items: &[
|
|
item("Output preview", "layers", "prepress.output_preview", Planned("M11")),
|
|
item("Preflight", "file-check", "preflight.run", Planned("M11")),
|
|
item("Convert colors", "palette", "prepress.convert_colors", Planned("M11")),
|
|
item("Set page boxes", "square-dashed-mouse-pointer", "page.boxes", Ready),
|
|
item("Add printer marks", "ruler", "prepress.marks", Planned("M11")),
|
|
item("Fix hairlines", "minus", "prepress.hairlines", Planned("M11")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "measure",
|
|
label: "Measure objects",
|
|
icon: "ruler",
|
|
hue: PINK,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Measure",
|
|
items: &[
|
|
item("Distance", "ruler", "measure.distance", Ready),
|
|
item("Perimeter", "ruler", "measure.perimeter", Ready),
|
|
item("Area", "ruler", "measure.area", Ready),
|
|
item("Drawing scale", "ruler", "measure.scale", Ready),
|
|
item("Measurement information", "ruler", "measure.info", Ready),
|
|
item("Snapping", "ruler", "measure.snap", Ready),
|
|
item("Export measurements", "file-output", "measure.export", Ready),
|
|
item("Geospatial location", "compass", "measure.geo", Planned("M12")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "compare",
|
|
label: "Compare files",
|
|
icon: "git-compare",
|
|
hue: PINK,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection { title: "Compare", items: &[item("Select files to compare", "git-compare", "doc.compare", Ready)] }],
|
|
},
|
|
ToolGroup {
|
|
id: "actions",
|
|
label: "Use guided actions",
|
|
icon: "list-checks",
|
|
hue: PURPLE,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Actions list",
|
|
items: &[
|
|
item("Action Wizard", "list-checks", "actions.wizard", Ready),
|
|
item("Make accessible", "accessibility", "actions.make_accessible", Planned("M13")),
|
|
item("Prepare for distribution", "send", "actions.distribution", Ready),
|
|
item("Optimize scanned documents", "scan-text", "actions.optimize_scans", Ready),
|
|
item("Archive documents", "file-check", "actions.archive", Planned("M13")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "accessibility",
|
|
label: "Prepare for accessibility",
|
|
icon: "accessibility",
|
|
hue: PURPLE,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Accessibility",
|
|
items: &[
|
|
item("Automatically tag PDF", "tag", "a11y.autotag", Planned("M12")),
|
|
item("Change reading options", "book-open", "a11y.reading_options", Ready),
|
|
item("Check for accessibility", "accessibility", "a11y.check", Ready),
|
|
item("Open accessibility report", "file-text", "a11y.report", Ready),
|
|
item("Add alternate text", "image", "a11y.alt_text", Ready),
|
|
item("Fix reading order", "list-ordered", "a11y.reading_order", Planned("M12")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "standards",
|
|
label: "Apply PDF standards",
|
|
icon: "file-check",
|
|
hue: RED,
|
|
badge: None,
|
|
availability: Ready,
|
|
sections: &[ToolSection {
|
|
title: "Standards",
|
|
items: &[
|
|
item("Save as PDF/A", "file-check", "standards.pdfa", Ready),
|
|
item("Save as PDF/X", "file-check", "standards.pdfx", Planned("M11")),
|
|
item("Save as PDF/UA", "accessibility", "standards.pdfua", Planned("M11")),
|
|
item("Preflight", "file-check", "preflight.run", Planned("M11")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "search_index",
|
|
label: "Add search index",
|
|
icon: "file-search",
|
|
hue: GREEN,
|
|
badge: None,
|
|
availability: Planned("M12"),
|
|
sections: &[ToolSection { title: "Index", items: &[item("Manage embedded index", "file-search", "search.index", Planned("M12"))] }],
|
|
},
|
|
ToolGroup {
|
|
id: "javascript",
|
|
label: "Use JavaScript",
|
|
icon: "braces",
|
|
hue: BLUE,
|
|
badge: None,
|
|
availability: Planned("M6"),
|
|
sections: &[ToolSection {
|
|
title: "JavaScript",
|
|
items: &[
|
|
item("Console", "braces", "js.console", Planned("M6")),
|
|
item("Document JavaScripts", "file-text", "js.document", Planned("M6")),
|
|
item("Document actions", "list-checks", "js.actions", Planned("M6")),
|
|
],
|
|
}],
|
|
},
|
|
ToolGroup {
|
|
id: "ai",
|
|
label: "AI assistant",
|
|
icon: "sparkles",
|
|
hue: PINK,
|
|
badge: Some("Optional"),
|
|
availability: Provider,
|
|
sections: &[ToolSection {
|
|
title: "Bring your own model (off by default)",
|
|
items: &[
|
|
item("Summarize", "sparkles", "ai.summary", Provider),
|
|
item("Ask about this document", "message-circle-reply", "ai.ask", Provider),
|
|
item("Translate", "languages", "ai.translate", Provider),
|
|
],
|
|
}],
|
|
},
|
|
];
|
|
|
|
pub fn group(id: &str) -> Option<&'static ToolGroup> {
|
|
TOOL_GROUPS.iter().find(|g| g.id == id)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::collections::HashSet;
|
|
|
|
#[test]
|
|
fn ids_are_unique_and_every_group_has_items() {
|
|
let mut seen = HashSet::new();
|
|
for g in TOOL_GROUPS {
|
|
assert!(seen.insert(g.id), "duplicate group id {}", g.id);
|
|
assert!(g.sections.iter().any(|s| !s.items.is_empty()), "{} has no items", g.id);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn commands_are_namespaced() {
|
|
for g in TOOL_GROUPS {
|
|
for s in g.sections {
|
|
for i in s.items {
|
|
assert!(i.command.contains('.'), "{} lacks a namespace", i.command);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|