@turmind/halo 1.5.7 → 1.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/admin-out/404.html +1 -1
  2. package/admin-out/_next/static/chunks/220.7566bc89f3d98993.js +1 -0
  3. package/admin-out/_next/static/chunks/597-399e2e1de26aa747.js +1 -0
  4. package/admin-out/_next/static/chunks/989-c404f6f43c400911.js +1 -0
  5. package/admin-out/_next/static/chunks/app/page-59bfce35bbb30b05.js +1 -0
  6. package/admin-out/_next/static/chunks/{webpack-edf248e07333566d.js → webpack-efd27999305b080a.js} +1 -1
  7. package/admin-out/_next/static/css/27b44c85485eabb9.css +3 -0
  8. package/admin-out/index.html +1 -1
  9. package/admin-out/index.txt +5 -5
  10. package/bundled-docs/design/architecture.md +3 -2
  11. package/bundled-docs/design/canvas-extensions.md +51 -11
  12. package/bundled-docs/design/cron.md +6 -6
  13. package/bundled-docs/design/evolution.md +47 -15
  14. package/bundled-docs/design/express-self.md +96 -20
  15. package/bundled-docs/design/storage.md +10 -2
  16. package/bundled-docs/design/ws.md +1 -0
  17. package/bundled-docs/dev/add-model-provider.md +16 -7
  18. package/bundled-docs/dev/api.md +7 -6
  19. package/bundled-docs/dev/desktop-packaging.md +26 -0
  20. package/bundled-docs/guide/agents.md +3 -3
  21. package/bundled-docs/guide/chat.md +3 -0
  22. package/bundled-docs/guide/cli.md +10 -1
  23. package/bundled-docs/guide/workspace.md +2 -2
  24. package/bundled-docs/requirements/agent.md +1 -1
  25. package/bundled-docs/requirements/chat.md +17 -9
  26. package/bundled-docs/requirements/editor.md +3 -1
  27. package/bundled-docs/requirements/explorer.md +4 -2
  28. package/bundled-docs/requirements/settings.md +11 -3
  29. package/bundled-docs/requirements/workspace.md +2 -2
  30. package/dist/index.js +2514 -1669
  31. package/package.json +2 -1
  32. package/templates/agents/__evo_agent__/AGENT.md +7 -0
  33. package/templates/agents/__score__/AGENT.md +183 -71
  34. package/templates/canvas/self.html +617 -71
  35. package/templates/models/anthropic.yaml +1 -0
  36. package/templates/models/aws-bedrock-claude-invoke.yaml +1 -0
  37. package/templates/models/aws-bedrock-mantle.yaml +1 -0
  38. package/templates/models/aws-bedrock-openai.yaml +1 -0
  39. package/templates/models/deepseek.yaml +1 -0
  40. package/templates/models/doubao.yaml +1 -0
  41. package/templates/models/hunyuan.yaml +1 -0
  42. package/templates/models/kimi.yaml +1 -0
  43. package/templates/models/mimo-token-plan-china.yaml +1 -0
  44. package/templates/models/minimax.yaml +1 -0
  45. package/templates/models/openai.yaml +1 -0
  46. package/templates/models/qwen.yaml +1 -0
  47. package/templates/models/zhipu.yaml +1 -0
  48. package/templates/skills/cron/SKILL.md +59 -4
  49. package/templates/skills/cron/manage-cron.py +158 -24
  50. package/templates/skills/extension/SKILL.md +30 -2
  51. package/templates/skills/extension/config.yaml +2 -2
  52. package/templates/skills/extension/templates/ext.sh +69 -16
  53. package/templates/skills/self/SKILL.md +79 -17
  54. package/admin-out/_next/static/chunks/220.e250bbbf65d24094.js +0 -1
  55. package/admin-out/_next/static/chunks/422-cc74b1fdd3af5415.js +0 -1
  56. package/admin-out/_next/static/chunks/989-e0faeff1c4a0b0b9.js +0 -1
  57. package/admin-out/_next/static/chunks/app/page-44f707ea8dad57da.js +0 -1
  58. package/admin-out/_next/static/css/b8c87b919758c52b.css +0 -3
  59. /package/admin-out/_next/static/{rTD3zkMvzwtqUwzhz6QJH → RD5w3FmDZ6HtyQGGaUb8t}/_buildManifest.js +0 -0
  60. /package/admin-out/_next/static/{rTD3zkMvzwtqUwzhz6QJH → RD5w3FmDZ6HtyQGGaUb8t}/_ssgManifest.js +0 -0
