openclacky 1.2.9 → 1.2.10

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.
@@ -350,80 +350,104 @@ module Clacky
350
350
  cache: { write: 0.30, read: 0.03 }
351
351
  },
352
352
 
353
+ # M3 (released 2026-06-01) is MiniMax's multimodal flagship. Official
354
+ # pricing is tiered by context length (≤512K vs 512K–1M); per the
355
+ # project's "displayed ≤ actual" convention we record only the lowest
356
+ # (≤512K) tier as a flat rate — the global TIERED_PRICING_THRESHOLD is
357
+ # 200K, so applying the 512K–1M rate to the 200K–512K band would over-
358
+ # charge. Listed at original (non-promotional) prices: input $0.60,
359
+ # output $2.40, cache read $0.12 per 1M tokens.
360
+ "minimax-m3" => {
361
+ input: { default: 0.60, over_200k: 0.60 },
362
+ output: { default: 2.40, over_200k: 2.40 },
363
+ cache: { write: 0.60, read: 0.12 }
364
+ },
365
+
353
366
  "minimax-m2.7" => {
354
367
  input: { default: 0.30, over_200k: 0.30 },
355
368
  output: { default: 1.20, over_200k: 1.20 },
356
369
  cache: { write: 0.30, read: 0.06 }
357
370
  },
358
371
 
359
- # Qwen (Alibaba DashScope) USD per 1M tokens, Singapore region list price.
360
- # Source: Alibaba Cloud Model Studio international pricing.
361
- # Cache convention (mirrors DeepSeek/Kimi/GLM "displayed ≤ actual"):
362
- # - DashScope has two cache modes; implicit is auto-on, explicit is opt-in.
363
- # Implicit: write @ 100% input, read @ 20% input (no setup, no guarantee)
364
- # Explicit: write @ 125% input, read @ 10% input (cache_control marker)
365
- # - We bill writes at the regular input rate (matches implicit, and avoids
366
- # surprising users with the explicit 25% surcharge).
367
- # - We bill reads at 20% (implicit rate) — the conservative side; users on
368
- # explicit caching will see real bills slightly *lower* than displayed.
372
+ # Qwen (Alibaba DashScope) - USD per 1M tokens, international (Singapore) list price.
373
+ # Source: Alibaba Cloud Model Studio international console per-model pages.
374
+ #
375
+ # Pricing convention:
376
+ # - These rates are used for user-facing cost ESTIMATION, so we always use
377
+ # the standard LIST price and intentionally ignore any limited-time promo
378
+ # discounts. A promo lowers the user's actual bill, never raises it, so
379
+ # estimating at list price keeps the estimate a safe upper bound and avoids
380
+ # churn whenever a promo starts or ends.
381
+ # - We record the model's LOWEST context tier (e.g. input<=256k / <=128k) as a
382
+ # flat rate, since the global TIERED_PRICING_THRESHOLD is 200K and does not
383
+ # match Qwen's per-model breakpoints.
384
+ # - cache.write = official explicit-cache-create price.
385
+ # - cache.read = official explicit-cache-hit price.
386
+ # - When a model has NO published explicit-cache price (e.g. qwen3.6-27b,
387
+ # qwen-plus-latest), cache.write/read fall back to the input rate.
388
+ # qwen3.7-max: NOT tiered (single flat tier per Alibaba's definition).
389
+ # List price: input 2.5, output 7.5, explicit write 3.125, explicit read 0.25.
369
390
  "qwen3.7-max" => {
370
- input: { default: 1.20, over_200k: 1.20 },
371
- output: { default: 6.00, over_200k: 6.00 },
372
- cache: { write: 1.20, read: 0.24 }
391
+ input: { default: 2.5, over_200k: 2.5 },
392
+ output: { default: 7.5, over_200k: 7.5 },
393
+ cache: { write: 3.125, read: 0.25 }
373
394
  },
374
395
 
396
+ # qwen3.7-plus: list price (<=256k tier):
397
+ # input 0.4, output 1.6, explicit write 0.5, explicit read 0.04.
375
398
  "qwen3.7-plus" => {
376
- input: { default: 0.40, over_200k: 0.40 },
377
- output: { default: 2.40, over_200k: 2.40 },
378
- cache: { write: 0.40, read: 0.08 }
379
- },
380
-
381
- "qwen3.7-flash" => {
382
- input: { default: 0.15, over_200k: 0.15 },
383
- output: { default: 0.90, over_200k: 0.90 },
384
- cache: { write: 0.15, read: 0.03 }
399
+ input: { default: 0.4, over_200k: 0.4 },
400
+ output: { default: 1.6, over_200k: 1.6 },
401
+ cache: { write: 0.5, read: 0.04 }
385
402
  },
