mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 19:20:16 +00:00
fc0152b2fc
* fix(#604): model picker shows all configured providers Two fixes to ensure the model picker surface every provider a user has configured: 1. Added env var detection for XAI_API_KEY (→ x-ai) and MISTRAL_API_KEY (→ mistralai). Previously these providers were only detectable via hermes auth or credential pool, not via environment variables. 2. Added config.yaml providers section scanning. Users who configure providers in config.yaml (e.g. providers.anthropic.api_key) without setting the corresponding env var will now see those providers in the model picker. Only providers with known model catalogs are added. - Added 12 regression tests * fix(#1112): allow Google Fonts in CSP style-src and font-src Mermaid themes inject @import for fonts.googleapis.com at render time. CSP style-src blocked these requests, causing console violations. - Add https://fonts.googleapis.com to style-src (CSS stylesheets) - Add https://fonts.gstatic.com to font-src (WOFF2/WOFF font files) - Add 3 regression tests + verify existing CSP tests still pass * fix(#1118): retry api() calls on network errors after long idle After a long idle period, the browser's TCP keep-alive connection to the server can become stale. The next fetch() throws a TypeError (network failure), causing 'Failed to load session' instead of transparently reconnecting. - Added retry loop in api() (workspace.js): up to 3 attempts - Only retries on TypeError (network failures), NOT on HTTP errors (4xx/5xx) - 401 redirects still fire immediately - Added 6 regression tests * feat(#1116): composer placeholder reflects active profile name When a named profile is active (not 'default'), the composer placeholder and title bar show the profile name (capitalised) instead of the global bot_name. Falls back to bot_name/'Hermes' for the default profile. - boot.js: applyBotName() checks S.activeProfile before _botName - panels.js: switchToProfile() calls applyBotName() after switch - Added 5 regression tests * feat(#1097): drag and drop workspace files into chat composer Files and folders in the workspace file tree are now draggable. Dropping them into the composer inserts @path reference at cursor position. OS file drag-and-drop (attach files) still works. - ui.js: _renderTreeItems sets draggable + dragstart with ws-path - panels.js: drop handler checks for application/ws-path first, inserts @path with smart spacing and cursor positioning - Added 9 regression tests * fix(#1096): copy buttons work — add clipboard-write Permissions-Policy Copy buttons on messages and code blocks were silently failing because the Permissions-Policy header did not include clipboard-write=(self). Firefox blocks navigator.clipboard.writeText() without explicit permission. - api/helpers.py: add clipboard-write=(self) to Permissions-Policy - ui.js: _copyText now catches clipboard API errors and falls back to execCommand('copy'). _fallbackCopy extracted as separate function with proper focus() call and visible-but-hidden positioning (not -9999px) - Added 8 regression tests * chore: CHANGELOG for v0.50.223 --------- Co-authored-by: bergeouss <bergeouss@users.noreply.github.com> Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
88 lines
3.8 KiB
Python
88 lines
3.8 KiB
Python
"""Tests for #1097 — drag & drop workspace files into chat composer."""
|
|
import re
|
|
|
|
|
|
def _src(name: str) -> str:
|
|
with open(f"static/{name}") as f:
|
|
return f.read()
|
|
|
|
|
|
class TestWorkspaceDragDrop:
|
|
"""File tree items are draggable and composer accepts workspace drops."""
|
|
|
|
def test_renderTreeItems_makes_items_draggable(self):
|
|
"""Each file-item must have draggable='true'."""
|
|
src = _src("ui.js")
|
|
assert "el.setAttribute('draggable','true')" in src, \
|
|
"_renderTreeItems must set draggable=true on each item"
|
|
|
|
def test_dragstart_stores_ws_path(self):
|
|
"""dragstart must store 'application/ws-path' with item.path."""
|
|
src = _src("ui.js")
|
|
assert "application/ws-path" in src, \
|
|
"dragstart must setData with application/ws-path"
|
|
assert "item.path" in src, \
|
|
"dragstart must include item.path in data transfer"
|
|
|
|
def test_dragstart_stores_ws_type(self):
|
|
"""dragstart must store 'application/ws-type' (file or dir)."""
|
|
src = _src("ui.js")
|
|
assert "application/ws-type" in src, \
|
|
"dragstart must setData with application/ws-type"
|
|
|
|
def test_dragstart_effectAllowed_copy(self):
|
|
"""Drag effect must be 'copy' (not move — we insert a reference)."""
|
|
src = _src("ui.js")
|
|
assert "effectAllowed='copy'" in src, \
|
|
"dragstart must set effectAllowed to 'copy'"
|
|
|
|
def test_drop_handler_checks_ws_path(self):
|
|
"""Global drop handler must check for application/ws-path first."""
|
|
src = _src("panels.js")
|
|
m = re.search(r"document\.addEventListener\('drop'", src)
|
|
assert m, "Global drop listener must exist"
|
|
after = src[m.start():m.start() + 2000]
|
|
assert "application/ws-path" in after, \
|
|
"Drop handler must check for workspace path data"
|
|
|
|
def test_workspace_drop_inserts_at_path(self):
|
|
"""Workspace drop must insert @path into composer textarea."""
|
|
src = _src("panels.js")
|
|
m = re.search(r"document\.addEventListener\('drop'", src)
|
|
after = src[m.start():m.start() + 2000]
|
|
# Must insert @-prefixed path
|
|
assert "'@'+wsPath" in after or '"@"+wsPath' in after or "@"+"" in after, \
|
|
"Workspace drop must insert @-prefixed path into composer"
|
|
# Must position cursor after insert
|
|
assert "selectionStart" in after, \
|
|
"Drop handler must update cursor position"
|
|
# Must focus composer
|
|
assert "msgEl.focus()" in after or "$('msg').focus()" in after, \
|
|
"Drop handler must focus the composer textarea"
|
|
|
|
def test_workspace_drop_has_prefix_logic(self):
|
|
"""Workspace drop should add space prefix if cursor is mid-word."""
|
|
src = _src("panels.js")
|
|
m = re.search(r"document\.addEventListener\('drop'", src)
|
|
after = src[m.start():m.start() + 2000]
|
|
assert "prefix" in after.lower(), \
|
|
"Drop handler should handle spacing between existing text and @path"
|
|
|
|
def test_dragenter_accepts_ws_path(self):
|
|
"""dragenter must highlight composer for workspace drags too."""
|
|
src = _src("panels.js")
|
|
# Find dragenter listener
|
|
m = re.search(r"document\.addEventListener\('dragenter'", src)
|
|
assert m, "dragenter listener must exist"
|
|
after = src[m.start():m.start() + 300]
|
|
assert "application/ws-path" in after, \
|
|
"dragenter must also trigger for workspace drags (application/ws-path)"
|
|
|
|
def test_os_file_drop_still_works(self):
|
|
"""OS file drag (dataTransfer.files) must still attach files."""
|
|
src = _src("panels.js")
|
|
m = re.search(r"document\.addEventListener\('drop'", src)
|
|
after = src[m.start():m.start() + 2000]
|
|
assert "addFiles(files)" in after, \
|
|
"OS file drop path (addFiles) must still work after workspace drop addition"
|