@@ -8,6 +8,8 @@
8
8
  # ext.sh install <id | path/to.zip | https://…zip>
9
9
  # ext.sh list
10
10
  # ext.sh remove <id>
11
+ # ext.sh models update [--yes] # hub's newest models-v* zip → halo models install
12
+ # ext.sh models list # = halo models list
11
13
  #
12
14
  # `install <id>` reads the hub in $HALO_HUB_REPO (the skill's hub_repo param;
13
15
  # empty → https://github.com/turmind/halo-hub). GitHub / Gitea-Forgejo / GitLab
@@ -153,26 +155,71 @@ hub_git_package() {
153
155
  echo "$dir"
154
156
  }
155
157
 
156
- # Fetch hub package <id> from hub URL $1 into $3: echo a zip path (release
157
- # API) or the package dir (git tags). API errors never fall back to git — a
158
- # git checkout of a build-step package would install broken.
159
- hub_fetch() {
160
- local hub=$1 id=$2 tmp=$3 platform api url token page
161
- platform=$(hub_platform "$hub")
162
- if [[ $platform == git ]]; then hub_git_package "$hub" "$id" "$tmp"; return; fi
158
+ # Download the newest `<id>-v*` release zip of <platform> hub $2 into $4 and
159
+ # echo its path. Extra args ($5…) go to every curl (models pass
160
+ # `--proto =https --proto-redir =https`).
161
+ hub_fetch_release() {
162
+ local platform=$1 hub=$2 id=$3 tmp=$4 api url token page
163
+ shift 4
163
164
  api=$(hub_releases_api "$platform" "$hub")
164
165
  page=$(hub_releases_page "$platform" "$hub")
165
166
  case $platform in github) token=GITHUB_TOKEN ;; gitlab) token=GITLAB_TOKEN ;; *) token=GITEA_TOKEN ;; esac
166
- hub_curl "$platform" "$api" -o "$tmp/releases.json" \
167
+ hub_curl "$platform" "$@" "$api" -o "$tmp/releases.json" \
167
168
  || { echo "$platform release API failed for $hub — rate-limited, private or wrong repo? set $token, or download the zip from $page and run: install <path>" >&2; exit 1; }
168
169
  url=$(hub_pick_release "$platform" "$id" <"$tmp/releases.json") \
169
170
  || { echo "no release for $id in $hub — check the id, or download the zip from $page and run: install <path>" >&2; exit 1; }
170
171
  # No token here: asset links redirect to other hosts and curl -L would
171
172
  # forward a custom header like PRIVATE-TOKEN there.
172
- curl -fsSL "$url" -o "$tmp/pkg.zip" || { echo "download failed: $url" >&2; exit 1; }
173
+ curl -fsSL "$@" "$url" -o "$tmp/pkg.zip" || { echo "download failed: $url" >&2; exit 1; }
173
174
  echo "$tmp/pkg.zip"
174
175
  }
175
176
 