386
403
 
404
+ # qwen3.6-plus: list price (<=256k tier). Official explicit-cache prices.
405
+ # input 0.50, output 3.00, explicit write 0.625, explicit read 0.05
387
406
  "qwen3.6-plus" => {
388
- input: { default: 0.40, over_200k: 0.40 },
389
- output: { default: 2.40, over_200k: 2.40 },
390
- cache: { write: 0.40, read: 0.08 }
407
+ input: { default: 0.50, over_200k: 0.50 },
408
+ output: { default: 3.00, over_200k: 3.00 },
409
+ cache: { write: 0.625, read: 0.05 }
391
410
  },
392
411
 
412
+ # qwen3.6-max (qwen3.6-max-preview): list price (<=128k tier).
413
+ # input 1.30, output 7.80, explicit write 1.625, explicit read 0.13
393
414
  "qwen3.6-max" => {
394
- input: { default: 1.20, over_200k: 1.20 },
395
- output: { default: 6.00, over_200k: 6.00 },
396
- cache: { write: 1.20, read: 0.24 }
415
+ input: { default: 1.30, over_200k: 1.30 },
416
+ output: { default: 7.80, over_200k: 7.80 },
417
+ cache: { write: 1.625, read: 0.13 }
397
418
  },
398
419
 
420
+ # qwen3.6-27b: list price, no explicit-cache pricing published.
421
+ # Cache write/read fall back to the input rate (no cache discount).
399
422
  "qwen3.6-27b" => {
400
- input: { default: 0.20, over_200k: 0.20 },
401
- output: { default: 0.80, over_200k: 0.80 },
402
- cache: { write: 0.20, read: 0.04 }
423
+ input: { default: 0.60, over_200k: 0.60 },
424
+ output: { default: 3.60, over_200k: 3.60 },
425
+ cache: { write: 0.60, read: 0.60 }
403
426
  },
404
427
 
428
+ # qwen3.6-flash: list price (<=256k tier).
429
+ # input 0.25, output 1.50, explicit write 0.3125, explicit read 0.025
405
430
  "qwen3.6-flash" => {
406
- input: { default: 0.15, over_200k: 0.15 },
407
- output: { default: 0.90, over_200k: 0.90 },
408
- cache: { write: 0.15, read: 0.03 }
431
+ input: { default: 0.25, over_200k: 0.25 },
432
+ output: { default: 1.50, over_200k: 1.50 },
433
+ cache: { write: 0.3125, read: 0.025 }
409
434
  },
410
435
 
436
+ # qwen-plus-latest: list price (<=256k tier), no explicit-cache pricing.
437
+ # Cache write/read fall back to the input rate (no cache discount).
411
438
  "qwen-plus-latest" => {
412
439
  input: { default: 0.40, over_200k: 0.40 },
413
440
  output: { default: 1.20, over_200k: 1.20 },
414
- cache: { write: 0.40, read: 0.08 }
415
- },
416
-
417
- "qwen-vl-plus" => {
418
- input: { default: 0.14, over_200k: 0.14 },
419
- output: { default: 0.41, over_200k: 0.41 },
420
- cache: { write: 0.14, read: 0.028 }
441
+ cache: { write: 0.40, read: 0.40 }
421
442
  },
422
443
 
423
- "qwen-vl-max" => {
424
- input: { default: 0.52, over_200k: 0.52 },
425
- output: { default: 2.08, over_200k: 2.08 },
426
- cache: { write: 0.52, read: 0.104 }
444
+ # qwen3-vl-plus: replaces the retiring qwen-vl-plus. List price
445
+ # (128k<input<=256k tier). input 0.60, output 4.80,
446
+ # explicit write 0.75, explicit read 0.06.
447
+ "qwen3-vl-plus" => {
448
+ input: { default: 0.60, over_200k: 0.60 },
449
+ output: { default: 4.80, over_200k: 4.80 },
450
+ cache: { write: 0.75, read: 0.06 }
427
451
  },
428
452
 
429
453
  }.freeze
@@ -583,6 +607,8 @@ module Clacky
583
607
  "glm-4.7"
