tavern-harness 0.1.21 → 0.1.22
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.
- package/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/src/components/SettingsView.tsx +31 -10
- package/src/components/Sidebar.tsx +20 -3
- package/src/components/shared.tsx +2 -0
- package/src/core/i18n.ts +12 -0
- package/src/generated/changelog.json +31 -1
- package/src/theme/global.css +5 -3
- package/src/theme/views.css +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
> 本文件由 `scripts/changelog.mjs` 从 `changelog/v*.md` 生成,请勿手动编辑。
|
|
4
4
|
> 版本对比链接见文末;源文件位于仓库 `changelog/` 目录。
|
|
5
5
|
|
|
6
|
+
## [0.1.22] - 2026-10-26
|
|
7
|
+
|
|
8
|
+
### 中文
|
|
9
|
+
|
|
10
|
+
#### 改进
|
|
11
|
+
|
|
12
|
+
- 生成参数默认锁定:「设置 → 生成参数」里的滑杆和下拉框要先点「解锁」才能调整,离开设置页后会自动重新锁定,避免滑动页面时误改温度、上下文压缩等参数
|
|
13
|
+
- 「重置」按钮在锁定状态下不可点击,解锁后才能恢复默认值
|
|
14
|
+
- 会话列表的标题和副标题只在内容确实溢出时才做末尾渐隐,短标题不再被淡边影响
|
|
15
|
+
- 移动端顶部导航按钮之间的间距略微加大,点击时更不容易误触相邻按钮
|
|
16
|
+
|
|
17
|
+
### English
|
|
18
|
+
|
|
19
|
+
#### Changed
|
|
20
|
+
|
|
21
|
+
- Generation parameters are now locked by default: the sliders and dropdowns under Settings → Generation parameters stay read-only until you tap "Unlock", and they lock again when you leave the settings page, so scrolling can no longer change temperature, context compression, and other values by accident
|
|
22
|
+
- The "Reset" button is disabled while parameters are locked and becomes available after unlocking
|
|
23
|
+
- Session list titles and subtitles only fade out at the end when the text actually overflows, so short titles are no longer dimmed by the mask
|
|
24
|
+
- Mobile top-bar navigation buttons have a little more spacing, reducing accidental taps on neighbours
|
|
25
|
+
|
|
6
26
|
## [0.1.21] - 2026-10-26
|
|
7
27
|
|
|
8
28
|
### 中文
|
|
@@ -608,6 +628,7 @@
|
|
|
608
628
|
- Vite + React + Zustand + Dexie stack with browser-side local persistence
|
|
609
629
|
|
|
610
630
|
<!-- 链接定义 -->
|
|
631
|
+
[0.1.22]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.21...v0.1.22
|
|
611
632
|
[0.1.21]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.20...v0.1.21
|
|
612
633
|
[0.1.20]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.19...v0.1.20
|
|
613
634
|
[0.1.19]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.18...v0.1.19
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { memo, useState } from 'react';
|
|
1
|
+
import { memo, useEffect, useState } from 'react';
|
|
2
2
|
import { useStore } from '../store/store';
|
|
3
3
|
import { db, DEFAULT_GENERATION_SETTINGS } from '../db/database';
|
|
4
4
|
import { type ThemeMode } from '../theme/theme';
|
|
@@ -24,6 +24,8 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
24
24
|
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
|
25
25
|
const [resetting, setResetting] = useState(false);
|
|
26
26
|
const [showVersionHistory, setShowVersionHistory] = useState(false);
|
|
27
|
+
const [generationParamsLocked, setGenerationParamsLocked] = useState(true);
|
|
28
|
+
const activeView = useStore((s) => s.activeView);
|
|
27
29
|
const t = useT();
|
|
28
30
|
const hasUnseenVersion = useHasUnseenVersion();
|
|
29
31
|
|
|
@@ -38,6 +40,7 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
38
40
|
max={max}
|
|
39
41
|
step={step}
|
|
40
42
|
value={settings[key] as number}
|
|
43
|
+
disabled={generationParamsLocked}
|
|
41
44
|
onChange={(e) => setSettings({ [key]: parseFloat(e.target.value) })}
|
|
42
45
|
/>
|
|
43
46
|
<span className="range-val">{fmt ? fmt(settings[key] as number) : (settings[key] as number)}</span>
|
|
@@ -52,6 +55,11 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
52
55
|
await setSettings({ ...generationDefaults });
|
|
53
56
|
addToast(t('toast.genParamsReset'));
|
|
54
57
|
};
|
|
58
|
+
const toggleGenerationParamsLock = () => setGenerationParamsLocked((locked) => !locked);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (activeView !== 'settings') setGenerationParamsLocked(true);
|
|
62
|
+
}, [activeView]);
|
|
55
63
|
|
|
56
64
|
return (
|
|
57
65
|
<div className="view-page">
|
|
@@ -104,15 +112,26 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
104
112
|
<div className="set-section">
|
|
105
113
|
<div className="settings-section-head">
|
|
106
114
|
<h3>{t('settings.genParams')}</h3>
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
<div className="settings-gen-actions">
|
|
116
|
+
<button
|
|
117
|
+
className="btn btn-sm settings-gen-lock"
|
|
118
|
+
title={t(generationParamsLocked ? 'settings.genParamsUnlock' : 'settings.genParamsLock')}
|
|
119
|
+
aria-label={t(generationParamsLocked ? 'settings.genParamsUnlock' : 'settings.genParamsLock')}
|
|
120
|
+
aria-pressed={generationParamsLocked}
|
|
121
|
+
onClick={toggleGenerationParamsLock}
|
|
122
|
+
>
|
|
123
|
+
<Icon name={generationParamsLocked ? 'lock' : 'unlock'} size={12} /> {t(generationParamsLocked ? 'settings.genParamsLocked' : 'settings.genParamsUnlocked')}
|
|
124
|
+
</button>
|
|
125
|
+
<button
|
|
126
|
+
className="btn btn-sm settings-gen-reset"
|
|
127
|
+
title={t('settings.genParamsResetTitle')}
|
|
128
|
+
aria-label={t('settings.genParamsResetTitle')}
|
|
129
|
+
disabled={generationParamsLocked || isGenerationDefault}
|
|
130
|
+
onClick={() => void resetGenerationParams()}
|
|
131
|
+
>
|
|
132
|
+
<Icon name="refresh" size={12} /> {t('settings.genParamsReset')}
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
116
135
|
</div>
|
|
117
136
|
<table className="table param-table">
|
|
118
137
|
<tbody>
|
|
@@ -178,6 +197,7 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
178
197
|
max="100"
|
|
179
198
|
step="1"
|
|
180
199
|
value={settings.contextCompressionThreshold === 'off' ? 0 : settings.contextCompressionThreshold}
|
|
200
|
+
disabled={generationParamsLocked}
|
|
181
201
|
onChange={(e) => setSettings({ contextCompressionThreshold: Number(e.target.value) || 'off' })}
|
|
182
202
|
/>
|
|
183
203
|
<span className="range-val">
|
|
@@ -195,6 +215,7 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
195
215
|
<select
|
|
196
216
|
className="select"
|
|
197
217
|
value={settings.reasoningEffort}
|
|
218
|
+
disabled={generationParamsLocked}
|
|
198
219
|
onChange={(e) => setSettings({ reasoningEffort: e.target.value as ReasoningEffort })}
|
|
199
220
|
>
|
|
200
221
|
{['auto', 'off', 'low', 'medium', 'xhigh'].map((r) => <option key={r} value={r}>{r}</option>)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState, type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { QRCodeSVG } from 'qrcode.react';
|
|
4
4
|
import { useStore } from '../store/store';
|
|
@@ -19,6 +19,23 @@ const SHAREHOLDER_CLICK_COUNT = 5;
|
|
|
19
19
|
const SHAREHOLDER_CLICK_WINDOW_MS = 5_000;
|
|
20
20
|
const SIDEBAR_MAX = 520;
|
|
21
21
|
|
|
22
|
+
function FadeOverflowText({ className, children, ...props }: ComponentPropsWithoutRef<'span'> & { children: ReactNode }) {
|
|
23
|
+
const ref = useRef<HTMLSpanElement>(null);
|
|
24
|
+
const [overflowing, setOverflowing] = useState(false);
|
|
25
|
+
|
|
26
|
+
useLayoutEffect(() => {
|
|
27
|
+
const element = ref.current;
|
|
28
|
+
if (!element) return;
|
|
29
|
+
const updateOverflow = () => setOverflowing(element.scrollWidth > element.clientWidth);
|
|
30
|
+
updateOverflow();
|
|
31
|
+
const observer = new ResizeObserver(updateOverflow);
|
|
32
|
+
observer.observe(element);
|
|
33
|
+
return () => observer.disconnect();
|
|
34
|
+
}, [children]);
|
|
35
|
+
|
|
36
|
+
return <span ref={ref} className={`${className ?? ''}${overflowing ? ' has-overflow' : ''}`} {...props}>{children}</span>;
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
/** 触发 App 级全局游戏导出弹窗(会话右键菜单共用) */
|
|
23
40
|
function openGlobalGameplayExport(sessionId: number, includeHistory: boolean) {
|
|
24
41
|
window.dispatchEvent(
|
|
@@ -409,10 +426,10 @@ export function Sidebar() {
|
|
|
409
426
|
{!!(s as { pinned?: unknown }).pinned && (
|
|
410
427
|
<span className="spinned" title={t('nav.pinSession')}><Icon name="pin" size={11} /></span>
|
|
411
428
|
)}
|
|
412
|
-
<
|
|
429
|
+
<FadeOverflowText className="stitle-text">{s.title}</FadeOverflowText>
|
|
413
430
|
{hasActivity && <span className="session-activity-dot" aria-label={t('nav.sessionActive')} />}
|
|
414
431
|
</div>
|
|
415
|
-
<
|
|
432
|
+
<FadeOverflowText className="sprev">{s.lastMessage || t('nav.newConversation')}</FadeOverflowText>
|
|
416
433
|
</div>
|
|
417
434
|
</button>
|
|
418
435
|
);
|
|
@@ -409,6 +409,8 @@ const ICONS: Record<string, React.ReactNode> = {
|
|
|
409
409
|
upload: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><path d="m17 8-5-5-5 5M12 3v12" /></>,
|
|
410
410
|
import: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><path d="M17 14l-5 5-5-5M12 19V7" /></>,
|
|
411
411
|
pin: <><path d="M9 4h6M12 4v3l4 3v3h-8v-3l4-3" /><path d="M12 13v7" /></>,
|
|
412
|
+
lock: <><rect x="5" y="10" width="14" height="11" rx="2" /><path d="M8 10V7a4 4 0 0 1 8 0v3" /></>,
|
|
413
|
+
unlock: <><rect x="5" y="10" width="14" height="11" rx="2" /><path d="M8 10V7a4 4 0 0 1 7.4-1.9" /></>,
|
|
412
414
|
'git-branch': <><path d="M6 3v12" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></>,
|
|
413
415
|
'zoom-in': <><circle cx="11" cy="11" r="7" /><path d="m21 21-4.35-4.35M11 8v6M8 11h6" /></>,
|
|
414
416
|
'zoom-out': <><circle cx="11" cy="11" r="7" /><path d="m21 21-4.35-4.35M8 11h6" /></>,
|
package/src/core/i18n.ts
CHANGED
|
@@ -202,6 +202,10 @@ const MESSAGES: Record<LangKey, Record<string, string>> = {
|
|
|
202
202
|
'settings.yoloMode': 'YOLO 模式',
|
|
203
203
|
'settings.yoloModeDesc': '自动批准工具修改和 Shell 执行,无需逐次确认。仅在信任角色和技能时开启。',
|
|
204
204
|
'settings.genParams': '🎛 生成参数',
|
|
205
|
+
'settings.genParamsLocked': '已锁定',
|
|
206
|
+
'settings.genParamsUnlocked': '已解锁',
|
|
207
|
+
'settings.genParamsLock': '锁定生成参数',
|
|
208
|
+
'settings.genParamsUnlock': '解锁生成参数',
|
|
205
209
|
'settings.genParamsReset': '重置',
|
|
206
210
|
'settings.genParamsResetTitle': '将生成参数恢复为默认值',
|
|
207
211
|
'settings.tempDesc': '创造性与随机性',
|
|
@@ -725,6 +729,10 @@ const MESSAGES: Record<LangKey, Record<string, string>> = {
|
|
|
725
729
|
'settings.yoloMode': 'YOLO 模式',
|
|
726
730
|
'settings.yoloModeDesc': '自動批准工具修改和 Shell 執行,無需逐次確認。僅在信任角色和技能時開啟。',
|
|
727
731
|
'settings.genParams': '🎛 產生參數',
|
|
732
|
+
'settings.genParamsLocked': '已鎖定',
|
|
733
|
+
'settings.genParamsUnlocked': '已解鎖',
|
|
734
|
+
'settings.genParamsLock': '鎖定產生參數',
|
|
735
|
+
'settings.genParamsUnlock': '解鎖產生參數',
|
|
728
736
|
'settings.genParamsReset': '重設',
|
|
729
737
|
'settings.genParamsResetTitle': '將產生參數恢復為預設值',
|
|
730
738
|
'settings.tempDesc': '創造性與隨機性',
|
|
@@ -1239,6 +1247,10 @@ const MESSAGES: Record<LangKey, Record<string, string>> = {
|
|
|
1239
1247
|
'settings.yoloMode': 'YOLO mode',
|
|
1240
1248
|
'settings.yoloModeDesc': 'Automatically approve tool changes and Shell commands without asking each time. Only enable for trusted characters and skills.',
|
|
1241
1249
|
'settings.genParams': '🎛 Generation parameters',
|
|
1250
|
+
'settings.genParamsLocked': 'Locked',
|
|
1251
|
+
'settings.genParamsUnlocked': 'Unlocked',
|
|
1252
|
+
'settings.genParamsLock': 'Lock generation parameters',
|
|
1253
|
+
'settings.genParamsUnlock': 'Unlock generation parameters',
|
|
1242
1254
|
'settings.genParamsReset': 'Reset',
|
|
1243
1255
|
'settings.genParamsResetTitle': 'Restore generation parameters to their default values',
|
|
1244
1256
|
'settings.tempDesc': 'Creativity & randomness',
|
|
@@ -1,7 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
"latest": "0.1.
|
|
2
|
+
"latest": "0.1.22",
|
|
3
3
|
"repo": "https://github.com/Rannichan/Tavern-Harness",
|
|
4
4
|
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.1.22",
|
|
7
|
+
"date": "2026-10-26",
|
|
8
|
+
"breaking": false,
|
|
9
|
+
"compare": "v0.1.21...v0.1.22",
|
|
10
|
+
"groups": {
|
|
11
|
+
"zh-CN": [
|
|
12
|
+
{
|
|
13
|
+
"kind": "changed",
|
|
14
|
+
"items": [
|
|
15
|
+
"生成参数默认锁定:「设置 → 生成参数」里的滑杆和下拉框要先点「解锁」才能调整,离开设置页后会自动重新锁定,避免滑动页面时误改温度、上下文压缩等参数",
|
|
16
|
+
"「重置」按钮在锁定状态下不可点击,解锁后才能恢复默认值",
|
|
17
|
+
"会话列表的标题和副标题只在内容确实溢出时才做末尾渐隐,短标题不再被淡边影响",
|
|
18
|
+
"移动端顶部导航按钮之间的间距略微加大,点击时更不容易误触相邻按钮"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"en": [
|
|
23
|
+
{
|
|
24
|
+
"kind": "changed",
|
|
25
|
+
"items": [
|
|
26
|
+
"Generation parameters are now locked by default: the sliders and dropdowns under Settings → Generation parameters stay read-only until you tap \"Unlock\", and they lock again when you leave the settings page, so scrolling can no longer change temperature, context compression, and other values by accident",
|
|
27
|
+
"The \"Reset\" button is disabled while parameters are locked and becomes available after unlocking",
|
|
28
|
+
"Session list titles and subtitles only fade out at the end when the text actually overflows, so short titles are no longer dimmed by the mask",
|
|
29
|
+
"Mobile top-bar navigation buttons have a little more spacing, reducing accidental taps on neighbours"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
5
35
|
{
|
|
6
36
|
"version": "0.1.21",
|
|
7
37
|
"date": "2026-10-26",
|
package/src/theme/global.css
CHANGED
|
@@ -575,6 +575,9 @@ body.resizing .sidebar-resizer {
|
|
|
575
575
|
overflow: hidden;
|
|
576
576
|
white-space: nowrap;
|
|
577
577
|
min-width: 0;
|
|
578
|
+
}
|
|
579
|
+
.session-item .stitle-text.has-overflow,
|
|
580
|
+
.session-item .sprev.has-overflow {
|
|
578
581
|
-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent);
|
|
579
582
|
mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent);
|
|
580
583
|
}
|
|
@@ -612,12 +615,11 @@ body.resizing .sidebar-resizer {
|
|
|
612
615
|
}
|
|
613
616
|
}
|
|
614
617
|
.session-item .sprev {
|
|
618
|
+
display: block;
|
|
615
619
|
font-size: 11.5px;
|
|
616
620
|
color: var(--text-faint);
|
|
617
621
|
white-space: nowrap;
|
|
618
622
|
overflow: hidden;
|
|
619
|
-
-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent);
|
|
620
|
-
mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent);
|
|
621
623
|
}
|
|
622
624
|
/* ---------- 会话区域(头部 + 滚动列表) ---------- */
|
|
623
625
|
.session-section {
|
|
@@ -1680,7 +1682,7 @@ hr.sep {
|
|
|
1680
1682
|
display: flex;
|
|
1681
1683
|
flex: 1;
|
|
1682
1684
|
min-width: 0;
|
|
1683
|
-
gap:
|
|
1685
|
+
gap: 8px;
|
|
1684
1686
|
margin: -12px -10px;
|
|
1685
1687
|
padding: 12px 10px;
|
|
1686
1688
|
overflow-x: auto;
|
package/src/theme/views.css
CHANGED
|
@@ -625,7 +625,13 @@
|
|
|
625
625
|
justify-content: space-between;
|
|
626
626
|
gap: 12px;
|
|
627
627
|
}
|
|
628
|
-
.settings-gen-
|
|
628
|
+
.settings-gen-actions {
|
|
629
|
+
display: flex;
|
|
630
|
+
flex-shrink: 0;
|
|
631
|
+
gap: 6px;
|
|
632
|
+
}
|
|
633
|
+
.settings-gen-reset,
|
|
634
|
+
.settings-gen-lock {
|
|
629
635
|
flex-shrink: 0;
|
|
630
636
|
}
|
|
631
637
|
.settings-reset-section {
|