@liguoshuai/pi-web-chat 1.7.3 → 1.7.4
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/docs/ARCHITECTURE.md +1 -1
- package/docs/CHANGELOG.md +11 -0
- package/package.json +1 -1
- package/public/app.js +102 -8
- package/public/style.css +92 -10
package/docs/ARCHITECTURE.md
CHANGED
package/docs/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.7.4] - 2026-07-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **模型菜单实时搜索与过滤 (Model Search & Instant Filter)**:
|
|
14
|
+
- 顶栏模型下拉菜单增加置顶固定的搜索过滤输入框,支持根据 Provider 提供商名称、模型名称、模型 ID 进行多维度实时模糊过滤。
|
|
15
|
+
- 打开模型下拉框时自动聚焦并全选搜索框,支持一键清空(`×`)、按 `Escape` 快捷关闭、按 `Enter` 快捷选中。
|
|
16
|
+
- 优化模型列表项布局,同时展示友好显示名与底层 Model ID,解决模型列表过长时翻找困难的问题。
|
|
17
|
+
- 移动端与桌面端自适应滚动与粘性搜索栏布局。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
10
21
|
## [1.7.3] - 2026-07-26
|
|
11
22
|
|
|
12
23
|
### Fixed
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -1439,29 +1439,112 @@ function renderModelPill() {
|
|
|
1439
1439
|
pill.title = `当前模型: ${provider} / ${name} (${m.id})`;
|
|
1440
1440
|
}
|
|
1441
1441
|
|
|
1442
|
+
let modelSearchQuery = "";
|
|
1443
|
+
|
|
1442
1444
|
function renderModelMenu() {
|
|
1443
1445
|
const menu = $("#modelMenu");
|
|
1444
|
-
menu
|
|
1445
|
-
|
|
1446
|
+
if (!menu) return;
|
|
1447
|
+
|
|
1448
|
+
let searchInput = $("#modelSearchInput", menu);
|
|
1449
|
+
let listContainer = $(".model-menu-list", menu);
|
|
1450
|
+
|
|
1451
|
+
if (!searchInput || !listContainer) {
|
|
1452
|
+
menu.innerHTML = "";
|
|
1453
|
+
const searchWrap = el("div", { class: "model-search-wrap" });
|
|
1454
|
+
searchInput = el("input", {
|
|
1455
|
+
type: "text",
|
|
1456
|
+
id: "modelSearchInput",
|
|
1457
|
+
class: "model-search-input",
|
|
1458
|
+
placeholder: "搜索模型 (如 claude, deepseek, 4o)…",
|
|
1459
|
+
autocomplete: "off",
|
|
1460
|
+
spellcheck: "false",
|
|
1461
|
+
value: modelSearchQuery,
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
searchInput.addEventListener("click", (e) => e.stopPropagation());
|
|
1465
|
+
searchInput.addEventListener("input", (e) => {
|
|
1466
|
+
modelSearchQuery = e.target.value;
|
|
1467
|
+
renderModelList(listContainer);
|
|
1468
|
+
const clearBtn = $(".btn-clear-model-search", searchWrap);
|
|
1469
|
+
if (clearBtn) clearBtn.style.display = modelSearchQuery ? "block" : "none";
|
|
1470
|
+
});
|
|
1471
|
+
searchInput.addEventListener("keydown", (e) => {
|
|
1472
|
+
if (e.key === "Escape") {
|
|
1473
|
+
menu.classList.remove("open");
|
|
1474
|
+
} else if (e.key === "Enter") {
|
|
1475
|
+
const visibleOpts = listContainer.querySelectorAll(".opt");
|
|
1476
|
+
if (visibleOpts.length > 0) {
|
|
1477
|
+
visibleOpts[0].click();
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
const clearBtn = el("button", {
|
|
1483
|
+
class: "btn-clear-model-search",
|
|
1484
|
+
text: "×",
|
|
1485
|
+
type: "button",
|
|
1486
|
+
title: "清空搜索",
|
|
1487
|
+
style: `display: ${modelSearchQuery ? "block" : "none"}`,
|
|
1488
|
+
onclick: (e) => {
|
|
1489
|
+
e.stopPropagation();
|
|
1490
|
+
modelSearchQuery = "";
|
|
1491
|
+
searchInput.value = "";
|
|
1492
|
+
clearBtn.style.display = "none";
|
|
1493
|
+
searchInput.focus();
|
|
1494
|
+
renderModelList(listContainer);
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
searchWrap.appendChild(searchInput);
|
|
1499
|
+
searchWrap.appendChild(clearBtn);
|
|
1500
|
+
menu.appendChild(searchWrap);
|
|
1501
|
+
|
|
1502
|
+
listContainer = el("div", { class: "model-menu-list" });
|
|
1503
|
+
menu.appendChild(listContainer);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
renderModelList(listContainer);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function renderModelList(listContainer) {
|
|
1510
|
+
if (!listContainer) return;
|
|
1511
|
+
listContainer.innerHTML = "";
|
|
1512
|
+
const q = modelSearchQuery.trim().toLowerCase();
|
|
1513
|
+
|
|
1514
|
+
const filteredModels = state.models.filter(m => {
|
|
1515
|
+
if (!q) return true;
|
|
1516
|
+
const idMatch = (m.id || "").toLowerCase().includes(q);
|
|
1517
|
+
const nameMatch = (m.name || "").toLowerCase().includes(q);
|
|
1518
|
+
const providerMatch = (m.provider || "").toLowerCase().includes(q);
|
|
1519
|
+
return idMatch || nameMatch || providerMatch;
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
if (filteredModels.length === 0) {
|
|
1523
|
+
listContainer.appendChild(el("div", { class: "model-empty", text: "未找到匹配的模型" }));
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1446
1527
|
const groups = {};
|
|
1447
|
-
for (const m of
|
|
1528
|
+
for (const m of filteredModels) {
|
|
1448
1529
|
const p = m.provider || "other";
|
|
1449
1530
|
(groups[p] = groups[p] || []).push(m);
|
|
1450
1531
|
}
|
|
1532
|
+
|
|
1451
1533
|
for (const [provider, items] of Object.entries(groups).sort()) {
|
|
1452
|
-
|
|
1534
|
+
listContainer.appendChild(el("div", { class: "group-label", text: provider }));
|
|
1453
1535
|
for (const m of items) {
|
|
1454
1536
|
const active = state.currentModel && m.id === state.currentModel.id && m.provider === state.currentModel.provider;
|
|
1455
|
-
|
|
1537
|
+
listContainer.appendChild(el("div", {
|
|
1456
1538
|
class: "opt" + (active ? " active" : ""),
|
|
1457
1539
|
onclick: () => {
|
|
1458
1540
|
$("#modelPill").textContent = "切换中…";
|
|
1459
1541
|
sendWs({ type: "set_model", provider: m.provider, modelId: m.id });
|
|
1460
|
-
|
|
1542
|
+
$("#modelMenu").classList.remove("open");
|
|
1461
1543
|
},
|
|
1462
1544
|
}, [
|
|
1463
1545
|
el("span", { class: "check", html: active ? "✓ " : "" }),
|
|
1464
|
-
|
|
1546
|
+
el("span", { class: "model-name", text: `${m.name || m.id}` }),
|
|
1547
|
+
(m.name && m.id && m.name !== m.id) ? el("span", { class: "model-id-sub", text: m.id }) : null,
|
|
1465
1548
|
]));
|
|
1466
1549
|
}
|
|
1467
1550
|
}
|
|
@@ -1649,7 +1732,18 @@ function init() {
|
|
|
1649
1732
|
$("#modelPill").addEventListener("click", (e) => {
|
|
1650
1733
|
e.stopPropagation();
|
|
1651
1734
|
sendWs({ type: "get_available_models" });
|
|
1652
|
-
$("#modelMenu")
|
|
1735
|
+
const menu = $("#modelMenu");
|
|
1736
|
+
const willOpen = !menu.classList.contains("open");
|
|
1737
|
+
menu.classList.toggle("open");
|
|
1738
|
+
if (willOpen) {
|
|
1739
|
+
setTimeout(() => {
|
|
1740
|
+
const input = $("#modelSearchInput");
|
|
1741
|
+
if (input) {
|
|
1742
|
+
input.focus();
|
|
1743
|
+
input.select();
|
|
1744
|
+
}
|
|
1745
|
+
}, 50);
|
|
1746
|
+
}
|
|
1653
1747
|
});
|
|
1654
1748
|
document.addEventListener("click", (e) => {
|
|
1655
1749
|
if (!e.target.closest("#modelMenu") && !e.target.closest("#modelPill")) {
|
package/public/style.css
CHANGED
|
@@ -798,18 +798,97 @@ body {
|
|
|
798
798
|
/* Model selector dropdown */
|
|
799
799
|
.model-menu {
|
|
800
800
|
position: absolute; top: 52px; right: 16px;
|
|
801
|
-
background: var(--bg-
|
|
802
|
-
border-radius:
|
|
803
|
-
box-shadow: 0
|
|
801
|
+
background: var(--bg-sidebar); border: 1px solid var(--border);
|
|
802
|
+
border-radius: 14px; padding: 0; width: 340px; max-height: 440px;
|
|
803
|
+
box-shadow: 0 12px 32px rgba(0,0,0,.5); z-index: 200; display: none;
|
|
804
|
+
flex-direction: column; overflow: hidden;
|
|
804
805
|
}
|
|
805
|
-
.model-menu.open { display:
|
|
806
|
-
|
|
807
|
-
.model-
|
|
808
|
-
padding: 8px 10px;
|
|
806
|
+
.model-menu.open { display: flex; }
|
|
807
|
+
|
|
808
|
+
.model-search-wrap {
|
|
809
|
+
padding: 8px 10px;
|
|
810
|
+
border-bottom: 1px solid var(--border);
|
|
811
|
+
background: var(--bg-sidebar);
|
|
812
|
+
position: relative;
|
|
813
|
+
display: flex;
|
|
814
|
+
align-items: center;
|
|
815
|
+
flex-shrink: 0;
|
|
816
|
+
}
|
|
817
|
+
.model-search-input {
|
|
818
|
+
width: 100%;
|
|
819
|
+
background: var(--bg-input);
|
|
820
|
+
border: 1px solid var(--border);
|
|
821
|
+
border-radius: 8px;
|
|
822
|
+
padding: 7px 12px;
|
|
823
|
+
padding-right: 28px;
|
|
824
|
+
color: var(--text);
|
|
825
|
+
font-size: 13px;
|
|
826
|
+
outline: none;
|
|
827
|
+
transition: border-color 0.15s ease;
|
|
828
|
+
}
|
|
829
|
+
.model-search-input:focus {
|
|
830
|
+
border-color: var(--accent);
|
|
831
|
+
}
|
|
832
|
+
.btn-clear-model-search {
|
|
833
|
+
position: absolute;
|
|
834
|
+
right: 18px;
|
|
835
|
+
background: transparent;
|
|
836
|
+
border: none;
|
|
837
|
+
color: var(--text-muted);
|
|
838
|
+
font-size: 16px;
|
|
839
|
+
cursor: pointer;
|
|
840
|
+
line-height: 1;
|
|
841
|
+
padding: 2px;
|
|
842
|
+
}
|
|
843
|
+
.btn-clear-model-search:hover {
|
|
844
|
+
color: var(--text);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.model-menu-list {
|
|
848
|
+
flex: 1;
|
|
849
|
+
min-height: 0;
|
|
850
|
+
overflow-y: auto;
|
|
851
|
+
padding: 6px;
|
|
852
|
+
max-height: 360px;
|
|
853
|
+
}
|
|
854
|
+
.model-menu-list .group-label {
|
|
855
|
+
font-size: 11px;
|
|
856
|
+
color: var(--text-dim);
|
|
857
|
+
text-transform: uppercase;
|
|
858
|
+
padding: 6px 8px 3px;
|
|
859
|
+
font-weight: 600;
|
|
860
|
+
letter-spacing: 0.5px;
|
|
861
|
+
}
|
|
862
|
+
.model-menu-list .opt {
|
|
863
|
+
padding: 8px 10px;
|
|
864
|
+
border-radius: 8px;
|
|
865
|
+
cursor: pointer;
|
|
866
|
+
font-size: 13px;
|
|
867
|
+
color: var(--text);
|
|
868
|
+
display: flex;
|
|
869
|
+
align-items: center;
|
|
870
|
+
gap: 6px;
|
|
871
|
+
transition: background 0.12s;
|
|
872
|
+
}
|
|
873
|
+
.model-menu-list .opt:hover { background: var(--bg-hover); }
|
|
874
|
+
.model-menu-list .opt.active { background: var(--bg-hover); color: var(--accent); }
|
|
875
|
+
.model-menu-list .opt .check { color: var(--accent); width: 14px; flex-shrink: 0; font-size: 12px; }
|
|
876
|
+
.model-menu-list .opt .model-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
877
|
+
.model-menu-list .opt .model-id-sub {
|
|
878
|
+
font-size: 11px;
|
|
879
|
+
color: var(--text-dim);
|
|
880
|
+
font-family: "SF Mono", Menlo, Consolas, monospace;
|
|
881
|
+
max-width: 120px;
|
|
882
|
+
overflow: hidden;
|
|
883
|
+
text-overflow: ellipsis;
|
|
884
|
+
white-space: nowrap;
|
|
885
|
+
}
|
|
886
|
+
.model-empty {
|
|
887
|
+
padding: 24px 12px;
|
|
888
|
+
text-align: center;
|
|
889
|
+
font-size: 13px;
|
|
890
|
+
color: var(--text-dim);
|
|
809
891
|
}
|
|
810
|
-
.model-menu .opt:hover { background: var(--bg-hover); }
|
|
811
|
-
.model-menu .opt.active { background: var(--bg-hover); }
|
|
812
|
-
.model-menu .opt .check { color: var(--accent); margin-right: 6px; }
|
|
813
892
|
|
|
814
893
|
|
|
815
894
|
/* ==========================================================================
|
|
@@ -942,6 +1021,9 @@ body {
|
|
|
942
1021
|
max-height: calc(100dvh - 70px);
|
|
943
1022
|
border-radius: 12px;
|
|
944
1023
|
}
|
|
1024
|
+
.model-menu-list {
|
|
1025
|
+
max-height: calc(100dvh - 130px);
|
|
1026
|
+
}
|
|
945
1027
|
|
|
946
1028
|
.mobile-toolbar-fab {
|
|
947
1029
|
display: flex;
|