feat(chat): slash command palette, action button containers, app detection fix
- Slash commands (/code, /nostr, /design, /search) now appear in the prompt palette with descriptions, auto-send on select - Chat message hover actions wrapped in a proper glass container with backdrop blur, divider between actions and feedback thumbs - Palette has 8px side margins, no scroll limit - Fix app extraction: queries mentioning known app names (e.g. "start9") now surface the Apps tab even without explicit app/nostr query patterns Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
84ccdc7048
commit
f346992924
@@ -199,10 +199,10 @@ const images = ref<ImageAttachment[]>([])
|
||||
|
||||
const paletteRef = ref<InstanceType<typeof PromptPalette> | null>(null)
|
||||
|
||||
// Prompt palette: opens when text starts with "/" (but not "/search " or "/code" etc.)
|
||||
// Prompt palette: opens when text starts with "/" and has no space yet
|
||||
const isPaletteMode = computed(() => {
|
||||
const t = text.value.trimStart()
|
||||
return t === '/' || (t.startsWith('/') && !t.includes(' ') && !t.startsWith('/search') && !t.startsWith('/code') && !t.startsWith('/nostr') && !t.startsWith('/exit'))
|
||||
return t === '/' || (t.startsWith('/') && !t.includes(' '))
|
||||
})
|
||||
|
||||
const paletteQuery = computed(() => {
|
||||
@@ -211,6 +211,20 @@ const paletteQuery = computed(() => {
|
||||
})
|
||||
|
||||
function handlePaletteSelect(templateText: string) {
|
||||
// Slash commands — send immediately (except /search which needs query input)
|
||||
if (templateText.startsWith('/') && !templateText.includes('{{')) {
|
||||
text.value = templateText
|
||||
if (templateText.trimEnd() === '/search') {
|
||||
// /search needs a query — set text and let user type
|
||||
nextTick(() => {
|
||||
autoResize()
|
||||
textareaRef.value?.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
nextTick(() => send())
|
||||
return
|
||||
}
|
||||
text.value = templateText
|
||||
nextTick(() => {
|
||||
autoResize()
|
||||
|
||||
@@ -9,59 +9,61 @@
|
||||
<!-- Action buttons (hover on desktop, always visible on touch) -->
|
||||
<div
|
||||
v-if="!isEditing"
|
||||
class="absolute -top-3 opacity-0 group-hover/msg:opacity-100 transition-opacity duration-200 z-10 flex gap-1"
|
||||
class="absolute -top-4 opacity-0 group-hover/msg:opacity-100 transition-opacity duration-200 z-10"
|
||||
:class="isUser ? 'right-1' : 'left-1'"
|
||||
>
|
||||
<button
|
||||
v-if="isUser"
|
||||
class="glass-button-sm !h-7 !min-h-0 !w-7 !min-w-0 !px-0 flex items-center justify-center text-white/60 hover:text-white/90"
|
||||
title="Edit message"
|
||||
@click.stop="startEditing"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" /></svg>
|
||||
</button>
|
||||
<button
|
||||
v-if="!isUser"
|
||||
class="glass-button-sm !h-7 !min-h-0 !w-7 !min-w-0 !px-0 flex items-center justify-center text-white/60 hover:text-white/90"
|
||||
title="Regenerate response"
|
||||
@click.stop="$emit('regenerate')"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd" /></svg>
|
||||
</button>
|
||||
<button
|
||||
class="glass-button-sm !h-7 !min-h-0 !w-7 !min-w-0 !px-0 flex items-center justify-center text-white/60 hover:text-white/90"
|
||||
title="Reply"
|
||||
@click.stop="$emit('reply', message.id, message.content)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M7.707 3.293a1 1 0 010 1.414L5.414 7H11a7 7 0 017 7v2a1 1 0 11-2 0v-2a5 5 0 00-5-5H5.414l2.293 2.293a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
|
||||
</button>
|
||||
<button
|
||||
v-if="!isUser"
|
||||
class="glass-button-sm !h-7 !min-h-0 !w-7 !min-w-0 !px-0 flex items-center justify-center text-white/60 hover:text-white/90"
|
||||
title="Branch from here"
|
||||
@click.stop="$emit('branch', message.id)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5z" clip-rule="evenodd" /></svg>
|
||||
</button>
|
||||
<!-- Feedback thumbs -->
|
||||
<button
|
||||
v-if="!isUser"
|
||||
class="glass-button-sm !h-7 !min-h-0 !w-7 !min-w-0 !px-0 flex items-center justify-center transition-colors"
|
||||
:class="message.feedback === 'up' ? 'text-green-400' : 'text-white/60 hover:text-green-400/80'"
|
||||
title="Good response"
|
||||
@click.stop="toggleFeedback('up')"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path d="M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.56 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z" /></svg>
|
||||
</button>
|
||||
<button
|
||||
v-if="!isUser"
|
||||
class="glass-button-sm !h-7 !min-h-0 !w-7 !min-w-0 !px-0 flex items-center justify-center transition-colors"
|
||||
:class="message.feedback === 'down' ? 'text-red-400' : 'text-white/60 hover:text-red-400/80'"
|
||||
title="Poor response"
|
||||
@click.stop="toggleFeedback('down')"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path d="M18 9.5a1.5 1.5 0 11-3 0v-6a1.5 1.5 0 013 0v6zM14 9.667v-5.43a2 2 0 00-1.106-1.79l-.05-.025A4 4 0 0011.057 2H5.64a2 2 0 00-1.962 1.608l-1.2 6A2 2 0 004.44 12H8v4a2 2 0 002 2 1 1 0 001-1v-.667a4 4 0 01.8-2.4l1.4-1.866a4 4 0 00.8-2.4z" /></svg>
|
||||
</button>
|
||||
<div class="flex items-center gap-0.5 px-1 py-0.5 rounded-lg bg-black/60 backdrop-blur-md border border-white/10 shadow-lg">
|
||||
<button
|
||||
v-if="isUser"
|
||||
class="w-7 h-7 flex items-center justify-center rounded-md text-white/50 hover:text-white/90 hover:bg-white/10 transition-colors"
|
||||
title="Edit message"
|
||||
@click.stop="startEditing"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" /></svg>
|
||||
</button>
|
||||
<button
|
||||
v-if="!isUser"
|
||||
class="w-7 h-7 flex items-center justify-center rounded-md text-white/50 hover:text-white/90 hover:bg-white/10 transition-colors"
|
||||
title="Regenerate response"
|
||||
@click.stop="$emit('regenerate')"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd" /></svg>
|
||||
</button>
|
||||
<button
|
||||
class="w-7 h-7 flex items-center justify-center rounded-md text-white/50 hover:text-white/90 hover:bg-white/10 transition-colors"
|
||||
title="Reply"
|
||||
@click.stop="$emit('reply', message.id, message.content)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M7.707 3.293a1 1 0 010 1.414L5.414 7H11a7 7 0 017 7v2a1 1 0 11-2 0v-2a5 5 0 00-5-5H5.414l2.293 2.293a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
|
||||
</button>
|
||||
<button
|
||||
v-if="!isUser"
|
||||
class="w-7 h-7 flex items-center justify-center rounded-md text-white/50 hover:text-white/90 hover:bg-white/10 transition-colors"
|
||||
title="Branch from here"
|
||||
@click.stop="$emit('branch', message.id)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5z" clip-rule="evenodd" /></svg>
|
||||
</button>
|
||||
<div v-if="!isUser" class="w-px h-4 bg-white/10 mx-0.5" />
|
||||
<button
|
||||
v-if="!isUser"
|
||||
class="w-7 h-7 flex items-center justify-center rounded-md transition-colors"
|
||||
:class="message.feedback === 'up' ? 'text-green-400' : 'text-white/50 hover:text-green-400/80 hover:bg-white/10'"
|
||||
title="Good response"
|
||||
@click.stop="toggleFeedback('up')"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path d="M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.56 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z" /></svg>
|
||||
</button>
|
||||
<button
|
||||
v-if="!isUser"
|
||||
class="w-7 h-7 flex items-center justify-center rounded-md transition-colors"
|
||||
:class="message.feedback === 'down' ? 'text-red-400' : 'text-white/50 hover:text-red-400/80 hover:bg-white/10'"
|
||||
title="Poor response"
|
||||
@click.stop="toggleFeedback('down')"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path d="M18 9.5a1.5 1.5 0 11-3 0v-6a1.5 1.5 0 013 0v6zM14 9.667v-5.43a2 2 0 00-1.106-1.79l-.05-.025A4 4 0 0011.057 2H5.64a2 2 0 00-1.962 1.608l-1.2 6A2 2 0 004.44 12H8v4a2 2 0 002 2 1 1 0 001-1v-.667a4 4 0 01.8-2.4l1.4-1.866a4 4 0 00.8-2.4z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -158,7 +158,7 @@ defineEmits<{
|
||||
|
||||
const chatStore = useChatStore()
|
||||
const { sendMessage, stopGeneration, editAndResend, regenerateLastResponse, activeModel } = useAI()
|
||||
const { updatePanelFromText, panelOpen, activeTab, availableTabs, setActiveTab } = useContentPanel()
|
||||
const { updatePanelFromText, panelOpen, activeTab, availableTabs, setActiveTab, enterDesignSystemMode } = useContentPanel()
|
||||
import { useCodeContext } from '@/composables/useCodeContext'
|
||||
const codeContext = useCodeContext()
|
||||
import { usePersonaStore } from '@/stores/personas'
|
||||
@@ -325,6 +325,16 @@ async function handleSend(text: string, images: ImageAttachment[] = []) {
|
||||
return
|
||||
}
|
||||
|
||||
if (trimmed === '/design') {
|
||||
enterDesignSystemMode()
|
||||
const convId = chatStore.activeConversationId
|
||||
if (convId) {
|
||||
chatStore.addMessage(convId, { role: 'user', content: '/design' })
|
||||
chatStore.addMessage(convId, { role: 'assistant', content: 'Design system viewer opened. Browse colors, typography, spacing, and components.' })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (trimmed === '/code exit' || trimmed === '/exit') {
|
||||
if (codeContext.isCodeMode.value) {
|
||||
codeContext.exitCodeMode()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="isOpen" class="absolute bottom-full left-0 right-0 mb-1 z-20">
|
||||
<div v-if="isOpen" class="absolute bottom-full left-2 right-2 mb-1 z-20">
|
||||
<!-- Variable fill form -->
|
||||
<div
|
||||
v-if="selectedTemplate && variables.length > 0"
|
||||
@@ -32,27 +32,52 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Template list -->
|
||||
<!-- Command + template list -->
|
||||
<div
|
||||
v-else
|
||||
class="glass-card max-h-[240px] overflow-y-auto scrollbar-hide animate-scale-in"
|
||||
class="glass-card animate-scale-in"
|
||||
>
|
||||
<div class="p-2 border-b border-white/5">
|
||||
<p class="text-[10px] text-white/30 px-2">Templates</p>
|
||||
<!-- Commands section -->
|
||||
<div v-if="filteredCommands.length > 0">
|
||||
<div class="p-2 border-b border-white/5">
|
||||
<p class="text-[10px] text-white/30 px-2">Commands</p>
|
||||
</div>
|
||||
<div
|
||||
v-for="(cmd, i) in filteredCommands"
|
||||
:key="cmd.id"
|
||||
class="px-3 py-2 cursor-pointer transition-colors"
|
||||
:class="i === highlightIndex ? 'bg-white/10' : 'hover:bg-white/5'"
|
||||
@click="selectCommand(cmd)"
|
||||
@mouseenter="highlightIndex = i"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-[11px] font-mono text-accent/70">{{ cmd.slash }}</span>
|
||||
<p class="text-sm text-white/80">{{ cmd.title }}</p>
|
||||
</div>
|
||||
<p v-if="cmd.preview" class="text-[11px] text-white/40 mt-0.5 truncate">{{ cmd.preview }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="(t, i) in filteredTemplates"
|
||||
:key="t.id"
|
||||
class="px-3 py-2 cursor-pointer transition-colors"
|
||||
:class="i === highlightIndex ? 'bg-white/10' : 'hover:bg-white/5'"
|
||||
@click="selectTemplate(t)"
|
||||
@mouseenter="highlightIndex = i"
|
||||
>
|
||||
<p class="text-sm text-white/80">{{ t.title }}</p>
|
||||
<p v-if="t.preview" class="text-[11px] text-white/40 mt-0.5 truncate">{{ t.preview }}</p>
|
||||
|
||||
<!-- Templates section -->
|
||||
<div v-if="filteredUserTemplates.length > 0">
|
||||
<div class="p-2 border-b border-white/5">
|
||||
<p class="text-[10px] text-white/30 px-2">Templates</p>
|
||||
</div>
|
||||
<div
|
||||
v-for="(t, i) in filteredUserTemplates"
|
||||
:key="t.id"
|
||||
class="px-3 py-2 cursor-pointer transition-colors"
|
||||
:class="(filteredCommands.length + i) === highlightIndex ? 'bg-white/10' : 'hover:bg-white/5'"
|
||||
@click="selectTemplate(t)"
|
||||
@mouseenter="highlightIndex = filteredCommands.length + i"
|
||||
>
|
||||
<p class="text-sm text-white/80">{{ t.title }}</p>
|
||||
<p v-if="t.preview" class="text-[11px] text-white/40 mt-0.5 truncate">{{ t.preview }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filteredTemplates.length === 0" class="px-3 py-4 text-center">
|
||||
<p class="text-xs text-white/30">No matching templates</p>
|
||||
|
||||
<div v-if="filteredCommands.length === 0 && filteredUserTemplates.length === 0" class="px-3 py-4 text-center">
|
||||
<p class="text-xs text-white/30">No matching commands</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,6 +87,20 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { usePromptTemplateStore, extractVariables, type PromptTemplate } from '@/stores/promptTemplates'
|
||||
|
||||
interface PaletteCommand {
|
||||
id: string
|
||||
slash: string
|
||||
title: string
|
||||
preview: string
|
||||
}
|
||||
|
||||
const BUILT_IN_COMMANDS: PaletteCommand[] = [
|
||||
{ id: 'cmd-code', slash: '/code', title: 'Code', preview: 'Open project browser and code editor' },
|
||||
{ id: 'cmd-nostr', slash: '/nostr', title: 'Nostr', preview: 'Browse the Nostr network feed' },
|
||||
{ id: 'cmd-design', slash: '/design', title: 'Design System', preview: 'Open the design system viewer' },
|
||||
{ id: 'cmd-search', slash: '/search ', title: 'Search', preview: 'Search your content library' },
|
||||
]
|
||||
|
||||
const props = defineProps<{
|
||||
query: string
|
||||
isOpen: boolean
|
||||
@@ -77,7 +116,17 @@ const highlightIndex = ref(0)
|
||||
const selectedTemplate = ref<PromptTemplate | null>(null)
|
||||
const variableValues = ref<Record<string, string>>({})
|
||||
|
||||
const filteredTemplates = computed(() => {
|
||||
const filteredCommands = computed(() => {
|
||||
const q = props.query.toLowerCase()
|
||||
if (!q) return BUILT_IN_COMMANDS
|
||||
return BUILT_IN_COMMANDS.filter(c =>
|
||||
c.slash.toLowerCase().includes('/' + q) ||
|
||||
c.title.toLowerCase().includes(q) ||
|
||||
c.preview.toLowerCase().includes(q)
|
||||
)
|
||||
})
|
||||
|
||||
const filteredUserTemplates = computed(() => {
|
||||
const q = props.query.toLowerCase()
|
||||
if (!q) return templateStore.sortedTemplates
|
||||
return templateStore.sortedTemplates.filter(t =>
|
||||
@@ -85,6 +134,8 @@ const filteredTemplates = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const allItemsCount = computed(() => filteredCommands.value.length + filteredUserTemplates.value.length)
|
||||
|
||||
const variables = computed(() => {
|
||||
if (!selectedTemplate.value) return []
|
||||
return extractVariables(selectedTemplate.value.content)
|
||||
@@ -103,10 +154,14 @@ watch(() => props.isOpen, (open) => {
|
||||
}
|
||||
})
|
||||
|
||||
function selectCommand(cmd: PaletteCommand) {
|
||||
// Commands like /code, /nostr send the slash text directly
|
||||
emit('select', cmd.slash)
|
||||
}
|
||||
|
||||
function selectTemplate(t: PromptTemplate) {
|
||||
const vars = extractVariables(t.content)
|
||||
if (vars.length === 0) {
|
||||
// No variables — insert directly
|
||||
emit('select', t.content)
|
||||
return
|
||||
}
|
||||
@@ -135,12 +190,17 @@ function navigateUp() {
|
||||
}
|
||||
|
||||
function navigateDown() {
|
||||
if (highlightIndex.value < filteredTemplates.value.length - 1) highlightIndex.value++
|
||||
if (highlightIndex.value < allItemsCount.value - 1) highlightIndex.value++
|
||||
}
|
||||
|
||||
function selectHighlighted() {
|
||||
const t = filteredTemplates.value[highlightIndex.value]
|
||||
if (t) selectTemplate(t)
|
||||
const idx = highlightIndex.value
|
||||
if (idx < filteredCommands.value.length) {
|
||||
selectCommand(filteredCommands.value[idx])
|
||||
} else {
|
||||
const t = filteredUserTemplates.value[idx - filteredCommands.value.length]
|
||||
if (t) selectTemplate(t)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -1218,8 +1218,15 @@ export function extractApps(text: string, userQuery: string): AppEntry[] {
|
||||
}
|
||||
}
|
||||
|
||||
// Surface apps if: app/nostr query with 1+, or 2+ apps detected in any context
|
||||
const isAppContext = isAppQuery(userQuery) || isNostrQuery(userQuery)
|
||||
// Check if query itself mentions a known app name
|
||||
const queryLower = userQuery.toLowerCase()
|
||||
const queryMatchesApp = APP_DATABASE.some(app =>
|
||||
[app.name.toLowerCase(), ...app.keywords.map(k => k.toLowerCase())]
|
||||
.some(kw => kw.length >= 3 && queryLower.includes(kw))
|
||||
)
|
||||
|
||||
// Surface apps if: app/nostr/known-app query with 1+, or 2+ apps detected in any context
|
||||
const isAppContext = isAppQuery(userQuery) || isNostrQuery(userQuery) || queryMatchesApp
|
||||
if (isAppContext && matched.length >= 1) return matched
|
||||
if (matched.length >= 2) return matched
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user