mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
600add1474 | ||
|
|
8451b06d53 | ||
|
|
7bb3024dc9 | ||
|
|
18cd2baec0 |
+3
-2
@@ -157,7 +157,9 @@ app/proprietary/build
|
||||
common/build
|
||||
proprietary/build
|
||||
stirling-pdf/build
|
||||
frontend/editor/src-tauri/provisioner/target
|
||||
frontend/src-tauri/provisioner/target
|
||||
frontend/src-tauri/target
|
||||
frontend/src-tauri/runtime/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
@@ -174,7 +176,6 @@ venv.bak/
|
||||
|
||||
# Env files (secrets / local overrides). Subproject .gitignore files whitelist any committed defaults.
|
||||
.env*
|
||||
!.env.saas.example
|
||||
|
||||
# VS Code
|
||||
/.vscode/**/*
|
||||
|
||||
@@ -25,9 +25,9 @@ tasks:
|
||||
env:
|
||||
SERVER_PORT: '{{.PORT}}'
|
||||
cmds:
|
||||
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
|
||||
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true AIENGINE_TIMEOUTSECONDS=120 {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
|
||||
platforms: [windows]
|
||||
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}./gradlew :stirling-pdf:bootRun'
|
||||
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true AIENGINE_TIMEOUTSECONDS=120 {{end}}./gradlew :stirling-pdf:bootRun'
|
||||
platforms: [linux, darwin]
|
||||
|
||||
dev:bundled:
|
||||
|
||||
@@ -257,6 +257,7 @@ tasks:
|
||||
- task: lint
|
||||
- task: format:check
|
||||
- task: test
|
||||
- task: theme:check-unused-vars
|
||||
|
||||
check:all:
|
||||
desc: "Full CI quality gate"
|
||||
@@ -298,3 +299,17 @@ tasks:
|
||||
deps: [install]
|
||||
cmds:
|
||||
- node editor/scripts/generate-licenses.js
|
||||
|
||||
theme:clean-unused-vars:
|
||||
desc: "Remove unused CSS variables from theme.css"
|
||||
deps: [install]
|
||||
vars:
|
||||
REMOVE: '{{.REMOVE | default "false"}}'
|
||||
cmds:
|
||||
- node editor/scripts/clean-unused-theme-vars.js{{if eq .REMOVE "true"}} --remove{{end}} {{.CLI_ARGS}}
|
||||
|
||||
theme:check-unused-vars:
|
||||
desc: "Fail if theme.css contains unused CSS variables"
|
||||
deps: [install]
|
||||
cmds:
|
||||
- node editor/scripts/clean-unused-theme-vars.js --check
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ tasks:
|
||||
vars:
|
||||
PORT: '{{.BACKEND_PORT}}'
|
||||
AIENGINE_URL: 'http://localhost:{{.ENGINE_PORT}}'
|
||||
- task: frontend:dev:prototypes
|
||||
- task: frontend:dev
|
||||
vars:
|
||||
PORT: '{{.FRONTEND_PORT}}'
|
||||
BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}'
|
||||
|
||||
+3
@@ -277,6 +277,9 @@ public class ConfigController {
|
||||
// Premium/Enterprise settings
|
||||
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
|
||||
|
||||
// AI Engine settings
|
||||
configData.put("aiEngineEnabled", applicationProperties.getAiEngine().isEnabled());
|
||||
|
||||
// Timestamp TSA settings — single source of truth for presets + admin URLs
|
||||
ApplicationProperties.Security.Timestamp tsConfig =
|
||||
applicationProperties.getSecurity().getTimestamp();
|
||||
|
||||
@@ -2672,6 +2672,47 @@ title = "Change Permissions"
|
||||
[changePermissions.tooltip.warning]
|
||||
text = "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
||||
|
||||
[agents]
|
||||
section_title = "Agents"
|
||||
fullscreen_title = "Stirling Agents"
|
||||
stirling_name = "Stirling"
|
||||
stirling_full_name = "Stirling General Agent"
|
||||
stirling_tooltip = "Stirling agent"
|
||||
stirling_description = "Your general-purpose PDF assistant"
|
||||
stirling_long_description = "General purpose PDF assistant that can run tools, create PDFs and extract insights from your documents."
|
||||
back_to_tools = "Back to tools"
|
||||
coming_soon = "Coming soon"
|
||||
view_all = "View all agents"
|
||||
show_less = "Show less"
|
||||
start_chat = "Start chatting"
|
||||
|
||||
[chat.header]
|
||||
settings = "Agent settings"
|
||||
agentMenu = "Stirling agent options"
|
||||
clearChat = "Clear chat"
|
||||
|
||||
[chat.input]
|
||||
placeholder = "What do you want to do?"
|
||||
send = "Send message"
|
||||
attach = "Attach files"
|
||||
|
||||
[chat.quickActions]
|
||||
heading = "Get started"
|
||||
openFromComputer = "Open from computer"
|
||||
browseYourFiles = "Browse your files"
|
||||
rotateOne = "Rotate this document"
|
||||
rotateMany = "Rotate these documents"
|
||||
compressOne = "Compress this document"
|
||||
compressMany = "Compress these documents"
|
||||
mergeMany = "Merge these {{count}} documents into 1"
|
||||
splitOne = "Split this document"
|
||||
convertOne = "Convert this document to PDF"
|
||||
convertMany = "Convert these documents to PDF"
|
||||
fileSummary_one = "1 file in workbench ({{types}})"
|
||||
fileSummary_other = "{{count}} files in workbench ({{types}})"
|
||||
moreFiles = "+{{count}} more"
|
||||
removeFile = "Remove {{name}}"
|
||||
|
||||
[chat.progress]
|
||||
analyzing = "Analysing your request..."
|
||||
calling_engine = "AI is thinking..."
|
||||
@@ -6864,6 +6905,7 @@ theme = "Theme"
|
||||
themeDark = "Dark"
|
||||
themeDescription = "Switch between light and dark mode"
|
||||
themeLight = "Light"
|
||||
themeMidnight = "Midnight"
|
||||
title = "General"
|
||||
user = "User"
|
||||
|
||||
@@ -7910,6 +7952,11 @@ expand = "Expand panel"
|
||||
placeholder = "Choose a tool to get started"
|
||||
premiumFeature = "Premium feature:"
|
||||
search = "Search tools"
|
||||
toolsHeader = "Tools"
|
||||
viewAllTools = "View all tools"
|
||||
backToDefault = "Back"
|
||||
backToAllTools = "Back to all tools"
|
||||
goBack = "Go back"
|
||||
|
||||
[toolPanel.fullscreen]
|
||||
comingSoon = "Coming soon:"
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Finds and optionally removes unused CSS custom properties from theme.css.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/clean-unused-theme-vars.js # dry run: report only
|
||||
* node scripts/clean-unused-theme-vars.js --check # CI mode: exit 1 if any unused
|
||||
* node scripts/clean-unused-theme-vars.js --remove # remove unused declarations
|
||||
* node scripts/clean-unused-theme-vars.js --verbose # show all usage counts
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const REMOVE = process.argv.includes("--remove");
|
||||
const CHECK = process.argv.includes("--check");
|
||||
const VERBOSE =
|
||||
process.argv.includes("--verbose") || process.argv.includes("-v");
|
||||
|
||||
const EDITOR_ROOT = path.join(__dirname, "..");
|
||||
const THEME_CSS_PATH = path.join(EDITOR_ROOT, "src/core/styles/theme.css");
|
||||
|
||||
const SCAN_EXTENSIONS = new Set([
|
||||
".ts",
|
||||
".tsx",
|
||||
".css",
|
||||
".js",
|
||||
".jsx",
|
||||
".mts",
|
||||
".mjs",
|
||||
]);
|
||||
|
||||
const SKIP_DIRS = new Set([
|
||||
"node_modules",
|
||||
"dist",
|
||||
".git",
|
||||
"target",
|
||||
"coverage",
|
||||
".turbo",
|
||||
]);
|
||||
|
||||
// ─── Parse declarations ───────────────────────────────────────────────────────
|
||||
|
||||
function parseDeclarations(content) {
|
||||
const lines = content.split("\n");
|
||||
// Map from varName -> array of 0-based line indices where it is declared
|
||||
const declarations = new Map();
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const match = lines[i].match(/^\s*(--[\w-]+)\s*:/);
|
||||
if (match) {
|
||||
const name = match[1];
|
||||
if (!declarations.has(name)) declarations.set(name, []);
|
||||
declarations.get(name).push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return declarations;
|
||||
}
|
||||
|
||||
// ─── Scan for usages ─────────────────────────────────────────────────────────
|
||||
|
||||
const VAR_USAGE_RE = /var\((--[\w-]+)/g;
|
||||
|
||||
function collectUsagesInFile(filePath, used) {
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(filePath, "utf8");
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
VAR_USAGE_RE.lastIndex = 0;
|
||||
let match;
|
||||
while ((match = VAR_USAGE_RE.exec(content)) !== null) {
|
||||
used.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
function scanDir(dir, used) {
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
||||
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
scanDir(fullPath, used);
|
||||
} else if (SCAN_EXTENSIONS.has(path.extname(entry.name))) {
|
||||
collectUsagesInFile(fullPath, used);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Remove unused declarations ───────────────────────────────────────────────
|
||||
|
||||
function removeDeclarations(content, unusedNames) {
|
||||
const lines = content.split("\n");
|
||||
const filtered = [];
|
||||
let skipping = false;
|
||||
|
||||
for (const line of lines) {
|
||||
if (skipping) {
|
||||
// Still consuming a multi-line value — stop when we hit the terminating semicolon
|
||||
if (line.includes(";")) skipping = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const match = line.match(/^\s*(--[\w-]+)\s*:/);
|
||||
if (match && unusedNames.has(match[1])) {
|
||||
// If the value doesn't end on this line, skip subsequent lines too
|
||||
if (!line.includes(";")) skipping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
filtered.push(line);
|
||||
}
|
||||
|
||||
// Collapse runs of 3+ blank lines down to 2 (avoids gaps from removed blocks)
|
||||
const collapsed = [];
|
||||
let blankRun = 0;
|
||||
for (const line of filtered) {
|
||||
if (line.trim() === "") {
|
||||
blankRun++;
|
||||
if (blankRun <= 2) collapsed.push(line);
|
||||
} else {
|
||||
blankRun = 0;
|
||||
collapsed.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
return collapsed.join("\n");
|
||||
}
|
||||
|
||||
// ─── Main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(THEME_CSS_PATH)) {
|
||||
console.error("theme.css not found at:", THEME_CSS_PATH);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const themeCss = fs.readFileSync(THEME_CSS_PATH, "utf8");
|
||||
const declarations = parseDeclarations(themeCss);
|
||||
|
||||
console.log(`Found ${declarations.size} declared CSS variables in theme.css`);
|
||||
console.log("Scanning source files for usages...");
|
||||
|
||||
const used = new Set();
|
||||
scanDir(EDITOR_ROOT, used);
|
||||
|
||||
const unused = new Set();
|
||||
for (const name of declarations.keys()) {
|
||||
if (!used.has(name)) unused.add(name);
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
const allNames = [...declarations.keys()].sort();
|
||||
for (const name of allNames) {
|
||||
const status = used.has(name) ? "used " : "UNUSED";
|
||||
console.log(` ${status} ${name}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (unused.size === 0) {
|
||||
console.log("No unused CSS variables found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const sortedUnused = [...unused].sort();
|
||||
console.log(`\nUnused variables (${unused.size}):`);
|
||||
for (const name of sortedUnused) {
|
||||
const lineNums = declarations
|
||||
.get(name)
|
||||
.map((i) => i + 1)
|
||||
.join(", ");
|
||||
console.log(
|
||||
` ${name} (line${declarations.get(name).length > 1 ? "s" : ""} ${lineNums})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (CHECK) {
|
||||
console.error(
|
||||
"\ntheme.css has unused CSS variables. Run the following to fix:",
|
||||
);
|
||||
console.error("\n task frontend:theme:clean-unused-vars REMOVE=true\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!REMOVE) {
|
||||
console.log(
|
||||
"\nDry run — no changes written. Re-run with --remove to delete unused declarations.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = removeDeclarations(themeCss, unused);
|
||||
fs.writeFileSync(THEME_CSS_PATH, updated, "utf8");
|
||||
|
||||
// Count declarations removed
|
||||
const declsRemoved = [...unused].reduce(
|
||||
(sum, name) => sum + declarations.get(name).length,
|
||||
0,
|
||||
);
|
||||
console.log(
|
||||
`\nRemoved ${declsRemoved} declaration${declsRemoved !== 1 ? "s" : ""} (${unused.size} unique variable${unused.size !== 1 ? "s" : ""}) from theme.css`,
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Core stubs for the right-rail Agents UI.
|
||||
*
|
||||
* The real implementations live in {@code proprietary/components/agents/AgentsPanel.tsx}
|
||||
* and shadow these stubs via the {@code @app/*} alias cascade when the proprietary
|
||||
* build is active. Core builds render nothing, so the right rail collapses to the
|
||||
* tool list unchanged.
|
||||
*/
|
||||
|
||||
/** Whether the right rail should reserve space for agents UI. False in core. */
|
||||
export function useAgentsEnabled(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the agent chat overlay is currently open. Core builds have no chat,
|
||||
* so this always returns false. Proprietary builds bridge to the ChatContext.
|
||||
* Used by {@code RightSidebar} so the fullscreen tool picker can yield to the
|
||||
* chat overlay just like it yields to a selected tool.
|
||||
*/
|
||||
export function useAgentChatOpen(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Inline "Agents" section rendered above the tool list in {@code ToolPicker}. */
|
||||
export function AgentsSection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon-only agent button rendered in the collapsed (minimised) right rail.
|
||||
* Returns null in core; proprietary renders the Stirling agent shortcut.
|
||||
*/
|
||||
export function AgentsCollapsedButton(_props: { onExpand: () => void }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full-rail chat overlay rendered inside {@code ToolPanel}. Covers the panel
|
||||
* (including the search bar) when an agent conversation is active.
|
||||
*/
|
||||
export function AgentsChatOverlay() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agents card rendered inside the fullscreen tool picker. Matches the visual
|
||||
* language of the fullscreen category cards (gradient border, title, items).
|
||||
* Returns null in core; proprietary renders the Stirling agent.
|
||||
*/
|
||||
export function AgentsFullscreenSection() {
|
||||
return null;
|
||||
}
|
||||
@@ -75,7 +75,7 @@
|
||||
box-shadow: var(--shadow-md);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: var(--bg-surface);
|
||||
background: white;
|
||||
transition:
|
||||
box-shadow 0.15s ease,
|
||||
transform 0.2s ease;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, useContext, ReactNode } from "react";
|
||||
import { createContext, useContext, ReactNode, useEffect } from "react";
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { useRainbowTheme } from "@app/hooks/useRainbowTheme";
|
||||
import { mantineTheme } from "@app/theme/mantineTheme";
|
||||
@@ -36,9 +36,20 @@ interface RainbowThemeProviderProps {
|
||||
export function RainbowThemeProvider({ children }: RainbowThemeProviderProps) {
|
||||
const rainbowTheme = useRainbowTheme();
|
||||
|
||||
// Determine the Mantine color scheme
|
||||
// Determine the Mantine color scheme (midnight and rainbow both use Mantine dark)
|
||||
const mantineColorScheme =
|
||||
rainbowTheme.themeMode === "rainbow" ? "dark" : rainbowTheme.themeMode;
|
||||
rainbowTheme.themeMode === "rainbow" ||
|
||||
rainbowTheme.themeMode === "midnight"
|
||||
? "dark"
|
||||
: rainbowTheme.themeMode;
|
||||
|
||||
// Apply data-color-scheme attribute to html element for CSS midnight overrides
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute(
|
||||
"data-color-scheme",
|
||||
rainbowTheme.themeMode,
|
||||
);
|
||||
}, [rainbowTheme.themeMode]);
|
||||
|
||||
return (
|
||||
<RainbowThemeContext.Provider value={rainbowTheme}>
|
||||
|
||||
@@ -49,7 +49,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
const { t } = useTranslation();
|
||||
const { preferences, updatePreference } = usePreferences();
|
||||
const { config } = useAppConfig();
|
||||
const { toggleTheme, themeMode } = useRainbowThemeContext();
|
||||
const { themeMode } = useRainbowThemeContext();
|
||||
const [fileLimitInput, setFileLimitInput] = useState<number | string>(
|
||||
preferences.autoUnzipFileLimit,
|
||||
);
|
||||
@@ -380,10 +380,10 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
</Text>
|
||||
</div>
|
||||
<SegmentedControl
|
||||
value={themeMode === "rainbow" ? "dark" : themeMode}
|
||||
onChange={(val) => {
|
||||
if ((themeMode === "dark") !== (val === "dark")) toggleTheme();
|
||||
}}
|
||||
value={themeMode === "rainbow" ? "dark" : (themeMode as string)}
|
||||
onChange={(val) =>
|
||||
updatePreference("theme", val as "light" | "dark" | "midnight")
|
||||
}
|
||||
data={[
|
||||
{
|
||||
label: t("settings.general.themeLight", "Light"),
|
||||
@@ -393,6 +393,10 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({
|
||||
label: t("settings.general.themeDark", "Dark"),
|
||||
value: "dark",
|
||||
},
|
||||
{
|
||||
label: t("settings.general.themeMidnight", "Midnight"),
|
||||
value: "midnight",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
|
||||
import { useIsMobile } from "@app/hooks/useIsMobile";
|
||||
import { usePreferences } from "@app/contexts/PreferencesContext";
|
||||
import { useWorkbenchBar } from "@app/contexts/WorkbenchBarContext";
|
||||
import {
|
||||
AgentsFullscreenSection,
|
||||
useAgentChatOpen,
|
||||
useAgentsEnabled,
|
||||
} from "@app/components/agents/AgentsPanel";
|
||||
import FullscreenToolSurface from "@app/components/tools/FullscreenToolSurface";
|
||||
import { ToolId } from "@app/types/toolId";
|
||||
import type { ToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry";
|
||||
|
||||
/** Derives whether the fullscreen tool picker is currently expanded. */
|
||||
export function useIsFullscreenExpanded(): boolean {
|
||||
const { toolPanelMode, leftPanelView, readerMode } = useToolWorkflow();
|
||||
const isMobile = useIsMobile();
|
||||
const agentChatOpen = useAgentChatOpen();
|
||||
return (
|
||||
toolPanelMode === "fullscreen" &&
|
||||
leftPanelView === "toolPicker" &&
|
||||
!isMobile &&
|
||||
!readerMode &&
|
||||
!agentChatOpen
|
||||
);
|
||||
}
|
||||
|
||||
interface FullscreenToolPanelProps {
|
||||
geometry: ToolPanelGeometry | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Self-contained fullscreen tool picker. Renders null when inactive, and takes
|
||||
* over the right rail (via FullscreenToolSurface) when fullscreen mode is on.
|
||||
* Geometry is computed by the parent (RightSidebar) so its useLayoutEffect runs
|
||||
* after the ref div is committed, ensuring toolPanelRef.current is always set.
|
||||
*/
|
||||
export function FullscreenToolPanel({ geometry }: FullscreenToolPanelProps) {
|
||||
const {
|
||||
toolPanelMode,
|
||||
setToolPanelMode,
|
||||
leftPanelView,
|
||||
readerMode,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filteredTools,
|
||||
toolRegistry,
|
||||
selectedToolKey,
|
||||
handleToolSelect,
|
||||
} = useToolWorkflow();
|
||||
const isMobile = useIsMobile();
|
||||
const agentsEnabled = useAgentsEnabled();
|
||||
const agentChatOpen = useAgentChatOpen();
|
||||
const { setAllButtonsDisabled } = useWorkbenchBar();
|
||||
const { preferences, updatePreference } = usePreferences();
|
||||
|
||||
const fullscreenExpanded =
|
||||
toolPanelMode === "fullscreen" &&
|
||||
leftPanelView === "toolPicker" &&
|
||||
!isMobile &&
|
||||
!readerMode &&
|
||||
!agentChatOpen;
|
||||
|
||||
useEffect(() => {
|
||||
setAllButtonsDisabled(fullscreenExpanded);
|
||||
}, [fullscreenExpanded, setAllButtonsDisabled]);
|
||||
|
||||
const matchedTextMap = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
filteredTools.forEach(({ item: [id], matchedText }) => {
|
||||
if (matchedText) map.set(id, matchedText);
|
||||
});
|
||||
return map;
|
||||
}, [filteredTools]);
|
||||
|
||||
if (!fullscreenExpanded) return null;
|
||||
|
||||
return (
|
||||
<FullscreenToolSurface
|
||||
searchQuery={searchQuery}
|
||||
toolRegistry={toolRegistry}
|
||||
filteredTools={filteredTools}
|
||||
selectedToolKey={selectedToolKey}
|
||||
showDescriptions={preferences.showLegacyToolDescriptions}
|
||||
matchedTextMap={matchedTextMap}
|
||||
onSearchChange={setSearchQuery}
|
||||
onSelect={(id: ToolId) => handleToolSelect(id)}
|
||||
onToggleDescriptions={() =>
|
||||
updatePreference(
|
||||
"showLegacyToolDescriptions",
|
||||
!preferences.showLegacyToolDescriptions,
|
||||
)
|
||||
}
|
||||
onExitFullscreenMode={() => setToolPanelMode("sidebar")}
|
||||
geometry={geometry}
|
||||
agentsSlot={
|
||||
agentsEnabled && !searchQuery ? <AgentsFullscreenSection /> : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { ScrollArea, Switch } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ToolSearch from "@app/components/tools/toolPicker/ToolSearch";
|
||||
@@ -6,8 +7,6 @@ import FullscreenToolList from "@app/components/tools/FullscreenToolList";
|
||||
import { ToolRegistryEntry } from "@app/data/toolsTaxonomy";
|
||||
import { ToolId } from "@app/types/toolId";
|
||||
import { useFocusTrap } from "@app/hooks/useFocusTrap";
|
||||
import { LogoIcon } from "@app/components/shared/LogoIcon";
|
||||
import { Wordmark } from "@app/components/shared/Wordmark";
|
||||
import "@app/components/tools/ToolPanel.css";
|
||||
import { ToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry";
|
||||
|
||||
@@ -26,6 +25,8 @@ interface FullscreenToolSurfaceProps {
|
||||
onToggleDescriptions: () => void;
|
||||
onExitFullscreenMode: () => void;
|
||||
geometry: ToolPanelGeometry | null;
|
||||
/** Optional agents block rendered above the tool list. */
|
||||
agentsSlot?: React.ReactNode;
|
||||
}
|
||||
|
||||
const FullscreenToolSurface = ({
|
||||
@@ -40,6 +41,7 @@ const FullscreenToolSurface = ({
|
||||
onToggleDescriptions,
|
||||
onExitFullscreenMode: _onExitFullscreenMode,
|
||||
geometry,
|
||||
agentsSlot,
|
||||
}: FullscreenToolSurfaceProps) => {
|
||||
const { t } = useTranslation();
|
||||
const surfaceRef = useRef<HTMLDivElement>(null);
|
||||
@@ -47,18 +49,16 @@ const FullscreenToolSurface = ({
|
||||
// Enable focus trap when surface is active
|
||||
useFocusTrap(surfaceRef, true);
|
||||
|
||||
const brandAltText = t("home.mobile.brandAlt", "Stirling PDF logo");
|
||||
if (!geometry) return null;
|
||||
|
||||
const style = geometry
|
||||
? {
|
||||
left: `${geometry.left}px`,
|
||||
top: `${geometry.top}px`,
|
||||
width: `${geometry.width}px`,
|
||||
height: `${geometry.height}px`,
|
||||
}
|
||||
: undefined;
|
||||
const style = {
|
||||
left: `${geometry.left}px`,
|
||||
top: `${geometry.top}px`,
|
||||
width: `${geometry.width}px`,
|
||||
height: `${geometry.height}px`,
|
||||
};
|
||||
|
||||
return (
|
||||
const surface = (
|
||||
<div
|
||||
className="tool-panel__fullscreen-surface"
|
||||
style={style}
|
||||
@@ -70,16 +70,6 @@ const FullscreenToolSurface = ({
|
||||
data-tour="tool-panel"
|
||||
>
|
||||
<div ref={surfaceRef} className="tool-panel__fullscreen-surface-inner">
|
||||
<header className="tool-panel__fullscreen-header">
|
||||
<div className="tool-panel__fullscreen-brand">
|
||||
<LogoIcon className="tool-panel__fullscreen-brand-icon" />
|
||||
<Wordmark
|
||||
alt={brandAltText}
|
||||
className="tool-panel__fullscreen-brand-text"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="tool-panel__fullscreen-controls">
|
||||
<ToolSearch
|
||||
value={searchQuery}
|
||||
@@ -102,6 +92,9 @@ const FullscreenToolSurface = ({
|
||||
className="tool-panel__fullscreen-scroll"
|
||||
offsetScrollbars
|
||||
>
|
||||
{agentsSlot && (
|
||||
<div className="tool-panel__fullscreen-agents">{agentsSlot}</div>
|
||||
)}
|
||||
<FullscreenToolList
|
||||
filteredTools={filteredTools}
|
||||
searchQuery={searchQuery}
|
||||
@@ -115,6 +108,9 @@ const FullscreenToolSurface = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (typeof document === "undefined") return surface;
|
||||
return createPortal(surface, document.body);
|
||||
};
|
||||
|
||||
export default FullscreenToolSurface;
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { ActionIcon } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider";
|
||||
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
|
||||
import { useSidebarContext } from "@app/contexts/SidebarContext";
|
||||
import rainbowStyles from "@app/styles/rainbow.module.css";
|
||||
import { useIsMobile } from "@app/hooks/useIsMobile";
|
||||
import ToolPanel from "@app/components/tools/ToolPanel";
|
||||
import ToolSearch from "@app/components/tools/toolPicker/ToolSearch";
|
||||
import {
|
||||
AgentsChatOverlay,
|
||||
AgentsCollapsedButton,
|
||||
AgentsSection,
|
||||
useAgentsEnabled,
|
||||
} from "@app/components/agents/AgentsPanel";
|
||||
import { useFavoriteToolItems } from "@app/hooks/tools/useFavoriteToolItems";
|
||||
import { useToolSections } from "@app/hooks/useToolSections";
|
||||
import type { SubcategoryGroup } from "@app/hooks/useToolSections";
|
||||
import { ToolIcon } from "@app/components/shared/ToolIcon";
|
||||
import { Tooltip as AppTooltip } from "@app/components/shared/Tooltip";
|
||||
import { withViewTransition } from "@app/utils/viewTransition";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { ToolId } from "@app/types/toolId";
|
||||
import type { ToolRegistryEntry } from "@app/data/toolsTaxonomy";
|
||||
import {
|
||||
FullscreenToolPanel,
|
||||
useIsFullscreenExpanded,
|
||||
} from "@app/components/tools/FullscreenToolPanel";
|
||||
import { useToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry";
|
||||
import "@app/components/tools/ToolPanel.css";
|
||||
|
||||
/**
|
||||
* Right-side rail wrapping the tool panel.
|
||||
*
|
||||
* Owns the rail-level concerns: collapse/expand chrome, the AGENTS top label,
|
||||
* the collapsed strip (agent button + favourite/recommended icon shortcuts),
|
||||
* and the agents chat overlay. Fullscreen takeover lives in FullscreenToolPanel.
|
||||
*/
|
||||
export default function RightSidebar() {
|
||||
const { t } = useTranslation();
|
||||
const { isRainbowMode } = useRainbowThemeContext();
|
||||
const { sidebarRefs } = useSidebarContext();
|
||||
const { toolPanelRef, quickAccessRef } = sidebarRefs;
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const {
|
||||
leftPanelView,
|
||||
isPanelVisible,
|
||||
searchQuery,
|
||||
filteredTools,
|
||||
toolRegistry,
|
||||
setSearchQuery,
|
||||
selectedToolKey,
|
||||
handleToolSelect,
|
||||
handleBackToTools,
|
||||
setLeftPanelView,
|
||||
setReaderMode,
|
||||
setSidebarsVisible,
|
||||
sidebarsVisible,
|
||||
readerMode,
|
||||
favoriteTools,
|
||||
} = useToolWorkflow();
|
||||
|
||||
const agentsEnabled = useAgentsEnabled();
|
||||
const fullscreenExpanded = useIsFullscreenExpanded();
|
||||
const fullscreenGeometry = useToolPanelGeometry({
|
||||
enabled: fullscreenExpanded,
|
||||
toolPanelRef,
|
||||
quickAccessRef,
|
||||
});
|
||||
|
||||
const handleExpand = () => {
|
||||
withViewTransition(() => {
|
||||
if (readerMode) setReaderMode(false);
|
||||
if (leftPanelView === "hidden") setLeftPanelView("toolPicker");
|
||||
if (!sidebarsVisible) setSidebarsVisible(true);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCollapse = () => {
|
||||
withViewTransition(() => setLeftPanelView("hidden"));
|
||||
};
|
||||
|
||||
const [allToolsView, setAllToolsView] = useState(false);
|
||||
|
||||
const handleShowAllTools = () => {
|
||||
withViewTransition(() => setAllToolsView(true));
|
||||
};
|
||||
|
||||
const handleBackToDefault = () => {
|
||||
withViewTransition(() => {
|
||||
setAllToolsView(false);
|
||||
setSearchQuery("");
|
||||
});
|
||||
};
|
||||
|
||||
// The header shows [back] [search] when we have somewhere to go back to —
|
||||
// i.e. the user is in a specific tool, or already in the all-tools/search view.
|
||||
const inToolView = leftPanelView !== "toolPicker";
|
||||
// Show X (close) button only when there's somewhere to go back to.
|
||||
const showCloseButton = inToolView || allToolsView;
|
||||
// Show search input whenever there's a close button, or when agents are off and
|
||||
// we're in the default tool-picker view (search filters the full list inline).
|
||||
const showHeaderSearch =
|
||||
showCloseButton || (!agentsEnabled && leftPanelView === "toolPicker");
|
||||
|
||||
const handleHeaderBack = () => {
|
||||
if (inToolView) {
|
||||
withViewTransition(() => handleBackToTools());
|
||||
} else {
|
||||
handleBackToDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const handleToolSelectWithTransition = (id: ToolId) => {
|
||||
withViewTransition(() => handleToolSelect(id));
|
||||
};
|
||||
|
||||
// Typing in the header search while inside a tool exits the tool and lifts the
|
||||
// panel into the all-tools view so the user immediately sees search results.
|
||||
const handleHeaderSearchChange = (value: string) => {
|
||||
if (inToolView) {
|
||||
withViewTransition(() => {
|
||||
handleBackToTools();
|
||||
setAllToolsView(true);
|
||||
setSearchQuery(value);
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSearchQuery(value);
|
||||
};
|
||||
|
||||
const activeTool: ToolRegistryEntry | null =
|
||||
inToolView && selectedToolKey
|
||||
? (toolRegistry[selectedToolKey as ToolId] ?? null)
|
||||
: null;
|
||||
|
||||
// Agents header + section is hidden when:
|
||||
// - the AI engine is off, or
|
||||
// - the user is in the all-tools view, or
|
||||
// - a specific tool is being rendered (leftPanelView ≠ "toolPicker").
|
||||
const showAgents =
|
||||
agentsEnabled && !allToolsView && leftPanelView === "toolPicker";
|
||||
|
||||
const computedWidth = () => {
|
||||
if (isMobile) return "100%";
|
||||
if (!isPanelVisible) return "3.5rem";
|
||||
return "18.5rem";
|
||||
};
|
||||
|
||||
// Collapsed rail: show favourites + recommended tools as icons.
|
||||
const favoriteToolItems = useFavoriteToolItems(favoriteTools, toolRegistry);
|
||||
const { sections: collapsedSections } = useToolSections(filteredTools);
|
||||
const collapsedQuickSection = useMemo(
|
||||
() => collapsedSections.find((s) => s.key === "quick"),
|
||||
[collapsedSections],
|
||||
);
|
||||
const collapsedRecommendedItems = useMemo(() => {
|
||||
if (!collapsedQuickSection) return [];
|
||||
const items: Array<{ id: ToolId; tool: ToolRegistryEntry }> = [];
|
||||
collapsedQuickSection.subcategories.forEach((sc: SubcategoryGroup) =>
|
||||
sc.tools.forEach((entry) =>
|
||||
items.push({ id: entry.id as ToolId, tool: entry.tool }),
|
||||
),
|
||||
);
|
||||
return items;
|
||||
}, [collapsedQuickSection]);
|
||||
const collapsedRailItems = useMemo(() => {
|
||||
const map = new Map<ToolId, ToolRegistryEntry>();
|
||||
favoriteToolItems.forEach(({ id, tool }) => map.set(id, tool));
|
||||
collapsedRecommendedItems.forEach(({ id, tool }) => {
|
||||
if (!map.has(id)) map.set(id, tool);
|
||||
});
|
||||
return Array.from(map, ([id, tool]) => ({ id, tool }));
|
||||
}, [favoriteToolItems, collapsedRecommendedItems]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={toolPanelRef}
|
||||
data-sidebar="tool-panel"
|
||||
data-tour={fullscreenExpanded ? undefined : "tool-panel"}
|
||||
className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : "overflow-hidden"} bg-[var(--bg-toolbar)] border-l border-[var(--border-subtle)] transition-all duration-300 ease-out ${
|
||||
isRainbowMode ? rainbowStyles.rainbowPaper : ""
|
||||
} ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`}
|
||||
style={{
|
||||
width: computedWidth(),
|
||||
padding: "0",
|
||||
}}
|
||||
>
|
||||
{!fullscreenExpanded && !isPanelVisible && !isMobile && (
|
||||
<div className="tool-panel__collapsed-strip">
|
||||
<div className="tool-panel__collapsed-top">
|
||||
<ActionIcon
|
||||
variant="outline"
|
||||
color="gray.4"
|
||||
radius="xl"
|
||||
size="md"
|
||||
className="tool-panel__expand-btn"
|
||||
onClick={handleExpand}
|
||||
aria-label={t("toolPanel.expand", "Expand panel")}
|
||||
>
|
||||
<ChevronLeftIcon sx={{ fontSize: "1.1rem" }} />
|
||||
</ActionIcon>
|
||||
<AgentsCollapsedButton onExpand={handleExpand} />
|
||||
</div>
|
||||
<div className="tool-panel__collapsed-divider" />
|
||||
<div className="tool-panel__collapsed-tools">
|
||||
{collapsedRailItems.map(({ id, tool }) => (
|
||||
<AppTooltip
|
||||
key={id}
|
||||
content={tool.name}
|
||||
position="left"
|
||||
arrow
|
||||
delay={300}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="tool-panel__collapsed-tool-btn"
|
||||
data-selected={selectedToolKey === id}
|
||||
onClick={() => {
|
||||
handleExpand();
|
||||
handleToolSelectWithTransition(id);
|
||||
}}
|
||||
aria-label={tool.name}
|
||||
>
|
||||
<ToolIcon icon={tool.icon} marginRight="0" />
|
||||
</button>
|
||||
</AppTooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!fullscreenExpanded && isPanelVisible && (
|
||||
<div
|
||||
/* Fixed width matches the expanded panel width so the inner content is
|
||||
laid out at its final size from the moment it mounts. The outer
|
||||
.tool-panel clips it (overflow-hidden) while it animates from the
|
||||
collapsed 3.5rem width — text/icons stay put and just come into view
|
||||
instead of jiggling as space becomes available. */
|
||||
style={{
|
||||
opacity: 1,
|
||||
transition: "opacity 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)",
|
||||
height: "100%",
|
||||
width: isMobile ? "100%" : "18.5rem",
|
||||
flexShrink: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div className="tool-panel__compact-header">
|
||||
{activeTool ? (
|
||||
<div
|
||||
className="tool-panel__active-tool-pill"
|
||||
aria-label={activeTool.name}
|
||||
>
|
||||
<span className="tool-panel__active-tool-pill-icon">
|
||||
<ToolIcon
|
||||
icon={activeTool.icon}
|
||||
marginRight="0"
|
||||
color="var(--mantine-color-blue-filled)"
|
||||
/>
|
||||
</span>
|
||||
<span className="tool-panel__active-tool-pill-label">
|
||||
{activeTool.name}
|
||||
</span>
|
||||
</div>
|
||||
) : showHeaderSearch ? (
|
||||
<div className="tool-panel__compact-header-search">
|
||||
<ToolSearch
|
||||
value={searchQuery}
|
||||
onChange={handleHeaderSearchChange}
|
||||
toolRegistry={toolRegistry}
|
||||
mode="filter"
|
||||
autoFocus={allToolsView && !inToolView}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
showAgents && (
|
||||
<span className="tool-panel__section-label">
|
||||
{t("agents.section_title", "Agents")}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
{showCloseButton ? (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
radius="xl"
|
||||
size="md"
|
||||
onClick={handleHeaderBack}
|
||||
aria-label={
|
||||
inToolView
|
||||
? t("toolPanel.backToAllTools", "Back to all tools")
|
||||
: t("toolPanel.goBack", "Go back")
|
||||
}
|
||||
className="tool-panel__expand-btn"
|
||||
>
|
||||
<CloseIcon sx={{ fontSize: "1.1rem" }} />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<ActionIcon
|
||||
variant="outline"
|
||||
radius="xl"
|
||||
size="md"
|
||||
onClick={handleCollapse}
|
||||
aria-label={t("toolPanel.collapse", "Collapse panel")}
|
||||
className="tool-panel__expand-btn"
|
||||
>
|
||||
<ChevronRightIcon sx={{ fontSize: "1.1rem" }} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showAgents && <AgentsSection />}
|
||||
|
||||
<ToolPanel
|
||||
allToolsView={allToolsView}
|
||||
onShowAllTools={handleShowAllTools}
|
||||
onToolSelect={handleToolSelectWithTransition}
|
||||
compact={agentsEnabled && !allToolsView}
|
||||
/>
|
||||
<AgentsChatOverlay />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FullscreenToolPanel geometry={fullscreenGeometry} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -129,17 +129,70 @@
|
||||
transition:
|
||||
width 0.3s ease,
|
||||
max-width 0.3s ease;
|
||||
view-transition-name: tool-rail;
|
||||
}
|
||||
|
||||
.tool-panel__collapsed-strip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
padding-top: 10px;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tool-panel__collapsed-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
|
||||
.tool-panel__collapsed-divider {
|
||||
height: 1px;
|
||||
background: var(--border-subtle);
|
||||
margin: 0 0.5rem 8px;
|
||||
}
|
||||
|
||||
.tool-panel__collapsed-tools {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0 0 12px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tool-panel__collapsed-tool-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.5rem;
|
||||
background: transparent;
|
||||
color: var(--tools-text-and-icon-color);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 120ms ease-out,
|
||||
border-color 120ms ease-out;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-panel__collapsed-tool-btn:hover {
|
||||
background: var(--mantine-color-default-hover);
|
||||
}
|
||||
|
||||
.tool-panel__collapsed-tool-btn[data-selected="true"] {
|
||||
background: var(--mantine-color-default-hover);
|
||||
border-color: var(--mantine-color-default-border);
|
||||
}
|
||||
|
||||
.tool-panel__expand-btn {
|
||||
@@ -174,6 +227,11 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-panel__back-bar {
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--border-subtle) !important;
|
||||
}
|
||||
|
||||
.tool-panel--fullscreen-active {
|
||||
overflow: visible !important;
|
||||
}
|
||||
@@ -191,6 +249,133 @@
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.tool-panel__compact-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 52px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tool-panel__compact-header .tool-panel__expand-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.tool-panel__compact-header-search {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-panel__compact-header-search .search-input-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-panel__active-tool-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
padding: 0.35rem 0.75rem 0.35rem 0.4rem;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 9999px;
|
||||
background: var(--mantine-color-body);
|
||||
}
|
||||
|
||||
.tool-panel__active-tool-pill-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 9999px;
|
||||
background: var(--mantine-color-blue-light);
|
||||
color: var(--mantine-color-blue-filled);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .tool-panel__active-tool-pill {
|
||||
background: transparent;
|
||||
border-color: var(--border-subtle);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .tool-panel__active-tool-pill-icon {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-blue-filled) 18%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mantine-color-blue-3, var(--mantine-color-blue-filled));
|
||||
}
|
||||
|
||||
/* The inner .tool-button-icon wrapper carries its own transform/margin; reset
|
||||
them so the icon glyph sits dead-centre in the circular background. */
|
||||
.tool-panel__active-tool-pill-icon .tool-button-icon {
|
||||
margin: 0 !important;
|
||||
transform: none !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tool-panel__active-tool-pill-icon svg {
|
||||
font-size: 1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.tool-panel__active-tool-pill-label {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
::view-transition-old(tool-rail) {
|
||||
animation: vt-rail-fade-out 180ms ease-out forwards;
|
||||
}
|
||||
|
||||
::view-transition-new(tool-rail) {
|
||||
animation: vt-rail-fade-in 220ms ease-in forwards;
|
||||
}
|
||||
|
||||
@keyframes vt-rail-fade-out {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vt-rail-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
::view-transition-old(tool-rail),
|
||||
::view-transition-new(tool-rail) {
|
||||
animation-duration: 1ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tool-panel__section-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tool-panel__mode-toggle {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
@@ -652,6 +837,33 @@
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Agents card lives in its own column above the tool grid. It uses the same
|
||||
.tool-panel__fullscreen-group base styles but with a colourful gradient
|
||||
border so it stands out as a distinct surface (and doesn't blend into the
|
||||
neighbouring category cards). */
|
||||
.tool-panel__fullscreen-agents {
|
||||
padding: 1.5rem 1.75rem 0;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-group--agents {
|
||||
position: relative;
|
||||
background:
|
||||
linear-gradient(var(--fullscreen-bg-group), var(--fullscreen-bg-group))
|
||||
padding-box,
|
||||
linear-gradient(
|
||||
135deg,
|
||||
var(--mantine-color-blue-6) 0%,
|
||||
var(--mantine-color-violet-6) 45%,
|
||||
var(--mantine-color-pink-6) 100%
|
||||
)
|
||||
border-box;
|
||||
border: 1.5px solid transparent;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-section-icon--agents {
|
||||
color: var(--mantine-color-blue-6);
|
||||
}
|
||||
|
||||
@keyframes tool-panel-fullscreen-slide-in {
|
||||
from {
|
||||
transform: translateX(6%) scaleX(0.85);
|
||||
|
||||
@@ -1,303 +1,85 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider";
|
||||
import {
|
||||
useToolWorkflow,
|
||||
useToolWorkflowActions,
|
||||
} from "@app/contexts/ToolWorkflowContext";
|
||||
import { usePreferences } from "@app/contexts/PreferencesContext";
|
||||
import { ScrollArea } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
|
||||
import ToolPicker from "@app/components/tools/ToolPicker";
|
||||
import SearchResults from "@app/components/tools/SearchResults";
|
||||
import ToolRenderer from "@app/components/tools/ToolRenderer";
|
||||
import ToolSearch from "@app/components/tools/toolPicker/ToolSearch";
|
||||
import { useSidebarContext } from "@app/contexts/SidebarContext";
|
||||
import rainbowStyles from "@app/styles/rainbow.module.css";
|
||||
import { ActionIcon, Button, ScrollArea } from "@mantine/core";
|
||||
import { ToolId } from "@app/types/toolId";
|
||||
import { useIsMobile } from "@app/hooks/useIsMobile";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FullscreenToolSurface from "@app/components/tools/FullscreenToolSurface";
|
||||
import { ToolPanelViewerBar } from "@app/components/tools/ToolPanelViewerBar";
|
||||
import { useToolPanelGeometry } from "@app/hooks/tools/useToolPanelGeometry";
|
||||
import { useWorkbenchBar } from "@app/contexts/WorkbenchBarContext";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import "@app/components/tools/ToolPanel.css";
|
||||
import { ToolId } from "@app/types/toolId";
|
||||
|
||||
// No props needed - component uses context
|
||||
interface ToolPanelProps {
|
||||
/** Whether to expand into the full categorised tools view (with search). */
|
||||
allToolsView: boolean;
|
||||
/** Trigger to enter the all-tools view (from the "View all tools" button). */
|
||||
onShowAllTools: () => void;
|
||||
/**
|
||||
* Tool-selection handler injected by {@code RightSidebar} so the click can
|
||||
* be wrapped in a View Transition (the tool card morphs into the header
|
||||
* pill). Falls back to the workflow context's handler when not provided.
|
||||
*/
|
||||
onToolSelect?: (id: ToolId) => void;
|
||||
/** Whether to render the compact (favourites + recommended only) view. */
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export default function ToolPanel() {
|
||||
/** Tool list and renderer for the right rail; rail chrome lives in RightSidebar. */
|
||||
export default function ToolPanel({
|
||||
allToolsView,
|
||||
onShowAllTools,
|
||||
onToolSelect,
|
||||
compact: compactProp,
|
||||
}: ToolPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const { isRainbowMode } = useRainbowThemeContext();
|
||||
const { sidebarRefs } = useSidebarContext();
|
||||
const { toolPanelRef, quickAccessRef } = sidebarRefs;
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const {
|
||||
leftPanelView,
|
||||
isPanelVisible,
|
||||
searchQuery,
|
||||
filteredTools,
|
||||
toolRegistry,
|
||||
setSearchQuery,
|
||||
selectedToolKey,
|
||||
toolPanelMode,
|
||||
sidebarsVisible,
|
||||
readerMode,
|
||||
} = useToolWorkflow();
|
||||
const {
|
||||
handleToolSelect,
|
||||
handleBackToTools,
|
||||
setPreviewFile,
|
||||
setToolPanelMode,
|
||||
setLeftPanelView,
|
||||
setReaderMode,
|
||||
setSidebarsVisible,
|
||||
} = useToolWorkflowActions();
|
||||
|
||||
const { setAllButtonsDisabled } = useWorkbenchBar();
|
||||
const { preferences, updatePreference } = usePreferences();
|
||||
|
||||
const isFullscreenMode = toolPanelMode === "fullscreen";
|
||||
const toolPickerVisible = !readerMode;
|
||||
const fullscreenExpanded =
|
||||
isFullscreenMode &&
|
||||
leftPanelView === "toolPicker" &&
|
||||
!isMobile &&
|
||||
toolPickerVisible;
|
||||
|
||||
// Disable workbench bar buttons when fullscreen mode is active
|
||||
useEffect(() => {
|
||||
setAllButtonsDisabled(fullscreenExpanded);
|
||||
}, [fullscreenExpanded, setAllButtonsDisabled]);
|
||||
|
||||
const fullscreenGeometry = useToolPanelGeometry({
|
||||
enabled: fullscreenExpanded,
|
||||
toolPanelRef,
|
||||
quickAccessRef,
|
||||
});
|
||||
|
||||
const handleExpand = () => {
|
||||
if (readerMode) setReaderMode(false);
|
||||
if (leftPanelView === "hidden") setLeftPanelView("toolPicker");
|
||||
if (!sidebarsVisible) setSidebarsVisible(true);
|
||||
};
|
||||
|
||||
const handleCollapse = () => {
|
||||
setLeftPanelView("hidden");
|
||||
};
|
||||
|
||||
const [focusSearch, setFocusSearch] = useState(false);
|
||||
const focusSearchOnNextOpen = useRef(false);
|
||||
|
||||
const handleExpandAndSearch = () => {
|
||||
focusSearchOnNextOpen.current = true;
|
||||
handleExpand();
|
||||
};
|
||||
|
||||
// Once the panel becomes visible, consume the focus-search request
|
||||
useEffect(() => {
|
||||
if (isPanelVisible && focusSearchOnNextOpen.current) {
|
||||
focusSearchOnNextOpen.current = false;
|
||||
setFocusSearch(true);
|
||||
// Reset after one render so autoFocus doesn't re-fire on subsequent renders
|
||||
const id = setTimeout(() => setFocusSearch(false), 100);
|
||||
return () => clearTimeout(id);
|
||||
}
|
||||
}, [isPanelVisible]);
|
||||
|
||||
const computedWidth = () => {
|
||||
if (isMobile) {
|
||||
return "100%";
|
||||
}
|
||||
|
||||
if (!isPanelVisible) {
|
||||
return "3.5rem";
|
||||
}
|
||||
|
||||
return "18.5rem";
|
||||
};
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(id: string) => handleToolSelect(id as ToolId),
|
||||
[handleToolSelect],
|
||||
);
|
||||
|
||||
const matchedTextMap = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
filteredTools.forEach(({ item: [id], matchedText }) => {
|
||||
if (matchedText) {
|
||||
map.set(id, matchedText);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}, [filteredTools]);
|
||||
} = useToolWorkflow();
|
||||
const selectTool = onToolSelect ?? handleToolSelect;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={toolPanelRef}
|
||||
data-sidebar="tool-panel"
|
||||
data-tour={fullscreenExpanded ? undefined : "tool-panel"}
|
||||
className={`tool-panel flex flex-col ${fullscreenExpanded ? "tool-panel--fullscreen-active" : "overflow-hidden"} bg-[var(--bg-toolbar)] border-l border-[var(--border-subtle)] transition-all duration-300 ease-out ${
|
||||
isRainbowMode ? rainbowStyles.rainbowPaper : ""
|
||||
} ${isMobile ? "h-full border-r-0" : "h-screen"} ${fullscreenExpanded ? "tool-panel--fullscreen" : ""}`}
|
||||
style={{
|
||||
width: computedWidth(),
|
||||
padding: "0",
|
||||
}}
|
||||
>
|
||||
{!fullscreenExpanded && !isPanelVisible && !isMobile && (
|
||||
<div className="tool-panel__collapsed-strip">
|
||||
<ActionIcon
|
||||
variant="outline"
|
||||
color="gray.4"
|
||||
radius="xl"
|
||||
size="md"
|
||||
className="tool-panel__expand-btn"
|
||||
onClick={handleExpand}
|
||||
aria-label={t("toolPanel.expand", "Expand panel")}
|
||||
>
|
||||
<ChevronLeftIcon sx={{ fontSize: "1.1rem" }} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
radius="md"
|
||||
size="md"
|
||||
className="tool-panel__collapsed-search-btn"
|
||||
onClick={handleExpandAndSearch}
|
||||
aria-label={t("toolPanel.search", "Search tools")}
|
||||
style={{ marginTop: "8px" }}
|
||||
>
|
||||
<SearchIcon sx={{ fontSize: "1.25rem" }} />
|
||||
</ActionIcon>
|
||||
<>
|
||||
{/* Viewer mode tools — annotate, redact, form fill */}
|
||||
<ToolPanelViewerBar />
|
||||
|
||||
{allToolsView && searchQuery.trim().length > 0 ? (
|
||||
<div className="flex-1 flex flex-col overflow-y-auto">
|
||||
<SearchResults
|
||||
filteredTools={filteredTools}
|
||||
onSelect={(id) => selectTool(id as ToolId)}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!fullscreenExpanded && isPanelVisible && (
|
||||
<div
|
||||
style={{
|
||||
opacity: 1,
|
||||
transition: "opacity 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{/* Viewer mode tools — annotate, redact, form fill */}
|
||||
<ToolPanelViewerBar />
|
||||
|
||||
<div
|
||||
className="tool-panel__search-row"
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
borderBottom: "1px solid var(--border-subtle)",
|
||||
}}
|
||||
>
|
||||
<ToolSearch
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
toolRegistry={toolRegistry}
|
||||
mode="filter"
|
||||
autoFocus={focusSearch}
|
||||
/>
|
||||
<ActionIcon
|
||||
variant="outline"
|
||||
radius="xl"
|
||||
size="md"
|
||||
onClick={handleCollapse}
|
||||
aria-label={t("toolPanel.collapse", "Collapse panel")}
|
||||
className="tool-panel__expand-btn"
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
<ChevronRightIcon sx={{ fontSize: "1.1rem" }} />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
|
||||
{searchQuery.trim().length > 0 ? (
|
||||
<div className="flex-1 flex flex-col overflow-y-auto">
|
||||
<SearchResults
|
||||
filteredTools={filteredTools}
|
||||
onSelect={handleSelect}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
</div>
|
||||
) : leftPanelView === "toolPicker" ? (
|
||||
<div className="flex-1 flex flex-col overflow-auto">
|
||||
<ToolPicker
|
||||
) : leftPanelView === "toolPicker" ? (
|
||||
<div className="flex-1 flex flex-col overflow-auto">
|
||||
<ToolPicker
|
||||
selectedToolKey={selectedToolKey}
|
||||
onSelect={(id) => selectTool(id as ToolId)}
|
||||
filteredTools={filteredTools}
|
||||
isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)}
|
||||
compact={compactProp ?? !allToolsView}
|
||||
onShowAllTools={onShowAllTools}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<ScrollArea h="100%">
|
||||
{selectedToolKey ? (
|
||||
<ToolRenderer
|
||||
selectedToolKey={selectedToolKey}
|
||||
onSelect={handleSelect}
|
||||
filteredTools={filteredTools}
|
||||
isSearching={Boolean(
|
||||
searchQuery && searchQuery.trim().length > 0,
|
||||
)}
|
||||
onPreviewFile={setPreviewFile}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<div
|
||||
style={{
|
||||
borderBottom: "1px solid var(--border-subtle)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
size="sm"
|
||||
fullWidth
|
||||
radius={0}
|
||||
leftSection={<ArrowBackIcon sx={{ fontSize: "0.9rem" }} />}
|
||||
onClick={handleBackToTools}
|
||||
aria-label={t("toolPanel.backToTools", "Back to tools")}
|
||||
styles={{ root: { justifyContent: "flex-start" } }}
|
||||
>
|
||||
{t("toolPanel.backToTools", "Back to tools")}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="tool-panel__placeholder">
|
||||
{t("toolPanel.placeholder", "Choose a tool to get started")}
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<ScrollArea h="100%">
|
||||
{selectedToolKey ? (
|
||||
<ToolRenderer
|
||||
selectedToolKey={selectedToolKey}
|
||||
onPreviewFile={setPreviewFile}
|
||||
/>
|
||||
) : (
|
||||
<div className="tool-panel__placeholder">
|
||||
{t(
|
||||
"toolPanel.placeholder",
|
||||
"Choose a tool to get started",
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{fullscreenExpanded && (
|
||||
<FullscreenToolSurface
|
||||
searchQuery={searchQuery}
|
||||
toolRegistry={toolRegistry}
|
||||
filteredTools={filteredTools}
|
||||
selectedToolKey={selectedToolKey}
|
||||
showDescriptions={preferences.showLegacyToolDescriptions}
|
||||
matchedTextMap={matchedTextMap}
|
||||
onSearchChange={setSearchQuery}
|
||||
onSelect={(id: ToolId) => handleToolSelect(id)}
|
||||
onToggleDescriptions={() =>
|
||||
updatePreference(
|
||||
"showLegacyToolDescriptions",
|
||||
!preferences.showLegacyToolDescriptions,
|
||||
)
|
||||
}
|
||||
onExitFullscreenMode={() => setToolPanelMode("sidebar")}
|
||||
geometry={fullscreenGeometry}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { memo, useMemo, useRef } from "react";
|
||||
import { Box, Stack } from "@mantine/core";
|
||||
import React, { useMemo, useRef } from "react";
|
||||
import { Box, Button, Stack } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ToolRegistryEntry } from "@app/data/toolsTaxonomy";
|
||||
import "@app/components/tools/toolPicker/ToolPicker.css";
|
||||
@@ -22,6 +22,10 @@ interface ToolPickerProps {
|
||||
matchedText?: string;
|
||||
}>;
|
||||
isSearching?: boolean;
|
||||
/** Compact "resting" view: favourites + recommended only, with a button to expand. */
|
||||
compact?: boolean;
|
||||
/** Called when the user clicks "View all tools" in compact mode. */
|
||||
onShowAllTools?: () => void;
|
||||
}
|
||||
|
||||
const EMPTY_FILTERED_TOOLS: ToolPickerProps["filteredTools"] = [];
|
||||
@@ -57,6 +61,8 @@ const ToolPicker = ({
|
||||
onSelect,
|
||||
filteredTools,
|
||||
isSearching = false,
|
||||
compact = false,
|
||||
onShowAllTools,
|
||||
}: ToolPickerProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -118,9 +124,60 @@ const ToolPicker = ({
|
||||
)
|
||||
)}
|
||||
</Stack>
|
||||
) : compact ? (
|
||||
/* Resting state: flat list of pinned + recommended only. */
|
||||
<Box className="tool-picker__compact">
|
||||
<div style={HEADER_TEXT_STYLE}>
|
||||
{t("toolPanel.toolsHeader", "Tools")}
|
||||
</div>
|
||||
{favoriteToolItems.length === 0 && recommendedItems.length === 0 ? (
|
||||
<NoToolsFound />
|
||||
) : (
|
||||
<div className="tool-picker__compact-list">
|
||||
{favoriteToolItems.map(({ id, tool }) => (
|
||||
<ToolButton
|
||||
key={`fav-${id}`}
|
||||
id={id}
|
||||
tool={tool}
|
||||
isSelected={selectedToolKey === id}
|
||||
onSelect={onSelect}
|
||||
hasStars
|
||||
showDescription
|
||||
/>
|
||||
))}
|
||||
{recommendedItems
|
||||
.filter(
|
||||
({ id }) => !favoriteToolItems.some((fav) => fav.id === id),
|
||||
)
|
||||
.map(({ id, tool }) => (
|
||||
<ToolButton
|
||||
key={`rec-${id}`}
|
||||
id={id as ToolId}
|
||||
tool={tool}
|
||||
isSelected={selectedToolKey === id}
|
||||
onSelect={onSelect}
|
||||
hasStars
|
||||
showDescription
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{onShowAllTools && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
fullWidth
|
||||
onClick={onShowAllTools}
|
||||
className="tool-picker__view-all"
|
||||
aria-label={t("toolPanel.viewAllTools", "View all tools")}
|
||||
>
|
||||
{t("toolPanel.viewAllTools", "View all tools")}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
{/* Flat list: favorites and recommended first, then all subcategories */}
|
||||
{/* All-tools view: favourites + recommended + all subcategories. */}
|
||||
<Stack p="sm" gap="xs">
|
||||
{favoriteToolItems.length > 0 && (
|
||||
<Box w="100%">
|
||||
@@ -182,7 +239,6 @@ const ToolPicker = ({
|
||||
|
||||
{!quickSection && !allSection && <NoToolsFound />}
|
||||
|
||||
{/* bottom spacer to allow scrolling past the last row */}
|
||||
<div aria-hidden style={{ height: 200 }} />
|
||||
</>
|
||||
)}
|
||||
@@ -192,4 +248,4 @@ const ToolPicker = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ToolPicker);
|
||||
export default ToolPicker;
|
||||
|
||||
@@ -32,6 +32,7 @@ interface ToolButtonProps {
|
||||
disableNavigation?: boolean;
|
||||
matchedSynonym?: string;
|
||||
hasStars?: boolean;
|
||||
showDescription?: boolean;
|
||||
/** Called when an unavailable tool is clicked; if provided, overrides the default no-op */
|
||||
onUnavailableClick?: () => void;
|
||||
}
|
||||
@@ -44,6 +45,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({
|
||||
disableNavigation = false,
|
||||
matchedSynonym,
|
||||
hasStars = false,
|
||||
showDescription = false,
|
||||
onUnavailableClick,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -183,6 +185,14 @@ const ToolButton: React.FC<ToolButtonProps> = ({
|
||||
)}
|
||||
{usesCloud && !visuallyUnavailable && <CloudBadge />}
|
||||
</div>
|
||||
{showDescription && tool.description && (
|
||||
<span
|
||||
className="tool-button__description"
|
||||
style={{ opacity: visuallyUnavailable ? 0.25 : 1 }}
|
||||
>
|
||||
{tool.description}
|
||||
</span>
|
||||
)}
|
||||
{matchedSynonym && (
|
||||
<span
|
||||
style={{
|
||||
@@ -205,8 +215,8 @@ const ToolButton: React.FC<ToolButtonProps> = ({
|
||||
handleUnlessSpecialClick(e, () => handleClick(id));
|
||||
};
|
||||
|
||||
const selectedStyles = isSelected
|
||||
? { backgroundColor: "#EAEAEA", color: "var(--tools-text-and-icon-color)" }
|
||||
const selectedBg = isSelected
|
||||
? { backgroundColor: "var(--tool-button-selected-bg)" }
|
||||
: {};
|
||||
|
||||
const buttonElement = navProps ? (
|
||||
@@ -227,7 +237,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({
|
||||
borderRadius: 0,
|
||||
color: "var(--tools-text-and-icon-color)",
|
||||
overflow: "visible",
|
||||
...selectedStyles,
|
||||
...selectedBg,
|
||||
},
|
||||
label: { overflow: "visible" },
|
||||
}}
|
||||
@@ -254,7 +264,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({
|
||||
borderRadius: 0,
|
||||
color: "var(--tools-text-and-icon-color)",
|
||||
overflow: "visible",
|
||||
...selectedStyles,
|
||||
...selectedBg,
|
||||
},
|
||||
label: { overflow: "visible" },
|
||||
}}
|
||||
@@ -279,7 +289,6 @@ const ToolButton: React.FC<ToolButtonProps> = ({
|
||||
color: "var(--tools-text-and-icon-color)",
|
||||
cursor: visuallyUnavailable ? "not-allowed" : undefined,
|
||||
overflow: "visible",
|
||||
...selectedStyles,
|
||||
},
|
||||
label: { overflow: "visible" },
|
||||
}}
|
||||
|
||||
@@ -55,6 +55,15 @@
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* Selected tool highlight — theme-aware via CSS variable */
|
||||
:root {
|
||||
--tool-button-selected-bg: var(--mantine-color-gray-2);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] {
|
||||
--tool-button-selected-bg: var(--mantine-color-dark-4);
|
||||
}
|
||||
|
||||
/* Compact tool buttons */
|
||||
.tool-button {
|
||||
font-size: 0.875rem;
|
||||
@@ -96,3 +105,37 @@
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tool-button__description {
|
||||
font-size: 0.75rem;
|
||||
color: var(--mantine-color-dimmed);
|
||||
line-height: 1.35;
|
||||
margin-top: 0.15rem;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Resting / compact tool list — taller rows, room for description. */
|
||||
.tool-picker__compact {
|
||||
padding: 0.5rem var(--mantine-spacing-sm) var(--mantine-spacing-sm);
|
||||
}
|
||||
|
||||
.tool-picker__compact .tool-button {
|
||||
padding-top: 0.65rem;
|
||||
padding-bottom: 0.65rem;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.tool-picker__compact .tool-button .mantine-Button-label {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tool-picker__compact-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.tool-picker__view-all {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
@@ -17,17 +17,13 @@ function CopyButton({ text }: { text: string }) {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
background: copied
|
||||
? "var(--mantine-color-green-0)"
|
||||
: "var(--mantine-color-gray-0)",
|
||||
border: "1px solid var(--mantine-color-gray-3)",
|
||||
background: copied ? "var(--color-green-100)" : "var(--bg-muted)",
|
||||
border: "1px solid var(--border-default)",
|
||||
borderRadius: 3,
|
||||
cursor: "pointer",
|
||||
fontSize: "0.7em",
|
||||
padding: "2px 8px",
|
||||
color: copied
|
||||
? "var(--mantine-color-green-7)"
|
||||
: "var(--mantine-color-gray-7)",
|
||||
color: copied ? "var(--color-green-700)" : "var(--text-secondary)",
|
||||
}}
|
||||
>
|
||||
{copied ? "✓ Copied" : "Copy"}
|
||||
@@ -36,6 +32,183 @@ function CopyButton({ text }: { text: string }) {
|
||||
}
|
||||
|
||||
const components: Components = {
|
||||
h1: ({ children }) => (
|
||||
<h1
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
borderBottom: "1px solid var(--border-default)",
|
||||
paddingBottom: "0.3em",
|
||||
marginTop: "1.2em",
|
||||
marginBottom: "0.6em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
borderBottom: "1px solid var(--border-subtle)",
|
||||
paddingBottom: "0.2em",
|
||||
marginTop: "1.1em",
|
||||
marginBottom: "0.5em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
marginTop: "1em",
|
||||
marginBottom: "0.4em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
h4: ({ children }) => (
|
||||
<h4
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
marginTop: "0.8em",
|
||||
marginBottom: "0.3em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
h5: ({ children }) => (
|
||||
<h5
|
||||
style={{
|
||||
color: "var(--text-secondary)",
|
||||
marginTop: "0.8em",
|
||||
marginBottom: "0.3em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h5>
|
||||
),
|
||||
h6: ({ children }) => (
|
||||
<h6
|
||||
style={{
|
||||
color: "var(--text-muted)",
|
||||
marginTop: "0.8em",
|
||||
marginBottom: "0.3em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h6>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
lineHeight: 1.7,
|
||||
marginBottom: "0.75em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
a: ({ children, href }) => (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
color: "var(--accent-interactive)",
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong style={{ color: "var(--text-primary)", fontWeight: 600 }}>
|
||||
{children}
|
||||
</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em style={{ color: "var(--text-secondary)" }}>{children}</em>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote
|
||||
style={{
|
||||
borderLeft: "3px solid var(--border-strong)",
|
||||
margin: "0.75em 0",
|
||||
padding: "4px 16px",
|
||||
color: "var(--text-secondary)",
|
||||
background: "var(--bg-muted)",
|
||||
borderRadius: "0 4px 4px 0",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
hr: () => (
|
||||
<hr
|
||||
style={{
|
||||
border: "none",
|
||||
borderTop: "1px solid var(--border-default)",
|
||||
margin: "1.2em 0",
|
||||
}}
|
||||
/>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
paddingLeft: "1.5em",
|
||||
marginBottom: "0.75em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
paddingLeft: "1.5em",
|
||||
marginBottom: "0.75em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
lineHeight: 1.6,
|
||||
marginBottom: "0.2em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</li>
|
||||
),
|
||||
code: ({ children, className }) => {
|
||||
const isBlock = className?.startsWith("language-");
|
||||
if (isBlock) return <code className={className}>{children}</code>;
|
||||
return (
|
||||
<code
|
||||
style={{
|
||||
background: "var(--bg-muted)",
|
||||
color: "var(--code-kw-color)",
|
||||
border: "1px solid var(--border-subtle)",
|
||||
borderRadius: 3,
|
||||
padding: "1px 5px",
|
||||
fontSize: "0.85em",
|
||||
fontFamily: "monospace",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
pre: ({ children }) => {
|
||||
const codeText = React.isValidElement(children)
|
||||
? String((children.props as { children?: unknown }).children ?? "")
|
||||
@@ -44,12 +217,15 @@ const components: Components = {
|
||||
<div style={{ position: "relative", margin: "8px 0" }}>
|
||||
<pre
|
||||
style={{
|
||||
background: "var(--mantine-color-gray-1)",
|
||||
background: "var(--bg-muted)",
|
||||
color: "var(--text-primary)",
|
||||
border: "1px solid var(--border-default)",
|
||||
padding: "10px 52px 10px 14px",
|
||||
borderRadius: 4,
|
||||
overflowX: "auto",
|
||||
fontSize: "0.85em",
|
||||
margin: 0,
|
||||
fontFamily: "monospace",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -65,18 +241,33 @@ const components: Components = {
|
||||
borderCollapse: "collapse",
|
||||
width: "100%",
|
||||
fontSize: "0.85em",
|
||||
color: "var(--text-primary)",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
thead: ({ children }) => (
|
||||
<thead style={{ background: "var(--bg-muted)" }}>{children}</thead>
|
||||
),
|
||||
tbody: ({ children }) => <tbody>{children}</tbody>,
|
||||
tr: ({ children }) => (
|
||||
<tr
|
||||
style={{
|
||||
borderBottom: "1px solid var(--border-default)",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</tr>
|
||||
),
|
||||
th: ({ children, style }) => (
|
||||
<th
|
||||
style={{
|
||||
border: "1px solid var(--mantine-color-gray-3)",
|
||||
border: "1px solid var(--border-default)",
|
||||
padding: "6px 10px",
|
||||
background: "var(--mantine-color-gray-1)",
|
||||
background: "var(--bg-raised)",
|
||||
color: "var(--text-primary)",
|
||||
textAlign: "left",
|
||||
fontWeight: 600,
|
||||
whiteSpace: "nowrap",
|
||||
@@ -89,8 +280,9 @@ const components: Components = {
|
||||
td: ({ children, style }) => (
|
||||
<td
|
||||
style={{
|
||||
border: "1px solid var(--mantine-color-gray-3)",
|
||||
border: "1px solid var(--border-default)",
|
||||
padding: "5px 10px",
|
||||
color: "var(--text-primary)",
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -92,8 +92,10 @@ export function TextViewer({ file, isMarkdown }: TextViewerProps) {
|
||||
maxWidth: 800,
|
||||
margin: "0 auto",
|
||||
padding: "20px 28px",
|
||||
background: "#ffffff",
|
||||
background: "var(--bg-surface)",
|
||||
color: "var(--text-primary)",
|
||||
borderRadius: 6,
|
||||
border: "1px solid var(--border-subtle)",
|
||||
}}
|
||||
>
|
||||
{renderedMarkdown}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Theme constants and utilities
|
||||
|
||||
export type ThemeMode = "light" | "dark" | "rainbow";
|
||||
export type ThemeMode = "light" | "dark" | "midnight" | "rainbow";
|
||||
|
||||
// Detect OS theme preference
|
||||
export function getSystemTheme(): "light" | "dark" {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { PageEditorFunctions } from "@app/types/pageEditor";
|
||||
import {
|
||||
type ToolPanelMode,
|
||||
DEFAULT_TOOL_PANEL_MODE,
|
||||
} from "@app/constants/toolPanel";
|
||||
import { type ToolPanelMode } from "@app/constants/toolPanel";
|
||||
import { preferencesService } from "@app/services/preferencesService";
|
||||
|
||||
export interface ToolWorkflowState {
|
||||
// UI State
|
||||
@@ -43,7 +41,7 @@ export const baseState: Omit<ToolWorkflowState, "toolPanelMode"> = {
|
||||
|
||||
export const createInitialState = (): ToolWorkflowState => ({
|
||||
...baseState,
|
||||
toolPanelMode: DEFAULT_TOOL_PANEL_MODE,
|
||||
toolPanelMode: preferencesService.getPreference("defaultToolPanelMode"),
|
||||
});
|
||||
|
||||
export function toolWorkflowReducer(
|
||||
|
||||
@@ -13,6 +13,31 @@ interface UseToolPanelGeometryOptions {
|
||||
quickAccessRef: RefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
function computeGeometry(
|
||||
panelEl: HTMLDivElement,
|
||||
quickAccessRef: RefObject<HTMLDivElement | null>,
|
||||
): ToolPanelGeometry {
|
||||
const rect = panelEl.getBoundingClientRect();
|
||||
const isRTL =
|
||||
typeof document !== "undefined" && document.documentElement.dir === "rtl";
|
||||
let width: number;
|
||||
let left: number;
|
||||
|
||||
if (isRTL) {
|
||||
// RTL: panel is on the left, expands rightward
|
||||
width = Math.max(360, window.innerWidth - rect.right);
|
||||
left = rect.right;
|
||||
} else {
|
||||
// LTR: panel is on the right, expands leftward to the file sidebar
|
||||
const quickAccessRect = quickAccessRef.current?.getBoundingClientRect();
|
||||
const leftOffset = quickAccessRect ? quickAccessRect.right : 0;
|
||||
width = Math.max(360, rect.right - leftOffset);
|
||||
left = leftOffset;
|
||||
}
|
||||
const height = Math.max(rect.height, window.innerHeight - rect.top);
|
||||
return { left, top: rect.top, width, height };
|
||||
}
|
||||
|
||||
export function useToolPanelGeometry({
|
||||
enabled,
|
||||
toolPanelRef,
|
||||
@@ -36,31 +61,7 @@ export function useToolPanelGeometry({
|
||||
let rafId: number | null = null;
|
||||
|
||||
const computeAndSetGeometry = () => {
|
||||
const rect = panelEl.getBoundingClientRect();
|
||||
const isRTL =
|
||||
typeof document !== "undefined" &&
|
||||
document.documentElement.dir === "rtl";
|
||||
let width: number;
|
||||
let left: number;
|
||||
|
||||
if (isRTL) {
|
||||
// RTL: panel is on the left, expands rightward
|
||||
width = Math.max(360, window.innerWidth - rect.right);
|
||||
left = rect.right;
|
||||
} else {
|
||||
// LTR: panel is on the right, expands leftward to the file sidebar
|
||||
const quickAccessRect = quickAccessRef.current?.getBoundingClientRect();
|
||||
const leftOffset = quickAccessRect ? quickAccessRect.right : 0;
|
||||
width = Math.max(360, rect.right - leftOffset);
|
||||
left = leftOffset;
|
||||
}
|
||||
const height = Math.max(rect.height, window.innerHeight - rect.top);
|
||||
setGeometry({
|
||||
left,
|
||||
top: rect.top,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
setGeometry(computeGeometry(panelEl, quickAccessRef));
|
||||
};
|
||||
|
||||
const scheduleUpdate = () => {
|
||||
|
||||
@@ -175,9 +175,13 @@ export function useRainbowTheme(): RainbowThemeHook {
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal theme switching
|
||||
const nextTheme = themeMode === "light" ? "dark" : "light";
|
||||
updatePreference("theme", nextTheme);
|
||||
// Normal theme switching: light → dark → midnight → light
|
||||
const cycle: Record<string, ThemeMode> = {
|
||||
light: "dark",
|
||||
dark: "midnight",
|
||||
midnight: "light",
|
||||
};
|
||||
updatePreference("theme", cycle[themeMode] ?? "light");
|
||||
}, [themeMode, updatePreference]);
|
||||
|
||||
const activateRainbow = useCallback(() => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useViewer } from "@app/contexts/ViewerContext";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import AppsIcon from "@mui/icons-material/AppsRounded";
|
||||
|
||||
import ToolPanel from "@app/components/tools/ToolPanel";
|
||||
import RightSidebar from "@app/components/tools/RightSidebar";
|
||||
import Workbench from "@app/components/layout/Workbench";
|
||||
import FileSidebar from "@app/components/shared/FileSidebar";
|
||||
import FileManager from "@app/components/FileManager";
|
||||
@@ -290,7 +290,7 @@ export default function HomePage() {
|
||||
aria-label={t("home.mobile.toolsSlide", "Tool selection panel")}
|
||||
>
|
||||
<div className="mobile-slide-content">
|
||||
<ToolPanel />
|
||||
<RightSidebar />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -381,7 +381,7 @@ export default function HomePage() {
|
||||
onOpenSettings={() => setConfigModalOpen(true)}
|
||||
/>
|
||||
<Workbench />
|
||||
{!hideToolPanel && <ToolPanel />}
|
||||
{!hideToolPanel && <RightSidebar />}
|
||||
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
|
||||
<AppConfigModal
|
||||
opened={configModalOpen}
|
||||
|
||||
@@ -14,12 +14,6 @@
|
||||
--spdf-compare-inline-added-bg: rgba(52, 199, 89, 0.25);
|
||||
|
||||
/* File Manager active file colors */
|
||||
--file-active-bg: rgba(
|
||||
59,
|
||||
130,
|
||||
246,
|
||||
0.1
|
||||
); /* Transparent blue for active file background */
|
||||
--file-active-badge-bg: rgba(
|
||||
34,
|
||||
197,
|
||||
@@ -43,9 +37,7 @@
|
||||
--fullscreen-anim-duration-out: 0.22s;
|
||||
|
||||
/* Z-index constants (added in this PR) */
|
||||
--z-over-fullscreen-surface: 1400;
|
||||
--z-fullscreen-surface: 1200;
|
||||
--z-fullscreen-favorite-star: 2;
|
||||
--z-fullscreen-icon-svg: 1;
|
||||
--z-toolpicker-star: 1;
|
||||
|
||||
@@ -162,10 +154,8 @@
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* Font weights */
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* Light theme semantic colors */
|
||||
--bg-surface: #ffffff;
|
||||
@@ -192,22 +182,9 @@
|
||||
/* Icon colors for light mode */
|
||||
--icon-user-bg: #9ca3af;
|
||||
--icon-user-color: #ffffff;
|
||||
--icon-notifications-bg: #9ca3af;
|
||||
--icon-notifications-color: #ffffff;
|
||||
--icon-tools-bg: #1e88e5;
|
||||
--icon-tools-color: #ffffff;
|
||||
--icon-read-bg: #4caf50;
|
||||
--icon-read-color: #ffffff;
|
||||
--icon-sign-bg: #3ba99c;
|
||||
--icon-sign-color: #ffffff;
|
||||
--icon-automate-bg: #a576e3;
|
||||
--icon-automate-color: #ffffff;
|
||||
--icon-files-bg: #d3e7f7;
|
||||
--icon-files-color: #0a8bff;
|
||||
--icon-activity-bg: #d3e7f7;
|
||||
--icon-activity-color: #0a8bff;
|
||||
--icon-config-bg: #9ca3af;
|
||||
--icon-config-color: #ffffff;
|
||||
|
||||
/* Colors for tooltips */
|
||||
--tooltip-title-bg: #dbefff;
|
||||
@@ -224,7 +201,6 @@
|
||||
--tools-text-and-icon-color: var(--text-primary);
|
||||
|
||||
/* Tool picker sticky header variables (light mode) */
|
||||
--tool-header-bg: #dbefff;
|
||||
--tool-header-border: #bee2ff;
|
||||
--tool-header-text: #1e88e5;
|
||||
--tool-header-badge-bg: #c0ddff;
|
||||
@@ -235,7 +211,6 @@
|
||||
--tool-subcategory-rule-color: #e5e7eb; /* doubly lighter rule line */
|
||||
--accent-interactive: #4a90e2;
|
||||
--text-instruction: #4a90e2;
|
||||
--text-brand: var(--color-gray-700);
|
||||
--text-brand-accent: #dc2626;
|
||||
|
||||
/* PDF text selection colors */
|
||||
@@ -251,16 +226,13 @@
|
||||
--tool-panel-search-border-bottom: #eff1f4;
|
||||
|
||||
/* container */
|
||||
--landing-paper-bg: var(--bg-surface);
|
||||
--landing-inner-paper-bg: #eef8ff;
|
||||
--landing-inner-paper-border: #cdeaff;
|
||||
--landing-button-bg: var(--bg-surface);
|
||||
--landing-button-color: var(--icon-tools-bg);
|
||||
--landing-button-border: #e0f2f7;
|
||||
--landing-button-hover-bg: rgb(251, 251, 251);
|
||||
|
||||
/* drop state */
|
||||
--landing-drop-paper-bg: #e3f2fd;
|
||||
--landing-drop-inner-paper-bg: #bbdefb;
|
||||
--landing-drop-inner-paper-border: #90caf9;
|
||||
|
||||
@@ -268,36 +240,15 @@
|
||||
--landing-hero-gradient: linear-gradient(135deg, #4c8bf5 0%, #3a7be8 100%);
|
||||
--landing-stack-w: 224px;
|
||||
--landing-stack-h: 176px;
|
||||
--landing-stack-glow-bg: radial-gradient(
|
||||
circle,
|
||||
rgba(74, 144, 226, 0.18) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
|
||||
/* landing doc stack shadows */
|
||||
--landing-doc-shadow-back-idle:
|
||||
0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
--landing-doc-shadow-back-hover:
|
||||
0 12px 40px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
--landing-doc-shadow-front-idle:
|
||||
0 8px 30px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06),
|
||||
0 0 0 1px rgba(0, 0, 0, 0.02);
|
||||
--landing-doc-shadow-front-hover:
|
||||
0 18px 48px rgba(0, 0, 0, 0.18), 0 8px 20px rgba(0, 0, 0, 0.1),
|
||||
0 0 0 1px rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* landing action button shadows */
|
||||
--landing-action-transition:
|
||||
transform 0.28s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.32s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--landing-action-shadow-idle:
|
||||
0 6px 18px rgba(0, 0, 0, 0), 0 2px 6px rgba(0, 0, 0, 0);
|
||||
--landing-action-shadow-hover:
|
||||
0 6px 18px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||
--landing-action-primary-shadow-idle:
|
||||
0 10px 26px rgba(58, 123, 232, 0), 0 4px 12px rgba(0, 0, 0, 0);
|
||||
--landing-action-primary-shadow-hover:
|
||||
0 10px 26px rgba(58, 123, 232, 0.42), 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* selected file header colors */
|
||||
--header-selected-bg: #1e88e5; /* light mode selected header matches dark */
|
||||
@@ -306,18 +257,10 @@
|
||||
--accordion-item-bg: #e8eaed; /* accordion item background - more distinguishable */
|
||||
|
||||
/* shadows */
|
||||
--drop-shadow-color: rgba(0, 0, 0, 0.08);
|
||||
--drop-shadow-color-strong: rgba(0, 0, 0, 0.04);
|
||||
--drop-shadow-filter: drop-shadow(0 0.2rem 0.4rem rgba(0, 0, 0, 0.08))
|
||||
drop-shadow(0 0.6rem 0.6rem rgba(0, 0, 0, 0.06))
|
||||
drop-shadow(0 1.2rem 1rem rgba(0, 0, 0, 0.04));
|
||||
|
||||
/* Light mode card hover and selection */
|
||||
--header-hover-bg: #3b4b6e; /* same family as selected, a touch muted for hover */
|
||||
--card-selected-border: #3fafff; /* slightly more blue than dark mode header */
|
||||
--checkbox-border: #2f83bf;
|
||||
--checkbox-checked-bg: #3fafff;
|
||||
--checkbox-tick: #ffffff;
|
||||
|
||||
--information-text-bg: #eaeaea;
|
||||
--information-text-color: #5e5e5e;
|
||||
@@ -348,15 +291,9 @@
|
||||
--api-keys-button-color: #333333;
|
||||
|
||||
/* PDF Report Colors (always light) */
|
||||
--pdf-light-header-bg: 239 246 255;
|
||||
--pdf-light-accent: 59 130 246;
|
||||
--pdf-light-text-primary: 30 41 59;
|
||||
--pdf-light-text-muted: 100 116 139;
|
||||
--pdf-light-box-bg: 248 250 252;
|
||||
--pdf-light-box-border: 226 232 240;
|
||||
--pdf-light-warning: 234 179 8;
|
||||
--pdf-light-danger: 248 113 113;
|
||||
--pdf-light-success: 34 197 94;
|
||||
--pdf-light-neutral: 148 163 184;
|
||||
--pdf-light-status-valid-bg: 209 250 229;
|
||||
--pdf-light-status-valid-text: 6 95 70;
|
||||
@@ -366,8 +303,6 @@
|
||||
--pdf-light-status-invalid-text: 153 27 27;
|
||||
--pdf-light-status-neutral-bg: 229 231 235;
|
||||
--pdf-light-status-neutral-text: 55 65 81;
|
||||
--pdf-light-report-container-bg: 249 250 251;
|
||||
--pdf-light-simulated-page-bg: 255 255 255;
|
||||
--pdf-light-simulated-page-text: 15 23 42;
|
||||
|
||||
/* Code token colors (light mode) */
|
||||
@@ -376,11 +311,6 @@
|
||||
--code-num-color: #4338ca; /* indigo-700 */
|
||||
--code-com-color: #6b7280; /* gray-500 */
|
||||
/* Compare tool specific colors - only for colors that don't have existing theme pairs */
|
||||
--compare-upload-dropzone-bg: rgba(241, 245, 249, 0.45);
|
||||
--compare-upload-dropzone-border: rgba(148, 163, 184, 0.6);
|
||||
--compare-upload-icon-bg: rgba(148, 163, 184, 0.2);
|
||||
--compare-upload-icon-color: rgba(17, 24, 39, 0.75);
|
||||
--compare-upload-divider: rgba(148, 163, 184, 0.5);
|
||||
|
||||
/* Compare page label chip (light mode): slightly lighter than surrounding rows */
|
||||
--compare-page-label-bg: var(--bg-muted);
|
||||
@@ -420,12 +350,12 @@
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] {
|
||||
/* Dark theme gray scale (inverted) */
|
||||
--gray-50: 17 24 39;
|
||||
--gray-100: 31 35 41;
|
||||
--gray-200: 42 47 54;
|
||||
--gray-300: 55 65 81;
|
||||
--gray-400: 75 85 99;
|
||||
/* Dark theme gray scale — very dark, near-black */
|
||||
--gray-50: 7 10 14;
|
||||
--gray-100: 10 13 18;
|
||||
--gray-200: 15 19 24;
|
||||
--gray-300: 22 28 36;
|
||||
--gray-400: 36 44 58;
|
||||
--gray-500: 107 114 128;
|
||||
--gray-600: 156 163 175;
|
||||
--gray-700: 209 213 219;
|
||||
@@ -433,9 +363,9 @@
|
||||
--gray-900: 243 244 246;
|
||||
|
||||
/* Dark semantic colors for Tailwind */
|
||||
--surface: 31 35 41;
|
||||
--background: 42 47 54;
|
||||
--border: 55 65 81;
|
||||
--surface: 15 19 24;
|
||||
--background: 10 13 18;
|
||||
--border: 22 28 36;
|
||||
|
||||
/* Dark theme Mantine colors */
|
||||
--color-red-50: #2d1b1b;
|
||||
@@ -449,11 +379,11 @@
|
||||
--color-red-800: #991b1b;
|
||||
--color-red-900: #7f1d1d;
|
||||
|
||||
--color-gray-50: #111827;
|
||||
--color-gray-100: #1f2329;
|
||||
--color-gray-200: #2a2f36;
|
||||
--color-gray-300: #374151;
|
||||
--color-gray-400: #4b5563;
|
||||
--color-gray-50: #070a0e;
|
||||
--color-gray-100: #0a0d12;
|
||||
--color-gray-200: #0f1318;
|
||||
--color-gray-300: #161c24;
|
||||
--color-gray-400: #242c3a;
|
||||
--color-gray-500: #6b7280;
|
||||
--color-gray-600: #9ca3af;
|
||||
--color-gray-700: #d1d5db;
|
||||
@@ -461,20 +391,20 @@
|
||||
--color-gray-900: #f3f4f6;
|
||||
|
||||
/* Category colors - same as light mode for consistency */
|
||||
--category-color-removal: #ef4444; /* Red for removal tools */
|
||||
--category-color-security: #f59e0b; /* Orange for security tools */
|
||||
--category-color-formatting: #8b5cf6; /* Purple for formatting tools */
|
||||
--category-color-extraction: #06b6d4; /* Cyan for extraction tools */
|
||||
--category-color-signing: #10b981; /* Green for signing tools */
|
||||
--category-color-general: #3b82f6; /* Blue for general tools */
|
||||
--category-color-verification: #f97316; /* Orange for verification tools */
|
||||
--category-color-automation: #ec4899; /* Pink for automation tools */
|
||||
--category-color-developer: #6b7280; /* Gray for developer tools */
|
||||
--category-color-default: #6b7280; /* Default gray */
|
||||
--category-color-removal: #ef4444;
|
||||
--category-color-security: #f59e0b;
|
||||
--category-color-formatting: #8b5cf6;
|
||||
--category-color-extraction: #06b6d4;
|
||||
--category-color-signing: #10b981;
|
||||
--category-color-general: #3b82f6;
|
||||
--category-color-verification: #f97316;
|
||||
--category-color-automation: #ec4899;
|
||||
--category-color-developer: #6b7280;
|
||||
--category-color-default: #6b7280;
|
||||
|
||||
/* Special section colors - same as light mode for consistency */
|
||||
--special-color-favorites: #ffc107; /* Yellow/gold for favorites */
|
||||
--special-color-recommended: #1bb1d4; /* Cyan for recommended */
|
||||
--special-color-favorites: #ffc107;
|
||||
--special-color-recommended: #1bb1d4;
|
||||
|
||||
/* Success (green) - dark */
|
||||
--color-green-50: #052e16;
|
||||
@@ -501,53 +431,39 @@
|
||||
--color-yellow-900: #fef9c3;
|
||||
|
||||
/* Dark theme semantic colors */
|
||||
--bg-surface: #2a2f36;
|
||||
--bg-raised: #1f2329;
|
||||
--bg-muted: #1f2329;
|
||||
--bg-background: #2a2f36;
|
||||
--bg-toolbar: #1f2329;
|
||||
--bg-file-manager: #1f2329;
|
||||
--bg-file-list: #2a2f36;
|
||||
--bg-surface: #0f1318;
|
||||
--bg-raised: #0a0d12;
|
||||
--bg-muted: #0a0d12;
|
||||
--bg-background: #0a0d12;
|
||||
--bg-toolbar: #0a0d12;
|
||||
--bg-file-manager: #0a0d12;
|
||||
--bg-file-list: #0f1318;
|
||||
--btn-open-file: #0a8bff;
|
||||
--text-primary: #f9fafb;
|
||||
--text-secondary: #d1d5db;
|
||||
--text-muted: #9ca3af;
|
||||
/* Always-dark text (for use on light backgrounds like alerts) - does not change in dark mode */
|
||||
--text-primary: #f0f2f5;
|
||||
--text-secondary: #c5ccd6;
|
||||
--text-muted: #8b96a3;
|
||||
--text-always-dark: #1f2937;
|
||||
--text-always-dark-muted: #6b7280;
|
||||
--border-subtle: #2a2f36;
|
||||
--border-default: #3a4047;
|
||||
--border-strong: #4b5563;
|
||||
--hover-bg: #374151;
|
||||
--active-bg: #4b5563;
|
||||
--border-subtle: #0f1318;
|
||||
--border-default: #161c24;
|
||||
--border-strong: #242c3a;
|
||||
--hover-bg: #13181f;
|
||||
--active-bg: #1a2030;
|
||||
--automation-entry-hover-bg: var(--color-gray-200);
|
||||
|
||||
/* Icon colors for dark mode */
|
||||
--icon-user-bg: #2a2f36;
|
||||
--icon-user-color: #6e7581;
|
||||
--icon-notifications-bg: #2a2f36;
|
||||
--icon-notifications-color: #6e7581;
|
||||
--icon-tools-bg: #4b525a;
|
||||
--icon-tools-color: #eaeaea;
|
||||
--icon-read-bg: #4b525a;
|
||||
--icon-read-color: #eaeaea;
|
||||
--icon-sign-bg: #4b525a;
|
||||
--icon-sign-color: #eaeaea;
|
||||
--icon-automate-bg: #4b525a;
|
||||
--icon-automate-color: #eaeaea;
|
||||
--icon-files-bg: #4b525a;
|
||||
--icon-files-color: #eaeaea;
|
||||
--icon-activity-bg: #4b525a;
|
||||
--icon-activity-color: #eaeaea;
|
||||
--icon-config-bg: #4b525a;
|
||||
--icon-config-color: #eaeaea;
|
||||
--icon-user-bg: #0f1318;
|
||||
--icon-user-color: #5a6475;
|
||||
--icon-tools-bg: #161c24;
|
||||
--icon-tools-color: #d8dce4;
|
||||
--icon-files-color: #d8dce4;
|
||||
|
||||
/* Inactive icon colors for dark mode */
|
||||
--icon-inactive-bg: #2a2f36;
|
||||
--icon-inactive-color: #6e7581;
|
||||
--icon-inactive-bg: #0f1318;
|
||||
--icon-inactive-color: #5a6475;
|
||||
|
||||
/* Dark mode tooltip colors */
|
||||
--tooltip-title-bg: #4b525a;
|
||||
--tooltip-title-bg: #161c24;
|
||||
--tooltip-title-color: #fff;
|
||||
--tooltip-header-bg: var(--bg-raised);
|
||||
--tooltip-header-color: var(--text-primary);
|
||||
@@ -555,7 +471,6 @@
|
||||
|
||||
--accent-interactive: #ffffff;
|
||||
--text-instruction: #ffffff;
|
||||
--text-brand: var(--color-gray-800);
|
||||
--text-brand-accent: #ef4444;
|
||||
|
||||
/* Compare badge text colors (dark mode): lighter for readability */
|
||||
@@ -563,62 +478,41 @@
|
||||
--spdf-compare-added-badge-fg: var(--color-green-500);
|
||||
|
||||
/* container */
|
||||
--landing-paper-bg: #171a1f;
|
||||
--landing-inner-paper-bg: var(--bg-raised);
|
||||
--landing-inner-paper-border: #2d3237;
|
||||
--landing-inner-paper-border: #161c24;
|
||||
|
||||
/* landing dark overrides */
|
||||
--landing-stack-glow-bg: radial-gradient(
|
||||
circle,
|
||||
rgba(30, 136, 229, 0.22) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
--landing-doc-shadow-back-idle:
|
||||
0 4px 20px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.25);
|
||||
--landing-doc-shadow-back-hover:
|
||||
0 14px 44px rgba(0, 0, 0, 0.55), 0 6px 16px rgba(0, 0, 0, 0.35);
|
||||
0 4px 20px rgba(0, 0, 0, 0.6), 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||
--landing-doc-shadow-front-idle:
|
||||
0 8px 30px rgba(0, 0, 0, 0.45), 0 4px 12px rgba(0, 0, 0, 0.3),
|
||||
0 8px 30px rgba(0, 0, 0, 0.7), 0 4px 12px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.04);
|
||||
--landing-doc-shadow-front-hover:
|
||||
0 20px 52px rgba(0, 0, 0, 0.6), 0 10px 24px rgba(0, 0, 0, 0.4),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
--landing-button-color: #ffffff;
|
||||
--landing-button-hover-bg: var(--bg-raised);
|
||||
|
||||
/* selected file header colors for dark */
|
||||
--header-selected-bg: #1e88e5;
|
||||
--header-selected-fg: #ffffff;
|
||||
/* file card background (dark) */
|
||||
--file-card-bg: #1f2329;
|
||||
--accordion-item-bg: #373d45; /* accordion item background - more distinguishable */
|
||||
|
||||
/* shadows */
|
||||
--drop-shadow-color: rgba(255, 255, 255, 0.08);
|
||||
--drop-shadow-color-strong: rgba(255, 255, 255, 0.04);
|
||||
--drop-shadow-filter: drop-shadow(0 0.2rem 0.4rem rgba(200, 200, 200, 0.08))
|
||||
drop-shadow(0 0.6rem 0.6rem rgba(200, 200, 200, 0.06))
|
||||
drop-shadow(0 1.2rem 1rem rgba(200, 200, 200, 0.04));
|
||||
--file-card-bg: #0a0d12;
|
||||
--accordion-item-bg: #13181f;
|
||||
|
||||
/* Adjust shadows for dark mode */
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.4);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.4);
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.6);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.6);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.6);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.6);
|
||||
|
||||
--tools-text-and-icon-color: var(--text-primary);
|
||||
|
||||
/* Tool picker sticky header variables (dark mode) */
|
||||
--tool-header-bg: #2a2f36;
|
||||
--tool-header-border: #3a4047;
|
||||
--tool-header-text: #d0d6dc;
|
||||
--tool-header-badge-bg: #4b525a;
|
||||
--tool-header-badge-text: #ffffff;
|
||||
--tool-header-border: #161c24;
|
||||
--tool-header-text: #c5ccd6;
|
||||
--tool-header-badge-bg: #161c24;
|
||||
--tool-header-badge-text: #f0f2f5;
|
||||
|
||||
/* Subcategory title styling (dark mode) */
|
||||
--tool-subcategory-text-color: #9ca3af; /* lighter text in dark mode as well */
|
||||
--tool-subcategory-rule-color: #3a4047; /* doubly lighter (relative) line in dark */
|
||||
--tool-subcategory-text-color: #8b96a3;
|
||||
--tool-subcategory-rule-color: #161c24;
|
||||
|
||||
/* PDF text selection colors (dark mode) */
|
||||
--pdf-selection-bg: rgba(96, 165, 250, 0.25);
|
||||
@@ -626,74 +520,67 @@
|
||||
|
||||
/* Placeholder text colors (dark mode) */
|
||||
--search-text-and-icon-color: #ffffff !important;
|
||||
--input-bg: #4b525a;
|
||||
--input-bg: #161c24;
|
||||
|
||||
/* Tool panel search bar background colors (dark mode) */
|
||||
--tool-panel-search-bg: #1f2329;
|
||||
--tool-panel-search-border-bottom: #4b525a;
|
||||
--tool-panel-search-bg: #0a0d12;
|
||||
--tool-panel-search-border-bottom: #242c3a;
|
||||
|
||||
--information-text-bg: #292e34;
|
||||
--information-text-color: #ececec;
|
||||
--information-text-bg: #0a0d12;
|
||||
--information-text-color: #d8dce4;
|
||||
|
||||
/* Bulk selection panel specific colors (dark mode) */
|
||||
--bulk-panel-bg: var(--bg-raised); /* dark background for parent container */
|
||||
--bulk-card-bg: var(--bg-raised); /* dark background for cards */
|
||||
--bulk-card-border: var(
|
||||
--border-default
|
||||
); /* default border for cards and buttons */
|
||||
--bulk-card-hover-border: var(--border-strong); /* stronger border on hover */
|
||||
--unsupported-bar-bg: #1f2329;
|
||||
--unsupported-bar-border: #4b525a;
|
||||
--bulk-panel-bg: var(--bg-raised);
|
||||
--bulk-card-bg: var(--bg-raised);
|
||||
--bulk-card-border: var(--border-default);
|
||||
--bulk-card-hover-border: var(--border-strong);
|
||||
--unsupported-bar-bg: #0a0d12;
|
||||
--unsupported-bar-border: #242c3a;
|
||||
|
||||
/* Config Modal colors (dark mode) */
|
||||
--modal-nav-bg: #1f2329;
|
||||
--modal-nav-section-title: #9ca3af;
|
||||
--modal-nav-item: #d0d6dc;
|
||||
--modal-nav-bg: #0a0d12;
|
||||
--modal-nav-section-title: #8b96a3;
|
||||
--modal-nav-item: #c5ccd6;
|
||||
--modal-nav-item-active: #0a8bff;
|
||||
--modal-nav-item-active-bg: rgba(10, 139, 255, 0.15);
|
||||
--modal-content-bg: #2a2f36;
|
||||
--modal-content-bg: #0f1318;
|
||||
--modal-header-border: rgba(255, 255, 255, 0.08);
|
||||
|
||||
/* Onboarding (dark mode) */
|
||||
--onboarding-title: #f9fafb;
|
||||
--onboarding-body: #d1d5db;
|
||||
--onboarding-title: #f0f2f5;
|
||||
--onboarding-body: #c5ccd6;
|
||||
--onboarding-primary-button-bg: #ffffff;
|
||||
--onboarding-primary-button-text: #0b1220;
|
||||
--onboarding-primary-button-text: #0a0d12;
|
||||
--onboarding-secondary-button-bg: transparent;
|
||||
--onboarding-secondary-button-text: #e5e7eb;
|
||||
--onboarding-secondary-button-border: #3a4047;
|
||||
--onboarding-step-active: #e5e7eb;
|
||||
--onboarding-step-inactive: #4b5563;
|
||||
--onboarding-secondary-button-text: #d8dce4;
|
||||
--onboarding-secondary-button-border: #161c24;
|
||||
--onboarding-step-active: #d8dce4;
|
||||
--onboarding-step-inactive: #242c3a;
|
||||
|
||||
/* API Keys section colors (dark mode) */
|
||||
--api-keys-card-bg: #2a2f36;
|
||||
--api-keys-card-border: #3a4047;
|
||||
--api-keys-card-bg: #0f1318;
|
||||
--api-keys-card-border: #161c24;
|
||||
--api-keys-card-shadow: none;
|
||||
--api-keys-input-bg: #1f2329;
|
||||
--api-keys-input-border: #3a4047;
|
||||
--api-keys-button-bg: #3a4047;
|
||||
--api-keys-button-color: #d0d6dc;
|
||||
--api-keys-input-bg: #0a0d12;
|
||||
--api-keys-input-border: #161c24;
|
||||
--api-keys-button-bg: #161c24;
|
||||
--api-keys-button-color: #c5ccd6;
|
||||
|
||||
/* Code token colors (dark mode - Cursor-like) */
|
||||
--code-kw-color: #c792ea; /* purple */
|
||||
--code-str-color: #c3e88d; /* green */
|
||||
--code-num-color: #f78c6c; /* orange */
|
||||
--code-com-color: #697098; /* muted gray-blue */
|
||||
/* Compare tool specific colors (dark mode) - only for colors that don't have existing theme pairs */
|
||||
--compare-upload-dropzone-bg: rgba(31, 35, 41, 0.45);
|
||||
--compare-upload-dropzone-border: rgba(75, 85, 99, 0.6);
|
||||
--compare-upload-icon-bg: rgba(75, 85, 99, 0.2);
|
||||
--compare-upload-icon-color: rgba(243, 244, 246, 0.75);
|
||||
--compare-upload-divider: rgba(75, 85, 99, 0.5);
|
||||
--code-kw-color: #c792ea;
|
||||
--code-str-color: #c3e88d;
|
||||
--code-num-color: #f78c6c;
|
||||
--code-com-color: #697098;
|
||||
|
||||
/* Compare page label chip (dark mode): slightly darker than surrounding rows */
|
||||
--compare-page-label-bg: #1f2329;
|
||||
/* Compare page label chip (dark mode) */
|
||||
--compare-page-label-bg: #0a0d12;
|
||||
--compare-page-label-fg: var(--text-secondary);
|
||||
|
||||
/* Status indicator dot ring shadow (dark) */
|
||||
--status-dot-ring: 0 0 0 2px rgba(255, 255, 255, 0.18);
|
||||
|
||||
/* Input element styling (dark) */
|
||||
--input-bg: #4b525a;
|
||||
--input-bg: #161c24;
|
||||
|
||||
/* Select/dropdown placeholder state (dark) */
|
||||
--select-placeholder-bg: var(--mantine-color-dark-5);
|
||||
@@ -709,6 +596,195 @@
|
||||
--dropdown-group-label: var(--mantine-color-dark-2);
|
||||
}
|
||||
|
||||
/* ── Midnight theme — blue-tinted dark workbench ── */
|
||||
/* Applied on top of dark mode: html has both data-mantine-color-scheme="dark"
|
||||
and data-color-scheme="midnight" */
|
||||
[data-color-scheme="midnight"] {
|
||||
--gray-50: 21 26 38;
|
||||
--gray-100: 30 36 52;
|
||||
--gray-200: 45 50 72;
|
||||
--gray-300: 58 65 90;
|
||||
--gray-400: 75 85 99;
|
||||
--gray-500: 107 114 128;
|
||||
--gray-600: 156 163 175;
|
||||
--gray-700: 209 213 219;
|
||||
--gray-800: 229 231 235;
|
||||
--gray-900: 243 244 246;
|
||||
|
||||
--surface: 30 36 52;
|
||||
--background: 21 26 38;
|
||||
--border: 58 65 90;
|
||||
|
||||
--color-gray-50: #151a24;
|
||||
--color-gray-100: #1a2030;
|
||||
--color-gray-200: #1e2435;
|
||||
--color-gray-300: #252836;
|
||||
--color-gray-400: #2d3340;
|
||||
--color-gray-500: #6b7280;
|
||||
--color-gray-600: #9ca3af;
|
||||
--color-gray-700: #d1d5db;
|
||||
--color-gray-800: #e5e7eb;
|
||||
--color-gray-900: #f3f4f6;
|
||||
|
||||
--category-color-general: #4b77e6;
|
||||
|
||||
--bg-surface: #1e2435;
|
||||
--bg-raised: #1a2030;
|
||||
--bg-muted: #1a2030;
|
||||
--bg-background: #151a24;
|
||||
--bg-toolbar: #1e2435;
|
||||
--bg-file-manager: #1a2030;
|
||||
--bg-file-list: #1e2435;
|
||||
--btn-open-file: #4b77e6;
|
||||
--text-primary: #f1f5f9;
|
||||
--text-secondary: #cbd5e1;
|
||||
--text-muted: #94a3b8;
|
||||
--text-always-dark: #1f2937;
|
||||
--text-always-dark-muted: #6b7280;
|
||||
--border-subtle: #2d3340;
|
||||
--border-default: #3a4152;
|
||||
--border-strong: #4a5568;
|
||||
--hover-bg: #252836;
|
||||
--active-bg: #2d3340;
|
||||
--automation-entry-hover-bg: var(--color-gray-200);
|
||||
|
||||
--icon-user-bg: #1e2435;
|
||||
--icon-user-color: #6b7280;
|
||||
--icon-tools-bg: #2d3340;
|
||||
--icon-tools-color: #cbd5e1;
|
||||
--icon-files-color: #4b77e6;
|
||||
--icon-inactive-bg: #1e2435;
|
||||
--icon-inactive-color: #6b7280;
|
||||
|
||||
--tooltip-title-bg: #252836;
|
||||
--tooltip-title-color: #f1f5f9;
|
||||
--tooltip-header-bg: #1a2030;
|
||||
--tooltip-header-color: #f1f5f9;
|
||||
--tooltip-border: var(--border-default);
|
||||
|
||||
--accent-interactive: #4b77e6;
|
||||
--text-instruction: #4b77e6;
|
||||
--text-brand-accent: #60a5fa;
|
||||
|
||||
--spdf-compare-removed-badge-fg: var(--color-red-500);
|
||||
--spdf-compare-added-badge-fg: var(--color-green-500);
|
||||
|
||||
--landing-inner-paper-bg: #1a2030;
|
||||
--landing-inner-paper-border: #3a4152;
|
||||
--landing-button-bg: #252836;
|
||||
--landing-button-color: #f1f5f9;
|
||||
--landing-button-border: #3a4152;
|
||||
--landing-drop-inner-paper-bg: #1e2435;
|
||||
--landing-drop-inner-paper-border: #3a4152;
|
||||
|
||||
--landing-doc-shadow-back-idle:
|
||||
0 4px 20px rgba(0, 0, 0, 0.45), 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
--landing-doc-shadow-front-idle:
|
||||
0 8px 30px rgba(0, 0, 0, 0.55), 0 4px 12px rgba(0, 0, 0, 0.4),
|
||||
0 0 0 1px rgba(75, 119, 230, 0.08);
|
||||
|
||||
--header-selected-bg: #4b77e6;
|
||||
--header-selected-fg: #ffffff;
|
||||
--file-card-bg: #1a2030;
|
||||
--accordion-item-bg: #252836;
|
||||
|
||||
--card-selected-border: #4b77e6;
|
||||
--checkbox-checked-bg: #4b77e6;
|
||||
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.5);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.5);
|
||||
|
||||
--tools-text-and-icon-color: #cbd5e1;
|
||||
|
||||
--tool-header-border: #3a4152;
|
||||
--tool-header-text: #cbd5e1;
|
||||
--tool-header-badge-bg: #252836;
|
||||
--tool-header-badge-text: #f1f5f9;
|
||||
|
||||
--tool-subcategory-text-color: #94a3b8;
|
||||
--tool-subcategory-rule-color: #3a4152;
|
||||
|
||||
--pdf-selection-bg: rgba(75, 119, 230, 0.25);
|
||||
--pdf-selection-ring: rgba(75, 119, 230, 0.28);
|
||||
|
||||
--search-text-and-icon-color: #f1f5f9 !important;
|
||||
--input-bg: #252836;
|
||||
|
||||
--tool-panel-search-bg: #1a2030;
|
||||
--tool-panel-search-border-bottom: #3a4152;
|
||||
|
||||
--information-text-bg: #1e2435;
|
||||
--information-text-color: #cbd5e1;
|
||||
|
||||
--bulk-panel-bg: var(--bg-raised);
|
||||
--bulk-card-bg: var(--bg-raised);
|
||||
--bulk-card-border: var(--border-default);
|
||||
--bulk-card-hover-border: var(--border-strong);
|
||||
--unsupported-bar-bg: #1a2030;
|
||||
--unsupported-bar-border: #3a4152;
|
||||
|
||||
--modal-nav-bg: #1a2030;
|
||||
--modal-nav-section-title: #94a3b8;
|
||||
--modal-nav-item: #cbd5e1;
|
||||
--modal-nav-item-active: #4b77e6;
|
||||
--modal-nav-item-active-bg: rgba(75, 119, 230, 0.18);
|
||||
--modal-content-bg: #1e2435;
|
||||
--modal-header-border: rgba(255, 255, 255, 0.08);
|
||||
|
||||
--onboarding-title: #f1f5f9;
|
||||
--onboarding-body: #cbd5e1;
|
||||
--onboarding-primary-button-bg: #4b77e6;
|
||||
--onboarding-primary-button-text: #ffffff;
|
||||
--onboarding-secondary-button-bg: transparent;
|
||||
--onboarding-secondary-button-text: #cbd5e1;
|
||||
--onboarding-secondary-button-border: #3a4152;
|
||||
--onboarding-step-active: #cbd5e1;
|
||||
--onboarding-step-inactive: #3a4152;
|
||||
|
||||
--api-keys-card-bg: #1e2435;
|
||||
--api-keys-card-border: #3a4152;
|
||||
--api-keys-card-shadow: none;
|
||||
--api-keys-input-bg: #1a2030;
|
||||
--api-keys-input-border: #3a4152;
|
||||
--api-keys-button-bg: #252836;
|
||||
--api-keys-button-color: #cbd5e1;
|
||||
|
||||
--code-kw-color: #7aa2f7;
|
||||
--code-str-color: #9ece6a;
|
||||
--code-num-color: #ff9e64;
|
||||
--code-com-color: #565f89;
|
||||
|
||||
--compare-page-label-bg: #1a2030;
|
||||
--compare-page-label-fg: var(--text-secondary);
|
||||
|
||||
--status-dot-ring: 0 0 0 2px rgba(75, 119, 230, 0.25);
|
||||
|
||||
--select-placeholder-bg: var(--mantine-color-dark-5);
|
||||
--select-placeholder-text: var(--mantine-color-dark-2);
|
||||
|
||||
--dropdown-trigger-bg: var(--mantine-color-dark-6);
|
||||
--dropdown-trigger-text: var(--mantine-color-dark-0);
|
||||
--dropdown-trigger-text-disabled: var(--mantine-color-dark-1);
|
||||
--dropdown-trigger-icon: var(--mantine-color-dark-2);
|
||||
--dropdown-panel-bg: var(--mantine-color-dark-7);
|
||||
--dropdown-panel-border: var(--mantine-color-dark-4);
|
||||
--dropdown-group-label: var(--mantine-color-dark-2);
|
||||
|
||||
--link-hover-bg: rgba(75, 119, 230, 0.06);
|
||||
--link-hover-border: rgba(75, 119, 230, 0.2);
|
||||
--link-hover-shadow:
|
||||
0 0 0 0.5px rgba(75, 119, 230, 0.1), 0 1px 3px rgba(75, 119, 230, 0.08);
|
||||
--link-focus-ring: rgba(75, 119, 230, 0.35);
|
||||
--link-toolbar-bg: rgba(21, 26, 36, 0.94);
|
||||
--link-toolbar-border: rgba(255, 255, 255, 0.1);
|
||||
--link-toolbar-shadow:
|
||||
0 12px 40px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3),
|
||||
0 0 0 0.5px rgba(75, 119, 230, 0.08);
|
||||
}
|
||||
|
||||
/* Dropzone drop state styling */
|
||||
[data-accept] .dropzone-inner {
|
||||
background-color: var(--landing-drop-inner-paper-bg) !important;
|
||||
@@ -717,7 +793,6 @@
|
||||
|
||||
/* Plan section card borders - only override in dark mode */
|
||||
[data-mantine-color-scheme="dark"] .plan-card {
|
||||
--paper-border-color: rgb(158, 158, 158) !important;
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .plan-card [data-size="sm"] {
|
||||
@@ -739,6 +814,19 @@
|
||||
background-color: #1c598e !important;
|
||||
}
|
||||
|
||||
[data-color-scheme="midnight"]
|
||||
.plan-button:not(:disabled):not([data-disabled]) {
|
||||
background-color: #4b77e6 !important;
|
||||
}
|
||||
|
||||
/* Override Mantine's internal body/surface colors for midnight */
|
||||
[data-color-scheme="midnight"] {
|
||||
--mantine-color-body: #151a24;
|
||||
--mantine-color-dark-6: #1e2435;
|
||||
--mantine-color-dark-7: #151a24;
|
||||
--mantine-color-dark-9: #0f1318;
|
||||
}
|
||||
|
||||
/* Lighter grey for disabled plan buttons */
|
||||
.plan-button:disabled,
|
||||
.plan-button[data-disabled] {
|
||||
@@ -787,12 +875,14 @@
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Smooth transitions for theme switching */
|
||||
/* Smooth transitions for theme switching - backgrounds and borders only.
|
||||
Color (text) is intentionally excluded: Mantine applies the color scheme
|
||||
after the first paint, so a color transition would cause text to visibly
|
||||
crawl from the wrong colour on every page load. */
|
||||
* {
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
border-color 0.2s ease;
|
||||
}
|
||||
/* ── PDF Link Overlay (viewer) ── */
|
||||
:root {
|
||||
@@ -992,10 +1082,15 @@
|
||||
--shadow-color: rgba(15, 23, 42, 0.55);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
[data-theme="dark"],
|
||||
[data-color-scheme="dark"] {
|
||||
--shadow-color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
[data-color-scheme="midnight"] {
|
||||
--shadow-color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
/* ---- Wordmark light/dark switching (use instead of JS colorScheme checks) ---- */
|
||||
.wordmark-dark-only {
|
||||
display: none;
|
||||
@@ -1099,3 +1194,17 @@
|
||||
animation-duration: 160ms;
|
||||
animation-timing-function: cubic-bezier(0.2, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Mantine animates a button's background-color and its text/icon colour on
|
||||
* separate transitions, so when a filled button flips state (selected ↔
|
||||
* unselected, enabled ↔ disabled) you briefly see e.g. a blue background
|
||||
* with the old dark-grey label still showing through. Drop the colour
|
||||
* tween — the swap is instant, but transforms/shadows/borders/opacity keep
|
||||
* animating for hover and press feedback. */
|
||||
.mantine-Button-root,
|
||||
.mantine-ActionIcon-root,
|
||||
.mantine-SegmentedControl-control,
|
||||
.mantine-SegmentedControl-label,
|
||||
.mantine-SegmentedControl-indicator {
|
||||
transition-property: transform, box-shadow, border-color, opacity !important;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ test.describe("Navigation", () => {
|
||||
await page.locator('a[href="/merge"]').first().click();
|
||||
await expect(page).toHaveURL(/\/merge/);
|
||||
|
||||
// In the redesigned UI, "Back to tools" button replaces the old "Tools" link
|
||||
// In the redesigned UI, "Back to all tools" button replaces the old "Tools" link
|
||||
await page
|
||||
.getByRole("button", { name: /Back to tools/i })
|
||||
.getByRole("button", { name: /Back to all tools/i })
|
||||
.first()
|
||||
.click();
|
||||
await expect(page).toHaveURL("/");
|
||||
|
||||
@@ -36,10 +36,10 @@ test.describe("4. PDF Tool Pages - Common Patterns", () => {
|
||||
await page.goto("/compress");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
// Step 2: Click the "Back to tools" button in ToolPanel to go back to /.
|
||||
// Step 2: Click the "Back to all tools" button in ToolPanel to go back to /.
|
||||
// In the redesigned UI this replaces the old "Tools" sidebar link.
|
||||
const homeLink = page
|
||||
.getByRole("button", { name: /Back to tools/i })
|
||||
.getByRole("button", { name: /Back to all tools/i })
|
||||
.first();
|
||||
await homeLink.click();
|
||||
|
||||
|
||||
@@ -53,6 +53,19 @@ const gray: MantineColorsTuple = [
|
||||
"var(--color-gray-900)",
|
||||
];
|
||||
|
||||
const dark: MantineColorsTuple = [
|
||||
"#c4c9d2",
|
||||
"#9aa3b0",
|
||||
"#9ca3af",
|
||||
"#6b7280",
|
||||
"#242c3a",
|
||||
"#161c24",
|
||||
"#0f1318",
|
||||
"#0a0d12",
|
||||
"#070a0e",
|
||||
"#040507",
|
||||
];
|
||||
|
||||
export const mantineTheme = createTheme({
|
||||
// Primary color
|
||||
primaryColor: "primary",
|
||||
@@ -63,6 +76,7 @@ export const mantineTheme = createTheme({
|
||||
green,
|
||||
yellow,
|
||||
gray,
|
||||
dark,
|
||||
},
|
||||
|
||||
// Spacing system - uses CSS variables
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface AppConfig {
|
||||
timestampDefaultTsaUrl?: string;
|
||||
timestampCustomTsaUrls?: string[];
|
||||
timestampTsaPresets?: { label: string; url: string }[];
|
||||
aiEngineEnabled?: boolean;
|
||||
}
|
||||
|
||||
export type AppConfigBootstrapMode = "blocking" | "non-blocking";
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { flushSync } from "react-dom";
|
||||
|
||||
type ViewTransitionDoc = Document & {
|
||||
startViewTransition?: (cb: () => void) => { finished: Promise<void> };
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a state update inside a View Transition so the browser cross-fades
|
||||
* (and morphs any elements sharing a {@code view-transition-name}) between
|
||||
* the before/after DOMs.
|
||||
*
|
||||
* Falls back to a plain synchronous update when the API is unavailable
|
||||
* (Firefox <130, JSDOM, motion-reduced preference).
|
||||
*/
|
||||
export function withViewTransition(update: () => void): Promise<void> {
|
||||
if (typeof document === "undefined") {
|
||||
update();
|
||||
return Promise.resolve();
|
||||
}
|
||||
const doc = document as ViewTransitionDoc;
|
||||
if (doc.startViewTransition) {
|
||||
return doc.startViewTransition(() => flushSync(update)).finished;
|
||||
}
|
||||
update();
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { CheckoutProvider } from "@app/contexts/CheckoutContext";
|
||||
import { UpgradeBannerInitializer } from "@app/components/shared/UpgradeBannerInitializer";
|
||||
import { ServerExperienceProvider } from "@app/contexts/ServerExperienceContext";
|
||||
import { UpdateSeatsProvider } from "@app/contexts/UpdateSeatsContext";
|
||||
import { ChatProvider } from "@app/components/chat/ChatContext";
|
||||
|
||||
export function AppProviders({
|
||||
children,
|
||||
@@ -25,7 +26,7 @@ export function AppProviders({
|
||||
<ServerExperienceProvider>
|
||||
<CheckoutProvider>
|
||||
<UpgradeBannerInitializer />
|
||||
{children}
|
||||
<ChatProvider>{children}</ChatProvider>
|
||||
</CheckoutProvider>
|
||||
</ServerExperienceProvider>
|
||||
</UpdateSeatsProvider>
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
/* ─── Chat takeover ─────────────────────────────────────────────────────── */
|
||||
|
||||
.agents-takeover {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-toolbar);
|
||||
z-index: 5;
|
||||
view-transition-name: agents-rail;
|
||||
}
|
||||
|
||||
/* ─── Sidebar agents section ─────────────────────────────────────────────── */
|
||||
|
||||
.agents-section {
|
||||
padding: 0.5rem 0.75rem 0.875rem;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
background: var(--bg-toolbar);
|
||||
flex-shrink: 0;
|
||||
view-transition-name: agents-rail;
|
||||
}
|
||||
|
||||
/* Hero button — real Stirling agent */
|
||||
.agent-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 0.625rem;
|
||||
background: var(--mantine-color-body);
|
||||
transition:
|
||||
background 150ms ease-out,
|
||||
border-color 150ms ease-out;
|
||||
}
|
||||
|
||||
.agent-button:hover {
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-gray-4) 60%,
|
||||
var(--border-subtle)
|
||||
);
|
||||
background: var(--mantine-color-default-hover);
|
||||
}
|
||||
|
||||
.agent-button__logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--mantine-color-blue-filled);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Coming-soon agent rows */
|
||||
.agents-sidebar-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
|
||||
.agent-button--coming-soon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 0.625rem;
|
||||
background: var(--mantine-color-body);
|
||||
cursor: not-allowed !important;
|
||||
opacity: 0.65;
|
||||
transition:
|
||||
opacity 180ms ease-out,
|
||||
transform 180ms ease-out,
|
||||
border-color 120ms ease-out;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
.agent-button--coming-soon {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
}
|
||||
|
||||
.agent-button--coming-soon:hover {
|
||||
opacity: 0.85;
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-gray-4) 60%,
|
||||
var(--border-subtle)
|
||||
);
|
||||
background: var(--mantine-color-default-hover);
|
||||
}
|
||||
|
||||
.agent-button__icon-plain {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
color: var(--mantine-color-dimmed);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.agents-view-all {
|
||||
all: unset;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 0.375rem;
|
||||
padding: 0.25rem 0.625rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--mantine-color-dimmed);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
border-radius: 0.375rem;
|
||||
transition: opacity 120ms ease-out;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.agents-view-all:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Dark mode — sidebar */
|
||||
[data-mantine-color-scheme="dark"] .agent-button--coming-soon {
|
||||
background: transparent;
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-dark-4) 60%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .agent-button--coming-soon:hover {
|
||||
border-color: var(--mantine-color-dark-3);
|
||||
background: var(--mantine-color-dark-6);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .agent-button {
|
||||
background: transparent;
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-dark-4) 60%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .agent-button:hover {
|
||||
border-color: var(--mantine-color-dark-3);
|
||||
background: var(--mantine-color-dark-6);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .agent-button__logo {
|
||||
color: var(--mantine-color-blue-3, var(--mantine-color-blue-filled));
|
||||
}
|
||||
|
||||
/* ─── Collapsed-rail agent button ────────────────────────────────────────── */
|
||||
|
||||
.agents-collapsed-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
background: transparent;
|
||||
color: var(--mantine-color-blue-filled);
|
||||
cursor: pointer;
|
||||
transition: background 120ms ease-out;
|
||||
}
|
||||
|
||||
.agents-collapsed-btn:hover {
|
||||
background: var(--mantine-color-default-hover);
|
||||
}
|
||||
|
||||
/* ─── Fullscreen hero card ───────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* .tool-panel__fullscreen-group--agents gives the gradient border via the
|
||||
* padding-box / border-box trick. We only override the padding-box with a
|
||||
* very light tint so the card doesn't read as a colourful surface.
|
||||
*/
|
||||
.agents-hero {
|
||||
padding: 1.25rem 1.5rem 1.5rem;
|
||||
border-radius: 1rem;
|
||||
background:
|
||||
linear-gradient(
|
||||
125deg,
|
||||
color-mix(in srgb, var(--mantine-color-blue-1) 35%, white) 0%,
|
||||
color-mix(in srgb, var(--mantine-color-violet-1) 45%, white) 50%,
|
||||
color-mix(in srgb, var(--mantine-color-pink-0) 35%, white) 100%
|
||||
)
|
||||
padding-box,
|
||||
linear-gradient(
|
||||
135deg,
|
||||
var(--mantine-color-blue-6) 0%,
|
||||
var(--mantine-color-violet-6) 45%,
|
||||
var(--mantine-color-pink-6) 100%
|
||||
)
|
||||
border-box;
|
||||
}
|
||||
|
||||
/* Body: left CTA + right grid */
|
||||
.agents-hero__body {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* ── Left CTA — plain content, only the button inside is interactive ── */
|
||||
.agents-hero__cta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
flex: 0 0 18rem;
|
||||
box-sizing: border-box;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.agents-hero__cta-logo {
|
||||
color: var(--mantine-color-blue-filled);
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
.agents-hero__cta-headline {
|
||||
font-size: 1.375rem;
|
||||
}
|
||||
|
||||
.agents-hero__cta-btn {
|
||||
all: unset;
|
||||
margin-top: 1.25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.4375rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--mantine-color-blue-6);
|
||||
color: white;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.agents-hero__cta-btn:hover {
|
||||
background: var(--mantine-color-blue-7);
|
||||
}
|
||||
|
||||
.agents-hero__cta-btn:focus-visible {
|
||||
outline: 2px solid var(--mantine-color-blue-5);
|
||||
outline-offset: 3px;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* ── Right 2×3 grid — sizes to content, pushed to the right ── */
|
||||
.agents-hero__grid {
|
||||
flex: 0 0 auto;
|
||||
margin-left: auto;
|
||||
display: grid;
|
||||
/* auto columns: each column = widest item in that column */
|
||||
grid-template-columns: repeat(2, auto);
|
||||
gap: 0.625rem;
|
||||
align-content: start;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Solid white cards — no opacity so the white is opaque */
|
||||
.agents-hero__grid-item {
|
||||
all: unset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.07);
|
||||
border-radius: 0.75rem;
|
||||
background: var(--mantine-color-body);
|
||||
cursor: not-allowed;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.agents-hero__grid-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: var(--mantine-color-blue-5);
|
||||
}
|
||||
|
||||
.agents-hero__grid-body {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ── Dark mode — fullscreen ── */
|
||||
[data-mantine-color-scheme="dark"] .agents-hero {
|
||||
background:
|
||||
linear-gradient(
|
||||
125deg,
|
||||
color-mix(in srgb, var(--mantine-color-blue-9) 55%, #1a1b2e) 0%,
|
||||
color-mix(in srgb, var(--mantine-color-violet-9) 45%, #1a1b2e) 50%,
|
||||
color-mix(in srgb, var(--mantine-color-pink-9) 40%, #1a1b2e) 100%
|
||||
)
|
||||
padding-box,
|
||||
linear-gradient(
|
||||
135deg,
|
||||
var(--mantine-color-blue-6) 0%,
|
||||
var(--mantine-color-violet-6) 45%,
|
||||
var(--mantine-color-pink-6) 100%
|
||||
)
|
||||
border-box;
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .agents-hero__cta-logo {
|
||||
color: var(--mantine-color-blue-3);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .agents-hero__grid-item {
|
||||
background: var(--fullscreen-bg-item);
|
||||
border-color: var(--fullscreen-border-subtle-70);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .agents-hero__grid-icon {
|
||||
color: var(--mantine-color-blue-3);
|
||||
}
|
||||
|
||||
/* ─── agents-rail view transition — slide up ─────────────────────────────── */
|
||||
|
||||
::view-transition-old(agents-rail) {
|
||||
animation: vt-agents-out 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
::view-transition-new(agents-rail) {
|
||||
animation: vt-agents-in 260ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes vt-agents-out {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vt-agents-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
::view-transition-old(agents-rail),
|
||||
::view-transition-new(agents-rail) {
|
||||
animation-duration: 1ms !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
import { useState } from "react";
|
||||
import type { ElementType } from "react";
|
||||
import { Box, Group, Text, UnstyledButton } from "@mantine/core";
|
||||
import TableChartRoundedIcon from "@mui/icons-material/TableChartRounded";
|
||||
import SummarizeRoundedIcon from "@mui/icons-material/SummarizeRounded";
|
||||
import VisibilityOffRoundedIcon from "@mui/icons-material/VisibilityOffRounded";
|
||||
import GavelRoundedIcon from "@mui/icons-material/GavelRounded";
|
||||
import AssignmentRoundedIcon from "@mui/icons-material/AssignmentRounded";
|
||||
import CodeRoundedIcon from "@mui/icons-material/CodeRounded";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||
import { useChat } from "@app/components/chat/ChatContext";
|
||||
import { ChatPanel } from "@app/components/chat/ChatPanel";
|
||||
import { Tooltip as AppTooltip } from "@app/components/shared/Tooltip";
|
||||
import { StirlingLogoOutline } from "@app/components/agents/StirlingLogoOutline";
|
||||
import { withViewTransition } from "@app/utils/viewTransition";
|
||||
import "@app/components/agents/AgentsPanel.css";
|
||||
|
||||
interface ComingSoonAgent {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
Icon: ElementType;
|
||||
}
|
||||
|
||||
const COMING_SOON_AGENTS: ComingSoonAgent[] = [
|
||||
{
|
||||
id: "data-extraction",
|
||||
name: "Data Extraction",
|
||||
description: "Extract tables & structured data",
|
||||
Icon: TableChartRoundedIcon,
|
||||
},
|
||||
{
|
||||
id: "doc-summary",
|
||||
name: "Summariser",
|
||||
description: "Summarise long documents",
|
||||
Icon: SummarizeRoundedIcon,
|
||||
},
|
||||
{
|
||||
id: "auto-redaction",
|
||||
name: "Auto Redaction",
|
||||
description: "Redact PII automatically",
|
||||
Icon: VisibilityOffRoundedIcon,
|
||||
},
|
||||
{
|
||||
id: "compliance",
|
||||
name: "Compliance Check",
|
||||
description: "Audit documents for compliance",
|
||||
Icon: GavelRoundedIcon,
|
||||
},
|
||||
{
|
||||
id: "form-filler",
|
||||
name: "Form Filler",
|
||||
description: "Fill PDF forms intelligently",
|
||||
Icon: AssignmentRoundedIcon,
|
||||
},
|
||||
{
|
||||
id: "pdf-to-markdown",
|
||||
name: "PDF to Markdown",
|
||||
description: "Convert PDFs to clean Markdown",
|
||||
Icon: CodeRoundedIcon,
|
||||
},
|
||||
];
|
||||
|
||||
export function useAgentsEnabled(): boolean {
|
||||
const { config } = useAppConfig();
|
||||
return Boolean(config?.aiEngineEnabled);
|
||||
}
|
||||
|
||||
export function useAgentChatOpen(): boolean {
|
||||
const { isOpen } = useChat();
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
const PREVIEW_COUNT = 3;
|
||||
|
||||
/** Sidebar agents section — Stirling as hero CTA, coming-soon agents below. */
|
||||
export function AgentsSection() {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, setOpen } = useChat();
|
||||
const enabled = useAgentsEnabled();
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
|
||||
if (!enabled || isOpen) return null;
|
||||
|
||||
const comingSoonLabel = t("agents.coming_soon", "Coming soon");
|
||||
const visibleAgents = showAll
|
||||
? COMING_SOON_AGENTS
|
||||
: COMING_SOON_AGENTS.slice(0, PREVIEW_COUNT);
|
||||
|
||||
return (
|
||||
<Box className="agents-section" w="100%">
|
||||
{/* Main Stirling agent — real and clickable */}
|
||||
<UnstyledButton
|
||||
className="agent-button agent-button--hero"
|
||||
onClick={() => withViewTransition(() => setOpen(true))}
|
||||
aria-label={t("agents.stirling_name", "Stirling")}
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap" align="center">
|
||||
<Box className="agent-button__logo">
|
||||
<StirlingLogoOutline size={28} />
|
||||
</Box>
|
||||
<Box style={{ minWidth: 0, flex: 1 }}>
|
||||
<Text size="sm" fw={600} truncate>
|
||||
{t("agents.stirling_name", "Stirling")}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{t(
|
||||
"agents.stirling_description",
|
||||
"Your general-purpose PDF assistant",
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
|
||||
{/* Coming-soon agents */}
|
||||
<div className="agents-sidebar-list">
|
||||
{visibleAgents.map(({ id, name, description, Icon }) => (
|
||||
<AppTooltip
|
||||
key={id}
|
||||
content={comingSoonLabel}
|
||||
position="left"
|
||||
arrow
|
||||
delay={0}
|
||||
>
|
||||
<UnstyledButton
|
||||
className="agent-button agent-button--coming-soon"
|
||||
aria-disabled="true"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap" align="center">
|
||||
<Box className="agent-button__icon-plain">
|
||||
<Icon sx={{ fontSize: "1.1rem" }} />
|
||||
</Box>
|
||||
<Box style={{ minWidth: 0, flex: 1 }}>
|
||||
<Text size="sm" fw={500} truncate>
|
||||
{name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{description}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</AppTooltip>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!showAll ? (
|
||||
<button
|
||||
type="button"
|
||||
className="agents-view-all"
|
||||
onClick={() => withViewTransition(() => setShowAll(true))}
|
||||
>
|
||||
{t("agents.view_all", "View all agents")} →
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="agents-view-all"
|
||||
onClick={() => withViewTransition(() => setShowAll(false))}
|
||||
>
|
||||
{t("agents.show_less", "Show less")} ↑
|
||||
</button>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/** Icon-only agent button in the collapsed (minimised) right rail. */
|
||||
export function AgentsCollapsedButton({ onExpand }: { onExpand: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const { setOpen } = useChat();
|
||||
const enabled = useAgentsEnabled();
|
||||
|
||||
if (!enabled) return null;
|
||||
|
||||
const label = t("agents.stirling_tooltip", "Stirling agent");
|
||||
|
||||
return (
|
||||
<AppTooltip content={label} position="left" arrow delay={300}>
|
||||
<UnstyledButton
|
||||
onClick={() => {
|
||||
onExpand();
|
||||
setOpen(true);
|
||||
}}
|
||||
aria-label={label}
|
||||
className="agents-collapsed-btn"
|
||||
>
|
||||
<StirlingLogoOutline size={22} />
|
||||
</UnstyledButton>
|
||||
</AppTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fullscreen hero card — Stirling CTA on the left, 2×3 coming-soon grid on
|
||||
* the right. Matches the gradient border of the other fullscreen category cards.
|
||||
*/
|
||||
export function AgentsFullscreenSection() {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, setOpen } = useChat();
|
||||
const enabled = useAgentsEnabled();
|
||||
|
||||
if (!enabled || isOpen) return null;
|
||||
|
||||
const comingSoonLabel = t("agents.coming_soon", "Coming soon");
|
||||
|
||||
return (
|
||||
<section
|
||||
className="agents-hero tool-panel__fullscreen-group--agents"
|
||||
aria-label={t("agents.section_title", "Agents")}
|
||||
>
|
||||
<div className="agents-hero__body">
|
||||
{/* Left: Stirling content — only the button is interactive */}
|
||||
<div className="agents-hero__cta">
|
||||
<div className="agents-hero__cta-logo">
|
||||
<StirlingLogoOutline size={36} />
|
||||
</div>
|
||||
<Text className="agents-hero__cta-headline" fw={700} lh={1.2} mt="xs">
|
||||
{t("agents.stirling_full_name", "Stirling General Agent")}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mt={8} lh={1.55}>
|
||||
{t(
|
||||
"agents.stirling_long_description",
|
||||
"General purpose PDF assistant that can run tools, create PDFs and extract insights from your documents.",
|
||||
)}
|
||||
</Text>
|
||||
<button
|
||||
type="button"
|
||||
className="agents-hero__cta-btn"
|
||||
onClick={() => withViewTransition(() => setOpen(true))}
|
||||
>
|
||||
{t("agents.start_chat", "Start chatting")} →
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Right: 2×3 grid of coming-soon agents */}
|
||||
<div className="agents-hero__grid">
|
||||
{COMING_SOON_AGENTS.map(({ id, name, description, Icon }) => (
|
||||
<AppTooltip
|
||||
key={id}
|
||||
content={comingSoonLabel}
|
||||
position="top"
|
||||
arrow
|
||||
delay={0}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="agents-hero__grid-item"
|
||||
aria-disabled="true"
|
||||
>
|
||||
<span className="agents-hero__grid-icon">
|
||||
<Icon sx={{ fontSize: "1rem" }} />
|
||||
</span>
|
||||
<div className="agents-hero__grid-body">
|
||||
<Text size="sm" fw={500} truncate>
|
||||
{name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
</button>
|
||||
</AppTooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/** Full-rail chat overlay rendered inside ToolPanel. */
|
||||
export function AgentsChatOverlay() {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, setOpen } = useChat();
|
||||
const enabled = useAgentsEnabled();
|
||||
|
||||
if (!enabled || !isOpen) return null;
|
||||
|
||||
return (
|
||||
<Box className="agents-takeover">
|
||||
<ChatPanel
|
||||
onBack={() => withViewTransition(() => setOpen(false))}
|
||||
backLabel={t("agents.back_to_tools", "Back to tools")}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* A temp stirling logo, may change in future.
|
||||
*/
|
||||
export function StirlingLogoOutline({ size = 20 }: { size?: number }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 192 192"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M68.48 102.4 L184.73 6.45 L184.73 96.05 L68.48 192 Z"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<path d="M7.26 95.83 L123.37 0 L123.37 89.5 L7.26 185.33 Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useReducer,
|
||||
useCallback,
|
||||
useRef,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useAllFiles, useFileActions } from "@app/contexts/FileContext";
|
||||
import apiClient from "@app/services/apiClient";
|
||||
import { getAuthHeaders } from "@app/services/apiClientSetup";
|
||||
import { createChildStub } from "@app/contexts/file/fileActions";
|
||||
import {
|
||||
createNewStirlingFileStub,
|
||||
createStirlingFile,
|
||||
type StirlingFileStub,
|
||||
} from "@app/types/fileContext";
|
||||
import type { ToolOperation } from "@app/types/file";
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
role: "user" | "assistant";
|
||||
content: string;
|
||||
timestamp: number;
|
||||
/**
|
||||
* Tool endpoint paths executed during this assistant turn (e.g.
|
||||
* {@code /api/v1/general/rotate-pdf}). Populated for assistant messages when the workflow
|
||||
* ran one or more tools, in execution order. Undefined for user messages and for assistant
|
||||
* turns that answered without running any tool.
|
||||
*/
|
||||
toolsUsed?: string[];
|
||||
}
|
||||
|
||||
export enum AiWorkflowPhase {
|
||||
ANALYZING = "analyzing",
|
||||
CALLING_ENGINE = "calling_engine",
|
||||
EXTRACTING_CONTENT = "extracting_content",
|
||||
EXECUTING_TOOL = "executing_tool",
|
||||
PROCESSING = "processing",
|
||||
ENGINE_PROGRESS = "engine_progress",
|
||||
}
|
||||
|
||||
/**
|
||||
* Engine-side progress detail for ENGINE_PROGRESS events. Mirrors the Python
|
||||
* {@code ProgressEvent} discriminated union (engine/src/stirling/contracts/progress.py)
|
||||
* and the Java {@code AiEngineProgressDetail} sealed interface; the {@code phase}
|
||||
* string is the discriminator. Field names are camelCase because the engine
|
||||
* serialises by alias.
|
||||
*/
|
||||
export interface WholeDocReadStartedDetail {
|
||||
phase: "whole_doc_read_started";
|
||||
question: string;
|
||||
pages: number;
|
||||
slices: number;
|
||||
}
|
||||
|
||||
export interface WholeDocSliceDoneDetail {
|
||||
phase: "whole_doc_slice_done";
|
||||
completed: number;
|
||||
total: number;
|
||||
/** Page-range label, e.g. "pages=1-5". */
|
||||
pages: string;
|
||||
durationMs: number;
|
||||
excerpts: number;
|
||||
facts: number;
|
||||
}
|
||||
|
||||
export interface WholeDocCompressionRoundDetail {
|
||||
phase: "whole_doc_compression_round";
|
||||
roundNumber: number;
|
||||
notesIn: number;
|
||||
groups: number;
|
||||
}
|
||||
|
||||
export interface WholeDocReadDoneDetail {
|
||||
phase: "whole_doc_read_done";
|
||||
completed: number;
|
||||
slices: number;
|
||||
durationSeconds: number;
|
||||
}
|
||||
|
||||
export type EngineProgressDetail =
|
||||
| WholeDocReadStartedDetail
|
||||
| WholeDocSliceDoneDetail
|
||||
| WholeDocCompressionRoundDetail
|
||||
| WholeDocReadDoneDetail;
|
||||
|
||||
/**
|
||||
* What we actually carry across the wire boundary: a known typed variant, or a
|
||||
* forward-compat shape with just the discriminator string. The "unknown" arm
|
||||
* exists so a new engine-side phase rolling out before a frontend update keeps
|
||||
* rendering the generic processing message instead of crashing the union.
|
||||
*/
|
||||
export interface UnknownEngineProgressDetail {
|
||||
phase: string;
|
||||
}
|
||||
|
||||
export type AnyEngineProgressDetail =
|
||||
| EngineProgressDetail
|
||||
| UnknownEngineProgressDetail;
|
||||
|
||||
const KNOWN_ENGINE_PHASES = new Set<string>([
|
||||
"whole_doc_read_started",
|
||||
"whole_doc_slice_done",
|
||||
"whole_doc_compression_round",
|
||||
"whole_doc_read_done",
|
||||
]);
|
||||
|
||||
export function isKnownEngineProgressDetail(
|
||||
detail: AnyEngineProgressDetail,
|
||||
): detail is EngineProgressDetail {
|
||||
return KNOWN_ENGINE_PHASES.has(detail.phase);
|
||||
}
|
||||
|
||||
export interface AiWorkflowProgress {
|
||||
phase: AiWorkflowPhase;
|
||||
/** Tool endpoint path currently executing, for EXECUTING_TOOL events. */
|
||||
tool?: string;
|
||||
/** 1-based step index, for EXECUTING_TOOL events. */
|
||||
stepIndex?: number;
|
||||
/** Total number of plan steps, for EXECUTING_TOOL events. */
|
||||
stepCount?: number;
|
||||
/**
|
||||
* Engine-side event payload, for ENGINE_PROGRESS events. Typed sub-phase
|
||||
* record (e.g. {@link WholeDocSliceDoneDetail}) the UI can render in detail.
|
||||
*/
|
||||
engineDetail?: AnyEngineProgressDetail;
|
||||
}
|
||||
|
||||
type AiWorkflowOutcome =
|
||||
| "answer"
|
||||
| "not_found"
|
||||
| "need_content"
|
||||
| "plan"
|
||||
| "need_clarification"
|
||||
| "cannot_do"
|
||||
| "tool_call"
|
||||
| "completed"
|
||||
| "unsupported_capability"
|
||||
| "cannot_continue";
|
||||
|
||||
interface AiWorkflowResultFile {
|
||||
/** Stirling file ID — download with /api/v1/general/files/{fileId}. */
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
interface AiWorkflowResponse {
|
||||
outcome: AiWorkflowOutcome;
|
||||
answer?: string;
|
||||
summary?: string;
|
||||
rationale?: string;
|
||||
reason?: string;
|
||||
question?: string;
|
||||
capability?: string;
|
||||
message?: string;
|
||||
evidence?: Array<{ pageNumber: number; text: string }>;
|
||||
steps?: Array<Record<string, unknown>>;
|
||||
/** Every file produced by the workflow (empty if the outcome has no files). */
|
||||
resultFiles?: AiWorkflowResultFile[];
|
||||
// Legacy single-file fields — mirror the first entry of resultFiles.
|
||||
fileId?: string;
|
||||
fileName?: string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
interface ChatState {
|
||||
messages: ChatMessage[];
|
||||
isOpen: boolean;
|
||||
isLoading: boolean;
|
||||
progress: AiWorkflowProgress | null;
|
||||
}
|
||||
|
||||
type ChatAction =
|
||||
| { type: "ADD_MESSAGE"; message: ChatMessage }
|
||||
| { type: "SET_LOADING"; loading: boolean }
|
||||
| { type: "SET_PROGRESS"; progress: AiWorkflowProgress | null }
|
||||
| { type: "TOGGLE_OPEN" }
|
||||
| { type: "SET_OPEN"; open: boolean }
|
||||
| { type: "CLEAR" };
|
||||
|
||||
function chatReducer(state: ChatState, action: ChatAction): ChatState {
|
||||
switch (action.type) {
|
||||
case "ADD_MESSAGE":
|
||||
return { ...state, messages: [...state.messages, action.message] };
|
||||
case "SET_LOADING":
|
||||
return { ...state, isLoading: action.loading };
|
||||
case "SET_PROGRESS":
|
||||
return { ...state, progress: action.progress };
|
||||
case "TOGGLE_OPEN":
|
||||
return { ...state, isOpen: !state.isOpen };
|
||||
case "SET_OPEN":
|
||||
return { ...state, isOpen: action.open };
|
||||
case "CLEAR":
|
||||
return { ...state, messages: [], isLoading: false, progress: null };
|
||||
}
|
||||
}
|
||||
|
||||
function formatWorkflowResponse(data: AiWorkflowResponse): string {
|
||||
switch (data.outcome) {
|
||||
case "answer":
|
||||
case "completed":
|
||||
return data.answer ?? data.summary ?? "Done.";
|
||||
case "need_clarification":
|
||||
return data.question ?? "Could you clarify your request?";
|
||||
case "cannot_do":
|
||||
return data.reason ?? "I'm unable to do that.";
|
||||
case "not_found":
|
||||
return data.reason ?? "I couldn't find the requested information.";
|
||||
case "unsupported_capability":
|
||||
return (
|
||||
data.message ??
|
||||
`Unsupported capability: ${data.capability ?? "unknown"}`
|
||||
);
|
||||
case "cannot_continue":
|
||||
return data.reason ?? "Something went wrong and I can't continue.";
|
||||
case "plan":
|
||||
return data.rationale
|
||||
? `${data.rationale}\n\n${(data.steps ?? []).map((s, i) => `${i + 1}. ${JSON.stringify(s)}`).join("\n")}`
|
||||
: JSON.stringify(data.steps, null, 2);
|
||||
case "need_content":
|
||||
case "tool_call":
|
||||
return (
|
||||
data.rationale ?? data.summary ?? `Processing (${data.outcome})...`
|
||||
);
|
||||
default:
|
||||
return (
|
||||
data.answer ?? data.summary ?? data.message ?? JSON.stringify(data)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an SSE text stream and invokes callbacks for each named event.
|
||||
*/
|
||||
interface ProgressEvent {
|
||||
phase: string;
|
||||
timestamp: number;
|
||||
tool?: string;
|
||||
stepIndex?: number;
|
||||
stepCount?: number;
|
||||
engineDetail?: AnyEngineProgressDetail;
|
||||
}
|
||||
|
||||
async function consumeSSEStream(
|
||||
response: Response,
|
||||
handlers: {
|
||||
onProgress: (data: ProgressEvent) => void;
|
||||
onResult: (data: AiWorkflowResponse) => void;
|
||||
onError: (data: { message: string }) => void;
|
||||
},
|
||||
) {
|
||||
if (!response.body) {
|
||||
throw new Error("Response body is null");
|
||||
}
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
let currentEvent = "";
|
||||
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
// SSE frames are separated by double newlines
|
||||
let boundary = buffer.indexOf("\n\n");
|
||||
while (boundary !== -1) {
|
||||
const frame = buffer.slice(0, boundary);
|
||||
buffer = buffer.slice(boundary + 2);
|
||||
|
||||
let dataPayload = "";
|
||||
for (const line of frame.split("\n")) {
|
||||
if (line.startsWith("event:")) {
|
||||
currentEvent = line.slice(6).trim();
|
||||
} else if (line.startsWith("data:")) {
|
||||
dataPayload += line.slice(5);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataPayload) {
|
||||
try {
|
||||
const parsed = JSON.parse(dataPayload);
|
||||
if (currentEvent === "progress") {
|
||||
handlers.onProgress(parsed);
|
||||
} else if (currentEvent === "result") {
|
||||
handlers.onResult(parsed);
|
||||
} else if (currentEvent === "error") {
|
||||
handlers.onError(parsed);
|
||||
}
|
||||
} catch {
|
||||
// Skip malformed JSON frames
|
||||
}
|
||||
}
|
||||
currentEvent = "";
|
||||
boundary = buffer.indexOf("\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ChatContextValue {
|
||||
messages: ChatMessage[];
|
||||
isOpen: boolean;
|
||||
isLoading: boolean;
|
||||
progress: AiWorkflowProgress | null;
|
||||
toggleOpen: () => void;
|
||||
setOpen: (open: boolean) => void;
|
||||
sendMessage: (content: string) => Promise<void>;
|
||||
/** Abort any in-flight request and reset the chat to an empty conversation. */
|
||||
clearChat: () => void;
|
||||
}
|
||||
|
||||
const ChatContext = createContext<ChatContextValue | null>(null);
|
||||
|
||||
const initialState: ChatState = {
|
||||
messages: [],
|
||||
isOpen: false,
|
||||
isLoading: false,
|
||||
progress: null,
|
||||
};
|
||||
|
||||
export function ChatProvider({ children }: { children: ReactNode }) {
|
||||
const [state, dispatch] = useReducer(chatReducer, initialState);
|
||||
const { files: activeFiles, fileStubs: activeFileStubs } = useAllFiles();
|
||||
const { actions: fileActions } = useFileActions();
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
const messagesRef = useRef<ChatMessage[]>(state.messages);
|
||||
messagesRef.current = state.messages;
|
||||
|
||||
// Download a File from the Stirling files endpoint.
|
||||
const downloadFile = useCallback(
|
||||
async (descriptor: AiWorkflowResultFile): Promise<File> => {
|
||||
const response = await apiClient.get<Blob>(
|
||||
`/api/v1/general/files/${descriptor.fileId}`,
|
||||
{ responseType: "blob" },
|
||||
);
|
||||
return new File([response.data], descriptor.fileName, {
|
||||
type: descriptor.contentType ?? response.data.type,
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Import the files produced by an AI workflow result into FileContext.
|
||||
//
|
||||
// If the workflow produced the same number of outputs as inputs, map each output to its
|
||||
// corresponding input as a new version in the same chain. Otherwise (merge, split, etc.)
|
||||
// add the outputs as new root files.
|
||||
const importResultFile = useCallback(
|
||||
async (
|
||||
result: AiWorkflowResponse,
|
||||
sourceStubs: StirlingFileStub[],
|
||||
): Promise<void> => {
|
||||
const descriptors = result.resultFiles?.length
|
||||
? result.resultFiles
|
||||
: result.fileId && result.fileName && result.contentType
|
||||
? [
|
||||
{
|
||||
fileId: result.fileId,
|
||||
fileName: result.fileName,
|
||||
contentType: result.contentType,
|
||||
} satisfies AiWorkflowResultFile,
|
||||
]
|
||||
: [];
|
||||
if (descriptors.length === 0) return;
|
||||
|
||||
const files = await Promise.all(descriptors.map(downloadFile));
|
||||
|
||||
const operation: ToolOperation = {
|
||||
toolId: "ai-workflow",
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
const isVersionMapping =
|
||||
sourceStubs.length > 0 && files.length === sourceStubs.length;
|
||||
const stubs = files.map((file, i) =>
|
||||
isVersionMapping
|
||||
? createChildStub(sourceStubs[i], operation, file)
|
||||
: createNewStirlingFileStub(file),
|
||||
);
|
||||
const stirlingFiles = files.map((file, i) =>
|
||||
createStirlingFile(file, stubs[i].id),
|
||||
);
|
||||
|
||||
if (sourceStubs.length > 0) {
|
||||
// Always consume the inputs so merge/split inputs are removed from the workbench.
|
||||
// For 1:1 operations (rotate, compress) the outputs carry the version chain; for
|
||||
// merge/split they're fresh roots.
|
||||
await fileActions.consumeFiles(
|
||||
sourceStubs.map((s) => s.id),
|
||||
stirlingFiles,
|
||||
stubs,
|
||||
);
|
||||
} else {
|
||||
// No inputs were provided (unlikely for completed workflows, but handle it safely).
|
||||
await fileActions.addFiles(files, { selectFiles: true });
|
||||
}
|
||||
},
|
||||
[fileActions, downloadFile],
|
||||
);
|
||||
|
||||
const toggleOpen = useCallback(() => dispatch({ type: "TOGGLE_OPEN" }), []);
|
||||
const setOpen = useCallback(
|
||||
(open: boolean) => dispatch({ type: "SET_OPEN", open }),
|
||||
[],
|
||||
);
|
||||
const clearChat = useCallback(() => {
|
||||
abortRef.current?.abort();
|
||||
abortRef.current = null;
|
||||
dispatch({ type: "CLEAR" });
|
||||
}, []);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async (content: string) => {
|
||||
// Abort any in-flight request
|
||||
abortRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
|
||||
const priorMessages = messagesRef.current;
|
||||
|
||||
const userMessage: ChatMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
role: "user",
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
dispatch({ type: "ADD_MESSAGE", message: userMessage });
|
||||
dispatch({ type: "SET_LOADING", loading: true });
|
||||
dispatch({ type: "SET_PROGRESS", progress: null });
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("userMessage", content);
|
||||
activeFiles.forEach((file, i) => {
|
||||
formData.append(`fileInputs[${i}].fileInput`, file);
|
||||
});
|
||||
priorMessages.forEach((message, i) => {
|
||||
formData.append(`conversationHistory[${i}].role`, message.role);
|
||||
formData.append(`conversationHistory[${i}].content`, message.content);
|
||||
});
|
||||
|
||||
const response = await fetch("/api/v1/ai/orchestrate/stream", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: getAuthHeaders(),
|
||||
credentials: "include",
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`AI engine request failed: ${response.status}`);
|
||||
}
|
||||
|
||||
let receivedResult = false;
|
||||
const toolsUsed: string[] = [];
|
||||
|
||||
await consumeSSEStream(response, {
|
||||
onProgress: (data) => {
|
||||
if (
|
||||
data.phase === AiWorkflowPhase.EXECUTING_TOOL &&
|
||||
typeof data.tool === "string"
|
||||
) {
|
||||
toolsUsed.push(data.tool);
|
||||
}
|
||||
dispatch({
|
||||
type: "SET_PROGRESS",
|
||||
progress: {
|
||||
phase: data.phase as AiWorkflowPhase,
|
||||
tool: data.tool,
|
||||
stepIndex: data.stepIndex,
|
||||
stepCount: data.stepCount,
|
||||
engineDetail: data.engineDetail,
|
||||
},
|
||||
});
|
||||
},
|
||||
onResult: (data) => {
|
||||
receivedResult = true;
|
||||
dispatch({ type: "SET_PROGRESS", progress: null });
|
||||
const replyContent = formatWorkflowResponse(data);
|
||||
dispatch({
|
||||
type: "ADD_MESSAGE",
|
||||
message: {
|
||||
id: crypto.randomUUID(),
|
||||
role: "assistant",
|
||||
content: replyContent,
|
||||
timestamp: Date.now(),
|
||||
toolsUsed: toolsUsed.length > 0 ? toolsUsed : undefined,
|
||||
},
|
||||
});
|
||||
if (data.fileId || data.resultFiles?.length) {
|
||||
importResultFile(data, activeFileStubs).catch((err) => {
|
||||
console.error("Failed to import AI result file", err);
|
||||
dispatch({
|
||||
type: "ADD_MESSAGE",
|
||||
message: {
|
||||
id: crypto.randomUUID(),
|
||||
role: "assistant",
|
||||
content:
|
||||
"The file was processed but I couldn't download it.",
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (data) => {
|
||||
receivedResult = true;
|
||||
dispatch({ type: "SET_PROGRESS", progress: null });
|
||||
dispatch({
|
||||
type: "ADD_MESSAGE",
|
||||
message: {
|
||||
id: crypto.randomUUID(),
|
||||
role: "assistant",
|
||||
content: data.message || "Something went wrong.",
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!receivedResult) {
|
||||
throw new Error("Stream ended without a result");
|
||||
}
|
||||
} catch (e) {
|
||||
if ((e as Error).name === "AbortError") return;
|
||||
dispatch({ type: "SET_PROGRESS", progress: null });
|
||||
dispatch({
|
||||
type: "ADD_MESSAGE",
|
||||
message: {
|
||||
id: crypto.randomUUID(),
|
||||
role: "assistant",
|
||||
content:
|
||||
"Failed to get a response. The AI engine may not be available yet.",
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
dispatch({ type: "SET_LOADING", loading: false });
|
||||
if (abortRef.current === controller) {
|
||||
abortRef.current = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
[activeFiles, activeFileStubs, importResultFile],
|
||||
);
|
||||
|
||||
return (
|
||||
<ChatContext.Provider
|
||||
value={{
|
||||
messages: state.messages,
|
||||
isOpen: state.isOpen,
|
||||
isLoading: state.isLoading,
|
||||
progress: state.progress,
|
||||
toggleOpen,
|
||||
setOpen,
|
||||
sendMessage,
|
||||
clearChat,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ChatContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useChat(): ChatContextValue {
|
||||
const context = useContext(ChatContext);
|
||||
if (!context) {
|
||||
throw new Error("useChat must be used within a ChatProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
.chat-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Match the right-rail toolbar background so dark-mode doesn't show a
|
||||
lighter card behind the chat, and so the agent-card → pill morph
|
||||
doesn't flash a different colour mid-transition. */
|
||||
background: var(--bg-toolbar);
|
||||
}
|
||||
|
||||
.chat-panel--embedded {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Top header: agent pill + close. */
|
||||
.chat-panel__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem 1rem 0.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-panel__agent-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0.4rem 0.75rem 0.4rem 0.4rem;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 9999px;
|
||||
background: var(--mantine-color-body);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 120ms ease-out,
|
||||
border-color 120ms ease-out;
|
||||
/* Pair with .agent-button to morph between the card and the pill. */
|
||||
view-transition-name: stirling-agent;
|
||||
}
|
||||
|
||||
/* Match the active-tool morph timing for a consistent rail feel. */
|
||||
::view-transition-group(stirling-agent),
|
||||
::view-transition-old(stirling-agent),
|
||||
::view-transition-new(stirling-agent) {
|
||||
animation-duration: 220ms;
|
||||
animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
|
||||
.chat-panel__agent-pill:hover {
|
||||
background: var(--mantine-color-default-hover);
|
||||
}
|
||||
|
||||
.chat-panel__agent-pill-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 9999px;
|
||||
background: var(--mantine-color-blue-light);
|
||||
color: var(--mantine-color-blue-filled);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Dark-mode: let the pill blend into the rail. Just a thin border, no fill —
|
||||
so it doesn't read as a clashing lighter card on the dark toolbar. */
|
||||
[data-mantine-color-scheme="dark"] .chat-panel__agent-pill {
|
||||
background: transparent;
|
||||
border-color: var(--border-subtle);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .chat-panel__agent-pill:hover {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .chat-panel__agent-pill-icon {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-blue-filled) 18%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mantine-color-blue-3, var(--mantine-color-blue-filled));
|
||||
}
|
||||
|
||||
/* Same treatment for the input pill and quick-action cards. */
|
||||
[data-mantine-color-scheme="dark"] .chat-panel-input {
|
||||
background: transparent;
|
||||
box-shadow:
|
||||
0 0 0 1px var(--border-subtle),
|
||||
0 6px 16px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .chat-panel-input:focus-within {
|
||||
box-shadow:
|
||||
0 0 0 1px color-mix(in srgb, var(--mantine-color-blue-3) 40%, transparent),
|
||||
0 8px 22px color-mix(in srgb, var(--mantine-color-blue-6) 18%, transparent);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .chat-quick-action {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .chat-quick-action:hover {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-color: var(--border-subtle);
|
||||
}
|
||||
|
||||
.chat-panel__agent-pill-label {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-panel-messages {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Quick action cards above the input. */
|
||||
.chat-panel__quick-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 0.75rem 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-panel__quick-actions-label {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
/* File pills shown above the quick actions when files are loaded. */
|
||||
.chat-file-pills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chat-file-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
max-width: 11rem;
|
||||
padding: 0.2rem 0.35rem 0.2rem 0.45rem;
|
||||
border: 1px solid var(--mantine-color-blue-light);
|
||||
border-radius: 9999px;
|
||||
background: var(--mantine-color-blue-light);
|
||||
color: var(--mantine-color-blue-filled);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.chat-file-pill__icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-file-pill__label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-file-pill__remove {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
transition:
|
||||
background 120ms ease-out,
|
||||
opacity 120ms ease-out;
|
||||
}
|
||||
|
||||
.chat-file-pill__remove:hover {
|
||||
background: var(--mantine-color-default-hover);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.chat-file-pill--more {
|
||||
background: transparent;
|
||||
border: 1px dashed var(--border-subtle);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0.55rem;
|
||||
}
|
||||
|
||||
.chat-file-pill--more:hover {
|
||||
background: var(--mantine-color-default-hover);
|
||||
}
|
||||
|
||||
.chat-quick-action {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 0.65rem;
|
||||
background: var(--mantine-color-body);
|
||||
transition:
|
||||
background 120ms ease-out,
|
||||
border-color 120ms ease-out;
|
||||
}
|
||||
|
||||
.chat-quick-action:hover {
|
||||
background: var(--mantine-color-default-hover);
|
||||
border-color: var(--mantine-color-default-border);
|
||||
}
|
||||
|
||||
.chat-quick-action__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--mantine-color-blue-light);
|
||||
color: var(--mantine-color-blue-filled);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Input area: textarea on top row, action icons on bottom row. */
|
||||
.chat-panel-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.85rem 1rem 0.75rem;
|
||||
margin: 0.75rem 0.75rem 1rem;
|
||||
border: none;
|
||||
border-radius: 1.1rem;
|
||||
background: var(--mantine-color-body);
|
||||
flex-shrink: 0;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0, 0, 0, 0.04),
|
||||
0 4px 14px rgba(15, 23, 42, 0.08);
|
||||
transition: box-shadow 160ms ease-out;
|
||||
}
|
||||
|
||||
.chat-panel-input:focus-within {
|
||||
box-shadow:
|
||||
0 0 0 1px color-mix(in srgb, var(--mantine-color-blue-6) 20%, transparent),
|
||||
0 6px 18px color-mix(in srgb, var(--mantine-color-blue-6) 12%, transparent);
|
||||
}
|
||||
|
||||
/* Kill the Mantine Textarea's own border/outline — the wrapper owns the chrome. */
|
||||
.chat-panel-input__field,
|
||||
.chat-panel-input__field:focus,
|
||||
.chat-panel-input__field:focus-visible {
|
||||
font-size: 0.95rem;
|
||||
padding: 0.15rem 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.chat-panel-input__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Message layout */
|
||||
.chat-message {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chat-message-user {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.chat-message-assistant {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Bubble styling */
|
||||
.chat-bubble {
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
.chat-bubble-user {
|
||||
background: var(--mantine-color-blue-filled) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.chat-bubble-user * {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.chat-bubble-assistant {
|
||||
background: var(--mantine-color-default-hover) !important;
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
import {
|
||||
useMemo,
|
||||
useRef,
|
||||
useEffect,
|
||||
useState,
|
||||
type KeyboardEvent,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Collapse,
|
||||
Group,
|
||||
List,
|
||||
Loader,
|
||||
Menu,
|
||||
Paper,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Text,
|
||||
Textarea,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
import {
|
||||
useChat,
|
||||
AiWorkflowPhase,
|
||||
isKnownEngineProgressDetail,
|
||||
type AiWorkflowProgress,
|
||||
type AnyEngineProgressDetail,
|
||||
} from "@app/components/chat/ChatContext";
|
||||
import { useTranslatedToolCatalog } from "@app/data/useTranslatedToolRegistry";
|
||||
import { StirlingLogoOutline } from "@app/components/agents/StirlingLogoOutline";
|
||||
import { ChatQuickActions } from "@app/components/chat/ChatQuickActions";
|
||||
import "@app/components/chat/ChatPanel.css";
|
||||
|
||||
type TranslateFn = (key: string, options?: Record<string, unknown>) => string;
|
||||
|
||||
/** Resolver mapping a tool endpoint path to its translated display name. */
|
||||
type ToolNameResolver = (endpoint: string) => string | null;
|
||||
|
||||
/**
|
||||
* Look up a tool's translated name from the tool catalog. The catalog's {@code operationConfig}
|
||||
* exposes the full API endpoint path for each tool, so we key the lookup on the exact path that
|
||||
* arrives in SSE progress events — no string parsing.
|
||||
*/
|
||||
function useToolNameResolver(): ToolNameResolver {
|
||||
const { allTools } = useTranslatedToolCatalog();
|
||||
return useMemo(() => {
|
||||
const nameByEndpoint = new Map<string, string>();
|
||||
Object.values(allTools).forEach((tool) => {
|
||||
const endpoint = tool.operationConfig?.endpoint;
|
||||
if (typeof endpoint === "string") {
|
||||
nameByEndpoint.set(endpoint, tool.name);
|
||||
}
|
||||
});
|
||||
return (endpoint: string) => nameByEndpoint.get(endpoint) ?? null;
|
||||
}, [allTools]);
|
||||
}
|
||||
|
||||
function formatProgress(
|
||||
progress: AiWorkflowProgress,
|
||||
t: TranslateFn,
|
||||
resolveToolName: ToolNameResolver,
|
||||
): string {
|
||||
if (progress.phase === AiWorkflowPhase.EXECUTING_TOOL && progress.tool) {
|
||||
const tool = resolveToolName(progress.tool);
|
||||
const hasSteps =
|
||||
progress.stepIndex != null &&
|
||||
progress.stepCount != null &&
|
||||
progress.stepCount > 1;
|
||||
if (tool) {
|
||||
return hasSteps
|
||||
? t("chat.progress.executing_tool_step", {
|
||||
tool,
|
||||
step: progress.stepIndex,
|
||||
total: progress.stepCount,
|
||||
})
|
||||
: t("chat.progress.executing_tool_single", { tool });
|
||||
}
|
||||
return hasSteps
|
||||
? t("chat.progress.executing_tool_generic_step", {
|
||||
step: progress.stepIndex,
|
||||
total: progress.stepCount,
|
||||
})
|
||||
: t("chat.progress.executing_tool_generic");
|
||||
}
|
||||
if (progress.phase === AiWorkflowPhase.ENGINE_PROGRESS) {
|
||||
return formatEngineProgress(progress.engineDetail, t);
|
||||
}
|
||||
return t(`chat.progress.${progress.phase}`);
|
||||
}
|
||||
|
||||
function formatEngineProgress(
|
||||
detail: AnyEngineProgressDetail | undefined,
|
||||
t: TranslateFn,
|
||||
): string {
|
||||
if (!detail || !isKnownEngineProgressDetail(detail)) {
|
||||
return t("chat.progress.processing");
|
||||
}
|
||||
switch (detail.phase) {
|
||||
case "whole_doc_read_started":
|
||||
return t("chat.progress.whole_doc_read_started");
|
||||
case "whole_doc_slice_done": {
|
||||
const percent =
|
||||
detail.total > 0
|
||||
? Math.round((detail.completed / detail.total) * 100)
|
||||
: 0;
|
||||
return t("chat.progress.whole_doc_slice_done", { percent });
|
||||
}
|
||||
case "whole_doc_compression_round":
|
||||
return t("chat.progress.whole_doc_compression_round");
|
||||
case "whole_doc_read_done":
|
||||
return t("chat.progress.whole_doc_read_done");
|
||||
}
|
||||
}
|
||||
|
||||
function ToolsUsedBlock({
|
||||
tools,
|
||||
resolveToolName,
|
||||
t,
|
||||
}: {
|
||||
tools: string[];
|
||||
resolveToolName: ToolNameResolver;
|
||||
t: TranslateFn;
|
||||
}) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const names = tools.map(
|
||||
(endpoint) => resolveToolName(endpoint) ?? t("chat.toolsUsed.unknownTool"),
|
||||
);
|
||||
const label = t("chat.toolsUsed.summary", { count: tools.length });
|
||||
return (
|
||||
<Box mt={6}>
|
||||
<UnstyledButton
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
aria-expanded={expanded}
|
||||
>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
{expanded ? (
|
||||
<ExpandLessIcon sx={{ fontSize: 14 }} />
|
||||
) : (
|
||||
<ExpandMoreIcon sx={{ fontSize: 14 }} />
|
||||
)}
|
||||
<Text size="xs" c="dimmed">
|
||||
{label}
|
||||
</Text>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
<Collapse in={expanded}>
|
||||
<List
|
||||
type="ordered"
|
||||
size="xs"
|
||||
mt={4}
|
||||
pl="lg"
|
||||
styles={{ itemWrapper: { lineHeight: 1.4 } }}
|
||||
>
|
||||
{names.map((name, i) => (
|
||||
<List.Item key={i}>{name}</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ChatMessageBubble({
|
||||
role,
|
||||
content,
|
||||
toolsUsed,
|
||||
resolveToolName,
|
||||
t,
|
||||
}: {
|
||||
role: "user" | "assistant";
|
||||
content: string;
|
||||
toolsUsed?: string[];
|
||||
resolveToolName: ToolNameResolver;
|
||||
t: TranslateFn;
|
||||
}) {
|
||||
return (
|
||||
<div className={`chat-message chat-message-${role}`}>
|
||||
<Paper className={`chat-bubble chat-bubble-${role}`} p="xs" radius="md">
|
||||
<Text size="sm" style={{ whiteSpace: "pre-wrap" }}>
|
||||
{content}
|
||||
</Text>
|
||||
{toolsUsed && toolsUsed.length > 0 && (
|
||||
<ToolsUsedBlock
|
||||
tools={toolsUsed}
|
||||
resolveToolName={resolveToolName}
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ChatPanelProps {
|
||||
/** Called when the user closes the chat to return to the tool list. */
|
||||
onBack: () => void;
|
||||
/** Accessible label for the close button. */
|
||||
backLabel: string;
|
||||
}
|
||||
|
||||
export function ChatPanel({ onBack, backLabel }: ChatPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const { messages, isLoading, progress, sendMessage, clearChat } = useChat();
|
||||
const resolveToolName = useToolNameResolver();
|
||||
const [input, setInput] = useState("");
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTo({
|
||||
top: scrollRef.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleSend = (override?: string) => {
|
||||
const text = (override ?? input).trim();
|
||||
if (!text || isLoading) return;
|
||||
setInput("");
|
||||
sendMessage(text);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
};
|
||||
|
||||
const showQuickActions = messages.length === 0 && !isLoading;
|
||||
|
||||
return (
|
||||
<Box className="chat-panel chat-panel--embedded">
|
||||
<div className="chat-panel__header">
|
||||
<Menu shadow="md" width={220} position="bottom-start" withinPortal>
|
||||
<Menu.Target>
|
||||
<button
|
||||
type="button"
|
||||
className="chat-panel__agent-pill"
|
||||
aria-label={t("chat.header.agentMenu", "Stirling agent options")}
|
||||
>
|
||||
<span className="chat-panel__agent-pill-icon">
|
||||
<StirlingLogoOutline size={16} />
|
||||
</span>
|
||||
<span className="chat-panel__agent-pill-label">
|
||||
{t("agents.stirling_name", "Stirling")}
|
||||
</span>
|
||||
<KeyboardArrowDownIcon
|
||||
sx={{ fontSize: 18, color: "var(--text-muted)" }}
|
||||
/>
|
||||
</button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
leftSection={<DeleteSweepIcon sx={{ fontSize: 18 }} />}
|
||||
onClick={clearChat}
|
||||
disabled={messages.length === 0 && !isLoading}
|
||||
>
|
||||
{t("chat.header.clearChat", "Clear chat")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="md"
|
||||
radius="xl"
|
||||
onClick={onBack}
|
||||
aria-label={backLabel}
|
||||
>
|
||||
<CloseIcon sx={{ fontSize: 18 }} />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="chat-panel-messages" viewportRef={scrollRef}>
|
||||
<Stack gap="sm" p="sm">
|
||||
{messages.map((msg) => (
|
||||
<ChatMessageBubble
|
||||
key={msg.id}
|
||||
role={msg.role}
|
||||
content={msg.content}
|
||||
toolsUsed={msg.toolsUsed}
|
||||
resolveToolName={resolveToolName}
|
||||
t={t}
|
||||
/>
|
||||
))}
|
||||
{isLoading && (
|
||||
<div className="chat-message chat-message-assistant">
|
||||
<Paper
|
||||
className="chat-bubble chat-bubble-assistant"
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Loader size="xs" type="dots" />
|
||||
<Text size="sm" c="dimmed">
|
||||
{progress
|
||||
? formatProgress(progress, t, resolveToolName)
|
||||
: t("chat.progress.thinking")}
|
||||
</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
|
||||
{showQuickActions && (
|
||||
<ChatQuickActions
|
||||
heading={t("chat.quickActions.heading", "Get started")}
|
||||
onAction={(text) => handleSend(text)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="chat-panel-input">
|
||||
<Textarea
|
||||
ref={inputRef}
|
||||
placeholder={t("chat.input.placeholder", "What do you want to do?")}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.currentTarget.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={isLoading}
|
||||
autosize
|
||||
minRows={1}
|
||||
maxRows={4}
|
||||
variant="unstyled"
|
||||
classNames={{ input: "chat-panel-input__field" }}
|
||||
/>
|
||||
<div className="chat-panel-input__actions">
|
||||
<ActionIcon
|
||||
variant="filled"
|
||||
color="blue"
|
||||
radius="xl"
|
||||
size="md"
|
||||
onClick={() => handleSend()}
|
||||
disabled={!input.trim() || isLoading}
|
||||
aria-label={t("chat.input.send", "Send message")}
|
||||
>
|
||||
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Box, Group, Stack, Text, UnstyledButton } from "@mantine/core";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import CompressIcon from "@mui/icons-material/Compress";
|
||||
import ContentCutIcon from "@mui/icons-material/ContentCut";
|
||||
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFileOutlined";
|
||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
import LayersIcon from "@mui/icons-material/Layers";
|
||||
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
||||
import RotateRightIcon from "@mui/icons-material/RotateRight";
|
||||
import UploadFileIcon from "@mui/icons-material/UploadFile";
|
||||
import { useAllFiles, useFileActions } from "@app/contexts/FileContext";
|
||||
import { useFilesModalContext } from "@app/contexts/FilesModalContext";
|
||||
import { detectFileExtension, isPdfFile } from "@app/utils/fileUtils";
|
||||
import type { StirlingFileStub } from "@app/types/fileContext";
|
||||
|
||||
const MAX_FILE_PILLS = 3;
|
||||
|
||||
interface QuickAction {
|
||||
key: string;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
function QuickActionCard({ action }: { action: QuickAction }) {
|
||||
return (
|
||||
<UnstyledButton
|
||||
className="chat-quick-action"
|
||||
onClick={action.onClick}
|
||||
aria-label={action.title}
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap" align="center">
|
||||
<Box className="chat-quick-action__icon">{action.icon}</Box>
|
||||
<Box style={{ minWidth: 0, flex: 1 }}>
|
||||
<Text size="sm" fw={500}>
|
||||
{action.title}
|
||||
</Text>
|
||||
{action.subtitle && (
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{action.subtitle}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<KeyboardArrowDownIcon
|
||||
sx={{
|
||||
fontSize: 18,
|
||||
transform: "rotate(-90deg)",
|
||||
color: "var(--text-muted)",
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
);
|
||||
}
|
||||
|
||||
function WorkbenchFilePills({
|
||||
stubs,
|
||||
onOpenFilesModal,
|
||||
onRemove,
|
||||
moreLabel,
|
||||
removeLabel,
|
||||
}: {
|
||||
stubs: StirlingFileStub[];
|
||||
onOpenFilesModal: () => void;
|
||||
onRemove: (id: StirlingFileStub["id"]) => void;
|
||||
moreLabel: (count: number) => string;
|
||||
removeLabel: (name: string) => string;
|
||||
}) {
|
||||
const visible = stubs.slice(0, MAX_FILE_PILLS);
|
||||
const overflow = Math.max(0, stubs.length - visible.length);
|
||||
return (
|
||||
<div className="chat-file-pills">
|
||||
{visible.map((stub) => (
|
||||
<span key={stub.id} className="chat-file-pill">
|
||||
<InsertDriveFileIcon
|
||||
className="chat-file-pill__icon"
|
||||
sx={{ fontSize: 14 }}
|
||||
/>
|
||||
<span className="chat-file-pill__label" title={stub.name}>
|
||||
{stub.name}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="chat-file-pill__remove"
|
||||
onClick={() => onRemove(stub.id)}
|
||||
aria-label={removeLabel(stub.name)}
|
||||
>
|
||||
<CloseIcon sx={{ fontSize: 12 }} />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
{overflow > 0 && (
|
||||
<UnstyledButton
|
||||
className="chat-file-pill chat-file-pill--more"
|
||||
onClick={onOpenFilesModal}
|
||||
>
|
||||
{moreLabel(overflow)}
|
||||
</UnstyledButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface WorkbenchSummary {
|
||||
fileCount: number;
|
||||
pdfCount: number;
|
||||
nonPdfCount: number;
|
||||
hasNonPdf: boolean;
|
||||
singleFilePageCount: number | null;
|
||||
typeBreakdown: { label: string; count: number }[];
|
||||
}
|
||||
|
||||
function summariseWorkbench(stubs: StirlingFileStub[]): WorkbenchSummary {
|
||||
const counts = new Map<string, number>();
|
||||
let pdfCount = 0;
|
||||
let nonPdfCount = 0;
|
||||
|
||||
for (const stub of stubs) {
|
||||
const ext = detectFileExtension(stub.name ?? "");
|
||||
const isPdf = isPdfFile({ name: stub.name, type: stub.type });
|
||||
if (isPdf) pdfCount += 1;
|
||||
else nonPdfCount += 1;
|
||||
const label = ext ? ext.toUpperCase() : "FILE";
|
||||
counts.set(label, (counts.get(label) ?? 0) + 1);
|
||||
}
|
||||
|
||||
const typeBreakdown = Array.from(counts.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([label, count]) => ({ label, count }));
|
||||
|
||||
return {
|
||||
fileCount: stubs.length,
|
||||
pdfCount,
|
||||
nonPdfCount,
|
||||
hasNonPdf: nonPdfCount > 0,
|
||||
singleFilePageCount:
|
||||
stubs.length === 1 ? (stubs[0].processedFile?.totalPages ?? null) : null,
|
||||
typeBreakdown,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChatQuickActionsProps {
|
||||
/** Heading text shown above the actions. */
|
||||
heading: string;
|
||||
/** Invoked when the user selects an action — sends the given text as a chat message. */
|
||||
onAction: (text: string) => void;
|
||||
}
|
||||
|
||||
export function ChatQuickActions({ heading, onAction }: ChatQuickActionsProps) {
|
||||
const { t } = useTranslation();
|
||||
const { fileStubs } = useAllFiles();
|
||||
const { actions: fileActions } = useFileActions();
|
||||
const { openFilesModal } = useFilesModalContext();
|
||||
|
||||
const summary = useMemo(() => summariseWorkbench(fileStubs), [fileStubs]);
|
||||
|
||||
const actions = useMemo<QuickAction[]>(() => {
|
||||
const send = (text: string) => () => onAction(text);
|
||||
|
||||
if (summary.fileCount === 0) {
|
||||
return [
|
||||
{
|
||||
key: "open-from-computer",
|
||||
icon: <UploadFileIcon sx={{ fontSize: 18 }} />,
|
||||
title: t("chat.quickActions.openFromComputer", "Open from computer"),
|
||||
subtitle: t("chat.quickActions.browseYourFiles", "Browse your files"),
|
||||
onClick: () => openFilesModal(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (summary.fileCount === 1) {
|
||||
// Non-PDF: only suggest converting to PDF.
|
||||
if (summary.hasNonPdf) {
|
||||
const text = t(
|
||||
"chat.quickActions.convertOne",
|
||||
"Convert this document to PDF",
|
||||
);
|
||||
return [
|
||||
{
|
||||
key: "convert",
|
||||
icon: <PictureAsPdfIcon sx={{ fontSize: 18 }} />,
|
||||
title: text,
|
||||
onClick: send(text),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const result: QuickAction[] = [];
|
||||
const hasMultiplePages =
|
||||
summary.singleFilePageCount != null && summary.singleFilePageCount > 1;
|
||||
if (hasMultiplePages) {
|
||||
const text = t("chat.quickActions.splitOne", "Split this document");
|
||||
result.push({
|
||||
key: "split",
|
||||
icon: <ContentCutIcon sx={{ fontSize: 18 }} />,
|
||||
title: text,
|
||||
onClick: send(text),
|
||||
});
|
||||
}
|
||||
const compressText = t(
|
||||
"chat.quickActions.compressOne",
|
||||
"Compress this document",
|
||||
);
|
||||
const rotateText = t(
|
||||
"chat.quickActions.rotateOne",
|
||||
"Rotate this document",
|
||||
);
|
||||
result.push({
|
||||
key: "compress",
|
||||
icon: <CompressIcon sx={{ fontSize: 18 }} />,
|
||||
title: compressText,
|
||||
onClick: send(compressText),
|
||||
});
|
||||
result.push({
|
||||
key: "rotate",
|
||||
icon: <RotateRightIcon sx={{ fontSize: 18 }} />,
|
||||
title: rotateText,
|
||||
onClick: send(rotateText),
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Multiple files.
|
||||
const result: QuickAction[] = [];
|
||||
if (summary.hasNonPdf) {
|
||||
const text = t(
|
||||
"chat.quickActions.convertMany",
|
||||
"Convert these documents to PDF",
|
||||
);
|
||||
result.push({
|
||||
key: "convert",
|
||||
icon: <PictureAsPdfIcon sx={{ fontSize: 18 }} />,
|
||||
title: text,
|
||||
onClick: send(text),
|
||||
});
|
||||
}
|
||||
const mergeText = t("chat.quickActions.mergeMany", {
|
||||
count: summary.fileCount,
|
||||
defaultValue: "Merge these {{count}} documents into 1",
|
||||
});
|
||||
const compressText = t(
|
||||
"chat.quickActions.compressMany",
|
||||
"Compress these documents",
|
||||
);
|
||||
const rotateText = t(
|
||||
"chat.quickActions.rotateMany",
|
||||
"Rotate these documents",
|
||||
);
|
||||
result.push({
|
||||
key: "merge",
|
||||
icon: <LayersIcon sx={{ fontSize: 18 }} />,
|
||||
title: mergeText,
|
||||
onClick: send(mergeText),
|
||||
});
|
||||
result.push({
|
||||
key: "compress",
|
||||
icon: <CompressIcon sx={{ fontSize: 18 }} />,
|
||||
title: compressText,
|
||||
onClick: send(compressText),
|
||||
});
|
||||
result.push({
|
||||
key: "rotate",
|
||||
icon: <RotateRightIcon sx={{ fontSize: 18 }} />,
|
||||
title: rotateText,
|
||||
onClick: send(rotateText),
|
||||
});
|
||||
return result;
|
||||
}, [summary, t, onAction, openFilesModal]);
|
||||
|
||||
return (
|
||||
<div className="chat-panel__quick-actions">
|
||||
{summary.fileCount > 0 && (
|
||||
<WorkbenchFilePills
|
||||
stubs={fileStubs}
|
||||
onOpenFilesModal={() => openFilesModal()}
|
||||
onRemove={(id) => fileActions.removeFiles([id])}
|
||||
moreLabel={(count) =>
|
||||
t("chat.quickActions.moreFiles", {
|
||||
count,
|
||||
defaultValue: "+{{count}} more",
|
||||
})
|
||||
}
|
||||
removeLabel={(name) =>
|
||||
t("chat.quickActions.removeFile", {
|
||||
name,
|
||||
defaultValue: "Remove {{name}}",
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Text className="chat-panel__quick-actions-label">{heading}</Text>
|
||||
<Stack gap="xs">
|
||||
{actions.map((action) => (
|
||||
<QuickActionCard key={action.key} action={action} />
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,5 +8,8 @@ import { type ProprietaryToolRegistry } from "@app/data/toolsTaxonomy";
|
||||
* and will be included in the main tool registry.
|
||||
*/
|
||||
export function useProprietaryToolRegistry(): ProprietaryToolRegistry {
|
||||
return useMemo<ProprietaryToolRegistry>(() => ({}), []);
|
||||
// "ai-workflow" is a marker super-tool (see proprietaryToolId.ts) with no
|
||||
// registry entry, so we widen via `as`. Add real proprietary tools here as
|
||||
// `{ id: { ... } }` entries.
|
||||
return useMemo(() => ({}) as ProprietaryToolRegistry, []);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
|
||||
export const PROPRIETARY_REGULAR_TOOL_IDS = [] as const;
|
||||
|
||||
export const PROPRIETARY_SUPER_TOOL_IDS = [] as const;
|
||||
// "ai-workflow" is a generic marker stamped onto files produced by the agents
|
||||
// chat orchestrator (which may invoke one or more underlying tools). Lives here
|
||||
// as a super-tool so ``ToolOperation.toolId`` stays typed; not user-pickable —
|
||||
// see ChatContext.tsx. The tool registry doesn't include it as an entry.
|
||||
export const PROPRIETARY_SUPER_TOOL_IDS = ["ai-workflow"] as const;
|
||||
|
||||
export const PROPRIETARY_LINK_TOOL_IDS = [] as const;
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders";
|
||||
import { type AppProvidersProps } from "@core/components/AppProviders";
|
||||
import { ChatProvider } from "@app/components/chat/ChatContext";
|
||||
|
||||
export type { AppProvidersProps };
|
||||
|
||||
export function AppProviders({
|
||||
children,
|
||||
appConfigRetryOptions,
|
||||
appConfigProviderProps,
|
||||
}: AppProvidersProps) {
|
||||
return (
|
||||
<ProprietaryAppProviders
|
||||
appConfigRetryOptions={appConfigRetryOptions}
|
||||
appConfigProviderProps={appConfigProviderProps}
|
||||
>
|
||||
<ChatProvider>{children}</ChatProvider>
|
||||
</ProprietaryAppProviders>
|
||||
);
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
.chat-toggle-button {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
right: 1.5rem;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 380px;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--mantine-color-body);
|
||||
border-left: 1px solid
|
||||
var(--border-subtle, var(--mantine-color-default-border));
|
||||
z-index: 999;
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chat-panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid
|
||||
var(--border-subtle, var(--mantine-color-default-border));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-panel-messages {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-panel-input {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
border-top: 1px solid
|
||||
var(--border-subtle, var(--mantine-color-default-border));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Message layout */
|
||||
.chat-message {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chat-message-user {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.chat-message-assistant {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Bubble styling */
|
||||
.chat-bubble {
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
.chat-bubble-user {
|
||||
background: var(--mantine-color-blue-filled) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.chat-bubble-user * {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.chat-bubble-assistant {
|
||||
background: var(--mantine-color-default-hover) !important;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { ChatPanel } from "@app/components/chat/ChatPanel";
|
||||
|
||||
export function HomePageExtensions() {
|
||||
return <ChatPanel />;
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
// Auto-generated icon types
|
||||
// This file is automatically generated by scripts/generate-icons.js
|
||||
// Do not edit manually - changes will be overwritten
|
||||
|
||||
export type MaterialSymbolIcon =
|
||||
| "123-rounded"
|
||||
| "account-circle"
|
||||
| "add"
|
||||
| "add-comment"
|
||||
| "add-photo-alternate"
|
||||
| "add-rounded"
|
||||
| "admin-panel-settings"
|
||||
| "admin-panel-settings-rounded"
|
||||
| "analytics"
|
||||
| "analytics-rounded"
|
||||
| "api-rounded"
|
||||
| "approval-outline-rounded"
|
||||
| "arrow-back"
|
||||
| "arrow-back-rounded"
|
||||
| "arrow-forward"
|
||||
| "article-outline-rounded"
|
||||
| "assignment-outline-rounded"
|
||||
| "attachment-rounded"
|
||||
| "auto-awesome-rounded"
|
||||
| "automation-outline"
|
||||
| "backup"
|
||||
| "bookmark-add-rounded"
|
||||
| "branding-watermark-outline-rounded"
|
||||
| "brush"
|
||||
| "build-outline-rounded"
|
||||
| "change-history"
|
||||
| "check"
|
||||
| "check-circle"
|
||||
| "check-circle-outline-rounded"
|
||||
| "check-circle-rounded"
|
||||
| "chevron-left"
|
||||
| "chevron-left-rounded"
|
||||
| "chevron-right-rounded"
|
||||
| "cleaning-services-outline-rounded"
|
||||
| "close"
|
||||
| "close-rounded"
|
||||
| "cloud-upload"
|
||||
| "comment"
|
||||
| "compare-rounded"
|
||||
| "compress"
|
||||
| "content-copy"
|
||||
| "content-copy-rounded"
|
||||
| "content-cut-rounded"
|
||||
| "content-paste-rounded"
|
||||
| "credit-card"
|
||||
| "crop-free-rounded"
|
||||
| "crop-rounded"
|
||||
| "crop-square"
|
||||
| "crop-square-outline"
|
||||
| "dashboard-customize-outline-rounded"
|
||||
| "dashboard-customize-rounded"
|
||||
| "dashboard-outline-rounded"
|
||||
| "delete"
|
||||
| "delete-outline-rounded"
|
||||
| "delete-rounded"
|
||||
| "description"
|
||||
| "desktop-cloud-rounded"
|
||||
| "download"
|
||||
| "download-rounded"
|
||||
| "edit"
|
||||
| "edit-outline-rounded"
|
||||
| "edit-rounded"
|
||||
| "edit-square-outline-rounded"
|
||||
| "edit-square-rounded"
|
||||
| "error"
|
||||
| "error-outline-rounded"
|
||||
| "error-rounded"
|
||||
| "expand-more-rounded"
|
||||
| "explore-rounded"
|
||||
| "extension-rounded"
|
||||
| "fact-check-outline-rounded"
|
||||
| "fact-check-rounded"
|
||||
| "find-replace"
|
||||
| "folder-rounded"
|
||||
| "format-align-center"
|
||||
| "format-align-left"
|
||||
| "format-align-right"
|
||||
| "format-color-fill-rounded"
|
||||
| "format-underlined"
|
||||
| "gavel-rounded"
|
||||
| "gpp-maybe-outline-rounded"
|
||||
| "group"
|
||||
| "group-add"
|
||||
| "group-rounded"
|
||||
| "groups-rounded"
|
||||
| "help-rounded"
|
||||
| "highlight"
|
||||
| "image-outline-rounded"
|
||||
| "info"
|
||||
| "info-outline-rounded"
|
||||
| "info-rounded"
|
||||
| "javascript-rounded"
|
||||
| "key"
|
||||
| "key-rounded"
|
||||
| "keyboard-arrow-down"
|
||||
| "keyboard-arrow-up"
|
||||
| "keyboard-rounded"
|
||||
| "language"
|
||||
| "layers-clear-outline-rounded"
|
||||
| "layers-outline-rounded"
|
||||
| "library-add-outline-rounded"
|
||||
| "link"
|
||||
| "link-rounded"
|
||||
| "lock"
|
||||
| "lock-open"
|
||||
| "lock-open-right-outline-rounded"
|
||||
| "lock-outline"
|
||||
| "login"
|
||||
| "logout"
|
||||
| "logout-rounded"
|
||||
| "looks-one-outline-rounded"
|
||||
| "mail-rounded"
|
||||
| "match-word-rounded"
|
||||
| "material-symbols:pause-rounded"
|
||||
| "material-symbols:play-arrow-rounded"
|
||||
| "menu-book-outline-rounded"
|
||||
| "menu-book-rounded"
|
||||
| "more-vert"
|
||||
| "move-down-rounded"
|
||||
| "opacity"
|
||||
| "open-in-new-rounded"
|
||||
| "palette-outline"
|
||||
| "pan-tool-rounded"
|
||||
| "password-rounded"
|
||||
| "pause-rounded"
|
||||
| "person"
|
||||
| "person-add"
|
||||
| "person-add-rounded"
|
||||
| "person-check"
|
||||
| "person-off"
|
||||
| "person-remove"
|
||||
| "person-rounded"
|
||||
| "photo-library-outline-rounded"
|
||||
| "picture-as-pdf-rounded"
|
||||
| "pin-end"
|
||||
| "play-arrow-rounded"
|
||||
| "play-circle"
|
||||
| "preview-off-rounded"
|
||||
| "qr-code-rounded"
|
||||
| "quick-reference-all-outline-rounded"
|
||||
| "radio-button-unchecked"
|
||||
| "redo"
|
||||
| "refresh"
|
||||
| "refresh-rounded"
|
||||
| "remove-moderator-outline-rounded"
|
||||
| "remove-selection-rounded"
|
||||
| "rocket-launch"
|
||||
| "rotate-left"
|
||||
| "rotate-right"
|
||||
| "rotate-right-rounded"
|
||||
| "save"
|
||||
| "save-rounded"
|
||||
| "scan-delete-outline-rounded"
|
||||
| "scan-delete-rounded"
|
||||
| "scanner-outline-rounded"
|
||||
| "schedule-outline-rounded"
|
||||
| "search"
|
||||
| "search-rounded"
|
||||
| "security"
|
||||
| "select-all"
|
||||
| "send-rounded"
|
||||
| "settings-rounded"
|
||||
| "shield-lock"
|
||||
| "shield-rounded"
|
||||
| "show-chart"
|
||||
| "signature-rounded"
|
||||
| "speed"
|
||||
| "star"
|
||||
| "star-rounded"
|
||||
| "sticky-note-2"
|
||||
| "storage-rounded"
|
||||
| "strikethrough-s"
|
||||
| "subdirectory-arrow-right-rounded"
|
||||
| "swap-horiz"
|
||||
| "sync-alt-rounded"
|
||||
| "system-update-alt-rounded"
|
||||
| "text-fields"
|
||||
| "text-fields-rounded"
|
||||
| "thread-unread-rounded"
|
||||
| "toc-rounded"
|
||||
| "touch-app-rounded"
|
||||
| "tune-rounded"
|
||||
| "undo"
|
||||
| "unfold-less"
|
||||
| "unfold-more"
|
||||
| "upload-file-rounded"
|
||||
| "upload-rounded"
|
||||
| "verified-outline-rounded"
|
||||
| "verified-user-outline"
|
||||
| "verified-user-rounded"
|
||||
| "view-carousel-rounded"
|
||||
| "view-list"
|
||||
| "visibility"
|
||||
| "visibility-off-outline-rounded"
|
||||
| "visibility-rounded"
|
||||
| "vital-signs-rounded"
|
||||
| "warning"
|
||||
| "warning-rounded"
|
||||
| "workspace-premium-outline-rounded"
|
||||
| "workspace-premium-rounded"
|
||||
| "zoom-in"
|
||||
| "zoom-in-map-rounded"
|
||||
| "zoom-out";
|
||||
|
||||
export interface IconSet {
|
||||
prefix: string;
|
||||
icons: Record<string, any>;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
// Re-export the icon set as the default export with proper typing
|
||||
declare const iconSet: IconSet;
|
||||
export default iconSet;
|
||||
@@ -0,0 +1,622 @@
|
||||
{
|
||||
"prefix": "material-symbols",
|
||||
"icons": {
|
||||
"123-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5.5 10.5h-.75q-.325 0-.537-.213T4 9.75t.213-.537T4.75 9h1.5q.325 0 .538.213T7 9.75v4.5q0 .325-.213.538T6.25 15t-.537-.213t-.213-.537zM9 14.25V12.5q0-.425.288-.712T10 11.5h2v-1H9.75q-.325 0-.537-.213T9 9.75t.213-.537T9.75 9h2.75q.425 0 .713.288T13.5 10v1.5q0 .425-.288.713t-.712.287h-2v1h2.25q.325 0 .538.213t.212.537t-.213.538t-.537.212h-3q-.325 0-.537-.213T9 14.25m9.5.75h-2.75q-.325 0-.537-.213T15 14.25t.213-.537t.537-.213H18v-1h-1.5q-.2 0-.35-.15T16 12t.15-.35t.35-.15H18v-1h-2.25q-.325 0-.537-.213T15 9.75t.213-.537T15.75 9h2.75q.425 0 .713.288T19.5 10v4q0 .425-.288.713T18.5 15\"/>"
|
||||
},
|
||||
"account-circle": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5.85 17.1q1.275-.975 2.85-1.537T12 15t3.3.563t2.85 1.537q.875-1.025 1.363-2.325T20 12q0-3.325-2.337-5.663T12 4T6.337 6.338T4 12q0 1.475.488 2.775T5.85 17.1m3.663-5.113Q8.5 10.976 8.5 9.5t1.013-2.488T12 6t2.488 1.013T15.5 9.5t-1.012 2.488T12 13t-2.488-1.012M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"add": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z\"/>"
|
||||
},
|
||||
"add-comment": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 14h2v-3h3V9h-3V6h-2v3H8v2h3zm-9 8V4q0-.825.588-1.412T4 2h16q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18H6z\"/>"
|
||||
},
|
||||
"add-photo-alternate": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h9q-.5.65-.75 1.425T13 6q0 2.075 1.463 3.538T18 11q.8 0 1.575-.25T21 10v9q0 .825-.587 1.413T19 21zm1-4h12l-3.75-5l-3 4L9 13zm11-8V7h-2V5h2V3h2v2h2v2h-2v2z\"/>"
|
||||
},
|
||||
"add-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 13H6q-.425 0-.712-.288T5 12t.288-.712T6 11h5V6q0-.425.288-.712T12 5t.713.288T13 6v5h5q.425 0 .713.288T19 12t-.288.713T18 13h-5v5q0 .425-.288.713T12 19t-.712-.288T11 18z\"/>"
|
||||
},
|
||||
"admin-panel-settings": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17 22q-2.075 0-3.537-1.463T12 17t1.463-3.537T17 12t3.538 1.463T22 17t-1.463 3.538T17 22m-5 0q-3.475-.875-5.738-3.988T4 11.1V5l8-3l8 3v5.675q-.65-.325-1.463-.5T17 10q-2.9 0-4.95 2.05T10 17q0 1.55.588 2.8t1.487 2.175q-.025 0-.037.013T12 22m6.063-5.437q.437-.438.437-1.063t-.437-1.062T17 14t-1.062.438T15.5 15.5t.438 1.063T17 17t1.063-.437M17 20q.775 0 1.425-.363t1.05-.962q-.55-.325-1.175-.5T17 18t-1.3.175t-1.175.5q.4.6 1.05.963T17 20\"/>"
|
||||
},
|
||||
"admin-panel-settings-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17 22q-2.075 0-3.537-1.463T12 17t1.463-3.537T17 12t3.538 1.463T22 17t-1.463 3.538T17 22M4 11.1V6.375q0-.625.363-1.125t.937-.725l6-2.25q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375v3.05q0 .425-.375.688t-.8.137q-.45-.125-.9-.187T17 10q-2.9 0-4.95 2.05T10 17q0 .8.188 1.563t.537 1.537q.225.475-.137.85t-.838.15q-1.05-.55-1.925-1.35T6.275 18Q5.2 16.525 4.6 14.762T4 11.1m14.063 5.463q.437-.438.437-1.063t-.437-1.062T17 14t-1.062.438T15.5 15.5t.438 1.063T17 17t1.063-.437M17 20q.625 0 1.175-.238t.975-.687q.125-.15.1-.337t-.225-.288q-.475-.225-.987-.337T17 18t-1.037.113t-.988.337q-.2.1-.225.288t.1.337q.425.45.975.688T17 20\"/>"
|
||||
},
|
||||
"analytics": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7 17h2v-5H7zm8 0h2V7h-2zm-4 0h2v-3h-2zm0-5h2v-2h-2zm-6 9q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z\"/>"
|
||||
},
|
||||
"analytics-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7.288 12.288Q7 12.575 7 13v3q0 .425.288.713T8 17t.713-.288T9 16v-3q0-.425-.288-.712T8 12t-.712.288m8-5Q15 7.575 15 8v8q0 .425.288.713T16 17t.713-.288T17 16V8q0-.425-.288-.712T16 7t-.712.288m-4 7Q11 14.575 11 15v1q0 .425.288.713T12 17t.713-.288T13 16v-1q0-.425-.288-.712T12 14t-.712.288M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm7.713-9.287Q13 11.425 13 11t-.288-.712T12 10t-.712.288T11 11t.288.713T12 12t.713-.288\"/>"
|
||||
},
|
||||
"api-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 14l-2-2l2-2l2 2zM9.875 8.125l-2.5-2.5l3.2-3.2q.3-.3.675-.45t.75-.15t.75.15t.675.45l3.2 3.2l-2.5 2.5L12 6zm-4.25 8.5l-3.2-3.2q-.3-.3-.45-.675t-.15-.75t.15-.75t.45-.675l3.2-3.2l2.5 2.5L6 12l2.125 2.125zm12.75 0l-2.5-2.5L18 12l-2.125-2.125l2.5-2.5l3.2 3.2q.3.3.45.675t.15.75t-.15.75t-.45.675zm-7.8 4.95l-3.2-3.2l2.5-2.5L12 18l2.125-2.125l2.5 2.5l-3.2 3.2q-.3.3-.675.45t-.75.15t-.75-.15t-.675-.45\"/>"
|
||||
},
|
||||
"approval-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 22q-.825 0-1.412-.587T4 20v-4q0-.825.588-1.412T6 14h12q.825 0 1.413.588T20 16v4q0 .825-.587 1.413T18 22zm1-4h10q.425 0 .713-.288T18 17t-.288-.712T17 16H7q-.425 0-.712.288T6 17t.288.713T7 18m4.175-5.15l-3.7-5.175q-.225-.325-.312-.712t-.038-.788q.3-1.825 1.65-3T12 2t3.225 1.175t1.65 3q.05.4-.038.788t-.312.712l-3.7 5.175q-.3.425-.825.425t-.825-.425M12 11.2L15 7q0-1.25-.875-2.125T12 4t-2.125.875T9 7zm0-3.6\"/>"
|
||||
},
|
||||
"arrow-back": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m7.825 13l5.6 5.6L12 20l-8-8l8-8l1.425 1.4l-5.6 5.6H20v2z\"/>"
|
||||
},
|
||||
"arrow-back-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m7.825 13l4.9 4.9q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-6.6-6.6q-.15-.15-.213-.325T4.426 12t.063-.375t.212-.325l6.6-6.6q.275-.275.688-.275t.712.275q.3.3.3.713t-.3.712L7.825 11H19q.425 0 .713.288T20 12t-.288.713T19 13z\"/>"
|
||||
},
|
||||
"arrow-forward": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M16.175 13H4v-2h12.175l-5.6-5.6L12 4l8 8l-8 8l-1.425-1.4z\"/>"
|
||||
},
|
||||
"article-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zM5 5v14zm3 12h5q.425 0 .713-.288T14 16t-.288-.712T13 15H8q-.425 0-.712.288T7 16t.288.713T8 17m0-4h8q.425 0 .713-.288T17 12t-.288-.712T16 11H8q-.425 0-.712.288T7 12t.288.713T8 13m0-4h8q.425 0 .713-.288T17 8t-.288-.712T16 7H8q-.425 0-.712.288T7 8t.288.713T8 9\"/>"
|
||||
},
|
||||
"assignment-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h4.2q.325-.9 1.088-1.45T12 1t1.713.55T14.8 3H19q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zm3-2h5q.425 0 .713-.288T14 16t-.288-.712T13 15H8q-.425 0-.712.288T7 16t.288.713T8 17m0-4h8q.425 0 .713-.288T17 12t-.288-.712T16 11H8q-.425 0-.712.288T7 12t.288.713T8 13m0-4h8q.425 0 .713-.288T17 8t-.288-.712T16 7H8q-.425 0-.712.288T7 8t.288.713T8 9m4.538-4.962q.212-.213.212-.538t-.213-.537T12 2.75t-.537.213t-.213.537t.213.538t.537.212t.538-.213M5 19V5z\"/>"
|
||||
},
|
||||
"attachment-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M8.25 18q-2.6 0-4.425-1.825T2 11.75t1.825-4.425T8.25 5.5h9.25q1.875 0 3.188 1.313T22 10t-1.312 3.188T17.5 14.5H8.75q-1.15 0-1.95-.8T6 11.75t.8-1.95T8.75 9H17q.425 0 .713.288T18 10t-.288.713T17 11H8.75q-.325 0-.537.213T8 11.75t.213.538t.537.212h8.75q1.05-.025 1.775-.737T20 10t-.725-1.775T17.5 7.5H8.25q-1.775-.025-3.012 1.225T4 11.75q0 1.75 1.238 2.975T8.25 16H17q.425 0 .713.288T18 17t-.288.713T17 18z\"/>"
|
||||
},
|
||||
"auto-awesome-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M19 8.3q-.125 0-.263-.075T18.55 8l-.8-1.75l-1.75-.8q-.15-.05-.225-.187T15.7 5q0-.125.075-.262T16 4.55l1.75-.8l.8-1.75q.05-.15.188-.225T19 1.7q.125 0 .263.075T19.45 2l.8 1.75l1.75.8q.15.05.225.188T22.3 5q0 .125-.075.263T22 5.45l-1.75.8l-.8 1.75q-.05.15-.188.225T19 8.3Zm0 14q-.125 0-.263-.075T18.55 22l-.8-1.75l-1.75-.8q-.15-.05-.225-.188T15.7 19q0-.125.075-.263T16 18.55l1.75-.8l.8-1.75q.05-.15.188-.225T19 15.7q.125 0 .263.075t.187.225l.8 1.75l1.75.8q.15.05.225.188T22.3 19q0 .125-.075.263T22 19.45l-1.75.8l-.8 1.75q-.05.15-.188.225T19 22.3ZM9 18.575q-.275 0-.525-.15T8.1 18l-1.6-3.5L3 12.9q-.275-.125-.425-.375T2.425 12q0-.275.15-.525T3 11.1l3.5-1.6L8.1 6q.125-.275.375-.425T9 5.425q.275 0 .525.15T9.9 6l1.6 3.5l3.5 1.6q.275.125.425.375t.15.525q0 .275-.15.525T15 12.9l-3.5 1.6L9.9 18q-.125.275-.375.425t-.525.15Z\"/>",
|
||||
"hidden": true
|
||||
},
|
||||
"automation-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7.4 17.25q-1.05.875-2.187.8t-1.988-.775t-1.162-1.837t.412-2.338L4.35 10q-.625-.55-.987-1.325T3 7q0-1.65 1.175-2.825T7 3t2.825 1.175T11 7T9.825 9.825T7 11q-.225 0-.45-.025t-.425-.075L4.2 14.15q-.275.45-.175.888t.425.712t.775.313t.875-.313l10.5-9.025q1.05-.875 2.2-.788t2 .788t1.15 1.838t-.425 2.337L19.65 14q.625.55.988 1.325T21 17q0 1.65-1.175 2.825T17 21t-2.825-1.175T13 17t1.175-2.825T17 13q.225 0 .438.025t.412.075l1.95-3.25q.275-.45.175-.888t-.425-.712t-.775-.312t-.875.312zm1.013-8.837Q9 7.825 9 7t-.587-1.412T7 5t-1.412.588T5 7t.588 1.413T7 9t1.413-.587m10 10Q19 17.825 19 17t-.587-1.412T17 15t-1.412.588T15 17t.588 1.413T17 19t1.413-.587M17 17\"/>"
|
||||
},
|
||||
"backup": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 20H6.5q-2.275 0-3.887-1.575T1 14.575q0-1.95 1.175-3.475T5.25 9.15q.625-2.3 2.5-3.725T12 4q2.925 0 4.963 2.038T19 11q1.725.2 2.863 1.488T23 15.5q0 1.875-1.312 3.188T18.5 20H13v-7.15l1.6 1.55L16 13l-4-4l-4 4l1.4 1.4l1.6-1.55z\"/>"
|
||||
},
|
||||
"bookmark-add-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17 7h-1q-.425 0-.712-.288T15 6t.288-.712T16 5h1V4q0-.425.288-.712T18 3t.713.288T19 4v1h1q.425 0 .713.288T21 6t-.288.713T20 7h-1v1q0 .425-.288.713T18 9t-.712-.288T17 8zm-5 11l-4.2 1.8q-1 .425-1.9-.162T5 17.975V5q0-.825.588-1.412T7 3h5.625q.45 0 .675.4t.025.825q-.175.425-.25.85T13 6q0 1.8 1.138 3.175T17 10.9q.3.05.538.063t.462.012q.425 0 .713.263T19 11.9v6.075q0 1.075-.9 1.663t-1.9.162z\"/>"
|
||||
},
|
||||
"branding-watermark-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 17h7q.425 0 .713-.288T19 16v-4q0-.425-.288-.712T18 11h-7q-.425 0-.712.288T10 12v4q0 .425.288.713T11 17m-7 3q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm0-2h16V6H4zm0 0V6z\"/>"
|
||||
},
|
||||
"brush": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 21q-1.125 0-2.225-.55T2 19q.65 0 1.325-.513T4 17q0-1.25.875-2.125T7 14t2.125.875T10 17q0 1.65-1.175 2.825T6 21m5.75-6L9 12.25l8.95-8.95q.275-.275.688-.288t.712.288l1.35 1.35q.3.3.3.7t-.3.7z\"/>"
|
||||
},
|
||||
"build-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9 15q-2.5 0-4.25-1.75T3 9q0-.5.075-1t.275-.95q.125-.25.313-.375t.412-.175t.463.013t.437.262L7.6 9.4l1.8-1.8l-2.625-2.625q-.2-.2-.262-.437T6.5 4.075t.175-.413t.375-.312q.45-.2.95-.275T9 3q2.5 0 4.25 1.75T15 9q0 .575-.1 1.088t-.3 1.012l5.05 5q.725.725.725 1.775t-.725 1.775t-1.775.725t-1.775-.75l-5-5.025q-.5.2-1.012.3T9 15m0-2q.65 0 1.3-.2t1.175-.625l6.075 6.075q.125.125.338.113t.337-.138t.125-.337t-.125-.338L12.15 11.5q.45-.5.65-1.162T13 9q0-1.5-.962-2.613T9.65 5.05L11.5 6.9q.3.3.3.7t-.3.7l-3.2 3.2q-.3.3-.7.3t-.7-.3L5.05 9.65q.225 1.425 1.338 2.388T9 13m2.725-1.275\"/>"
|
||||
},
|
||||
"change-history": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M2 20L12 4l10 16z\"/>"
|
||||
},
|
||||
"check": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z\"/>"
|
||||
},
|
||||
"check-circle": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m10.6 16.6l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"check-circle-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m10.6 13.8l-2.15-2.15q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/>"
|
||||
},
|
||||
"check-circle-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m10.6 13.8l-2.15-2.15q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"chevron-left": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m14 18l-6-6l6-6l1.4 1.4l-4.6 4.6l4.6 4.6z\"/>"
|
||||
},
|
||||
"chevron-left-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m10.8 12l3.9 3.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-4.6-4.6q-.15-.15-.212-.325T8.425 12t.063-.375t.212-.325l4.6-4.6q.275-.275.7-.275t.7.275t.275.7t-.275.7z\"/>"
|
||||
},
|
||||
"chevron-right-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.6 12L8.7 8.1q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.6 4.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-4.6 4.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7z\"/>"
|
||||
},
|
||||
"cleaning-services-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 23q-.825 0-1.412-.587T3 21v-5q0-2.075 1.463-3.537T8 11h1V3q0-.825.588-1.412T11 1h2q.825 0 1.413.588T15 3v8h1q2.075 0 3.538 1.463T21 16v5q0 .825-.587 1.413T19 23zm0-2h2v-3q0-.425.288-.712T8 17t.713.288T9 18v3h2v-3q0-.425.288-.712T12 17t.713.288T13 18v3h2v-3q0-.425.288-.712T16 17t.713.288T17 18v3h2v-5q0-1.25-.875-2.125T16 13H8q-1.25 0-2.125.875T5 16zm8-10V3h-2v8zm0 0h-2z\"/>"
|
||||
},
|
||||
"close": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6z\"/>"
|
||||
},
|
||||
"close-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275t.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275z\"/>"
|
||||
},
|
||||
"comment": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 14h12v-2H6zm0-3h12V9H6zm0-3h12V6H6zM4 18q-.825 0-1.412-.587T2 16V4q0-.825.588-1.412T4 2h16q.825 0 1.413.588T22 4v18l-4-4z\"/>"
|
||||
},
|
||||
"compare-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10 21H5q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h5V2q0-.425.288-.712T11 1t.713.288T12 2v20q0 .425-.288.713T11 23t-.712-.288T10 22zm-5-3h5v-6zm9 3v-9l5 6V5h-5V3h5q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z\"/>"
|
||||
},
|
||||
"compress": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 14v-2h16v2zm0-3V9h16v2zm7 11v-3.2l-1.6 1.6L8 19l4-4l4 4l-1.4 1.4l-1.6-1.55V22zm1-14L8 4l1.4-1.4L11 4.2V1h2v3.2l1.6-1.6L16 4z\"/>"
|
||||
},
|
||||
"content-copy": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9 18q-.825 0-1.412-.587T7 16V4q0-.825.588-1.412T9 2h9q.825 0 1.413.588T20 4v12q0 .825-.587 1.413T18 18zm-4 4q-.825 0-1.412-.587T3 20V6h2v14h11v2z\"/>"
|
||||
},
|
||||
"content-copy-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9 18q-.825 0-1.412-.587T7 16V4q0-.825.588-1.412T9 2h9q.825 0 1.413.588T20 4v12q0 .825-.587 1.413T18 18zm-4 4q-.825 0-1.412-.587T3 20V7q0-.425.288-.712T4 6t.713.288T5 7v13h10q.425 0 .713.288T16 21t-.288.713T15 22z\"/>"
|
||||
},
|
||||
"content-cut-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 14l-2.35 2.35q.2.375.275.8T10 18q0 1.65-1.175 2.825T6 22t-2.825-1.175T2 18t1.175-2.825T6 14q.425 0 .85.075t.8.275L10 12L7.65 9.65q-.375.2-.8.275T6 10q-1.65 0-2.825-1.175T2 6t1.175-2.825T6 2t2.825 1.175T10 6q0 .425-.075.85t-.275.8L20.6 18.6q.675.675.3 1.538T19.575 21q-.275 0-.537-.112t-.463-.313zm3-3l-2-2l5.575-5.575q.2-.2.463-.312T19.574 3q.95 0 1.313.875t-.313 1.55zM7.413 7.413Q8 6.825 8 6t-.587-1.412T6 4t-1.412.588T4 6t.588 1.413T6 8t1.413-.587m4.937 4.937q.15-.15.15-.35t-.15-.35t-.35-.15t-.35.15t-.15.35t.15.35t.35.15t.35-.15m-4.937 7.063Q8 18.825 8 18t-.587-1.412T6 16t-1.412.588T4 18t.588 1.413T6 20t1.413-.587\"/>"
|
||||
},
|
||||
"content-paste-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h4.175q.275-.875 1.075-1.437T12 1q1 0 1.788.563T14.85 3H19q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5h-2v2q0 .425-.288.713T16 8H8q-.425 0-.712-.288T7 7V5H5zm7.713-14.288Q13 4.425 13 4t-.288-.712T12 3t-.712.288T11 4t.288.713T12 5t.713-.288\"/>"
|
||||
},
|
||||
"credit-card": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm0-8h16V8H4z\"/>"
|
||||
},
|
||||
"crop-free-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19v-3q0-.425.288-.712T4 15t.713.288T5 16v3h3q.425 0 .713.288T9 20t-.288.713T8 21zm14 0h-3q-.425 0-.712-.288T15 20t.288-.712T16 19h3v-3q0-.425.288-.712T20 15t.713.288T21 16v3q0 .825-.587 1.413T19 21M3 8V5q0-.825.588-1.412T5 3h3q.425 0 .713.288T9 4t-.288.713T8 5H5v3q0 .425-.288.713T4 9t-.712-.288T3 8m16 0V5h-3q-.425 0-.712-.288T15 4t.288-.712T16 3h3q.825 0 1.413.588T21 5v3q0 .425-.288.713T20 9t-.712-.288T19 8\"/>"
|
||||
},
|
||||
"crop-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17 22v-3H7q-.825 0-1.412-.587T5 17V7H2q-.425 0-.712-.288T1 6t.288-.712T2 5h3V2q0-.425.288-.712T6 1t.713.288T7 2v15h15q.425 0 .713.288T23 18t-.288.713T22 19h-3v3q0 .425-.288.713T18 23t-.712-.288T17 22m0-7V7H9V5h8q.825 0 1.413.588T19 7v8z\"/>"
|
||||
},
|
||||
"square-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z\"/>"
|
||||
},
|
||||
"crop-square-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm0 0V5v14Z\"/>",
|
||||
"hidden": true
|
||||
},
|
||||
"dashboard-customize-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 11q-.425 0-.712-.288T3 10V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v6q0 .425-.288.713T10 11zm1-6v4zm9 6q-.425 0-.712-.288T13 10V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v6q0 .425-.288.713T20 11zm1-6v4zM4 21q-.425 0-.712-.288T3 20v-6q0-.425.288-.712T4 13h6q.425 0 .713.288T11 14v6q0 .425-.288.713T10 21zm1-6v4zm12 6q-.425 0-.712-.288T16 20v-2h-2.025q-.425 0-.7-.288T13 17t.288-.712T14 16h2v-2.025q0-.425.288-.7T17 13t.713.288T18 14v2h2.025q.425 0 .7.288T21 17t-.288.713T20 18h-2v2.025q0 .425-.288.7T17 21M15 5v4h4V5zM5 5v4h4V5zm0 10v4h4v-4z\"/>"
|
||||
},
|
||||
"dashboard-customize-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 11q-.425 0-.712-.288T3 10V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v6q0 .425-.288.713T10 11zm10 0q-.425 0-.712-.288T13 10V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v6q0 .425-.288.713T20 11zM4 21q-.425 0-.712-.288T3 20v-6q0-.425.288-.712T4 13h6q.425 0 .713.288T11 14v6q0 .425-.288.713T10 21zm13 0q-.425 0-.712-.288T16 20v-2h-2.025q-.425 0-.7-.288T13 17t.288-.712T14 16h2v-2.025q0-.425.288-.7T17 13t.713.288T18 14v2h2.025q.425 0 .7.288T21 17t-.288.713T20 18h-2v2.025q0 .425-.288.7T17 21\"/>"
|
||||
},
|
||||
"dashboard-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13 8V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v4q0 .425-.288.713T20 9h-6q-.425 0-.712-.288T13 8M3 12V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v8q0 .425-.288.713T10 13H4q-.425 0-.712-.288T3 12m10 8v-8q0-.425.288-.712T14 11h6q.425 0 .713.288T21 12v8q0 .425-.288.713T20 21h-6q-.425 0-.712-.288T13 20M3 20v-4q0-.425.288-.712T4 15h6q.425 0 .713.288T11 16v4q0 .425-.288.713T10 21H4q-.425 0-.712-.288T3 20m2-9h4V5H5zm10 8h4v-6h-4zm0-12h4V5h-4zM5 19h4v-2H5zm4-2\"/>"
|
||||
},
|
||||
"delete": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zm2-4h2V8H9zm4 0h2V8h-2z\"/>"
|
||||
},
|
||||
"delete-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7 21q-.825 0-1.412-.587T5 19V6q-.425 0-.712-.288T4 5t.288-.712T5 4h4q0-.425.288-.712T10 3h4q.425 0 .713.288T15 4h4q.425 0 .713.288T20 5t-.288.713T19 6v13q0 .825-.587 1.413T17 21zM17 6H7v13h10zm-6.287 10.713Q11 16.425 11 16V9q0-.425-.288-.712T10 8t-.712.288T9 9v7q0 .425.288.713T10 17t.713-.288m4 0Q15 16.426 15 16V9q0-.425-.288-.712T14 8t-.712.288T13 9v7q0 .425.288.713T14 17t.713-.288M7 6v13z\"/>"
|
||||
},
|
||||
"delete-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7 21q-.825 0-1.412-.587T5 19V6q-.425 0-.712-.288T4 5t.288-.712T5 4h4q0-.425.288-.712T10 3h4q.425 0 .713.288T15 4h4q.425 0 .713.288T20 5t-.288.713T19 6v13q0 .825-.587 1.413T17 21zm3.713-4.288Q11 16.426 11 16V9q0-.425-.288-.712T10 8t-.712.288T9 9v7q0 .425.288.713T10 17t.713-.288m4 0Q15 16.426 15 16V9q0-.425-.288-.712T14 8t-.712.288T13 9v7q0 .425.288.713T14 17t.713-.288\"/>"
|
||||
},
|
||||
"description": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M8 18h8v-2H8zm0-4h8v-2H8zm-2 8q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22zm7-13h5l-5-5z\"/>"
|
||||
},
|
||||
"desktop-cloud-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9 14h6.5q1.05 0 1.775-.737T18 11.474T17.25 9.7t-1.8-.725q-.2-1.275-1.175-2.125T12 6q-1.025 0-1.875.55T8.85 8.025q-1.2.05-2.025.913T6 11q0 1.25.875 2.125T9 14m1 5v-2H4q-.825 0-1.412-.587T2 15V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v10q0 .825-.587 1.413T20 17h-6v2h1q.425 0 .713.288T16 20t-.288.713T15 21H9q-.425 0-.712-.288T8 20t.288-.712T9 19z\"/>"
|
||||
},
|
||||
"download": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z\"/>"
|
||||
},
|
||||
"download-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11.625 15.513q-.175-.063-.325-.213l-3.6-3.6q-.3-.3-.288-.7t.288-.7q.3-.3.713-.312t.712.287L11 12.15V5q0-.425.288-.712T12 4t.713.288T13 5v7.15l1.875-1.875q.3-.3.713-.288t.712.313q.275.3.288.7t-.288.7l-3.6 3.6q-.15.15-.325.213t-.375.062t-.375-.062M6 20q-.825 0-1.412-.587T4 18v-2q0-.425.288-.712T5 15t.713.288T6 16v2h12v-2q0-.425.288-.712T19 15t.713.288T20 16v2q0 .825-.587 1.413T18 20z\"/>"
|
||||
},
|
||||
"edit": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 21v-4.25L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.438.65T21 6.4q0 .4-.137.763t-.438.662L7.25 21zM17.6 7.8L19 6.4L17.6 5l-1.4 1.4z\"/>"
|
||||
},
|
||||
"edit-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 19h1.425L16.2 9.225L14.775 7.8L5 17.575zm-1 2q-.425 0-.712-.288T3 20v-2.425q0-.4.15-.763t.425-.637L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.437.65T21 6.4q0 .4-.138.763t-.437.662l-12.6 12.6q-.275.275-.638.425t-.762.15zM19 6.4L17.6 5zm-3.525 2.125l-.7-.725L16.2 9.225z\"/>"
|
||||
},
|
||||
"edit-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 21q-.425 0-.712-.288T3 20v-2.425q0-.4.15-.763t.425-.637L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.437.65T21 6.4q0 .4-.138.763t-.437.662l-12.6 12.6q-.275.275-.638.425t-.762.15zM17.6 7.8L19 6.4L17.6 5l-1.4 1.4z\"/>"
|
||||
},
|
||||
"edit-square-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h6.525q.5 0 .75.313t.25.687t-.262.688T11.5 5H5v14h14v-6.525q0-.5.313-.75t.687-.25t.688.25t.312.75V19q0 .825-.587 1.413T19 21zm4-7v-2.425q0-.4.15-.763t.425-.637l8.6-8.6q.3-.3.675-.45t.75-.15q.4 0 .763.15t.662.45L22.425 3q.275.3.425.663T23 4.4t-.137.738t-.438.662l-8.6 8.6q-.275.275-.637.438t-.763.162H10q-.425 0-.712-.288T9 14m12.025-9.6l-1.4-1.4zM11 13h1.4l5.8-5.8l-.7-.7l-.725-.7L11 11.575zm6.5-6.5l-.725-.7zl.7.7z\"/>"
|
||||
},
|
||||
"edit-square-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10 15q-.425 0-.712-.288T9 14v-2.425q0-.4.15-.763t.425-.637l8.6-8.6q.3-.3.675-.45t.75-.15q.4 0 .763.15t.662.45L22.425 3q.275.3.425.663T23 4.4t-.137.738t-.438.662l-8.6 8.6q-.275.275-.637.438t-.763.162zm9.6-9.2l1.425-1.4l-1.4-1.4L18.2 4.4zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h6.5q.35 0 .575.175t.35.45t.087.55t-.287.525l-4.65 4.65q-.275.275-.425.638T7 10.75V15q0 .825.588 1.412T9 17h4.225q.4 0 .763-.15t.637-.425L19.3 11.75q.25-.25.525-.288t.55.088t.45.35t.175.575V19q0 .825-.587 1.413T19 21z\"/>"
|
||||
},
|
||||
"error": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17t.713-.288M11 13h2V7h-2zm1 9q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"error-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17t.713-.288m0-4Q13 12.425 13 12V8q0-.425-.288-.712T12 7t-.712.288T11 8v4q0 .425.288.713T12 13t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/>"
|
||||
},
|
||||
"error-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17t.713-.288m0-4Q13 12.425 13 12V8q0-.425-.288-.712T12 7t-.712.288T11 8v4q0 .425.288.713T12 13t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"expand-more-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 14.95q-.2 0-.375-.062t-.325-.213l-4.6-4.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l3.9 3.9l3.9-3.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-4.6 4.6q-.15.15-.325.213T12 14.95\"/>",
|
||||
"hidden": true
|
||||
},
|
||||
"explore-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m8.375 16.25l5.05-1.45q.5-.15.863-.513t.512-.862l1.45-5.05q.075-.275-.137-.488t-.488-.137l-5.05 1.45q-.5.15-.862.513t-.513.862l-1.45 5.05q-.075.275.138.488t.487.137M12 13.5q-.625 0-1.062-.437T10.5 12t.438-1.062T12 10.5t1.063.438T13.5 12t-.437 1.063T12 13.5m0 8.5q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"extension-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.413-.587Q3 19.825 3 19v-3.8q1.2 0 2.1-.762q.9-.763.9-1.938q0-1.175-.9-1.938Q4.2 9.8 3 9.8V6q0-.825.587-1.412Q4.175 4 5 4h4q0-1.05.725-1.775Q10.45 1.5 11.5 1.5q1.05 0 1.775.725Q14 2.95 14 4h4q.825 0 1.413.588Q20 5.175 20 6v4q1.05 0 1.775.725q.725.725.725 1.775q0 1.05-.725 1.775Q21.05 15 20 15v4q0 .825-.587 1.413Q18.825 21 18 21h-3.8q0-1.2-.762-2.1q-.763-.9-1.938-.9q-1.175 0-1.938.9q-.762.9-.762 2.1Z\"/>",
|
||||
"hidden": true
|
||||
},
|
||||
"fact-check-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 21q-.825 0-1.412-.587T2 19V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v14q0 .825-.587 1.413T20 21zm0-2h16V5H4zm5-2q.425 0 .713-.288T10 16t-.288-.712T9 15H6q-.425 0-.712.288T5 16t.288.713T6 17zm5.55-4.825l-.725-.725q-.3-.3-.7-.287t-.7.312q-.275.3-.288.7t.288.7L13.85 14.3q.3.3.7.3t.7-.3l3.55-3.55q.3-.3.3-.7t-.3-.7t-.712-.3t-.713.3zM9 13q.425 0 .713-.288T10 12t-.288-.712T9 11H6q-.425 0-.712.288T5 12t.288.713T6 13zm0-4q.425 0 .713-.288T10 8t-.288-.712T9 7H6q-.425 0-.712.288T5 8t.288.713T6 9zM4 19V5z\"/>"
|
||||
},
|
||||
"fact-check-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 21q-.825 0-1.412-.587T2 19V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v14q0 .825-.587 1.413T20 21zm5-4q.425 0 .713-.288T10 16t-.288-.712T9 15H6q-.425 0-.712.288T5 16t.288.713T6 17zm5.55-4.825l-.725-.725q-.3-.3-.7-.287t-.7.312q-.275.3-.288.7t.288.7L13.85 14.3q.3.3.7.3t.7-.3l3.55-3.55q.3-.3.3-.7t-.3-.7t-.712-.3t-.713.3zM9 13q.425 0 .713-.288T10 12t-.288-.712T9 11H6q-.425 0-.712.288T5 12t.288.713T6 13zm0-4q.425 0 .713-.288T10 8t-.288-.712T9 7H6q-.425 0-.712.288T5 8t.288.713T6 9z\"/>"
|
||||
},
|
||||
"find-replace": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4.1 10q.35-2.575 2.288-4.288T11 4q1.475 0 2.763.563T16 6.1V4h2v6h-6V8h3q-.725-.9-1.737-1.45T11 6Q9.2 6 7.825 7.138T6.1 10zm15.5 11l-4.4-4.4q-.9.675-1.962 1.038T11 18q-1.475 0-2.762-.562T6 15.9V18H4v-6h6v2H7q.725.9 1.738 1.45T11 16q1.8 0 3.175-1.137T15.9 12h2q-.125.9-.45 1.688T16.6 15.2l4.4 4.4z\"/>"
|
||||
},
|
||||
"folder-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h5.175q.4 0 .763.15t.637.425L12 6h8q.825 0 1.413.588T22 8v10q0 .825-.587 1.413T20 20z\"/>"
|
||||
},
|
||||
"format-align-center": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 21v-2h18v2zm4-4v-2h10v2zm-4-4v-2h18v2zm4-4V7h10v2zM3 5V3h18v2z\"/>"
|
||||
},
|
||||
"format-align-left": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 21v-2h18v2zm0-4v-2h12v2zm0-4v-2h18v2zm0-4V7h12v2zm0-4V3h18v2z\"/>"
|
||||
},
|
||||
"format-align-right": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 5V3h18v2zm6 4V7h12v2zm-6 4v-2h18v2zm6 4v-2h12v2zm-6 4v-2h18v2z\"/>"
|
||||
},
|
||||
"format-color-fill-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m8.3.7l7.875 7.875q.575.575.575 1.425t-.575 1.425l-4.75 4.75q-.575.575-1.425.575t-1.425-.575l-4.75-4.75Q3.25 10.85 3.25 10t.575-1.425L8.575 3.8l-1.7-1.7q-.3-.3-.288-.7T6.9.7q.3-.275.7-.287T8.3.7M10 5.225L5.225 10h9.55zm7.588 11.188Q17 15.825 17 15q0-.525.313-1.125T18 12.75q.225-.3.475-.625T19 11.5q.275.3.525.625t.475.625q.375.525.688 1.125T21 15q0 .825-.587 1.413T19 17t-1.412-.587M4 24q-.825 0-1.412-.587T2 22t.588-1.412T4 20h16q.825 0 1.413.588T22 22t-.587 1.413T20 24z\"/>"
|
||||
},
|
||||
"format-underlined": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21v-2h14v2zm3.075-5.575q-1.4-1.575-1.4-4.175V3H9.25v8.4q0 1.4.7 2.275t2.05.875t2.05-.875t.7-2.275V3h2.575v8.25q0 2.6-1.4 4.175T12 17t-3.925-1.575\"/>"
|
||||
},
|
||||
"gavel-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 19h10q.425 0 .713.288T16 20t-.288.713T15 21H5q-.425 0-.712-.288T4 20t.288-.712T5 19m3.225-4.275L5.4 11.9q-.575-.575-.587-1.412t.562-1.413L6.1 8.35L11.8 14l-.725.725q-.575.575-1.425.575t-1.425-.575M16 9.8l-5.65-5.7l.725-.725q.575-.575 1.413-.562T13.9 3.4l2.825 2.825q.575.575.575 1.425t-.575 1.425zm3.9 9.5L7.55 6.95l1.4-1.4L21.3 17.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275\"/>"
|
||||
},
|
||||
"gpp-maybe-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.713 15.713Q13 15.425 13 15t-.288-.712T12 14t-.712.288T11 15t.288.713T12 16t.713-.288m0-4Q13 11.426 13 11V8q0-.425-.288-.712T12 7t-.712.288T11 8v3q0 .425.288.713T12 12t.713-.288m-1.038 10.163q-.15-.025-.3-.075Q8 20.675 6 17.637T4 11.1V6.375q0-.625.363-1.125t.937-.725l6-2.25q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375V11.1q0 3.5-2 6.538T12.625 21.8q-.15.05-.3.075T12 21.9t-.325-.025M12 19.9q2.6-.825 4.3-3.3t1.7-5.5V6.375l-6-2.25l-6 2.25V11.1q0 3.025 1.7 5.5t4.3 3.3m0-7.9\"/>"
|
||||
},
|
||||
"group": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M1 20v-2.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2V20zm18 0v-3q0-1.1-.612-2.113T16.65 13.15q1.275.15 2.4.513t2.1.887q.9.5 1.375 1.112T23 17v3zM6.175 10.825Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175m11.65 0Q16.65 12 15 12q-.275 0-.7-.062t-.7-.138q.675-.8 1.038-1.775T15 8t-.362-2.025T13.6 4.2q.35-.125.7-.163T15 4q1.65 0 2.825 1.175T19 8t-1.175 2.825\"/>"
|
||||
},
|
||||
"group-add": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.5 11.95q.725-.8 1.113-1.825T14 8t-.387-2.125T12.5 4.05q1.5.2 2.5 1.325T16 8t-1 2.625t-2.5 1.325M18 20v-3q0-.9-.4-1.713t-1.05-1.437q1.275.45 2.363 1.163T20 17v3zm2-7v-2h-2V9h2V7h2v2h2v2h-2v2zM5.175 10.825Q4 9.65 4 8t1.175-2.825T8 4t2.825 1.175T12 8t-1.175 2.825T8 12t-2.825-1.175M0 20v-2.8q0-.85.438-1.562T1.6 14.55q1.55-.775 3.15-1.162T8 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T16 17.2V20z\"/>"
|
||||
},
|
||||
"group-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M1 17.2q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2v.8q0 .825-.587 1.413T15 20H3q-.825 0-1.412-.587T1 18zM18.45 20q.275-.45.413-.962T19 18v-1q0-1.1-.612-2.113T16.65 13.15q1.275.15 2.4.513t2.1.887q.9.5 1.375 1.112T23 17v1q0 .825-.587 1.413T21 20zM6.175 10.825Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175m11.65 0Q16.65 12 15 12q-.275 0-.7-.062t-.7-.138q.675-.8 1.038-1.775T15 8t-.362-2.025T13.6 4.2q.35-.125.7-.163T15 4q1.65 0 2.825 1.175T19 8t-1.175 2.825\"/>"
|
||||
},
|
||||
"groups-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M1 18q-.425 0-.712-.288T0 17v-.575q0-1.075 1.1-1.75T4 14q.325 0 .625.013t.575.062q-.35.525-.525 1.1t-.175 1.2V18zm6 0q-.425 0-.712-.288T6 17v-.625q0-.8.438-1.463t1.237-1.162T9.588 13T12 12.75q1.325 0 2.438.25t1.912.75t1.225 1.163t.425 1.462V17q0 .425-.287.713T17 18zm12.5 0v-1.625q0-.65-.162-1.225t-.488-1.075q.275-.05.563-.062T20 14q1.8 0 2.9.663t1.1 1.762V17q0 .425-.288.713T23 18zM4 13q-.825 0-1.412-.587T2 11q0-.85.588-1.425T4 9q.85 0 1.425.575T6 11q0 .825-.575 1.413T4 13m16 0q-.825 0-1.412-.587T18 11q0-.85.588-1.425T20 9q.85 0 1.425.575T22 11q0 .825-.575 1.413T20 13m-8-1q-1.25 0-2.125-.875T9 9q0-1.275.875-2.137T12 6q1.275 0 2.138.863T15 9q0 1.25-.862 2.125T12 12\"/>"
|
||||
},
|
||||
"help-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.838 17.638q.362-.363.362-.888t-.362-.888t-.888-.362t-.887.363t-.363.887t.363.888t.887.362t.888-.363M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m.1-14.3q.625 0 1.088.4t.462 1q0 .55-.337.975t-.763.8q-.575.5-1.012 1.1t-.438 1.35q0 .35.263.588t.612.237q.375 0 .638-.25t.337-.625q.1-.525.45-.937t.75-.788q.575-.55.988-1.2t.412-1.45q0-1.275-1.037-2.087T12.1 6q-.95 0-1.812.4T8.975 7.625q-.175.3-.112.638t.337.512q.35.2.725.125t.625-.425q.275-.375.688-.575t.862-.2\"/>"
|
||||
},
|
||||
"highlight": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5.65 8L3.5 5.9l1.4-1.45L7.05 6.6zM11 5V2h2v3zm7.4 3l-1.45-1.4l2.15-2.1l1.4 1.4zM9 22v-5l-3-3V9h12v5l-3 3v5z\"/>"
|
||||
},
|
||||
"image-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zm0 0V5zm2-2h10q.3 0 .45-.275t-.05-.525l-2.75-3.675q-.15-.2-.4-.2t-.4.2L11.25 16L9.4 13.525q-.15-.2-.4-.2t-.4.2l-2 2.675q-.2.25-.05.525T7 17\"/>"
|
||||
},
|
||||
"info": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 17h2v-6h-2zm1.713-8.287Q13 8.425 13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"info-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16v-4q0-.425-.288-.712T12 11t-.712.288T11 12v4q0 .425.288.713T12 17t.713-.288m0-8Q13 8.425 13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/>"
|
||||
},
|
||||
"info-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.713 16.713Q13 16.425 13 16v-4q0-.425-.288-.712T12 11t-.712.288T11 12v4q0 .425.288.713T12 17t.713-.288m0-8Q13 8.425 13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9t.713-.288M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"javascript-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7.5 15q-.625 0-1.062-.437T6 13.5v-.25q0-.325.213-.537t.537-.213t.538.213t.212.537v.25H9V9.75q0-.325.213-.537T9.75 9t.538.213t.212.537v3.75q0 .625-.437 1.063T9 15zm5.5 0q-.425 0-.712-.288T12 14v-.5q0-.2.15-.35t.35-.15h.5q.2 0 .35.15t.15.35h2v-1H13q-.425 0-.712-.288T12 11.5V10q0-.425.288-.712T13 9h3q.425 0 .713.288T17 10v.5q0 .2-.15.35t-.35.15H16q-.2 0-.35-.15t-.15-.35h-2v1H16q.425 0 .713.288T17 12.5V14q0 .425-.288.713T16 15z\"/>"
|
||||
},
|
||||
"key": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9.125 14.125Q10 13.25 10 12t-.875-2.125T7 9t-2.125.875T4 12t.875 2.125T7 15t2.125-.875M7 18q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q2.025 0 3.538 1.15T12.65 10h8.375L23 11.975l-3.5 4L17 14l-2 2l-2-2h-.35q-.625 1.8-2.175 2.9T7 18\"/>"
|
||||
},
|
||||
"key-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4.875 14.125Q4 13.25 4 12t.875-2.125T7 9t2.125.875T10 12t-.875 2.125T7 15t-2.125-.875M7 18q1.925 0 3.475-1.1T12.65 14H13l1.3 1.3q.15.15.325.212t.375.063t.375-.062t.325-.213L17 14l1.75 1.375q.15.125.338.187t.387.038t.363-.112t.287-.238l2.25-2.575q.125-.15.188-.325t.062-.375t-.075-.362t-.2-.288L21.325 10.3q-.15-.15-.337-.225T20.6 10h-7.95q-.6-1.7-2.113-2.85T7 6Q4.5 6 2.75 7.75T1 12t1.75 4.25T7 18\"/>"
|
||||
},
|
||||
"keyboard-arrow-down": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 15.4l-6-6L7.4 8l4.6 4.6L16.6 8L18 9.4z\"/>"
|
||||
},
|
||||
"keyboard-arrow-up": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 10.8l-4.6 4.6L6 14l6-6l6 6l-1.4 1.4z\"/>"
|
||||
},
|
||||
"keyboard-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 19q-.825 0-1.412-.587T2 17V7q0-.825.588-1.412T4 5h16q.825 0 1.413.588T22 7v10q0 .825-.587 1.413T20 19zm5-3h6q.425 0 .713-.288T16 15t-.288-.712T15 14H9q-.425 0-.712.288T8 15t.288.713T9 16M6.713 9.712Q7 9.425 7 9t-.288-.712T6 8t-.712.288T5 9t.288.713T6 10t.713-.288m3 0Q10 9.426 10 9t-.288-.712T9 8t-.712.288T8 9t.288.713T9 10t.713-.288m3 0Q13 9.426 13 9t-.288-.712T12 8t-.712.288T11 9t.288.713T12 10t.713-.288m3 0Q16 9.426 16 9t-.288-.712T15 8t-.712.288T14 9t.288.713T15 10t.713-.288m3 0Q19 9.426 19 9t-.288-.712T18 8t-.712.288T17 9t.288.713T18 10t.713-.288m-12 3Q7 12.426 7 12t-.288-.712T6 11t-.712.288T5 12t.288.713T6 13t.713-.288m3 0Q10 12.426 10 12t-.288-.712T9 11t-.712.288T8 12t.288.713T9 13t.713-.288m3 0Q13 12.426 13 12t-.288-.712T12 11t-.712.288T11 12t.288.713T12 13t.713-.288m3 0Q16 12.426 16 12t-.288-.712T15 11t-.712.288T14 12t.288.713T15 13t.713-.288m3 0Q19 12.426 19 12t-.288-.712T18 11t-.712.288T17 12t.288.713T18 13t.713-.288\"/>"
|
||||
},
|
||||
"language": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M8.125 21.213q-1.825-.788-3.187-2.15t-2.15-3.188T2 11.988t.788-3.875t2.15-3.175t3.187-2.15T12.013 2t3.875.788t3.175 2.15t2.15 3.175t.787 3.875t-.787 3.887t-2.15 3.188t-3.175 2.15t-3.875.787t-3.888-.787M12 19.95q.65-.9 1.125-1.875T13.9 16h-3.8q.3 1.1.775 2.075T12 19.95m-2.6-.4q-.45-.825-.787-1.713T8.05 16H5.1q.725 1.25 1.813 2.175T9.4 19.55m5.2 0q1.4-.45 2.488-1.375T18.9 16h-2.95q-.225.95-.562 1.838T14.6 19.55M4.25 14h3.4q-.075-.5-.112-.987T7.5 12t.038-1.012T7.65 10h-3.4q-.125.5-.187.988T4 12t.063 1.013t.187.987m5.4 0h4.7q.075-.5.113-.987T14.5 12t-.038-1.012T14.35 10h-4.7q-.075.5-.112.988T9.5 12t.038 1.013t.112.987m6.7 0h3.4q.125-.5.188-.987T20 12t-.062-1.012T19.75 10h-3.4q.075.5.113.988T16.5 12t-.038 1.013t-.112.987m-.4-6h2.95q-.725-1.25-1.812-2.175T14.6 4.45q.45.825.788 1.713T15.95 8M10.1 8h3.8q-.3-1.1-.775-2.075T12 4.05q-.65.9-1.125 1.875T10.1 8m-5 0h2.95q.225-.95.563-1.838T9.4 4.45Q8 4.9 6.912 5.825T5.1 8\"/>"
|
||||
},
|
||||
"layers-clear-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M18.975 7.425q.775.6.775 1.575t-.775 1.575L16.8 12.25q-.3.225-.675.2t-.65-.3q-.325-.325-.288-.763t.388-.712L17.75 9L12 4.55L10.35 5.8q-.3.225-.675.2t-.65-.3q-.325-.325-.288-.763t.388-.712l1.65-1.275q.55-.425 1.225-.425t1.225.425zM19.1 21.4l-3.3-3.3l-2.575 2q-.55.425-1.225.425t-1.225-.425l-6.75-5.25q-.4-.3-.387-.787t.412-.788q.275-.2.6-.2t.6.2L12 18.5l2.35-1.825l-1.75-1.7h.725l-.1.05q-.55.425-1.225.438t-1.225-.413l-5.75-4.475Q4.25 9.975 4.25 9t.775-1.575l.05-.05L2.1 4.425q-.3-.3-.312-.712T2.075 3t.713-.3t.712.3l17 17q.275.275.275.7t-.275.7t-.7.275t-.7-.275m.875-8.125q.375.3.375.775t-.375.8l-.3.25q-.3.25-.675.225t-.65-.3q-.325-.325-.3-.788t.4-.737l.3-.225q.275-.2.613-.2t.612.2\"/>"
|
||||
},
|
||||
"layers-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4.025 14.85q-.4-.3-.387-.787t.412-.788q.275-.2.6-.2t.6.2L12 18.5l6.75-5.225q.275-.2.6-.2t.6.2q.4.3.413.787t-.388.788l-6.75 5.25q-.55.425-1.225.425t-1.225-.425zm6.75.2l-5.75-4.475Q4.25 9.975 4.25 9t.775-1.575l5.75-4.475q.55-.425 1.225-.425t1.225.425l5.75 4.475q.775.6.775 1.575t-.775 1.575l-5.75 4.475q-.55.425-1.225.425t-1.225-.425M12 13.45L17.75 9L12 4.55L6.25 9zM12 9\"/>"
|
||||
},
|
||||
"library-add-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M14.713 13.713Q15 13.425 15 13v-2h2q.425 0 .713-.288T18 10t-.288-.712T17 9h-2V7q0-.425-.288-.712T14 6t-.712.288T13 7v2h-2q-.425 0-.712.288T10 10t.288.713T11 11h2v2q0 .425.288.713T14 14t.713-.288M8 18q-.825 0-1.412-.587T6 16V4q0-.825.588-1.412T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18zm0-2h12V4H8zm-4 6q-.825 0-1.412-.587T2 20V7q0-.425.288-.712T3 6t.713.288T4 7v13h13q.425 0 .713.288T18 21t-.288.713T17 22zM8 4v12z\"/>"
|
||||
},
|
||||
"link": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 17H7q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h4v2H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h4zm-3-4v-2h8v2zm5 4v-2h4q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-4V7h4q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z\"/>"
|
||||
},
|
||||
"link-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7 17q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h3q.425 0 .713.288T11 8t-.288.713T10 9H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h3q.425 0 .713.288T11 16t-.288.713T10 17zm2-4q-.425 0-.712-.288T8 12t.288-.712T9 11h6q.425 0 .713.288T16 12t-.288.713T15 13zm5 4q-.425 0-.712-.288T13 16t.288-.712T14 15h3q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-3q-.425 0-.712-.288T13 8t.288-.712T14 7h3q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z\"/>"
|
||||
},
|
||||
"lock": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 22q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h1V6q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22zm7.413-5.587Q14 15.825 14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17t1.413-.587M9 8h6V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6z\"/>"
|
||||
},
|
||||
"lock-open": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 8h9V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6H7q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22H6q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8m7.413 8.413Q14 15.825 14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17t1.413-.587\"/>"
|
||||
},
|
||||
"lock-open-right-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 20h12V10H6zm7.413-3.588Q14 15.826 14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17t1.413-.587M6 20V10zm0 2q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h7V6q0-2.075 1.463-3.537T18 1q1.775 0 3.1 1.075t1.75 2.7q.125.425-.162.825T22 6q-.425 0-.7-.175t-.4-.575q-.275-.95-1.062-1.6T18 3q-1.25 0-2.125.875T15 6v2h3q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22z\"/>"
|
||||
},
|
||||
"lock-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 22q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h1V6q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22zm0-2h12V10H6zm7.413-3.588Q14 15.826 14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17t1.413-.587M9 8h6V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6zM6 20V10z\"/>"
|
||||
},
|
||||
"login": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 21v-2h7V5h-7V3h7q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm-2-4l-1.375-1.45l2.55-2.55H3v-2h8.175l-2.55-2.55L10 7l5 5z\"/>"
|
||||
},
|
||||
"logout": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h7v2H5v14h7v2zm11-4l-1.375-1.45l2.55-2.55H9v-2h8.175l-2.55-2.55L16 7l5 5z\"/>"
|
||||
},
|
||||
"logout-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h6q.425 0 .713.288T12 4t-.288.713T11 5H5v14h6q.425 0 .713.288T12 20t-.288.713T11 21zm12.175-8H10q-.425 0-.712-.288T9 12t.288-.712T10 11h7.175L15.3 9.125q-.275-.275-.275-.675t.275-.7t.7-.313t.725.288L20.3 11.3q.3.3.3.7t-.3.7l-3.575 3.575q-.3.3-.712.288t-.713-.313q-.275-.3-.262-.712t.287-.688z\"/>"
|
||||
},
|
||||
"looks-one-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 9v7q0 .425.288.713T13 17t.713-.288T14 16V8q0-.425-.288-.712T13 7h-2q-.425 0-.712.288T10 8t.288.713T11 9zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zM5 5v14z\"/>"
|
||||
},
|
||||
"mail-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm8.263-7.212q.137-.038.262-.113L19.6 8.25q.2-.125.3-.312t.1-.413q0-.5-.425-.75T18.7 6.8L12 11L5.3 6.8q-.45-.275-.875-.012T4 7.525q0 .25.1.438t.3.287l7.075 4.425q.125.075.263.113t.262.037t.263-.037\"/>"
|
||||
},
|
||||
"match-word-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 19.025q-.825 0-1.412-.587T1 17.025v-2q0-.425.288-.712T2 14.025t.713.288t.287.712v2h18v-2q0-.425.288-.713t.712-.287t.713.288t.287.712v2q0 .825-.587 1.413T21 19.025zm4.35-3.8q-1.225 0-1.925-.637t-.7-1.738q0-1.05.813-1.712t2.087-.663q.575 0 1.063.088t.837.287v-.35q0-.675-.462-1.075t-1.263-.4q-.375 0-.712.113t-.613.312q-.225.175-.487.2T5.5 9.5t-.275-.437t.15-.463q.45-.425 1.063-.65t1.387-.225q1.55 0 2.375.738t.825 2.137v3.675q0 .3-.213.513T10.3 15q-.325 0-.537-.213t-.213-.537v-.1h-.075q-.325.5-.875.788t-1.25.287m.55-3.575q-.8 0-1.225.313t-.425.887q0 .5.375.813t.975.312q.8 0 1.363-.562t.562-1.363q-.35-.2-.8-.3t-.825-.1m8.425 3.575q-1.025 0-1.562-.45t-.688-.7H14v.325q0 .3-.212.513t-.513.212t-.525-.225t-.225-.525V5.75q0-.325.225-.55t.55-.225t.55.225t.225.55V7.8L14 8.8h.075q.075-.125.6-.638t1.65-.512q1.6 0 2.525 1.15t.925 2.65t-.912 2.638t-2.538 1.137M16.1 9.05q-1 0-1.55.738T14 11.425q0 .925.55 1.65t1.55.725t1.563-.725t.562-1.65t-.562-1.65T16.1 9.05\"/>"
|
||||
},
|
||||
"menu-book-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6.5 16q1.175 0 2.288.263T11 17.05V7.2q-1.025-.6-2.175-.9T6.5 6q-.9 0-1.788.175T3 6.7v9.9q.875-.3 1.738-.45T6.5 16m6.5 1.05q1.1-.525 2.213-.787T17.5 16q.9 0 1.763.15T21 16.6V6.7q-.825-.35-1.713-.525T17.5 6q-1.175 0-2.325.3T13 7.2zm-1.662 2.338q-.313-.088-.588-.238q-.975-.575-2.05-.862T6.5 18q-1.05 0-2.062.275T2.5 19.05q-.525.275-1.012-.025T1 18.15V6.1q0-.275.138-.525T1.55 5.2q1.15-.6 2.4-.9T6.5 4q1.45 0 2.838.375T12 5.5q1.275-.75 2.663-1.125T17.5 4q1.3 0 2.55.3t2.4.9q.275.125.413.375T23 6.1v12.05q0 .575-.487.875t-1.013.025q-.925-.5-1.937-.775T17.5 18q-1.125 0-2.2.288t-2.05.862q-.275.15-.587.238t-.663.087t-.663-.088M14 8.776q0-.225.163-.462T14.525 8q.725-.25 1.45-.375T17.5 7.5q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112T17.5 9q-.65 0-1.275.125t-1.2.325q-.45.175-.737-.025T14 8.775m0 5.5q0-.225.163-.462t.362-.313q.725-.25 1.45-.375T17.5 13q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112T17.5 14.5q-.65 0-1.275.113t-1.2.312q-.45.175-.737-.012T14 14.275m0-2.75q0-.225.163-.462t.362-.313q.725-.25 1.45-.375t1.525-.125q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112t-.788-.038q-.65 0-1.275.125t-1.2.325q-.45.175-.737-.025t-.288-.65\"/>"
|
||||
},
|
||||
"menu-book-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13 17.05q1.1-.525 2.213-.788T17.5 16q.9 0 1.763.15T21 16.6V6.7q-.825-.35-1.713-.525T17.5 6q-1.175 0-2.325.3T13 7.2zm-1.662 2.338q-.313-.088-.588-.238q-.975-.575-2.05-.862T6.5 18q-1.05 0-2.062.275T2.5 19.05q-.525.275-1.012-.025T1 18.15V6.1q0-.275.138-.525T1.55 5.2q1.175-.575 2.413-.888T6.5 4q1.45 0 2.838.375T12 5.5q1.275-.75 2.663-1.125T17.5 4q1.3 0 2.538.313t2.412.887q.275.125.413.375T23 6.1v12.05q0 .575-.487.875t-1.013.025q-.925-.5-1.937-.775T17.5 18q-1.125 0-2.2.288t-2.05.862q-.275.15-.587.238t-.663.087t-.663-.088M14 8.776q0-.225.163-.462T14.525 8q.725-.25 1.45-.375T17.5 7.5q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112T17.5 9q-.65 0-1.275.125t-1.2.325q-.45.175-.737-.025T14 8.775m0 5.5q0-.225.163-.462t.362-.313q.725-.25 1.45-.375T17.5 13q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112T17.5 14.5q-.65 0-1.275.113t-1.2.312q-.45.175-.737-.012T14 14.275m0-2.75q0-.225.163-.462t.362-.313q.725-.25 1.45-.375t1.525-.125q.5 0 .988.063t.962.162q.225.05.388.25t.162.45q0 .425-.275.625t-.7.1q-.35-.075-.737-.112t-.788-.038q-.65 0-1.275.125t-1.2.325q-.45.175-.737-.025t-.288-.65\"/>"
|
||||
},
|
||||
"more-vert": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 20q-.825 0-1.412-.587T10 18t.588-1.412T12 16t1.413.588T14 18t-.587 1.413T12 20m0-6q-.825 0-1.412-.587T10 12t.588-1.412T12 10t1.413.588T14 12t-.587 1.413T12 14m0-6q-.825 0-1.412-.587T10 6t.588-1.412T12 4t1.413.588T14 6t-.587 1.413T12 8\"/>"
|
||||
},
|
||||
"move-down-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 11q0 1.8 1.15 3.175T7.075 15.9l-.775-.775q-.275-.275-.275-.687t.275-.713q.3-.3.713-.3t.712.3L10.3 16.3q.3.3.3.7t-.3.7l-2.6 2.6q-.275.275-.7.275t-.725-.3Q6 20 6 19.6t.275-.7l.9-.95q-2.65-.3-4.412-2.287T1 11q0-2.925 2.038-4.962T8 4h2q.425 0 .713.288T11 5t-.288.713T10 6H8Q5.925 6 4.463 7.463T3 11m11 9q-.425 0-.712-.288T13 19v-5q0-.425.288-.712T14 13h7q.425 0 .713.288T22 14v5q0 .425-.288.713T21 20zm0-9q-.425 0-.712-.288T13 10V5q0-.425.288-.712T14 4h7q.425 0 .713.288T22 5v5q0 .425-.288.713T21 11zm1-2h5V6h-5z\"/>"
|
||||
},
|
||||
"opacity": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6.338 18.7Q4 16.4 4 13.1q0-1.625.625-3.037T6.35 7.55L12 2l5.65 5.55q1.1 1.1 1.725 2.513T20 13.1q0 3.3-2.337 5.6T12 21t-5.663-2.3M6.05 14H17.9q.3-1.8-.337-3.075T16.25 9L12 4.8L7.75 9q-.675.65-1.325 1.925T6.05 14\"/>"
|
||||
},
|
||||
"open-in-new-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h6q.425 0 .713.288T12 4t-.288.713T11 5H5v14h14v-6q0-.425.288-.712T20 12t.713.288T21 13v6q0 .825-.587 1.413T19 21zM19 6.4L10.4 15q-.275.275-.7.275T9 15t-.275-.7t.275-.7L17.6 5H15q-.425 0-.712-.288T14 4t.288-.712T15 3h5q.425 0 .713.288T21 4v5q0 .425-.288.713T20 10t-.712-.288T19 9z\"/>"
|
||||
},
|
||||
"palette-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 22q-2.05 0-3.875-.788t-3.187-2.15t-2.15-3.187T2 12q0-2.075.813-3.9t2.2-3.175T8.25 2.788T12.2 2q2 0 3.775.688t3.113 1.9t2.125 2.875T22 11.05q0 2.875-1.75 4.413T16 17h-1.85q-.225 0-.312.125t-.088.275q0 .3.375.863t.375 1.287q0 1.25-.687 1.85T12 22m-4.425-9.425Q8 12.15 8 11.5t-.425-1.075T6.5 10t-1.075.425T5 11.5t.425 1.075T6.5 13t1.075-.425m3-4Q11 8.15 11 7.5t-.425-1.075T9.5 6t-1.075.425T8 7.5t.425 1.075T9.5 9t1.075-.425m5 0Q16 8.15 16 7.5t-.425-1.075T14.5 6t-1.075.425T13 7.5t.425 1.075T14.5 9t1.075-.425m3 4Q19 12.15 19 11.5t-.425-1.075T17.5 10t-1.075.425T16 11.5t.425 1.075T17.5 13t1.075-.425M12 20q.225 0 .363-.125t.137-.325q0-.35-.375-.825T11.75 17.3q0-1.05.725-1.675T14.25 15H16q1.65 0 2.825-.962T20 11.05q0-3.025-2.312-5.038T12.2 4Q8.8 4 6.4 6.325T4 12q0 3.325 2.338 5.663T12 20\"/>"
|
||||
},
|
||||
"pan-tool-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10.05 23q-.75 0-1.4-.337T7.575 21.7l-5.9-8.65q-.2-.3-.175-.65t.3-.6q.475-.475 1.125-.55t1.175.3L7 13.575V4q0-.425.288-.712T8 3t.713.288T9 4v8h2V2q0-.425.288-.712T12 1t.713.288T13 2v10h2V3q0-.425.288-.712T16 2t.713.288T17 3v9h2V5q0-.425.288-.712T20 4t.713.288T21 5v14q0 1.65-1.175 2.825T17 23z\"/>"
|
||||
},
|
||||
"password-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 17h18q.425 0 .713.288T22 18t-.288.713T21 19H3q-.425 0-.712-.288T2 18t.288-.712T3 17m1-5.55l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T1 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .538.213T7 9.95t-.213.538t-.537.212H5.3l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35zm8 0l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T9 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .537.213T15 9.95t-.213.538t-.537.212h-.95l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35zm8 0l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T17 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .538.213T23 9.95t-.213.538t-.537.212h-.95l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35z\"/>"
|
||||
},
|
||||
"pause-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M16 19q-.825 0-1.412-.587T14 17V7q0-.825.588-1.412T16 5t1.413.588T18 7v10q0 .825-.587 1.413T16 19m-8 0q-.825 0-1.412-.587T6 17V7q0-.825.588-1.412T8 5t1.413.588T10 7v10q0 .825-.587 1.413T8 19\"/>"
|
||||
},
|
||||
"person": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9.175 10.825Q8 9.65 8 8t1.175-2.825T12 4t2.825 1.175T16 8t-1.175 2.825T12 12t-2.825-1.175M4 20v-2.8q0-.85.438-1.562T5.6 14.55q1.55-.775 3.15-1.162T12 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T20 17.2V20z\"/>"
|
||||
},
|
||||
"person-add": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M18 14v-3h-3V9h3V6h2v3h3v2h-3v3zM6.175 10.825Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175M1 20v-2.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2V20z\"/>"
|
||||
},
|
||||
"person-add-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M18 11h-2q-.425 0-.712-.288T15 10t.288-.712T16 9h2V7q0-.425.288-.712T19 6t.713.288T20 7v2h2q.425 0 .713.288T23 10t-.288.713T22 11h-2v2q0 .425-.288.713T19 14t-.712-.288T18 13zm-11.825-.175Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175M1 18v-.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2v.8q0 .825-.587 1.413T15 20H3q-.825 0-1.412-.587T1 18\"/>"
|
||||
},
|
||||
"person-check": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17.55 12L14 8.45l1.425-1.4l2.125 2.125l4.25-4.25l1.4 1.425zM6.175 10.825Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175M1 20v-2.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2V20z\"/>"
|
||||
},
|
||||
"person-off": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M19.775 22.625L17.15 20H4v-2.8q0-.85.438-1.562T5.6 14.55q1.125-.575 2.288-.925t2.362-.525L1.375 4.225L2.8 2.8l18.4 18.4zM18.4 14.55q.725.35 1.15 1.062T20 17.15l-3.35-3.35q.45.175.888.35t.862.4m-4.2-3.2L8.65 5.8q.575-.85 1.45-1.325T12 4q1.65 0 2.825 1.175T16 8q0 1.025-.475 1.9T14.2 11.35\"/>"
|
||||
},
|
||||
"person-remove": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M16 11V9h6v2zm-9.825-.175Q5 9.65 5 8t1.175-2.825T9 4t2.825 1.175T13 8t-1.175 2.825T9 12t-2.825-1.175M1 20v-2.8q0-.85.438-1.562T2.6 14.55q1.55-.775 3.15-1.162T9 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T17 17.2V20z\"/>"
|
||||
},
|
||||
"person-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9.175 10.825Q8 9.65 8 8t1.175-2.825T12 4t2.825 1.175T16 8t-1.175 2.825T12 12t-2.825-1.175M4 18v-.8q0-.85.438-1.562T5.6 14.55q1.55-.775 3.15-1.162T12 13t3.25.388t3.15 1.162q.725.375 1.163 1.088T20 17.2v.8q0 .825-.587 1.413T18 20H6q-.825 0-1.412-.587T4 18\"/>"
|
||||
},
|
||||
"photo-library-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13.25 12.5L12.1 11q-.15-.175-.4-.175t-.4.175l-1.675 2.2q-.2.25-.063.525t.463.275h7.95q.325 0 .462-.275t-.062-.525l-2.425-3.175q-.15-.2-.4-.2t-.4.2l-1.9 2.475ZM8 18q-.825 0-1.413-.588T6 16V4q0-.825.588-1.413T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.588 1.413T20 18H8Zm0-2h12V4H8v12Zm-4 6q-.825 0-1.413-.588T2 20V7q0-.425.288-.713T3 6q.425 0 .713.288T4 7v13h13q.425 0 .713.288T18 21q0 .425-.288.713T17 22H4ZM8 4v12V4Z\"/>",
|
||||
"hidden": true
|
||||
},
|
||||
"picture-as-pdf-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10 10.5h1q.425 0 .713-.288T12 9.5v-1q0-.425-.288-.712T11 7.5H9.5q-.2 0-.35.15T9 8v4q0 .2.15.35t.35.15t.35-.15T10 12zm0-1v-1h1v1zm5 3q.425 0 .713-.288T16 11.5v-3q0-.425-.288-.712T15 7.5h-1.5q-.2 0-.35.15T13 8v4q0 .2.15.35t.35.15zm-1-1v-3h1v3zm4-1h.5q.2 0 .35-.15T19 10t-.15-.35t-.35-.15H18v-1h.5q.2 0 .35-.15T19 8t-.15-.35t-.35-.15h-1q-.2 0-.35.15T17 8v4q0 .2.15.35t.35.15t.35-.15T18 12zM8 18q-.825 0-1.412-.587T6 16V4q0-.825.588-1.412T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18zm-4 4q-.825 0-1.412-.587T2 20V7q0-.425.288-.712T3 6t.713.288T4 7v13h13q.425 0 .713.288T18 21t-.288.713T17 22z\"/>"
|
||||
},
|
||||
"pin-end": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v6h-2V6H4v12h10v2zm9.95-5.625L11 11.425v2.225H9V8h5.65v2H12.4l2.95 2.95zM19 20q-1.25 0-2.125-.875T16 17t.875-2.125T19 14t2.125.875T22 17t-.875 2.125T19 20\"/>"
|
||||
},
|
||||
"play-arrow-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M8 17.175V6.825q0-.425.3-.713t.7-.287q.125 0 .263.037t.262.113l8.15 5.175q.225.15.338.375t.112.475t-.112.475t-.338.375l-8.15 5.175q-.125.075-.262.113T9 18.175q-.4 0-.7-.288t-.3-.712\"/>"
|
||||
},
|
||||
"play-circle": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m9.5 16.5l7-4.5l-7-4.5zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
|
||||
},
|
||||
"preview-off-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 17q-2.05 0-3.662-1.112T6 13q.5-1.2 1.4-2.1t2.1-1.4l1.175 1.175q-.975.275-1.75.863T7.65 13q.65 1.175 1.8 1.838T12 15.5q.75 0 1.45-.2t1.275-.575L15.8 15.8q-.8.575-1.763.888T12 17m5.225-2.6l-1.075-1.075q.05-.075.1-.162t.1-.163q-.45-.825-1.175-1.412t-1.625-.863L11.825 9q2.05 0 3.75 1.113T18 13q-.15.375-.337.725t-.438.675M5 21q-.825 0-1.412-.587T3 19V5.825L2.075 4.9Q1.8 4.625 1.8 4.212t.3-.712q.275-.275.7-.275t.7.275l17 17q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-.9-.9zm0-2h11.175L5 7.825zm16-.825l-2-2V8h-8.175l-5-5H19q.825 0 1.413.588T21 5z\"/>"
|
||||
},
|
||||
"qr-code-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 10V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v6q0 .425-.288.713T10 11H4q-.425 0-.712-.288T3 10m2-1h4V5H5zM3 20v-6q0-.425.288-.712T4 13h6q.425 0 .713.288T11 14v6q0 .425-.288.713T10 21H4q-.425 0-.712-.288T3 20m2-1h4v-4H5zm8-9V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v6q0 .425-.288.713T20 11h-6q-.425 0-.712-.288T13 10m2-1h4V5h-4zm4 12v-2h2v2zm-6-6v-2h2v2zm2 2v-2h2v2zm-2 2v-2h2v2zm2 2v-2h2v2zm2-2v-2h2v2zm0-4v-2h2v2zm2 2v-2h2v2z\"/>"
|
||||
},
|
||||
"quick-reference-all-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 4v6.025V10v10zv5zm3 10h2q.425 0 .713-.288T11 13t-.288-.712T10 12H8q-.425 0-.712.288T7 13t.288.713T8 14m0 4h1q.425 0 .713-.288T10 17t-.288-.712T9 16H8q-.425 0-.712.288T7 17t.288.713T8 18m-3 4q-.825 0-1.412-.587T3 20V4q0-.825.588-1.412T5 2h7.175q.4 0 .763.15t.637.425l4.85 4.85q.275.275.425.638t.15.762v.7q0 .425-.288.7T18 10.5t-.712-.288T17 9.5V9h-4q-.425 0-.712-.288T12 8V4H5v16h5.5q.425 0 .713.288T11.5 21t-.288.713T10.5 22zm13.275-3.725Q19 17.55 19 16.5t-.725-1.775T16.5 14t-1.775.725T14 16.5t.725 1.775T16.5 19t1.775-.725m3.325 4.3q-.425 0-.7-.275l-2-2q-.525.35-1.137.525T16.5 21q-1.875 0-3.187-1.312T12 16.5t1.313-3.187T16.5 12t3.188 1.313T21 16.5q0 .65-.175 1.263T20.3 18.9l2 2q.275.275.275.7t-.275.7t-.7.275\"/>"
|
||||
},
|
||||
"radio-button-unchecked": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20\"/>"
|
||||
},
|
||||
"redo": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9.9 19q-2.425 0-4.163-1.575T4 13.5t1.738-3.925T9.9 8h6.3l-2.6-2.6L15 4l5 5l-5 5l-1.4-1.4l2.6-2.6H9.9q-1.575 0-2.738 1T6 13.5T7.163 16T9.9 17H17v2z\"/>"
|
||||
},
|
||||
"refresh": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V4h2v7h-7V9h4.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.925 0 3.475-1.1T17.65 14h2.1q-.7 2.65-2.85 4.325T12 20\"/>"
|
||||
},
|
||||
"refresh-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V5q0-.425.288-.712T19 4t.713.288T20 5v5q0 .425-.288.713T19 11h-5q-.425 0-.712-.288T13 10t.288-.712T14 9h3.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.7 0 3.113-.862t2.187-2.313q.2-.35.563-.487t.737-.013q.4.125.575.525t-.025.75q-1.025 2-2.925 3.2T12 20\"/>"
|
||||
},
|
||||
"remove-moderator-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M20 6.375V11.1q0 .875-.137 1.738t-.413 1.712q-.175.525-.55.688t-.725.037t-.587-.437t-.088-.763q.25-.7.375-1.462T18 11.1V6.375l-6-2.25l-3.05 1.15q-.275.1-.562.038T7.9 5.05q-.4-.4-.275-.925T8.25 3.4l3.05-1.125q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375M12 21.9q-.1 0-.625-.1Q8 20.675 6 17.637T4 11.1V6.8L2.1 4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l17 17q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-2.55-2.55q-.825.875-1.812 1.475t-2.113.975q-.15.05-.3.075T12 21.9m0-2q.875-.275 1.675-.775t1.475-1.175L6 8.8v2.3q0 3.025 1.7 5.5t4.3 3.3\"/>"
|
||||
},
|
||||
"remove-selection-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m16.075 17.475l-3.25 3.25q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l3.25-3.25l-3.25-3.25q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l3.25 3.25l3.25-3.25q.275-.275.7-.275t.7.275t.275.7t-.275.7l-3.25 3.25l3.25 3.25q.275.275.275.7t-.275.7t-.7.275t-.7-.275zM3.288 4.712Q3 4.426 3 4t.288-.712T4 3t.713.288T5 4t-.288.713T4 5t-.712-.288m4 0Q7 4.426 7 4t.288-.712T8 3t.713.288T9 4t-.288.713T8 5t-.712-.288m4 0Q11 4.426 11 4t.288-.712T12 3t.713.288T13 4t-.288.713T12 5t-.712-.288m4 0Q15 4.426 15 4t.288-.712T16 3t.713.288T17 4t-.288.713T16 5t-.712-.288m4 0Q19 4.426 19 4t.288-.712T20 3t.713.288T21 4t-.288.713T20 5t-.712-.288m-12 16Q7 20.426 7 20t.288-.712T8 19t.713.288T9 20t-.288.713T8 21t-.712-.288m-4-12Q3 8.425 3 8t.288-.712T4 7t.713.288T5 8t-.288.713T4 9t-.712-.288m0 4Q3 12.426 3 12t.288-.712T4 11t.713.288T5 12t-.288.713T4 13t-.712-.288m0 4Q3 16.426 3 16t.288-.712T4 15t.713.288T5 16t-.288.713T4 17t-.712-.288m0 4Q3 20.426 3 20t.288-.712T4 19t.713.288T5 20t-.288.713T4 21t-.712-.288m16-12Q19 8.425 19 8t.288-.712T20 7t.713.288T21 8t-.288.713T20 9t-.712-.288\"/>"
|
||||
},
|
||||
"rocket-launch": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m2.45 10.575l4.2-4.2q.35-.35.825-.5t.975-.05l1.3.275Q8.4 7.7 7.625 9t-1.5 3.15zm5.125 2.275q.575-1.8 1.563-3.4t2.387-3q2.2-2.2 5.025-3.287t5.275-.663q.425 2.45-.65 5.275T17.9 12.8q-1.375 1.375-3 2.388t-3.425 1.587zm8.313-2.425q.837 0 1.412-.575t.575-1.412t-.575-1.413t-1.412-.575t-1.413.575t-.575 1.413t.575 1.412t1.413.575m-2.113 11.45l-1.6-3.675q1.85-.725 3.163-1.5t2.912-2.125l.25 1.3q.1.5-.05.988t-.5.837zM4.05 16.05q.875-.875 2.125-.888t2.125.863t.875 2.125t-.875 2.125q-.625.625-2.088 1.075t-4.037.8q.35-2.575.8-4.025T4.05 16.05\"/>"
|
||||
},
|
||||
"rotate-left": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 22q-1.25-.125-2.4-.612T6.4 20.1l1.4-1.45q.725.525 1.538.85t1.662.45zm2 0v-2.05q2.6-.375 4.3-2.337T19 13.05q0-2.925-2.037-4.962T12 6.05h-.2l1.6 1.6l-1.4 1.4l-4-4l4-4l1.4 1.45l-1.55 1.55H12q1.875 0 3.513.713t2.85 1.925t1.925 2.85T21 13.05q0 3.425-2.275 5.963T13 22m-8.05-3.35q-.8-1.05-1.288-2.2t-.612-2.4H5.1q.125.85.45 1.663t.85 1.537zm-1.9-6.6q.15-1.275.625-2.45T4.95 7.45l1.45 1.4q-.525.725-.85 1.538T5.1 12.05z\"/>"
|
||||
},
|
||||
"rotate-right": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13.05 22v-2.05q.85-.125 1.663-.45t1.537-.85l1.4 1.45q-1.05.8-2.2 1.287t-2.4.613m-2 0q-3.45-.45-5.725-2.988T3.05 13.05q0-1.875.713-3.512t1.925-2.85t2.85-1.925t3.512-.713h.15L10.65 2.5l1.4-1.45l4 4l-4 4l-1.4-1.4l1.6-1.6h-.2q-2.925 0-4.962 2.038T5.05 13.05q0 2.6 1.7 4.563t4.3 2.337zm8.05-3.35l-1.45-1.4q.525-.725.85-1.537t.45-1.663H21q-.125 1.25-.612 2.4t-1.288 2.2m1.9-6.6h-2.05q-.125-.85-.45-1.662t-.85-1.538l1.45-1.4q.8.975 1.275 2.15T21 12.05\"/>"
|
||||
},
|
||||
"rotate-right-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M19.75 12.05q-.3 0-.587-.213T18.8 11.3q-.125-.475-.312-.937t-.438-.888q-.175-.275-.125-.612T18.2 8.3q.375-.35.863-.3t.737.475q.325.55.575 1.125t.4 1.2q.125.5-.187.875t-.838.375m-6.7 8.7q0-.3.213-.587t.537-.363q.475-.125.938-.312t.887-.438q.275-.175.613-.125t.562.275q.35.375.3.863t-.475.737q-.575.325-1.15.563t-1.175.412q-.5.125-.875-.175t-.375-.85m5.15-2.95q-.225-.2-.275-.55t.125-.625q.25-.425.438-.888t.312-.937q.075-.325.35-.537t.6-.213q.55 0 .85.375t.175.875q-.175.6-.412 1.175t-.563 1.15q-.25.425-.737.475t-.863-.3m-8.375 3.95q-2.975-.8-4.875-3.2t-1.9-5.5q0-3.75 2.625-6.375T12.05 4.05h.15l-.875-.875q-.275-.275-.275-.7t.3-.725q.275-.275.7-.275t.725.275l2.575 2.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-2.6 2.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l.9-.9h-.2q-2.925 0-4.962 2.037T5.05 13.05q0 2.425 1.475 4.288T10.3 19.8q.325.075.538.35t.212.6q0 .525-.363.825t-.862.175\"/>"
|
||||
},
|
||||
"save": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M21 7v12q0 .825-.587 1.413T19 21H5q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h12zm-6.875 10.125Q15 16.25 15 15t-.875-2.125T12 12t-2.125.875T9 15t.875 2.125T12 18t2.125-.875M6 10h9V6H6z\"/>"
|
||||
},
|
||||
"save-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h11.175q.4 0 .763.15t.637.425l2.85 2.85q.275.275.425.638t.15.762V19q0 .825-.587 1.413T19 21zm9.125-3.875Q15 16.25 15 15t-.875-2.125T12 12t-2.125.875T9 15t.875 2.125T12 18t2.125-.875M7 10h7q.425 0 .713-.288T15 9V7q0-.425-.288-.712T14 6H7q-.425 0-.712.288T6 7v2q0 .425.288.713T7 10\"/>"
|
||||
},
|
||||
"scan-delete-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 4v5zv16v-.238V20zm0 18q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h7.175q.4 0 .763.15t.637.425l4.85 4.85q.275.275.425.638t.15.762V11.6q0 .425-.288.713T19 12.6t-.712-.287T18 11.6V9h-3q-.825 0-1.412-.587T13 7V4H6v16h6.2q.425 0 .713.288T13.2 21t-.287.713T12.2 22zm12-2.575L16.6 20.8q-.275.275-.687.288T15.2 20.8q-.275-.275-.275-.7t.275-.7l1.4-1.4l-1.4-1.4q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l1.4 1.4l1.4-1.4q.275-.275.688-.287t.712.287q.275.275.275.7t-.275.7L19.425 18l1.375 1.4q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275z\"/>"
|
||||
},
|
||||
"scan-delete-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M15 9h3l-5-5v3q0 .825.588 1.413T15 9m3 10.425L16.6 20.8q-.275.275-.687.288T15.2 20.8q-.275-.275-.275-.7t.275-.7l1.4-1.4l-1.4-1.4q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l1.4 1.4l1.4-1.4q.275-.275.688-.287t.712.287q.275.275.275.7t-.275.7L19.425 18l1.375 1.4q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275zM6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h7.175q.4 0 .763.15t.637.425l4.85 4.85q.275.275.425.638t.15.762v2.725q0 .3-.238.475t-.537.1q-1.475-.325-2.925.1t-2.55 1.525q-.8.8-1.275 1.887T12 17.976q0 .725.175 1.45T12.7 20.8q.225.425.025.813T12.1 22z\"/>"
|
||||
},
|
||||
"scanner-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17.6 12L4.45 7.25q-.4-.15-.575-.525T3.85 5.95t.525-.575t.775-.025L19.8 10.7q.5.2.85.7t.35 1.1V18q0 .825-.587 1.413T19 20H5q-.825 0-1.412-.587T3 18v-4q0-.825.588-1.412T5 12zm1.4 6v-4H5v4zm-8-1h6q.425 0 .713-.288T18 16t-.288-.712T17 15h-6q-.425 0-.712.288T10 16t.288.713T11 17m-3.287-.288Q8 16.426 8 16t-.288-.712T7 15t-.712.288T6 16t.288.713T7 17t.713-.288M5 18v-4z\"/>"
|
||||
},
|
||||
"schedule-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13 11.6V8q0-.425-.288-.712T12 7t-.712.288T11 8v3.975q0 .2.075.388t.225.337l3.3 3.3q.275.275.7.275T16 16t.275-.7t-.275-.7zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.325 0 5.663-2.337T20 12t-2.337-5.663T12 4T6.337 6.338T4 12t2.338 5.663T12 20\"/>"
|
||||
},
|
||||
"search": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14\"/>"
|
||||
},
|
||||
"search-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l5.6 5.6q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-5.6-5.6q-.75.6-1.725.95T9.5 16m0-2q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14\"/>"
|
||||
},
|
||||
"security": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 22q-3.475-.875-5.738-3.988T4 11.1V5l8-3l8 3v6.1q0 3.8-2.262 6.913T12 22m0-2.1q2.425-.75 4.05-2.963T17.95 12H12V4.125l-6 2.25v5.175q0 .175.05.45H12z\"/>"
|
||||
},
|
||||
"select-all": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7 17V7h10v10zm2-2h6V9H9zm-4 4v2q-.825 0-1.412-.587T3 19zm-2-2v-2h2v2zm0-4v-2h2v2zm0-4V7h2v2zm2-4H3q0-.825.588-1.412T5 3zm2 16v-2h2v2zM7 5V3h2v2zm4 16v-2h2v2zm0-16V3h2v2zm4 16v-2h2v2zm0-16V3h2v2zm4 16v-2h2q0 .825-.587 1.413T19 21m0-4v-2h2v2zm0-4v-2h2v2zm0-4V7h2v2zm0-4V3q.825 0 1.413.588T21 5z\"/>"
|
||||
},
|
||||
"send-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4.4 19.425q-.5.2-.95-.088T3 18.5V14l8-2l-8-2V5.5q0-.55.45-.837t.95-.088l15.4 6.5q.625.275.625.925t-.625.925z\"/>"
|
||||
},
|
||||
"settings-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10.825 22q-.675 0-1.162-.45t-.588-1.1L8.85 18.8q-.325-.125-.612-.3t-.563-.375l-1.55.65q-.625.275-1.25.05t-.975-.8l-1.175-2.05q-.35-.575-.2-1.225t.675-1.075l1.325-1Q4.5 12.5 4.5 12.337v-.675q0-.162.025-.337l-1.325-1Q2.675 9.9 2.525 9.25t.2-1.225L3.9 5.975q.35-.575.975-.8t1.25.05l1.55.65q.275-.2.575-.375t.6-.3l.225-1.65q.1-.65.588-1.1T10.825 2h2.35q.675 0 1.163.45t.587 1.1l.225 1.65q.325.125.613.3t.562.375l1.55-.65q.625-.275 1.25-.05t.975.8l1.175 2.05q.35.575.2 1.225t-.675 1.075l-1.325 1q.025.175.025.338v.674q0 .163-.05.338l1.325 1q.525.425.675 1.075t-.2 1.225l-1.2 2.05q-.35.575-.975.8t-1.25-.05l-1.5-.65q-.275.2-.575.375t-.6.3l-.225 1.65q-.1.65-.587 1.1t-1.163.45zm1.225-6.5q1.45 0 2.475-1.025T15.55 12t-1.025-2.475T12.05 8.5q-1.475 0-2.488 1.025T8.55 12t1.013 2.475T12.05 15.5\"/>"
|
||||
},
|
||||
"shield-lock": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 22q-3.475-.875-5.738-3.988T4 11.1V5l8-3l8 3v6.1q0 3.8-2.262 6.913T12 22m-2-6h4q.425 0 .713-.288T15 15v-3q0-.425-.288-.712T14 11v-1q0-.825-.587-1.412T12 8t-1.412.588T10 10v1q-.425 0-.712.288T9 12v3q0 .425.288.713T10 16m1-5v-1q0-.425.288-.712T12 9t.713.288T13 10v1z\"/>"
|
||||
},
|
||||
"shield-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11.675 21.875q-.15-.025-.3-.075Q8 20.675 6 17.637T4 11.1V6.375q0-.625.363-1.125t.937-.725l6-2.25q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375V11.1q0 3.5-2 6.538T12.625 21.8q-.15.05-.3.075T12 21.9t-.325-.025\"/>"
|
||||
},
|
||||
"show-chart": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3.5 18.5L2 17l7.5-7.5l4 4l7.1-8L22 6.9l-8.5 9.6l-4-4z\"/>"
|
||||
},
|
||||
"signature-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M14.075 11.725q1.825-1.35 2.85-2.962T17.95 5.55q0-.8-.262-1.175T16.975 4Q15.8 4 14.9 5.988t-.9 4.487q0 .35.013.663t.062.587M4.1 16.3q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l.9-.9l-.9-.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l.9.9l.9-.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-.9.9l.9.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-.9-.9zm11.35-.3q-.75 0-1.375-.288T13 14.776q-.4.2-.825.4l-.85.4q-.4.175-.787.013T10 15.025t.038-.775t.587-.55l.825-.387q.4-.188.775-.388q-.125-.55-.187-1.2t-.063-1.4q0-3.6 1.425-5.962T16.975 2q1.3 0 2.125.963t.825 2.687q0 2.15-1.362 4.25t-3.788 3.775q.175.175.363.263t.387.087q.525 0 1.225-.575t1.35-1.55q.25-.35.638-.488t.762.038q.375.2.55.588t.1.812q-.05.3-.05.575t.075.525q.125-.05.288-.162t.337-.288q.3-.275.713-.312t.737.212q.35.275.375.675t-.25.675q-.575.575-1.213.913T19.95 16q-.525 0-.937-.312t-.688-.963q-.7.625-1.425.95T15.45 16M3.288 20.713Q3 20.425 3 20t.288-.712T4 19t.713.288T5 20t-.288.713T4 21t-.712-.288m4 0Q7 20.426 7 20t.288-.712T8 19t.713.288T9 20t-.288.713T8 21t-.712-.288m4 0Q11 20.426 11 20t.288-.712T12 19t.713.288T13 20t-.288.713T12 21t-.712-.288m4 0Q15 20.426 15 20t.288-.712T16 19t.713.288T17 20t-.288.713T16 21t-.712-.288m4 0Q19 20.426 19 20t.288-.712T20 19t.713.288T21 20t-.288.713T20 21t-.712-.288\"/>"
|
||||
},
|
||||
"speed": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10.45 15.5q.625.625 1.575.588T13.4 15.4L19 7l-8.4 5.6q-.65.45-.712 1.362t.562 1.538M5.1 20q-.55 0-1.012-.238t-.738-.712q-.65-1.175-1-2.437T2 14q0-2.075.788-3.9t2.137-3.175T8.1 4.788T12 4q2.05 0 3.85.775T19 6.888t2.15 3.125t.825 3.837q.025 1.375-.312 2.688t-1.038 2.512q-.275.475-.737.713T18.874 20z\"/>"
|
||||
},
|
||||
"star": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m5.825 21l1.625-7.025L2 9.25l7.2-.625L12 2l2.8 6.625l7.2.625l-5.45 4.725L18.175 21L12 17.275z\"/>"
|
||||
},
|
||||
"star-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 17.275l-4.15 2.5q-.275.175-.575.15t-.525-.2t-.35-.437t-.05-.588l1.1-4.725L3.775 10.8q-.25-.225-.312-.513t.037-.562t.3-.45t.55-.225l4.85-.425l1.875-4.45q.125-.3.388-.45t.537-.15t.537.15t.388.45l1.875 4.45l4.85.425q.35.05.55.225t.3.45t.038.563t-.313.512l-3.675 3.175l1.1 4.725q.075.325-.05.588t-.35.437t-.525.2t-.575-.15z\"/>"
|
||||
},
|
||||
"sticky-note-2": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v10l-6 6zm9-2l5-5h-5zm-7-5h5v-2H7zm0-4h10V8H7z\"/>"
|
||||
},
|
||||
"storage-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 20q-.825 0-1.412-.587T3 18t.588-1.412T5 16h14q.825 0 1.413.588T21 18t-.587 1.413T19 20zM5 8q-.825 0-1.412-.587T3 6t.588-1.412T5 4h14q.825 0 1.413.588T21 6t-.587 1.413T19 8zm0 6q-.825 0-1.412-.587T3 12t.588-1.412T5 10h14q.825 0 1.413.588T21 12t-.587 1.413T19 14zm1.713-7.288Q7 6.426 7 6t-.288-.712T6 5t-.712.288T5 6t.288.713T6 7t.713-.288m0 6Q7 12.426 7 12t-.288-.712T6 11t-.712.288T5 12t.288.713T6 13t.713-.288m0 6Q7 18.426 7 18t-.288-.712T6 17t-.712.288T5 18t.288.713T6 19t.713-.288\"/>"
|
||||
},
|
||||
"strikethrough-s": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12.15 20q-1.9 0-3.375-1.125T6.65 15.8l2.2-.95q.35 1.2 1.213 1.975t2.137.775q1.05 0 1.9-.5t.85-1.6q0-.45-.175-.825T14.3 14h2.8q.125.35.188.713t.062.787q0 2.15-1.537 3.325T12.15 20M2 12v-2h20v2zm10.05-8.15q1.65 0 2.888.812T16.85 7.15l-2.2.975q-.225-.725-.837-1.3T12.1 6.25q-1.025 0-1.7.463T9.65 8h-2.4q.05-1.725 1.363-2.937T12.05 3.85\"/>"
|
||||
},
|
||||
"subdirectory-arrow-right-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13 19.575q0-.4.3-.7L16.175 16H7q-.825 0-1.412-.587T5 14V5q0-.425.288-.712T6 4t.713.288T7 5v9h9.175l-2.9-2.9q-.3-.3-.288-.7t.288-.7q.3-.3.7-.312t.7.287L19.3 14.3q.15.15.212.325t.063.375t-.063.375t-.212.325l-4.575 4.575q-.3.3-.712.3t-.713-.3t-.3-.7\"/>"
|
||||
},
|
||||
"swap-horiz": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m7 20l-5-5l5-5l1.4 1.425L5.825 14H13v2H5.825L8.4 18.575zm10-6l-1.4-1.425L18.175 10H11V8h7.175L15.6 5.425L17 4l5 5z\"/>"
|
||||
},
|
||||
"sync-alt-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m5.825 17l1.9 1.9q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-3.6-3.6q-.15-.15-.213-.325T2.426 16t.063-.375t.212-.325l3.6-3.6q.275-.275.688-.275t.712.275q.3.3.3.713t-.3.712L5.825 15H20q.425 0 .713.288T21 16t-.288.713T20 17zm12.35-8H4q-.425 0-.712-.288T3 8t.288-.712T4 7h14.175l-1.9-1.9q-.3-.3-.287-.7t.312-.7q.3-.275.7-.288t.7.288l3.6 3.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-3.6 3.6q-.275.275-.687.275T16.3 12.3q-.3-.3-.3-.712t.3-.713z\"/>"
|
||||
},
|
||||
"system-update-alt-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h4q.425 0 .713.288T9 5t-.288.713T8 6H4v12h16V6h-4q-.425 0-.712-.288T15 5t.288-.712T16 4h4q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm7-8.4V5q0-.425.288-.712T12 4t.713.288T13 5v6.6l1.9-1.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-3.6 3.6q-.3.3-.7.3t-.7-.3l-3.6-3.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275z\"/>"
|
||||
},
|
||||
"text-fields": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7 20V7H2V4h13v3h-5v13zm9 0v-8h-3V9h9v3h-3v8z\"/>"
|
||||
},
|
||||
"text-fields-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7.438 19.563Q7 19.125 7 18.5V7H3.5q-.625 0-1.062-.437T2 5.5t.438-1.062T3.5 4h10q.625 0 1.063.438T15 5.5t-.437 1.063T13.5 7H10v11.5q0 .625-.437 1.063T8.5 20t-1.062-.437m9 0Q16 19.125 16 18.5V12h-1.5q-.625 0-1.062-.437T13 10.5t.438-1.062T14.5 9h6q.625 0 1.063.438T22 10.5t-.437 1.063T20.5 12H19v6.5q0 .625-.437 1.063T17.5 20t-1.062-.437\"/>"
|
||||
},
|
||||
"thread-unread-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17.875 6.125Q17 5.25 17 4t.875-2.125T20 1t2.125.875T23 4t-.875 2.125T20 7t-2.125-.875M13.85 21q-1.35 0-2.275-.925t-.925-2.225q0-1.9 1.538-3.438t3.837-1.912q-.075-.9-.45-1.362t-1.025-.463q-.75 0-1.625.625t-2.075 2.05q-1.95 2.325-2.862 3.025t-1.963.7q-1.275 0-2.15-.95T3 13.825t.588-2.762t1.987-3.388q.475-.65.7-1.1T6.5 5.85q0-.175-.062-.262T6.25 5.5t-.3.075t-.375.275q-.375.35-.862.375T3.875 5.9q-.375-.4-.387-.937t.387-.888q.6-.525 1.2-.8T6.25 3q1.15 0 1.95.8t.8 2q0 .725-.375 1.6t-1.25 2.1q-.95 1.35-1.412 2.375t-.463 1.8q0 .425.138.663t.387.237t.438-.137t.687-.663q.325-.35.775-.862t1.1-1.263q1.575-1.875 2.85-2.675t2.675-.8q1.675 0 2.75 1.125t1.225 3.075h1.225q.525 0 .888.362t.362.888t-.363.888t-.887.362h-1.225q-.2 2.8-1.463 4.463T13.85 21m.05-2.5q.8 0 1.35-.913T16 15.05q-1.15.275-2 1.088t-.85 1.612q0 .35.2.55t.55.2\"/>"
|
||||
},
|
||||
"toc-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 17q-.425 0-.712-.288T3 16t.288-.712T4 15h12q.425 0 .713.288T17 16t-.288.713T16 17zm0-4q-.425 0-.712-.288T3 12t.288-.712T4 11h12q.425 0 .713.288T17 12t-.288.713T16 13zm0-4q-.425 0-.712-.288T3 8t.288-.712T4 7h12q.425 0 .713.288T17 8t-.288.713T16 9zm16 8q-.425 0-.712-.288T19 16t.288-.712T20 15t.713.288T21 16t-.288.713T20 17m0-4q-.425 0-.712-.288T19 12t.288-.712T20 11t.713.288T21 12t-.288.713T20 13m0-4q-.425 0-.712-.288T19 8t.288-.712T20 7t.713.288T21 8t-.288.713T20 9\"/>"
|
||||
},
|
||||
"touch-app-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10.475 22q-.7 0-1.312-.3t-1.038-.85L3.1 14.475q-.2-.225-.175-.537t.225-.513q.5-.525 1.2-.625t1.3.275L7.5 14.2V6q0-.425.288-.712T8.5 5t.725.288t.3.712v5H17q1.25 0 2.125.875T20 14v4q0 1.65-1.175 2.825T16 22zm1.5-13q-.425 0-.712-.288T10.975 8q0-.05.125-.5q.2-.35.3-.712T11.5 6q0-1.25-.875-2.125T8.5 3t-2.125.875T5.5 6q0 .425.1.788t.3.712q.075.125.1.25t.025.25q0 .425-.275.713T5.05 9q-.275 0-.512-.15t-.363-.375q-.325-.55-.5-1.175T3.5 6q0-2.075 1.463-3.537T8.5 1t3.538 1.463T13.5 6q0 .675-.175 1.3t-.5 1.175q-.125.225-.35.375t-.5.15\"/>"
|
||||
},
|
||||
"tune-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11.288 20.713Q11 20.425 11 20v-4q0-.425.288-.712T12 15t.713.288T13 16v1h7q.425 0 .713.288T21 18t-.288.713T20 19h-7v1q0 .425-.288.713T12 21t-.712-.288M4 19q-.425 0-.712-.288T3 18t.288-.712T4 17h4q.425 0 .713.288T9 18t-.288.713T8 19zm3.288-4.288Q7 14.425 7 14v-1H4q-.425 0-.712-.288T3 12t.288-.712T4 11h3v-1q0-.425.288-.712T8 9t.713.288T9 10v4q0 .425-.288.713T8 15t-.712-.288M12 13q-.425 0-.712-.288T11 12t.288-.712T12 11h8q.425 0 .713.288T21 12t-.288.713T20 13zm3.288-4.288Q15 8.425 15 8V4q0-.425.288-.712T16 3t.713.288T17 4v1h3q.425 0 .713.288T21 6t-.288.713T20 7h-3v1q0 .425-.288.713T16 9t-.712-.288M4 7q-.425 0-.712-.288T3 6t.288-.712T4 5h8q.425 0 .713.288T13 6t-.288.713T12 7z\"/>"
|
||||
},
|
||||
"undo": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M7 19v-2h7.1q1.575 0 2.738-1T18 13.5T16.838 11T14.1 10H7.8l2.6 2.6L9 14L4 9l5-5l1.4 1.4L7.8 8h6.3q2.425 0 4.163 1.575T20 13.5t-1.737 3.925T14.1 19z\"/>"
|
||||
},
|
||||
"unfold-less": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m8.9 20l-1.4-1.4l4.5-4.5l4.5 4.5l-1.4 1.4l-3.1-3.1zM12 9.9L7.5 5.4L8.9 4L12 7.1L15.1 4l1.4 1.4z\"/>"
|
||||
},
|
||||
"unfold-more": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 21l-4.5-4.5l1.45-1.45L12 18.1l3.05-3.05l1.45 1.45zM8.95 9.05L7.5 7.6L12 3.1l4.5 4.5l-1.45 1.45L12 6z\"/>"
|
||||
},
|
||||
"upload-file-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 14.825V18q0 .425.288.713T12 19t.713-.288T13 18v-3.175l.9.9q.15.15.338.225t.375.063t.362-.088t.325-.225q.275-.3.288-.7t-.288-.7l-2.6-2.6q-.15-.15-.325-.212T12 11.425t-.375.063t-.325.212l-2.6 2.6q-.3.3-.287.7t.312.7q.3.275.7.288t.7-.288zM6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h7.175q.4 0 .763.15t.637.425l4.85 4.85q.275.275.425.638t.15.762V20q0 .825-.587 1.413T18 22zm7-14q0 .425.288.713T14 9h4l-5-5z\"/>"
|
||||
},
|
||||
"upload-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6 20q-.825 0-1.412-.587T4 18v-2q0-.425.288-.712T5 15t.713.288T6 16v2h12v-2q0-.425.288-.712T19 15t.713.288T20 16v2q0 .825-.587 1.413T18 20zm5-12.15L9.125 9.725q-.3.3-.712.288T7.7 9.7q-.275-.3-.288-.7t.288-.7l3.6-3.6q.15-.15.325-.212T12 4.425t.375.063t.325.212l3.6 3.6q.3.3.288.7t-.288.7q-.3.3-.712.313t-.713-.288L13 7.85V15q0 .425-.288.713T12 16t-.712-.288T11 15z\"/>"
|
||||
},
|
||||
"verified-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10.95 12.7L9.5 11.275Q9.225 11 8.813 11t-.713.3q-.275.275-.275.7t.275.7l2.15 2.15q.3.3.7.3t.7-.3l4.25-4.25q.3-.3.287-.7t-.287-.7q-.3-.3-.712-.312t-.713.287zm-2.8 9.05L6.7 19.3l-2.75-.6q-.375-.075-.6-.387t-.175-.688L3.45 14.8l-1.875-2.15q-.25-.275-.25-.65t.25-.65L3.45 9.2l-.275-2.825q-.05-.375.175-.688t.6-.387l2.75-.6l1.45-2.45q.2-.325.55-.438t.7.038l2.6 1.1l2.6-1.1q.35-.15.7-.038t.55.438L17.3 4.7l2.75.6q.375.075.6.388t.175.687L20.55 9.2l1.875 2.15q.25.275.25.65t-.25.65L20.55 14.8l.275 2.825q.05.375-.175.688t-.6.387l-2.75.6l-1.45 2.45q-.2.325-.55.438t-.7-.038l-2.6-1.1l-2.6 1.1q-.35.15-.7.038t-.55-.438m1.3-1.8l2.55-1.1l2.6 1.1l1.4-2.4l2.75-.65l-.25-2.8l1.85-2.1l-1.85-2.15l.25-2.8l-2.75-.6l-1.45-2.4L12 5.15l-2.6-1.1L8 6.45l-2.75.6l.25 2.8L3.65 12l1.85 2.1l-.25 2.85l2.75.6zM12 12\"/>"
|
||||
},
|
||||
"verified-user-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M10.95 15.55L16.6 9.9l-1.425-1.425L10.95 12.7l-2.1-2.1l-1.425 1.425zM12 22q-3.475-.875-5.737-3.988T4 11.1V5l8-3l8 3v6.1q0 3.8-2.262 6.913T12 22m0-2.1q2.6-.825 4.3-3.3t1.7-5.5V6.375l-6-2.25l-6 2.25V11.1q0 3.025 1.7 5.5t4.3 3.3m0-7.9\"/>"
|
||||
},
|
||||
"verified-user-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m10.95 12.7l-1.4-1.4q-.3-.3-.7-.3t-.7.3t-.3.713t.3.712l2.1 2.125q.3.3.7.3t.7-.3l4.25-4.25q.3-.3.3-.712t-.3-.713t-.712-.3t-.713.3zm.725 9.175q-.15-.025-.3-.075Q8 20.675 6 17.637T4 11.1V6.375q0-.625.363-1.125t.937-.725l6-2.25q.35-.125.7-.125t.7.125l6 2.25q.575.225.938.725T20 6.375V11.1q0 3.5-2 6.538T12.625 21.8q-.15.05-.3.075T12 21.9t-.325-.025\"/>"
|
||||
},
|
||||
"view-carousel-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 17q-.425 0-.712-.288T2 16V8q0-.425.288-.712T3 7h2q.425 0 .713.288T6 8v8q0 .425-.288.713T5 17H3Zm5 2q-.425 0-.712-.288T7 18V6q0-.425.288-.712T8 5h8q.425 0 .713.288T17 6v12q0 .425-.288.713T16 19H8Zm11-2q-.425 0-.712-.288T18 16V8q0-.425.288-.712T19 7h2q.425 0 .713.288T22 8v8q0 .425-.288.713T21 17h-2Z\"/>",
|
||||
"hidden": true
|
||||
},
|
||||
"view-list": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9 20h11q.825 0 1.413-.587T22 18v-2H9zM2 8h5V4H4q-.825 0-1.412.588T2 6zm0 6h5v-4H2zm2 6h3v-4H2v2q0 .825.588 1.413T4 20m5-6h13v-4H9zm0-6h13V6q0-.825-.587-1.412T20 4H9z\"/>"
|
||||
},
|
||||
"visibility": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M15.188 14.688Q16.5 13.375 16.5 11.5t-1.312-3.187T12 7T8.813 8.313T7.5 11.5t1.313 3.188T12 16t3.188-1.312m-5.1-1.276Q9.3 12.625 9.3 11.5t.788-1.912T12 8.8t1.913.788t.787 1.912t-.787 1.913T12 14.2t-1.912-.787m-4.738 3.55Q2.35 14.925 1 11.5q1.35-3.425 4.35-5.462T12 4t6.65 2.038T23 11.5q-1.35 3.425-4.35 5.463T12 19t-6.65-2.037\"/>"
|
||||
},
|
||||
"visibility-off-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M15.175 8.325q.725.725 1.063 1.65t.237 1.9q0 .375-.275.638t-.65.262t-.638-.262t-.262-.638q.125-.65-.075-1.25T13.95 9.6t-1.025-.65t-1.275-.1q-.375 0-.638-.275t-.262-.65t.263-.637t.637-.263q.95-.1 1.875.238t1.65 1.062M12 6q-.475 0-.925.037t-.9.138q-.425.075-.763-.125t-.462-.6t.088-.775t.612-.45q.575-.125 1.163-.175T12 4q3.425 0 6.263 1.8t4.337 4.85q.1.2.15.413t.05.437t-.038.438t-.137.412q-.45 1-1.112 1.875t-1.463 1.6q-.3.275-.7.225t-.65-.4t-.212-.763t.337-.687q.6-.575 1.1-1.25t.875-1.45q-1.25-2.525-3.613-4.012T12 6m0 13q-3.35 0-6.125-1.812T1.5 12.425q-.125-.2-.187-.437T1.25 11.5t.05-.475t.175-.45q.5-1 1.163-1.912T4.15 7L2.075 4.9q-.275-.3-.262-.712T2.1 3.5t.7-.275t.7.275l17 17q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275l-3.5-3.45q-.875.275-1.775.413T12 19M5.55 8.4q-.725.65-1.325 1.425T3.2 11.5q1.25 2.525 3.613 4.013T12 17q.5 0 .975-.062t.975-.138l-.9-.95q-.275.075-.525.113T12 16q-1.875 0-3.188-1.312T7.5 11.5q0-.275.038-.525t.112-.525zm4.2 4.2\"/>"
|
||||
},
|
||||
"visibility-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M15.188 14.688Q16.5 13.375 16.5 11.5t-1.312-3.187T12 7T8.813 8.313T7.5 11.5t1.313 3.188T12 16t3.188-1.312m-5.1-1.276Q9.3 12.625 9.3 11.5t.788-1.912T12 8.8t1.913.788t.787 1.912t-.787 1.913T12 14.2t-1.912-.787m-4.2 3.787q-2.763-1.8-4.363-4.75q-.125-.225-.187-.462t-.063-.488t.063-.488t.187-.462q1.6-2.95 4.363-4.75T12 4t6.113 1.8t4.362 4.75q.125.225.188.463t.062.487t-.062.488t-.188.462q-1.6 2.95-4.362 4.75T12 19t-6.113-1.8\"/>"
|
||||
},
|
||||
"vital-signs-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M8.15 19.725q-.375-.275-.55-.7L5.3 13H2q-.425 0-.712-.288T1 12t.288-.712T2 11h4q.325 0 .563.175t.362.475L9 17.1l4.6-12.125q.175-.425.55-.7T15 4t.85.275t.55.7L18.7 11H22q.425 0 .713.288T23 12t-.288.713T22 13h-4q-.325 0-.562-.175t-.363-.475L15 6.9l-4.6 12.125q-.175.425-.55.7T9 20t-.85-.275\"/>"
|
||||
},
|
||||
"warning": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M1 21L12 2l11 19zm11.713-3.287Q13 17.425 13 17t-.288-.712T12 16t-.712.288T11 17t.288.713T12 18t.713-.288M11 15h2v-5h-2z\"/>"
|
||||
},
|
||||
"warning-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M2.725 21q-.275 0-.5-.137t-.35-.363t-.137-.488t.137-.512l9.25-16q.15-.25.388-.375T12 3t.488.125t.387.375l9.25 16q.15.25.138.513t-.138.487t-.35.363t-.5.137zm9.988-3.287Q13 17.425 13 17t-.288-.712T12 16t-.712.288T11 17t.288.713T12 18t.713-.288m0-3Q13 14.425 13 14v-3q0-.425-.288-.712T12 10t-.712.288T11 11v3q0 .425.288.713T12 15t.713-.288\"/>"
|
||||
},
|
||||
"workspace-premium-outline-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 11.925l-1.7 1.3q-.15.125-.3.013t-.1-.288l.65-2.1l-1.75-1.4q-.125-.125-.075-.287T8.95 9h2.15l.65-2.05q.05-.175.25-.175t.25.175L12.9 9h2.125q.175 0 .238.163t-.063.287l-1.775 1.4l.65 2.1q.05.175-.1.288t-.3-.013zM12 21l-4.675 1.55q-.5.175-.913-.125t-.412-.8v-6.35q-.95-1.05-1.475-2.4T4 10q0-3.35 2.325-5.675T12 2t5.675 2.325T20 10q0 1.525-.525 2.875T18 15.275v6.35q0 .5-.413.8t-.912.125zm4.25-6.75Q18 12.5 18 10t-1.75-4.25T12 4T7.75 5.75T6 10t1.75 4.25T12 16t4.25-1.75M8 20.025L12 19l4 1.025v-3.1q-.875.5-1.888.788T12 18t-2.113-.288T8 16.926zm4-1.55\"/>"
|
||||
},
|
||||
"workspace-premium-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12 11.925l-1.7 1.3q-.15.125-.3.013t-.1-.288l.65-2.1l-1.75-1.4q-.125-.125-.075-.287T8.95 9h2.15l.65-2.05q.05-.175.25-.175t.25.175L12.9 9h2.125q.175 0 .238.163t-.063.287l-1.775 1.4l.65 2.1q.05.175-.1.288t-.3-.013zM12 21l-4.675 1.55q-.5.175-.913-.125t-.412-.8v-6.35q-.95-1.05-1.475-2.4T4 10q0-3.35 2.325-5.675T12 2t5.675 2.325T20 10q0 1.525-.525 2.875T18 15.275v6.35q0 .5-.413.8t-.912.125zm4.25-6.75Q18 12.5 18 10t-1.75-4.25T12 4T7.75 5.75T6 10t1.75 4.25T12 16t4.25-1.75\"/>"
|
||||
},
|
||||
"zoom-in": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14m-1-1.5v-2h-2v-2h2v-2h2v2h2v2h-2v2z\"/>"
|
||||
},
|
||||
"zoom-in-map-rounded": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m7 18.4l-2.4 2.4q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7L5.6 17H4q-.425 0-.713-.287T3 16t.288-.712T4 15h4q.425 0 .713.288T9 16v4q0 .425-.288.713T8 21t-.712-.288T7 20zm10 0V20q0 .425-.288.713T16 21t-.712-.288T15 20v-4q0-.425.288-.712T16 15h4q.425 0 .713.288T21 16t-.288.713T20 17h-1.6l2.4 2.4q.275.275.275.7t-.275.7t-.7.275t-.7-.275zM5.6 7L3.2 4.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275L7 5.6V4q0-.425.288-.713T8 3t.713.288T9 4v4q0 .425-.288.713T8 9H4q-.425 0-.712-.288T3 8t.288-.712T4 7zm12.8 0H20q.425 0 .713.288T21 8t-.288.713T20 9h-4q-.425 0-.712-.288T15 8V4q0-.425.288-.712T16 3t.713.288T17 4v1.6l2.4-2.4q.275-.275.7-.275t.7.275t.275.7t-.275.7z\"/>"
|
||||
},
|
||||
"zoom-out": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14M7 10.5v-2h5v2z\"/>"
|
||||
}
|
||||
},
|
||||
"lastModified": 1773740798,
|
||||
"aliases": {
|
||||
"cloud-upload": {
|
||||
"parent": "backup"
|
||||
},
|
||||
"crop-square": {
|
||||
"parent": "square-rounded"
|
||||
}
|
||||
},
|
||||
"width": 24,
|
||||
"height": 24
|
||||
}
|
||||
Reference in New Issue
Block a user