fix: handle special char search 500 & polish live badge (#144)

* fix(search): handle numeric query FTS errors regardless of table existence

Remove the `no such table: messages_fts` condition so numeric queries
fall back to LIKE search on any FTS failure (malformed MATCH, missing
table, etc.).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(search): handle special char queries, polish live badge UI

- Add hasUnsafeChars() to catch FTS5-breaking queries (¥, @, #, etc.)
  and fall back to LIKE search, preventing 500 errors
- Polish session live badge: smaller size, remove border/shadow,
  add pulsing dot indicator for a cleaner look
- Remove spinner drop-shadow glow effect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-23 11:18:56 +08:00
committed by GitHub
parent 5f40ae6258
commit 1f91b902da
3 changed files with 29 additions and 13 deletions
@@ -623,26 +623,35 @@ async function handleRenameConfirm() {
:deep(.session-item-active-spinner) {
animation: session-spin 1.1s linear infinite;
filter: drop-shadow(0 0 6px rgba(var(--accent-primary-rgb), 0.35));
}
:deep(.session-item-live-badge) {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
flex-shrink: 0;
padding: 0 8px;
min-height: 20px;
padding: 1px 7px;
border-radius: 999px;
font-size: 11px;
line-height: 20px;
font-weight: 700;
letter-spacing: 0.05em;
font-size: 10px;
line-height: 16px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: $accent-primary;
background: rgba(var(--accent-primary-rgb), 0.18);
border: 1px solid rgba(var(--accent-primary-rgb), 0.34);
box-shadow: 0 0 0 1px rgba(var(--accent-primary-rgb), 0.06), 0 0 10px rgba(var(--accent-primary-rgb), 0.14);
background: rgba(var(--accent-primary-rgb), 0.10);
}
:deep(.live-dot) {
width: 6px;
height: 6px;
border-radius: 50%;
background: $accent-primary;
animation: live-pulse 2s ease-in-out infinite;
}
@keyframes live-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.7); }
}
:deep(.session-item-pin) {
@@ -45,7 +45,10 @@ const { t } = useI18n()
</svg>
</span>
<span class="session-item-title">{{ session.title }}</span>
<span v-if="live" class="session-item-live-badge">{{ t('chat.liveMode') }}</span>
<span v-if="live" class="session-item-live-badge">
<span class="live-dot"></span>
<span>{{ t('chat.liveMode') }}</span>
</span>
</span>
<span class="session-item-meta">
<span v-if="session.model" class="session-item-model">{{ session.model }}</span>
+5 -1
View File
@@ -163,6 +163,10 @@ function isNumericQuery(text: string): boolean {
return /^\d+(?:\s+\d+)*$/.test(text.trim())
}
function hasUnsafeChars(text: string): boolean {
return /[^\w\s\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(text)
}
function runLikeContentSearch(
db: { prepare: (sql: string) => { all: (...params: any[]) => Record<string, unknown>[] } },
source: string | undefined,
@@ -358,7 +362,7 @@ export async function searchSessionSummaries(
return [...merged.values()].slice(0, limit)
}
if (message.includes('no such table: messages_fts') && isNumericQuery(trimmed)) {
if (isNumericQuery(trimmed) || hasUnsafeChars(trimmed)) {
const likeRows = runLikeContentSearch(db, source, trimmed)
const merged = new Map<string, HermesSessionSearchRow>()
for (const row of titleRows) {