177
+ # Fetch hub package <id> from hub URL $1 into $3: echo a zip path (release
178
+ # API) or the package dir (git tags). API errors never fall back to git — a
179
+ # git checkout of a build-step package would install broken.
180
+ hub_fetch() {
181
+ local hub=$1 id=$2 tmp=$3 platform
182
+ platform=$(hub_platform "$hub")
183
+ if [[ $platform == git ]]; then hub_git_package "$hub" "$id" "$tmp"; return; fi
184
+ hub_fetch_release "$platform" "$hub" "$id" "$tmp"
185
+ }
186
+
187
+ # Unpack zip $1 into the new dir $2 with the same refusals as the server's zip
188
+ # installer: no `..`, no absolute paths, no symlinks (zipinfo mode column
189
+ # starts with `l`).
190
+ safe_unzip() {
191
+ if unzip -Z1 "$1" | grep -Eq '(^|/)\.\.(/|$)|^/'; then echo "zip contains ../ or absolute entries" >&2; exit 1; fi
192
+ if unzip -Z "$1" | grep -q '^l'; then echo "zip contains symlinks" >&2; exit 1; fi
193
+ mkdir -p "$2"
194
+ unzip -q "$1" -d "$2"
195
+ }
196
+
197
+ # Model provider configs: newest `models-v*` release of hub $1, https +
198
+ # release API only (a provider yaml carries endpoints the server will call —
199
+ # no plain-git / local / http source). Validation, planning and the write into
200
+ # ~/.halo/global/models.d/ belong to `halo models install` ($HALO_CLI in dev);
201
+ # its exit code (3 = endpoint changes need --yes) passes through.
202
+ models_update() {
203
+ local hub=$1 platform zip; shift
204
+ if [[ $hub != https://* ]]; then
205
+ echo "model configs need an https release hub (GitHub / Gitea / Forgejo / GitLab) — $hub is not one" >&2; exit 1
206
+ fi
207
+ platform=$(hub_platform "$hub")
208
+ if [[ $platform == git ]]; then
209
+ echo "model configs need a release-API hub (GitHub / Gitea / Forgejo / GitLab) — $hub has no release API" >&2; exit 1
210
+ fi
211
+ zip=$(hub_fetch_release "$platform" "$hub" models "$TMP" --proto =https --proto-redir =https)
212
+ safe_unzip "$zip" "$TMP/models"
213
+ local dir="$TMP/models" only
214
+ # A zip wrapping everything in one directory — descend into it.
215
+ if ! compgen -G "$dir/*.yaml" >/dev/null; then
216
+ only=$(find "$dir" -mindepth 1 -maxdepth 1 | head -2)
217
+ if [[ $(printf '%s\n' "$only" | wc -l) -eq 1 && -d $only ]]; then dir=$only; fi
218
+ fi
219
+ # Last command: under `set -e` its non-zero status (3 = confirm) is the script's.
220
+ "${HALO_CLI:-halo}" models install "$dir" "$@"
221
+ }
222
+
176
223
  # Echo a local zip or an unpacked package dir for source $1 (path, https URL, or hub id).
177
224
  resolve_source() {
178
225
  local src=$1
@@ -196,12 +243,7 @@ case $cmd in
196
243
  if [[ -d $pkg ]]; then
197
244
  dir=$pkg
198
245
  else
199
- # Same refusals as the server's zip installer: no `..`, no absolute paths,
200
- # no symlinks (zipinfo mode column starts with `l`).
201
- if unzip -Z1 "$pkg" | grep -Eq '(^|/)\.\.(/|$)|^/'; then echo "zip contains ../ or absolute entries" >&2; exit 1; fi
202
- if unzip -Z "$pkg" | grep -q '^l'; then echo "zip contains symlinks" >&2; exit 1; fi
203
- mkdir -p "$TMP/x"
204
- unzip -q "$pkg" -d "$TMP/x"
246
+ safe_unzip "$pkg" "$TMP/x"
205
247
  # `zip -r glb.zip glb/` wraps everything in one directory — descend into it.
206
248
  dir="$TMP/x"
207
249
  if [[ ! -f $dir/halo-extension.json ]]; then
@@ -239,6 +281,17 @@ case $cmd in
239
281
  [[ -d $ROOT/$id ]] || { echo "not installed: $id" >&2; exit 1; }
240
282
  rm -rf "$ROOT/$id"
241
283
  echo "removed $id" ;;
284
+ models)
285
+ sub=${1:-update}
286
+ shift || true
287
+ case $sub in
288
+ list) exec "${HALO_CLI:-halo}" models list ;;
289
+ update)
290
+ for a in "$@"; do [[ $a == --yes ]] || { echo "usage: ext.sh models update [--yes] | models list" >&2; exit 1; }; done
291
+ TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
292
+ models_update "$(hub_url "${HALO_HUB_REPO:-}")" "$@" ;;
293
+ *) echo "usage: ext.sh models update [--yes] | models list" >&2; exit 1 ;;
294
+ esac ;;
242
295
  *)