584
608
  # MiniMax — model ids in providers.rb use capitalised "MiniMax-M2.x"
585
609
  # but we match case-insensitively and map to the lowercased table key.
610
+ when /^minimax-m3$/i
611
+ "minimax-m3"
586
612
  when /^minimax-m2\.5$/i
587
613
  "minimax-m2.5"
588
614
  when /^minimax-m2\.7$/i
@@ -591,14 +617,12 @@ module Clacky
591
617
  # Qwen (Alibaba DashScope) — strict anchored match per registered
592
618
  # model id in providers.rb. qwen3.7-* is the latest flagship line;
593
619
  # qwen3.6-* are the previous generation; qwen-plus-latest is the
594
- # rolling alias for the latest Qwen-Plus release; qwen-vl-* are
595
- # the multimodal SKUs.
620
+ # rolling alias for the latest Qwen-Plus release; qwen3-vl-plus is
621
+ # the multimodal SKU (replaces the retired qwen-vl-plus/max).
596
622
  when /^qwen3\.7-max$/i
597
623
  "qwen3.7-max"
598
624
  when /^qwen3\.7-plus$/i
599
625
  "qwen3.7-plus"
600
- when /^qwen3\.7-flash$/i
601
- "qwen3.7-flash"
602
626
  when /^qwen3\.6-plus$/i
603
627
  "qwen3.6-plus"
604
628
  when /^qwen3\.6-max$/i
@@ -609,10 +633,8 @@ module Clacky
609
633
  "qwen3.6-flash"
610
634
  when /^qwen-plus-latest$/i
611
635
  "qwen-plus-latest"
612
- when /^qwen-vl-plus$/i
613
- "qwen-vl-plus"
614
- when /^qwen-vl-max$/i
615
- "qwen-vl-max"
636
+ when /^qwen3-vl-plus$/i
637
+ "qwen3-vl-plus"
616
638
 
617
639
  # OpenAI GPT-5.x models — match various dashed/dotted/compact forms
618
640
  # (e.g. "gpt-5.5", "gpt-5-5", "gpt5.5", "gpt55")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "1.2.9"
4
+ VERSION = "1.2.10"
5
5
  end
@@ -4108,6 +4108,175 @@ body {
4108
4108
  border-color: var(--color-border-secondary);
4109
4109
  }
4110
4110
 
