"Search or ask AI" now has a door for the asking half

The field said search OR ask; only searching worked. Full-text matches TERMS, and
a question is mostly terms that appear in no page — measured against production:
"api key", "quickstart" and "kms" all return results, "how do I get an api key"
returns none. The reader most in need of an answer got the emptiest screen, under
a label that had promised otherwise.

Ask AI was removed from this app deliberately — its floating button landed in the
same corner as the Hanzo Edit launcher and the two overlapped — and the label was
never taken down with it.

This is not that button coming back, and not a chat surface bolted into a search
dialog. It is the door this product already gives everywhere else: hand the
question to the reader's own agent, carrying the setup that points it at Hanzo's
models and the skills at hanzoskills.com. Same prompt as the Use Agent control,
imported rather than restated, so there is one thing to keep true.

It sits LAST in the list, because a question is the fallback when the terms did
not land rather than a competitor to a page that did — and it appears from three
characters, so it is there before the reader has finished discovering that
nothing matched.
This commit is contained in:
2026-08-22 18:54:07 -07:00
parent 2d1fa7338b
commit 993ed6da44
+51 -2
View File
@@ -16,13 +16,15 @@ import {
import { useDocsSearch } from '@hanzo/docs/core/search/client';
import { useMemo, useState } from 'react';
import { Popover, PopoverContent, PopoverTrigger } from '@hanzo/docs-base-ui/components/ui/popover';
import { ArrowRight, ChevronDown } from 'lucide-react';
import { ArrowRight, Check, ChevronDown, Sparkle } from 'lucide-react';
import { buttonVariants } from '@hanzo/docs-base-ui/components/ui/button';
import { cn } from '@/lib/cn';
import { useTreeContext } from '@hanzo/docs-base-ui/contexts/tree';
import type { Item, Node } from '@hanzo/docs/core/page-tree';
import { useRouter } from 'next/navigation';
import { AGENT_SETUP_PROMPT } from '@/lib/agent-setup-prompt';
const items = [
{
name: 'All',
@@ -75,6 +77,50 @@ export default function CustomSearchDialog(props: SharedProps) {
for (const item of full.children) onNode(item);
return map;
}, [full]);
// ASKING IS A DIFFERENT INTENTION FROM FINDING, and until now only one of them
// had a door. The field says "search or ask", full-text search matches TERMS,
// and a question is mostly terms that are not in any page — measured, "api
// key", "quickstart" and "kms" all return results while "how do I get an api
// key" returns none. So the reader most in need of an answer got the emptiest
// screen, under a label that had promised otherwise.
//
// The answer is not a chat surface bolted into this dialog. It is the one this
// product already gives everywhere else: hand the question to the reader's own
// agent, with the setup that points it at Hanzo's models and skills. Same
// prompt as the Use Agent control, so there is one thing to keep true.
const [asked, setAsked] = useState(false);
const askAction = useMemo<SearchItemType | undefined>(() => {
const q = search.trim();
if (q.length < 3) return;
return {
id: 'ask-agent',
type: 'action',
node: (
<div className="inline-flex items-center gap-2 text-fd-muted-foreground">
{asked ? <Check className="size-4" /> : <Sparkle className="size-4" />}
<p>
{asked ? (
'Copied — paste it into your agent'
) : (
<>
Ask an agent<span className="font-medium text-fd-foreground"> “{q}”</span>
</>
)}
</p>
</div>
),
onSelect: () => {
void navigator.clipboard
.writeText(
`${AGENT_SETUP_PROMPT}\n\n---\n\nThen answer this using the Hanzo documentation at ` +
`https://docs.hanzo.ai, and use the Hanzo skills at https://hanzoskills.com for any ` +
`capability it touches:\n\n${q}`,
)
.then(() => setAsked(true));
},
};
}, [asked, search]);
const pageTreeAction = useMemo<SearchItemType | undefined>(() => {
if (search.length === 0) return;
@@ -109,10 +155,13 @@ export default function CustomSearchDialog(props: SharedProps) {
</SearchDialogHeader>
<SearchDialogList
items={
query.data !== 'empty' || pageTreeAction
query.data !== 'empty' || pageTreeAction || askAction
? [
...(pageTreeAction ? [pageTreeAction] : []),
...(Array.isArray(query.data) ? query.data : []),
// LAST, always. A question is the fallback when the terms did
// not land, not a competitor to a page that did.
...(askAction ? [askAction] : []),
]
: null
}