mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-26 22:31:54 +03:00
Compare commits
3 Commits
df069657b8
...
c97ba9bd1f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c97ba9bd1f | ||
|
|
ec44398b1c | ||
|
|
5d0d89a3f2 |
215
docs/package-lock.json
generated
215
docs/package-lock.json
generated
@ -2072,43 +2072,10 @@
|
||||
"integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@chevrotain/cst-dts-gen": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
|
||||
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@chevrotain/gast": "11.0.3",
|
||||
"@chevrotain/types": "11.0.3",
|
||||
"lodash-es": "4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@chevrotain/gast": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
|
||||
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@chevrotain/types": "11.0.3",
|
||||
"lodash-es": "4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@chevrotain/regexp-to-ast": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
|
||||
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@chevrotain/types": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
|
||||
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@chevrotain/utils": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
|
||||
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
|
||||
"version": "11.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz",
|
||||
"integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
@ -4504,12 +4471,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mermaid-js/parser": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz",
|
||||
"integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz",
|
||||
"integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"langium": "3.3.1"
|
||||
"@chevrotain/types": "~11.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
@ -5995,6 +5962,16 @@
|
||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@upsetjs/venn.js": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz",
|
||||
"integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-transition": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/oidc": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz",
|
||||
@ -7199,32 +7176,6 @@
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/chevrotain": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
|
||||
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@chevrotain/cst-dts-gen": "11.0.3",
|
||||
"@chevrotain/gast": "11.0.3",
|
||||
"@chevrotain/regexp-to-ast": "11.0.3",
|
||||
"@chevrotain/types": "11.0.3",
|
||||
"@chevrotain/utils": "11.0.3",
|
||||
"lodash-es": "4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/chevrotain-allstar": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz",
|
||||
"integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"chevrotain": "^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@ -8560,9 +8511,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
|
||||
"integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@ -8796,9 +8747,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dagre-d3-es": {
|
||||
"version": "7.0.13",
|
||||
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz",
|
||||
"integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==",
|
||||
"version": "7.0.14",
|
||||
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz",
|
||||
"integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d3": "^7.9.0",
|
||||
@ -8973,9 +8924,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/delaunator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
||||
"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
|
||||
"integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"robust-predicates": "^3.0.2"
|
||||
@ -10499,6 +10450,16 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.46.1",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz",
|
||||
"integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"docs",
|
||||
"benchmarks"
|
||||
]
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
|
||||
@ -13058,22 +13019,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/langium": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz",
|
||||
"integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chevrotain": "~11.0.3",
|
||||
"chevrotain-allstar": "~0.3.0",
|
||||
"vscode-languageserver": "~9.0.1",
|
||||
"vscode-languageserver-textdocument": "~1.0.11",
|
||||
"vscode-uri": "~3.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/latest-version": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz",
|
||||
@ -13190,9 +13135,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
|
||||
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
@ -13840,31 +13785,32 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mermaid": {
|
||||
"version": "11.12.2",
|
||||
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz",
|
||||
"integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==",
|
||||
"version": "11.15.0",
|
||||
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz",
|
||||
"integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^7.1.1",
|
||||
"@iconify/utils": "^3.0.1",
|
||||
"@mermaid-js/parser": "^0.6.3",
|
||||
"@iconify/utils": "^3.0.2",
|
||||
"@mermaid-js/parser": "^1.1.1",
|
||||
"@types/d3": "^7.4.3",
|
||||
"cytoscape": "^3.29.3",
|
||||
"@upsetjs/venn.js": "^2.0.0",
|
||||
"cytoscape": "^3.33.1",
|
||||
"cytoscape-cose-bilkent": "^4.1.0",
|
||||
"cytoscape-fcose": "^2.2.0",
|
||||
"d3": "^7.9.0",
|
||||
"d3-sankey": "^0.12.3",
|
||||
"dagre-d3-es": "7.0.13",
|
||||
"dayjs": "^1.11.18",
|
||||
"dompurify": "^3.2.5",
|
||||
"katex": "^0.16.22",
|
||||
"dagre-d3-es": "7.0.14",
|
||||
"dayjs": "^1.11.19",
|
||||
"dompurify": "^3.3.1",
|
||||
"es-toolkit": "^1.45.1",
|
||||
"katex": "^0.16.25",
|
||||
"khroma": "^2.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^16.2.1",
|
||||
"marked": "^16.3.0",
|
||||
"roughjs": "^4.6.6",
|
||||
"stylis": "^4.3.6",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"uuid": "^11.1.0"
|
||||
"uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
@ -20210,9 +20156,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/robust-predicates": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
|
||||
"integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/roughjs": {
|
||||
@ -22561,55 +22507,6 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-jsonrpc": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-languageserver": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
|
||||
"integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vscode-languageserver-protocol": "3.17.5"
|
||||
},
|
||||
"bin": {
|
||||
"installServerIntoExtension": "bin/installServerIntoExtension"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-languageserver-protocol": {
|
||||
"version": "3.17.5",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
|
||||
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vscode-jsonrpc": "8.2.0",
|
||||
"vscode-languageserver-types": "3.17.5"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-languageserver-textdocument": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
|
||||
"integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vscode-languageserver-types": {
|
||||
"version": "3.17.5",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
|
||||
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
|
||||
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
|
||||
@ -547,9 +547,21 @@ async def _execute_get_live_context(
|
||||
camera: str,
|
||||
allowed_cameras: List[str],
|
||||
) -> Dict[str, Any]:
|
||||
# Reject wildcards explicitly so models retry with a real camera name
|
||||
# instead of silently fanning out across every camera.
|
||||
if camera in ("*", "all"):
|
||||
return {
|
||||
"error": (
|
||||
"get_live_context requires a single camera name; wildcards "
|
||||
"are not supported. Call this tool once per camera."
|
||||
),
|
||||
"available_cameras": allowed_cameras,
|
||||
}
|
||||
|
||||
if camera not in allowed_cameras:
|
||||
return {
|
||||
"error": f"Camera '{camera}' not found or access denied",
|
||||
"available_cameras": allowed_cameras,
|
||||
}
|
||||
|
||||
if camera not in request.app.frigate_config.cameras:
|
||||
@ -721,7 +733,14 @@ async def _execute_tool_internal(
|
||||
"Arguments: %s",
|
||||
json.dumps(arguments),
|
||||
)
|
||||
return {"error": "Camera parameter is required"}
|
||||
return {
|
||||
"error": (
|
||||
"get_live_context requires a single camera name; "
|
||||
"wildcards and empty values are not supported. "
|
||||
"Call this tool once per camera."
|
||||
),
|
||||
"available_cameras": allowed_cameras,
|
||||
}
|
||||
return await _execute_get_live_context(request, camera, allowed_cameras)
|
||||
elif tool_name == "start_camera_watch":
|
||||
return await _execute_start_camera_watch(request, arguments)
|
||||
|
||||
@ -518,16 +518,21 @@ def get_tool_definitions(
|
||||
"function": {
|
||||
"name": "get_live_context",
|
||||
"description": (
|
||||
"Get the current live image and detection information for a camera: objects being tracked, "
|
||||
"Get the current live image and detection information for a single camera: objects being tracked, "
|
||||
"zones, timestamps. Use this to understand what is visible in the live view. "
|
||||
"Call this when answering questions about what is happening right now on a specific camera."
|
||||
"Call this when answering questions about what is happening right now on a specific camera. "
|
||||
"Operates on one camera at a time; call the tool again for each additional camera. "
|
||||
"Wildcards and empty values are not accepted."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera name to get live context for.",
|
||||
"description": (
|
||||
"Exact name of a single camera to get live context for. "
|
||||
"Wildcards (e.g. '*', 'all') and empty strings are not accepted."
|
||||
),
|
||||
},
|
||||
},
|
||||
"required": ["camera"],
|
||||
|
||||
@ -579,7 +579,9 @@ class RecordingExporter(threading.Thread):
|
||||
else:
|
||||
chapters_path = self._build_chapter_metadata_file(recordings)
|
||||
chapter_args = (
|
||||
f" -i {chapters_path} -map 0 -map_metadata 1" if chapters_path else ""
|
||||
f" -i {chapters_path} -map 0 -dn -map_metadata 1"
|
||||
if chapters_path
|
||||
else ""
|
||||
)
|
||||
ffmpeg_cmd = (
|
||||
f"{self.config.ffmpeg.ffmpeg_path} -hide_banner {ffmpeg_input}{chapter_args} -c copy -movflags +faststart"
|
||||
|
||||
@ -478,7 +478,7 @@ def get_intel_gpu_stats(
|
||||
overall_pct = min(100.0, compute_pct + dec_pct)
|
||||
|
||||
entry: dict[str, Any] = {
|
||||
"name": names.get(pdev) or f"Intel GPU {pdev}",
|
||||
"name": names.get(pdev) or "Intel iGPU",
|
||||
"vendor": "intel",
|
||||
"gpu": f"{round(overall_pct, 2)}%",
|
||||
"mem": "-%",
|
||||
|
||||
@ -130,9 +130,15 @@ export default function SearchResultActions({
|
||||
},
|
||||
);
|
||||
} else {
|
||||
toast.error(t("dialog.toast.error", { error: errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("dialog.toast.error", {
|
||||
ns: "views/replay",
|
||||
error: errorMessage,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@ -206,7 +212,7 @@ export default function SearchResultActions({
|
||||
<span>{t("itemMenu.addTrigger.label")}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
{searchResult.has_clip && (
|
||||
{isAdmin && searchResult.has_clip && (
|
||||
<MenuItem
|
||||
className="cursor-pointer"
|
||||
aria-label={t("itemMenu.debugReplay.aria")}
|
||||
|
||||
@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { FaFilm } from "react-icons/fa6";
|
||||
|
||||
type ActionsDropdownProps = {
|
||||
onDebugReplayClick: () => void;
|
||||
onDebugReplayClick?: () => void;
|
||||
onExportClick: () => void;
|
||||
onShareTimestampClick: () => void;
|
||||
};
|
||||
@ -42,9 +42,11 @@ export default function ActionsDropdown({
|
||||
<DropdownMenuItem onClick={onShareTimestampClick}>
|
||||
{t("recording.shareTimestamp.label", { ns: "components/dialog" })}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onDebugReplayClick}>
|
||||
{t("title", { ns: "views/replay" })}
|
||||
</DropdownMenuItem>
|
||||
{onDebugReplayClick && (
|
||||
<DropdownMenuItem onClick={onDebugReplayClick}>
|
||||
{t("title", { ns: "views/replay" })}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@ -29,6 +29,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { StartExportResponse } from "@/types/export";
|
||||
import { ShareTimestampContent } from "./ShareTimestampDialog";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
|
||||
type DrawerMode =
|
||||
| "none"
|
||||
@ -109,6 +110,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
"views/replay",
|
||||
"common",
|
||||
]);
|
||||
const isAdmin = useIsAdmin();
|
||||
const navigate = useNavigate();
|
||||
const [drawerMode, setDrawerMode] = useState<DrawerMode>("none");
|
||||
const [exportTab, setExportTab] = useState<ExportTab>("export");
|
||||
@ -388,7 +390,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
{t("filter")}
|
||||
</Button>
|
||||
)}
|
||||
{features.includes("debug-replay") && (
|
||||
{isAdmin && features.includes("debug-replay") && (
|
||||
<Button
|
||||
className="flex w-full items-center justify-center gap-2"
|
||||
aria-label={t("title", { ns: "views/replay" })}
|
||||
|
||||
@ -95,9 +95,15 @@ export default function DetailActionsMenu({
|
||||
),
|
||||
});
|
||||
} else {
|
||||
toast.error(t("dialog.toast.error", { error: errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("dialog.toast.error", {
|
||||
ns: "views/replay",
|
||||
error: errorMessage,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@ -229,7 +235,7 @@ export default function DetailActionsMenu({
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{search.has_clip && (
|
||||
{isAdmin && search.has_clip && (
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
aria-label={t("itemMenu.debugReplay.aria")}
|
||||
|
||||
@ -94,9 +94,15 @@ export default function EventMenu({
|
||||
},
|
||||
);
|
||||
} else {
|
||||
toast.error(t("dialog.toast.error", { error: errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("dialog.toast.error", {
|
||||
ns: "views/replay",
|
||||
error: errorMessage,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@ -177,7 +183,7 @@ export default function EventMenu({
|
||||
{t("itemMenu.findSimilar.label")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{event.has_clip && (
|
||||
{isAdmin && event.has_clip && (
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
disabled={isStarting}
|
||||
|
||||
@ -16,6 +16,11 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
useCallback,
|
||||
@ -589,7 +594,7 @@ function MobileMenuItem({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex h-10 w-full cursor-pointer items-center justify-between whitespace-nowrap rounded-md px-4 py-2 pr-2 text-sm font-medium text-primary-variant disabled:pointer-events-none disabled:opacity-50",
|
||||
"inline-flex h-10 w-full cursor-pointer items-center whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium text-primary-variant disabled:pointer-events-none disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
onClick={() => {
|
||||
@ -600,7 +605,6 @@ function MobileMenuItem({
|
||||
<div className="w-full">
|
||||
{label ?? <div>{t("menu." + item.key)}</div>}
|
||||
</div>
|
||||
<LuChevronRight className="size-4" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -613,6 +617,39 @@ export default function Settings() {
|
||||
const [sectionStatusByKey, setSectionStatusByKey] = useState<
|
||||
Partial<Record<SettingsType, SectionStatus>>
|
||||
>({});
|
||||
const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(
|
||||
() =>
|
||||
// all collapsed by default
|
||||
new Set(
|
||||
settingsGroups.filter((g) => g.items.length > 1).map((g) => g.label),
|
||||
),
|
||||
);
|
||||
|
||||
const toggleGroupCollapsed = useCallback((label: string) => {
|
||||
setCollapsedGroups((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(label)) {
|
||||
next.delete(label);
|
||||
} else {
|
||||
next.add(label);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Auto-expand the group containing the active page whenever pageToggle changes
|
||||
useEffect(() => {
|
||||
const containingGroup = settingsGroups.find((group) =>
|
||||
group.items.some((item) => item.key === pageToggle),
|
||||
);
|
||||
if (!containingGroup) return;
|
||||
setCollapsedGroups((prev) => {
|
||||
if (!prev.has(containingGroup.label)) return prev;
|
||||
const next = new Set(prev);
|
||||
next.delete(containingGroup.label);
|
||||
return next;
|
||||
});
|
||||
}, [pageToggle]);
|
||||
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const { data: profilesData } = useSWR<ProfilesApiResponse>("profiles");
|
||||
@ -1611,34 +1648,49 @@ export default function Settings() {
|
||||
visibleSettingsViews.includes(item.key as SettingsType),
|
||||
);
|
||||
if (filteredItems.length === 0) return null;
|
||||
const isMultiItem = filteredItems.length > 1;
|
||||
const renderedExpanded =
|
||||
!isMultiItem || !collapsedGroups.has(group.label);
|
||||
const items = filteredItems.map((item) => (
|
||||
<MobileMenuItem
|
||||
key={item.key}
|
||||
item={item}
|
||||
className={cn(filteredItems.length == 1 && "pl-2")}
|
||||
label={renderMenuItemLabel(item.key as SettingsType)}
|
||||
onSelect={(key) => {
|
||||
if (
|
||||
!isAdmin &&
|
||||
!ALLOWED_VIEWS_FOR_VIEWER.includes(key as SettingsType)
|
||||
) {
|
||||
setPageToggle("uiSettings");
|
||||
} else {
|
||||
setPageToggle(key as SettingsType);
|
||||
}
|
||||
setContentMobileOpen(true);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
return (
|
||||
<div key={group.label} className="mb-3">
|
||||
{filteredItems.length > 1 && (
|
||||
<h3 className="mb-2 ml-2 text-sm font-medium text-secondary-foreground">
|
||||
<div>{t("menu." + group.label)}</div>
|
||||
</h3>
|
||||
{isMultiItem ? (
|
||||
<Collapsible
|
||||
open={renderedExpanded}
|
||||
onOpenChange={() => toggleGroupCollapsed(group.label)}
|
||||
>
|
||||
<CollapsibleTrigger className="flex min-h-10 w-full items-center justify-between rounded-md py-2 pl-2 pr-2 text-sm font-medium text-secondary-foreground">
|
||||
<div>{t("menu." + group.label)}</div>
|
||||
<LuChevronRight
|
||||
className={cn(
|
||||
"size-4 shrink-0 transition-transform duration-200",
|
||||
renderedExpanded && "rotate-90",
|
||||
)}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>{items}</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : (
|
||||
items
|
||||
)}
|
||||
{filteredItems.map((item) => (
|
||||
<MobileMenuItem
|
||||
key={item.key}
|
||||
item={item}
|
||||
className={cn(filteredItems.length == 1 && "pl-2")}
|
||||
label={renderMenuItemLabel(item.key as SettingsType)}
|
||||
onSelect={(key) => {
|
||||
if (
|
||||
!isAdmin &&
|
||||
!ALLOWED_VIEWS_FOR_VIEWER.includes(
|
||||
key as SettingsType,
|
||||
)
|
||||
) {
|
||||
setPageToggle("uiSettings");
|
||||
} else {
|
||||
setPageToggle(key as SettingsType);
|
||||
}
|
||||
setContentMobileOpen(true);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@ -1940,48 +1992,74 @@ export default function Settings() {
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
) : (
|
||||
<>
|
||||
<SidebarGroupLabel
|
||||
className={cn(
|
||||
"ml-2 cursor-default pl-0 text-sm",
|
||||
filteredItems.some(
|
||||
(item) => pageToggle === item.key,
|
||||
)
|
||||
? "text-primary"
|
||||
: "text-sidebar-foreground/80",
|
||||
)}
|
||||
>
|
||||
<div>{t("menu." + group.label)}</div>
|
||||
</SidebarGroupLabel>
|
||||
<SidebarMenuSub className="mx-2 border-0">
|
||||
{filteredItems.map((item) => (
|
||||
<SidebarMenuSubItem key={item.key}>
|
||||
<SidebarMenuSubButton
|
||||
className="h-auto w-full py-1.5"
|
||||
isActive={pageToggle === item.key}
|
||||
onClick={() => {
|
||||
if (
|
||||
!isAdmin &&
|
||||
!ALLOWED_VIEWS_FOR_VIEWER.includes(
|
||||
item.key as SettingsType,
|
||||
)
|
||||
) {
|
||||
setPageToggle("uiSettings");
|
||||
} else {
|
||||
setPageToggle(item.key as SettingsType);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="w-full cursor-pointer">
|
||||
{renderMenuItemLabel(
|
||||
item.key as SettingsType,
|
||||
(() => {
|
||||
const hasActiveItem = filteredItems.some(
|
||||
(item) => pageToggle === item.key,
|
||||
);
|
||||
const renderedExpanded = !collapsedGroups.has(
|
||||
group.label,
|
||||
);
|
||||
return (
|
||||
<Collapsible
|
||||
open={renderedExpanded}
|
||||
onOpenChange={() =>
|
||||
toggleGroupCollapsed(group.label)
|
||||
}
|
||||
>
|
||||
<SidebarGroupLabel
|
||||
asChild
|
||||
className={cn(
|
||||
"ml-2 pl-0 text-sm",
|
||||
hasActiveItem
|
||||
? "text-primary"
|
||||
: "text-sidebar-foreground/80",
|
||||
)}
|
||||
>
|
||||
<CollapsibleTrigger className="flex w-full items-center justify-between">
|
||||
<div>{t("menu." + group.label)}</div>
|
||||
<LuChevronRight
|
||||
className={cn(
|
||||
"size-4 shrink-0 transition-transform duration-200",
|
||||
renderedExpanded && "rotate-90",
|
||||
)}
|
||||
</div>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</>
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
</SidebarGroupLabel>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub className="mx-2 border-0 md:mx-0">
|
||||
{filteredItems.map((item) => (
|
||||
<SidebarMenuSubItem key={item.key}>
|
||||
<SidebarMenuSubButton
|
||||
className="h-auto w-full py-1.5"
|
||||
isActive={pageToggle === item.key}
|
||||
onClick={() => {
|
||||
if (
|
||||
!isAdmin &&
|
||||
!ALLOWED_VIEWS_FOR_VIEWER.includes(
|
||||
item.key as SettingsType,
|
||||
)
|
||||
) {
|
||||
setPageToggle("uiSettings");
|
||||
} else {
|
||||
setPageToggle(
|
||||
item.key as SettingsType,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="w-full cursor-pointer">
|
||||
{renderMenuItemLabel(
|
||||
item.key as SettingsType,
|
||||
)}
|
||||
</div>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
})()
|
||||
)}
|
||||
</SidebarGroup>
|
||||
);
|
||||
|
||||
@ -66,6 +66,7 @@ import SummaryTimeline from "@/components/timeline/SummaryTimeline";
|
||||
import { RecordingStartingPoint } from "@/types/record";
|
||||
import VideoControls from "@/components/player/VideoControls";
|
||||
import { TimeRange } from "@/types/timeline";
|
||||
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
|
||||
import {
|
||||
useCameraMotionNextTimestamp,
|
||||
useCameraMotionOnlyRanges,
|
||||
@ -1008,27 +1009,29 @@ function MotionReview({
|
||||
const { t } = useTranslation(["views/events", "common"]);
|
||||
const segmentDuration = 30;
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const allowedCameras = useAllowedCameras();
|
||||
|
||||
const reviewCameras = useMemo(() => {
|
||||
if (!config) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let cameras;
|
||||
if (!filter || !filter.cameras) {
|
||||
cameras = Object.values(config.cameras).filter(
|
||||
(cam) => !isReplayCamera(cam.name),
|
||||
);
|
||||
} else {
|
||||
const filteredCams = filter.cameras;
|
||||
|
||||
cameras = Object.values(config.cameras).filter(
|
||||
(cam) => filteredCams.includes(cam.name) && !isReplayCamera(cam.name),
|
||||
);
|
||||
}
|
||||
const selectedCams = filter?.cameras;
|
||||
const cameras = Object.values(config.cameras).filter((cam) => {
|
||||
if (isReplayCamera(cam.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!allowedCameras.includes(cam.name)) {
|
||||
return false;
|
||||
}
|
||||
if (selectedCams && !selectedCams.includes(cam.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return cameras.sort((a, b) => a.ui.order - b.ui.order);
|
||||
}, [config, filter]);
|
||||
}, [config, filter, allowedCameras]);
|
||||
|
||||
const videoPlayersRef = useRef<{ [camera: string]: PreviewController }>({});
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
|
||||
import { useUserPersistence } from "@/hooks/use-user-persistence";
|
||||
import {
|
||||
AllGroupsStreamingSettings,
|
||||
@ -90,6 +91,7 @@ export default function LiveDashboardView({
|
||||
// recent events
|
||||
|
||||
const eventUpdate = useFrigateReviews();
|
||||
const allowedCameras = useAllowedCameras();
|
||||
|
||||
const alertCameras = useMemo(() => {
|
||||
if (!config) {
|
||||
@ -98,14 +100,16 @@ export default function LiveDashboardView({
|
||||
|
||||
if (cameraGroup == "default") {
|
||||
return Object.values(config.cameras)
|
||||
.filter((cam) => cam.ui.dashboard)
|
||||
.filter((cam) => cam.ui.dashboard && allowedCameras.includes(cam.name))
|
||||
.map((cam) => cam.name)
|
||||
.join(",");
|
||||
}
|
||||
|
||||
if (includeBirdseye && cameras.length == 0) {
|
||||
return Object.values(config.cameras)
|
||||
.filter((cam) => cam.birdseye.enabled)
|
||||
.filter(
|
||||
(cam) => cam.birdseye.enabled && allowedCameras.includes(cam.name),
|
||||
)
|
||||
.map((cam) => cam.name)
|
||||
.join(",");
|
||||
}
|
||||
@ -114,7 +118,7 @@ export default function LiveDashboardView({
|
||||
.map((cam) => cam.name)
|
||||
.filter((cam) => config.camera_groups[cameraGroup]?.cameras.includes(cam))
|
||||
.join(",");
|
||||
}, [cameras, cameraGroup, config, includeBirdseye]);
|
||||
}, [cameras, cameraGroup, config, includeBirdseye, allowedCameras]);
|
||||
|
||||
const { data: allEvents, mutate: updateEvents } = useSWR<ReviewSegment[]>([
|
||||
"review",
|
||||
|
||||
@ -44,6 +44,7 @@ import {
|
||||
import { IoMdArrowRoundBack } from "react-icons/io";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
import useSWR from "swr";
|
||||
import { TimeRange, TimelineType } from "@/types/timeline";
|
||||
import MobileCameraDrawer from "@/components/overlay/MobileCameraDrawer";
|
||||
@ -109,6 +110,7 @@ export function RecordingView({
|
||||
}: RecordingViewProps) {
|
||||
const { t } = useTranslation(["views/events", "components/dialog"]);
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const isAdmin = useIsAdmin();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -723,13 +725,17 @@ export function RecordingView({
|
||||
setCustomShareTimestamp(initialTimestamp);
|
||||
setShareTimestampOpen(true);
|
||||
}}
|
||||
onDebugReplayClick={() => {
|
||||
setDebugReplayRange({
|
||||
after: timeRange.before - 60,
|
||||
before: timeRange.before,
|
||||
});
|
||||
setDebugReplayMode("select");
|
||||
}}
|
||||
onDebugReplayClick={
|
||||
isAdmin
|
||||
? () => {
|
||||
setDebugReplayRange({
|
||||
after: timeRange.before - 60,
|
||||
before: timeRange.before,
|
||||
});
|
||||
setDebugReplayMode("select");
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onExportClick={() => {
|
||||
const now = new Date(timeRange.before * 1000);
|
||||
now.setHours(now.getHours() - 1);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user