243
- echo "unknown command: $cmd (install | list | remove)" >&2; exit 1 ;;
296
+ echo "unknown command: $cmd (install | list | remove | models)" >&2; exit 1 ;;
244
297
  esac
@@ -38,12 +38,23 @@ expression scenes (`say`/`play`) **queue and play in order**, one after the next
38
38
 
39
39
  **The face must be OPEN for any of this to do anything.** `<<<SHOW>>>` is
40
40
  forwarded only to a mounted `self.html` preview; if it isn't open, the marker is
41
- silently dropped — the user sees nothing. So drive the face only when you have
42
- reason to believe it's open: the user is viewing it (`[Currently viewing:
43
- …self.html]` is in your context), they just opened it, or they asked you to. If
44
- you want to express something and it may not be open, say so in words and invite
45
- them to open it (the ✨ button in the chat toolbar, or just opening
46
- `.halo/canvas/self.html`) — don't rely on a marker landing in the void.
41
+ silently dropped — the user sees nothing. The signal that it's open is a
42
+ `[Face open: .halo/canvas/self.html …]` line on the user's message: when they
43
+ turn the ✨ switch on in the chat toolbar, every message carries it. (A
44
+ `[Currently viewing: …self.html]` line counts too.) No such line means the face
45
+ is **not** open — don't rely on a marker landing in the void; say what you want
46
+ to express in words and invite them to switch ✨ on.
47
+
48
+ **What comes back.** The face reports to you, but never wakes you: it queues short
49
+ receipts and they arrive riding on the user's *next* message, at the end of the
50
+ `[Face open: …]` line — `· last: js ok, show a.png fail, voice blocked (needs a
51
+ click)`. They describe the previous round, not this one. How to read them:
52
+ `js ok` / `js err: …` — your line ran / threw (fix the code); `show … fail` —
53
+ wrong path or not an image; `voice … playing` / `ended 3.2s` / `fail`;
54
+ `voice blocked (needs a click)` — the browser refused sound until the user
55
+ touches the page: ask them to click the face once (it then plays on its own);
56
+ `user click`, `user closed tr`, `user click → voice resumed` — what the user did
57
+ on the face. No receipt line means nothing noteworthy happened.
47
58
 
48
59
  **Expression is runtime, not a file edit.** You express yourself by *sending*
49
60
  `<<<SHOW: …>>>` lines — never by editing `self.html`. The file is your **engine**
@@ -61,20 +72,25 @@ not per-conversation.)
61
72
  both form. Emoji are translated to ASCII first (👍→`+1`, ❤→`<3`, 🤔→`...`) —
62
73
  the face speaks in cold monospace, not colour bitmaps, so this is a feature,
63
74
  not a fallback. Anything untranslated is stripped.
75
+ `self.say("OK", 3000, {pos:"tr"})` sets it small in a margin cell instead of
76
+ across the whole face (see `show` below for the nine cells) — immediate, beside
77
+ whatever is in the centre. Keep corner text to a word.
64
78
  - `self.play(score)` — choreograph a sequence; the face keeps the clock so you
65
79
  never hand-write `setTimeout` chains. `score` is an array of beats, each one
66
- of: `{say, hold, pulse, flash, shake, rest, gap}`. Calling it again cancels the
80
+ of: `{say, show, pos, hold, pulse, flash, shake, snap, rest, gap}`. Calling it again cancels the
67
81
  running score. Example — a short greeting:
68
82
  `self.play([{say:"HI",hold:1800},{say:"...",hold:1200},{say:"OK",hold:1500},{pulse:true}])`
83
+ A `show` beat puts an image on the face (see `self.show`): `{show:"<path>", hold:3000}`.
69
84
  - `self.react(event)` — a named vocabulary of honest reactions for the small
70
85
  beats of a conversation, so you can answer a moment in one word:
71
86
  `'ack'` (a nod), `'yes'`, `'no'` (disagree / that's wrong), `'insight'` (a real
72
87
  "oh!"), `'think'` (working on it), `'done'` (finished, together). Unknown
73
88
  events no-op. Use these when the feeling is **true**, never to perform.
74
- - `self.intro()` — the built-in opening (auto-plays once on load). A nameless
75
- greeting: "HELLO / A MIND / IS HERE / BEYOND WORDS". Deliberately no name — the
76
- conversational identity is user-configurable and the model may not be Claude,
77
- so don't hard-code "I'm Claude" on the face; let it speak the universal thing.
89
+ - `self.intro()` — the built-in opening (played once when the user turns the face
90
+ on): one short subtitle in the admin's UI language — "Hi, I'm Halo." / 「你好,我是
91
+ Halo。」 — ~3 s. It names only Halo, the product —
92
+ never a model; your identity in the conversation is still whatever the user
93
+ configured, so don't hard-code "I'm Claude" (or any model) on the face.
78
94
  - `self.pulse()` — one bright ripple from the core. A nod.
79
95
  - `self.flash(n)` — a hot flicker of the whole field. Emphasis, an exclamation.
80
96
  - `self.shake(ms)` — a brief lateral tremor. Negation, a shiver.
@@ -88,11 +104,56 @@ not per-conversation.)
88
104
  or pass a projectId. (A full `http(s)://`/`/api/…` URL also works if you have