4111
+ /* ── Directory Picker Modal (tree-based) ─────────────────────────────────── */
4112
+ .modal-title {
4113
+ padding: 1rem 1.25rem;
4114
+ border-bottom: 1px solid var(--color-border-primary);
4115
+ font-size: 1.0625rem;
4116
+ font-weight: 600;
4117
+ color: var(--color-text-primary);
4118
+ }
4119
+ .modal-buttons {
4120
+ display: flex;
4121
+ align-items: center;
4122
+ justify-content: flex-end;
4123
+ gap: 0.625rem;
4124
+ padding: 1rem 1.25rem;
4125
+ border-top: 1px solid var(--color-border-primary);
4126
+ }
4127
+ .dir-picker-input {
4128
+ width: 100%;
4129
+ padding: 0.5rem 0.75rem;
4130
+ border: 1px solid var(--color-border-primary);
4131
+ border-radius: 6px;
4132
+ background: var(--color-bg-primary);
4133
+ color: var(--color-text-primary);
4134
+ font-size: 0.875rem;
4135
+ font-family: var(--font-mono, monospace);
4136
+ transition: border-color 0.15s;
4137
+ box-sizing: border-box;
4138
+ }.dir-picker-input:focus {
4139
+ outline: none;
4140
+ border-color: var(--color-accent);
4141
+ }
4142
+ .dir-picker-input::placeholder {
4143
+ color: var(--color-text-tertiary);
4144
+ }
4145
+ /* Preset buttons row */
4146
+ .dp-presets {
4147
+ display: flex;
4148
+ gap: 0.5rem;
4149
+ flex-wrap: wrap;
4150
+ }
4151
+ .dp-presets .btn-sm {
4152
+ padding: 0.25rem 0.625rem;
4153
+ font-size: 0.75rem;
4154
+ }
4155
+ /* Path input container */
4156
+ .dp-path-container {
4157
+ flex-shrink: 0;
4158
+ position: relative;
4159
+ }/* Autocomplete dropdown */
4160
+ .dp-autocomplete {
4161
+ position: absolute;
4162
+ top: 100%;
4163
+ left: 0;
4164
+ right: 0;
4165
+ z-index: 10;
4166
+ max-height: 200px;
4167
+ overflow-y: auto;
4168
+ background: var(--color-bg-primary);
4169
+ border: 1px solid var(--color-border-primary);
4170
+ border-top: none;
4171
+ border-radius: 0 0 6px 6px;
4172
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
4173
+ }
4174
+ .dp-ac-item {
4175
+ display: flex;
4176
+ align-items: center;
4177
+ gap: 0.5rem;
4178
+ padding: 0.4rem 0.75rem;
4179
+ cursor: pointer;
4180
+ font-size: 0.8125rem;
4181
+ color: var(--color-text-primary);
4182
+ transition: background 0.1s;
4183
+ }
4184
+ .dp-ac-item:hover,
4185
+ .dp-ac-item.active {
4186
+ background: var(--color-bg-hover);
4187
+ }
4188
+ .dp-ac-icon {
4189
+ display: inline-flex;
4190
+ align-items: center;
4191
+ flex-shrink: 0;
4192
+ color: var(--color-text-secondary);
4193
+ }
4194
+ .dp-ac-name {
4195
+ overflow: hidden;
4196
+ text-overflow: ellipsis;
4197
+ white-space: nowrap;
4198
+ }
4199
+ /* Tree container */
4200
+ .dp-tree {
4201
+ border: 1px solid var(--color-border-primary);
4202
+ border-radius: 6px;
4203
+ background: var(--color-bg-secondary);
4204
+ min-height: 200px;
4205
+ max-height: 340px;
4206
+ overflow: auto;
4207
+ font-size: 0.8125rem;
4208
+ }
4209
+ /* Tree node */
4210
+ .dp-node {
4211
+ user-select: none;
4212
+ }
4213
+ .dp-row {
4214
+ display: flex;
4215
+ align-items: center;
4216
+ gap: 0.375rem;
4217
+ padding: 0.375rem 0.5rem;
4218
+ cursor: pointer;
4219
+ border-radius: 4px;
4220
+ transition: background 0.1s;
4221
+ }
4222
+ .dp-row:hover {
4223
+ background: var(--color-bg-hover);
4224
+ }
4225
+ .dp-row.selected {
4226
+ background: var(--color-accent-subtle, rgba(59, 130, 246, 0.12));
4227
+ }
4228
+ .dp-row.selected .dp-name {
4229
+ color: var(--color-accent);
4230
+ font-weight: 500;
4231
+ }
4232
+ /* Caret */
4233
+ .dp-caret {
4234
+ display: inline-flex;
4235
+ align-items: center;
4236
+ justify-content: center;
4237
+ width: 16px;
4238
+ height: 16px;
4239
+ flex-shrink: 0;
4240
+ color: var(--color-text-tertiary);
4241
+ transition: transform 0.15s;
4242
+ }
4243
+ .dp-caret.open {
4244
+ transform: rotate(90deg);
4245
+ }
4246
+ /* Folder icon */
4247
+ .dp-icon {
4248
+ display: inline-flex;
4249
+ align-items: center;
4250
+ flex-shrink: 0;
4251
+ color: var(--color-text-secondary);
4252
+ }
4253
+ .dp-row.selected .dp-icon {
4254
+ color: var(--color-accent);
4255
+ }
4256
+ /* Name */
4257
+ .dp-name {
4258
+ flex: 1;
4259
+ overflow: hidden;
4260
+ text-overflow: ellipsis;
4261
+ white-space: nowrap;
4262
+ color: var(--color-text-primary);
4263
+ }
4264
+ /* Children container */
4265
+ .dp-children {
4266
+ display: flex;
4267
+ flex-direction: column;
4268
+ }/* Loading / empty / error states */
4269
+ .dp-loading,
4270
+ .dp-empty,
4271
+ .dp-error {
4272
+ padding: 0.5rem 1rem;
4273
+ color: var(--color-text-tertiary);
4274
+ font-size: 0.75rem;
4275
+ font-style: italic;
4276
+ }
4277
+ .dp-error {
4278
+ color: var(--color-text-danger, #ef4444);
4279
+ }
4111
4280
  /* Model name combobox */
4112
4281
  .model-name-combobox {
4113
4282
  position: relative;
@@ -9069,11 +9238,14 @@ body.setup-mode[data-theme="dark"] {
9069
9238
  .tooltip-date {
9070
9239
  font-weight: 600;
9071
9240
  color: var(--color-text-primary);
9241
+ white-space: nowrap;
9072
9242
  }
9073
- .tooltip-total {
9243
+ .tooltip-total-value {
9074
9244
  font-size: 0.875rem;
9075
9245
  color: var(--color-accent);
9076
- font-weight: 500;
9246
+ font-weight: 600;
9247
+ white-space: nowrap;
9248
+ margin-left: 0.75rem;
9077
9249
  }
9078
9250
  .tooltip-row {
9079
9251
  display: flex;
@@ -9571,6 +9743,53 @@ body.setup-mode[data-theme="dark"] {
9571
9743
  color: var(--color-text-secondary);
9572
9744
  font-weight: 500;
9573
9745
  }
9746
+ .settings-exchange-rate-refresh {
9747
+ display: inline-flex;
9748
+ align-items: center;
9749
+ justify-content: center;
9750
+ gap: 0.35rem;
9751
+ min-height: 34px;
9752
+ padding: 0.45rem 0.8rem;
9753
+ border: 1px solid color-mix(in srgb, var(--color-accent-primary) 28%, var(--color-border));
9754
+ border-radius: 999px;
9755
+ background: color-mix(in srgb, var(--color-accent-primary) 8%, var(--color-bg-secondary));
9756
+ color: var(--color-accent-primary);
9757
+ font-size: 0.8125rem;
9758
+ font-weight: 600;
9759
+ line-height: 1;
9760
+ white-space: nowrap;
9761
+ cursor: pointer;
9762
+ transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
9763
+ }
9764
+ .settings-exchange-rate-refresh svg {
9765
+ flex: 0 0 auto;
9766
+ }
9767
+ .settings-exchange-rate-refresh:hover:not(:disabled) {
9768
+ border-color: color-mix(in srgb, var(--color-accent-primary) 52%, var(--color-border));
9769
+ background: color-mix(in srgb, var(--color-accent-primary) 12%, var(--color-bg-secondary));
9770
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--color-accent-primary) 16%, transparent);
9771
+ transform: translateY(-1px);
9772
+ }
9773
+ .settings-exchange-rate-refresh:active:not(:disabled) {
9774
+ transform: translateY(0);
9775
+ }
9776
+ .settings-exchange-rate-refresh:disabled {
9777
+ opacity: 0.6;
9778
+ cursor: not-allowed;
9779
+ transform: none;
9780
+ }
9781
+ .settings-exchange-rate-status {
9782
+ min-height: 1.1rem;
9783
+ margin-top: 0.4rem;
9784
+ font-size: 0.8125rem;
9785
+ color: var(--color-text-secondary);
9786
+ }
9787
+ .settings-exchange-rate-status.success {
9788
+ color: var(--color-success, #2e7d32);
9789
+ }
9790
+ .settings-exchange-rate-status.error {
9791
+ color: var(--color-error, #d33);
9792
+ }
9574
9793
 
9575
9794
  /* ── Sessions List ───────────────────────────────────────────────────── */
9576
9795
  .billing-sessions-row {
@@ -226,7 +226,7 @@ const Billing = (() => {
226
226
  tooltip.innerHTML = `
227
227
  <div class="tooltip-header">
228
228
  <span class="tooltip-date">${date}</span>
229
- <span class="tooltip-total">${total} tokens</span>
229
+ <span class="tooltip-total-value">${total} tokens</span>
230
230
  </div>
231
231
  <div class="tooltip-row">
232
232
  <span class="tooltip-dot tooltip-total"></span>
@@ -80,8 +80,16 @@ const I18n = (() => {
80
80
  "sessions.actions.download": "Download session files",
81
81
  "sessions.actions.downloadHint": "for debugging",
82
82
  "sib.dir.tooltip": "Click to change directory",
83
- "sib.dir.changePrompt": "Change working directory:",
84
- "workspace.title": "Workspace",
83
+ "sib.dir.changePrompt": "Change working directory",
84
+ "sib.dir.home": "Home",
85
+ "sib.dir.root": "Root",
86
+ "sib.dir.default": "Default workspace",
87
+ "sib.dir.current": "Current workspace", "sib.dir.inputPlaceholder": "Type or select a directory path",
88
+ "sib.dir.loading": "Loading…",
89
+ "sib.dir.empty": "Empty directory",
90
+ "sib.dir.loadError": "Failed to load",
91
+ "sib.dir.confirm": "Confirm",
92
+ "sib.dir.cancel": "Cancel", "workspace.title": "Workspace",
85
93
  "workspace.expand": "Open workspace",
86
94
  "workspace.collapse": "Collapse workspace",
87
95
  "workspace.refresh": "Refresh",
@@ -624,6 +632,10 @@ const I18n = (() => {
624
632
  "settings.currency.usd": "$ USD",
625
633
  "settings.currency.cny": "¥ CNY",
626
634
  "settings.currency.exchangeRate": "Exchange Rate (1 USD =)",
635
+ "settings.currency.updateLatest": "Update latest",
636
+ "settings.currency.updating": "Updating…",
637
+ "settings.currency.updated": "Updated from {{source}} on {{date}}",
638
+ "settings.currency.updateFailed": "Failed to fetch the latest rate. You can still enter it manually.",
627
639
 
628
640
  // ── Onboard ──
629
641
  "onboard.title": "Welcome to {{brand}}",
@@ -800,8 +812,16 @@ const I18n = (() => {
800
812
  "sessions.actions.download": "下载会话文件",
801
813
  "sessions.actions.downloadHint": "用于调试",
802
814
  "sib.dir.tooltip": "点击切换工作目录",
803
- "sib.dir.changePrompt": "切换工作目录:",
804
- "workspace.title": "工作区",
815
+ "sib.dir.changePrompt": "切换工作目录",
816
+ "sib.dir.home": "主目录",
817
+ "sib.dir.root": "根目录",
818
+ "sib.dir.default": "默认工作目录",
819
+ "sib.dir.current": "当前工作目录", "sib.dir.inputPlaceholder": "输入或选择目录路径",
820
+ "sib.dir.loading": "加载中…",
821
+ "sib.dir.empty": "空目录",
822
+ "sib.dir.loadError": "加载失败",
823
+ "sib.dir.confirm": "确认",
824
+ "sib.dir.cancel": "取消", "workspace.title": "工作区",
805
825
  "workspace.expand": "打开工作区",
806
826
  "workspace.collapse": "收起工作区",
807
827
  "workspace.refresh": "刷新",
@@ -1343,6 +1363,10 @@ const I18n = (() => {
1343
1363
  "settings.currency.usd": "$ 美元",
1344
1364
  "settings.currency.cny": "¥ 人民币",
1345
1365
  "settings.currency.exchangeRate": "汇率 (1 美元 =)",
1366
+ "settings.currency.updateLatest": "获取最新汇率",
1367
+ "settings.currency.updating": "获取中…",
1368
+ "settings.currency.updated": "已从 {{source}} 更新,日期 {{date}}",
1369
+ "settings.currency.updateFailed": "获取最新汇率失败,仍可手动输入。",
1346
1370
 
1347
1371
  // ── Onboard ──
1348
1372
  "onboard.title": "欢迎使用 {{brand}}",
@@ -384,7 +384,7 @@
384
384
  <div id="image-preview-strip" style="display:none"></div>
385
385
  <div id="input-bar">
386
386
  <!-- Hidden file picker -->
387
- <input type="file" id="image-file-input" accept="image/png,image/jpeg,image/gif,image/webp,*/*" multiple style="display:none">
387
+ <input type="file" id="image-file-input" accept="*/*" multiple style="display:none">
388
388
  <button id="btn-attach" title="Attach file (image, pdf, docx, md, tar.gz…) — drag & drop / Ctrl+V also work">
389
389
  <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
390
390
  <path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66L9.41 17.41a2 2 0 0 1-2.83-2.83l8.49-8.48"/>
@@ -824,7 +824,15 @@
824
824
  <div class="settings-exchange-rate-input-wrapper">
825
825
  <input type="number" id="settings-exchange-rate" class="settings-exchange-rate-input" step="0.0001" min="0.0001" placeholder="6.7944">
826
826
  <span class="settings-exchange-rate-unit">CNY</span>
827
+ <button type="button" id="btn-update-exchange-rate" class="settings-exchange-rate-refresh">
828
+ <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
829
+ <path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/>
830
+ <path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/>
831
+ </svg>
832
+ <span data-i18n="settings.currency.updateLatest">Update latest</span>
833
+ </button>
827
834
  </div>
835
+ <div id="settings-exchange-rate-status" class="settings-exchange-rate-status"></div>
828
836
  </label>
829
837
  </div>
830
838
  </section>