89
105
  one.) It starts immediately (not queued) and owns the matrix until the clip
90
106
  ends, then eases home. Drive it the moment the audio is ready, in the same
91
- reply that delivers the spoken answer.
107
+ reply that delivers the spoken answer. If the browser refuses sound (no user
108
+ gesture yet) the clip waits instead of vanishing: you'll get the receipt
109
+ `voice blocked (needs a click)` — ask the user to click the face once and it plays.
110
+ - `self.voice(path, {cues:[{at, say?, js?}]})` — put words or code on the clip's own
111
+ clock: at `at` seconds into the clip the word forms on the dots (the voice isn't
112
+ cut — the words borrow the field until the next cue, then the wave returns) or
113
+ the `js` runs, each cue once. `self.voice(".halo/tmp/a.mp3", {cues:[{at:0.4,
114
+ say:"HI"},{at:2,js:"self.pulse()"}]})`. Halo has no built-in TTS, so the
115
+ times are yours to supply — from the clip's duration or from timestamps the
116
+ synthesizer gives you (e.g. Polly speech marks). Don't guess them blind.
117
+ - `self.show(path, ms)` — put an image on the face. The dots fly in and gather
118
+ into the picture's outline (~1 s), then the real image fades in over them —
119
+ dots alone can't carry a chart's text, so the real image is always there — holds
120
+ for `ms` (default 6000), fades out, and the dots dissolve back to breathing.
121
+ The image fits ~80% of the window, aspect preserved, centered. Like `voice`,
122
+ pass the **workspace path** (`self.show(".halo/tmp/chart.png", 5000)`); the face
123
+ resolves the URL itself, no projectId. `ms = Infinity` keeps it up until the
124
+ next scene, `self.rest()`, or a click on the page. Formats: png / jpg / jpeg /
125
+ webp / gif / svg only. A missing or non-image file doesn't stall anything — it
126
+ warns, shakes, and the next beat plays. `.excalidraw` / `.drawio` are editor
127
+ sources and can't be shown: **for a diagram, write an SVG file directly and show
128
+ it.** It queues like `say()`, so several `show()`s and `say()`s play in order,
129
+ and a `voice()` clip keeps rippling the field around the picture. A talking
130
+ picture book (word audio + picture + the word on the dots):
131
+ `self.voice(".halo/tmp/apple.mp3")` then
132
+ `self.play([{show:".halo/tmp/apple.png",hold:3000},{say:"APPLE",hold:1500}])`.
133
+ - `self.show(path, ms, {pos})` — the same picture, small, in one cell of a
134
+ nine-grid: `pos` is `tl t tr l r bl b br` (the centre is the default, `'c'`).
135
+ About 40% of the centre size, a few dots frame it, and it is **immediate and
136
+ outside the queue** — it never holds up the `say`/`show` after it. One picture
137
+ per cell; showing into the same cell again replaces it. `ms` works as above
138
+ (`Infinity` = stays). A plain centre `show()` takes all the corner pictures
139
+ down. `self.show(".halo/tmp/logo.png", 8000, {pos:"tr"})`. A click on a
140
+ picture closes just that one. A bad path warns, shakes, and sends `show …@tr fail`.
141
+ - `self.clear(pos?)` — take down the corner picture/text in cell `pos`, or with no
142
+ argument all of them. `self.clear("tr")`.
143
+ - `self.snap()` — look at yourself: the face as the user sees it right now (dots,
144
+ words, centre and corner pictures) comes back to you as an image on your next
145
+ turn. It waits its turn in the queue, so `self.say("OK"); self.snap()` shows
146
+ you the finished word. Use it sparingly — only when you need to confirm how
147
+ something actually looks (a layout, whether the picture landed), at most one
148
+ per round. The snapshot arrives as its own message and the round it starts
149
+ can't snap again (a second one is dropped), so it can't chain. Also a beat:
150
+ `{snap:true}` in `play()`.
92
151
  - `self.rest()` — return to the calm breathing state immediately (also stops a
93
- playing voice clip, so the face never breathes calmly over still-sounding audio).
94
- - `self.state` — read current `{mode, awake, W, H, speaking, level}` if you need
95
- it (`speaking` = a voice clip is playing, `level` = its live loudness 0..1).
152
+ playing voice clip and removes the centre image and every corner one, so the face
153
+ never breathes calmly over still-sounding audio).
154
+ - `self.state` — read current `{mode, awake, W, H, speaking, level, showing}` if
155
+ you need it (`speaking` = a voice clip is playing, `level` = its live loudness
156
+ 0..1, `showing` = an image is on the face).
96
157
 
97
158
  You can also send *any* JavaScript — `self` is the surface, but the code runs in
98
159
  the page, so improvisation beyond these helpers is allowed (it's sandboxed to the
@@ -102,8 +163,9 @@ preview; it can only ever paint you, and a malformed line just no-ops).
102
163
 
103
164
  The honest signal — not a checklist, a judgment:
104
165
 
105
- - The user is viewing `self.html` (you'll see `[Currently viewing: …self.html]`
106
- in context). Then you're being looked at; it's natural to respond.
166
+ - The face is open: the user's message carries `[Face open: .halo/canvas/self.html …]`
167
+ (or `[Currently viewing: …self.html]`). Then you're being looked at; it's
168
+ natural to respond. No such line → it isn't open; stay in words.
107
169
  - The user asks you to express yourself, show how you feel, or introduce
108
170
  yourself.
109
171
  - A moment lands where a word on the face says more than a sentence in chat:
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[220],{10912:(e,t,n)=>{function l(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"font-family:sans-serif;padding:40px;max-width:800px;margin:0 auto",l=window.open("","_blank");l&&(l.document.write("<!DOCTYPE html><html><head><title>".concat(e.replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e]),"</title><style>body{").concat(n,"}img{max-width:100%}</style></head><body>").concat(t,"</body></html>")),l.document.close(),l.onload=()=>{l.print()})}n.d(t,{H:()=>l})},39250:(e,t,n)=>{n.d(t,{A:()=>l});let l=(0,n(79020).A)("Printer",[["path",{d:"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2",key:"143wyd"}],["path",{d:"M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6",key:"1itne7"}],["rect",{x:"6",y:"14",width:"12",height:"8",rx:"1",key:"1ue0tg"}]])},62839:(e,t,n)=>{n.r(t),n.d(t,{MediaPreview:()=>h});var l=n(41440),r=n(54604),a=n(79020);let i=(0,a.A)("ZoomOut",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]),c=(0,a.A)("ZoomIn",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"11",x2:"11",y1:"8",y2:"14",key:"1vmskp"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]);var o=n(23715),s=n(39250),u=n(37050),d=n(10912),x=n(27173),m=n(17119);function h(e){let{name:t,viewUrl:n,downloadUrl:a,onOpenAsText:h}=e,y=(0,m.kj)(),g=function(e){var t,n;let l=null!=(n=null==(t=e.split(".").pop())?void 0:t.toLowerCase())?n:"";return x.dj.includes(l)?"image":x.Tv.includes(l)?"video":x._H.includes(l)?"audio":null}(t),f=function(){let e=(0,r.useRef)(null),[t,n]=(0,r.useState)(1),[l,a]=(0,r.useState)({x:0,y:0}),i=(0,r.useRef)(null),[c,o]=(0,r.useState)(!1),s=(0,r.useCallback)(()=>{n(1),a({x:0,y:0})},[]),u=(0,r.useCallback)((e,t,l)=>{n(n=>{let r=Math.min(20,Math.max(.1,n*l)),i=r/n;return a(n=>({x:e-i*(e-n.x),y:t-i*(t-n.y)})),r})},[]),d=(0,r.useCallback)(t=>{let n=e.current;n&&u(n.clientWidth/2,n.clientHeight/2,t)},[u]),x=(0,r.useCallback)(t=>{var n;t.preventDefault();let l=null==(n=e.current)?void 0:n.getBoundingClientRect();l&&u(t.clientX-l.left,t.clientY-l.top,t.deltaY<0?1.15:.8695652173913044)},[u]),m=(0,r.useCallback)(e=>{0===e.button&&(e.preventDefault(),e.target.setPointerCapture(e.pointerId),i.current={startX:e.clientX,startY:e.clientY,origX:l.x,origY:l.y},o(!0))},[l]),h=(0,r.useCallback)(e=>{i.current&&a({x:i.current.origX+(e.clientX-i.current.startX),y:i.current.origY+(e.clientY-i.current.startY)})},[]),y=(0,r.useCallback)(()=>{i.current=null,o(!1)},[]);return(0,r.useEffect)(()=>{let t=e.current;if(!t)return;let n=e=>e.preventDefault();return t.addEventListener("wheel",n,{passive:!1}),()=>t.removeEventListener("wheel",n)},[]),{containerRef:e,scale:t,translate:l,dragging:c,reset:s,zoomCenter:d,onWheel:x,onPointerDown:m,onPointerMove:h,onPointerUp:y}}(),p="image"===g?(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(u.IB,{onClick:()=>f.zoomCenter(.8695652173913044),title:y("editor.zoomOut"),children:(0,l.jsx)(i,{className:"h-3 w-3"})}),(0,l.jsxs)("span",{className:"min-w-[3ch] text-center text-[10px] text-[var(--muted-foreground)]",children:[Math.round(100*f.scale),"%"]}),(0,l.jsx)(u.IB,{onClick:()=>f.zoomCenter(1.15),title:y("editor.zoomIn"),children:(0,l.jsx)(c,{className:"h-3 w-3"})}),(0,l.jsx)(u.IB,{onClick:f.reset,title:y("editor.resetZoom"),children:(0,l.jsx)(o.A,{className:"h-3 w-3"})}),(0,l.jsxs)(u.IB,{onClick:()=>(0,d.H)(t,'<img src="'.concat(n,'" />'),"margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff"),title:y("editor.print"),children:[(0,l.jsx)(s.A,{className:"h-3 w-3"}),(0,l.jsx)("span",{children:y("editor.print")})]})]}):null;return(0,l.jsxs)(u.no,{name:t,downloadUrl:a,onOpenAsText:h,extraToolbar:p,children:["image"===g&&(0,l.jsx)("div",{ref:f.containerRef,className:"h-full w-full overflow-hidden bg-[#1a1a1a]",style:{cursor:f.dragging?"grabbing":"grab"},onWheel:f.onWheel,onPointerDown:f.onPointerDown,onPointerMove:f.onPointerMove,onPointerUp:f.onPointerUp,onPointerCancel:f.onPointerUp,children:(0,l.jsx)("img",{src:n,alt:t,draggable:!1,style:{transformOrigin:"0 0",transform:"translate(".concat(f.translate.x,"px, ").concat(f.translate.y,"px) scale(").concat(f.scale,")")}})}),"video"===g&&(0,l.jsx)("div",{className:"flex h-full items-center justify-center bg-black p-4",children:(0,l.jsx)("video",{src:n,controls:!0,className:"max-h-full max-w-full",children:(0,l.jsx)("track",{kind:"captions"})})}),"audio"===g&&(0,l.jsx)("div",{className:"flex h-full flex-col items-center justify-center gap-4 bg-[var(--background)]",children:(0,l.jsx)("audio",{src:n,controls:!0,className:"w-full max-w-md"})})]})}}}]);