mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 10:55:57 +00:00
AI: improve defaults
This commit is contained in:
@@ -15,18 +15,19 @@ import { Loader2, MessageCircleIcon, RefreshCw, Send, X } from 'lucide-react';
|
||||
import { cn } from '@/lib/cn';
|
||||
import { buttonVariants } from '@hanzo/docs-base-ui/components/ui/button';
|
||||
import Link from '@hanzo/docs-core/link';
|
||||
import { type UIMessage, useChat, type UseChatHelpers } from '@ai-sdk/react';
|
||||
import { useChat, type UseChatHelpers } from '@ai-sdk/react';
|
||||
import type { ProvideLinksToolSchema } from '@/lib/chat/inkeep-qa-schema';
|
||||
import type { z } from 'zod';
|
||||
import { DefaultChatTransport } from 'ai';
|
||||
import { chatEndpoint, publishableKey } from '@/lib/hanzo/client';
|
||||
import { Markdown } from './markdown';
|
||||
import { Presence } from '@radix-ui/react-presence';
|
||||
import type { InkeepUIMessage } from '@/lib/inkeep/server';
|
||||
|
||||
const Context = createContext<{
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
chat: UseChatHelpers<UIMessage>;
|
||||
chat: UseChatHelpers<InkeepUIMessage>;
|
||||
} | null>(null);
|
||||
|
||||
export function AISearchPanelHeader({ className, ...props }: ComponentProps<'div'>) {
|
||||
@@ -116,11 +117,27 @@ export function AISearchInput(props: ComponentProps<'form'>) {
|
||||
const isLoading = status === 'streaming' || status === 'submitted';
|
||||
const onStart = (e?: SyntheticEvent) => {
|
||||
e?.preventDefault();
|
||||
void sendMessage({ text: input });
|
||||
setInput('');
|
||||
};
|
||||
const message = input.trim();
|
||||
if (message.length === 0) return;
|
||||
|
||||
localStorage.setItem(StorageKeyInput, input);
|
||||
void sendMessage({
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
type: 'data-client',
|
||||
data: {
|
||||
location: location.href,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: message,
|
||||
},
|
||||
],
|
||||
});
|
||||
setInput('');
|
||||
localStorage.removeItem(StorageKeyInput);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) document.getElementById('nd-ai-input')?.focus();
|
||||
@@ -136,6 +153,7 @@ export function AISearchInput(props: ComponentProps<'form'>) {
|
||||
disabled={status === 'streaming' || status === 'submitted'}
|
||||
onChange={(e) => {
|
||||
setInput(e.target.value);
|
||||
localStorage.setItem(StorageKeyInput, e.target.value);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (!event.shiftKey && event.key === 'Enter') {
|
||||
@@ -243,7 +261,7 @@ const roleName: Record<string, string> = {
|
||||
assistant: 'hanzo-bot',
|
||||
};
|
||||
|
||||
function Message({ message, ...props }: { message: UIMessage } & ComponentProps<'div'>) {
|
||||
function Message({ message, ...props }: { message: InkeepUIMessage } & ComponentProps<'div'>) {
|
||||
let markdown = '';
|
||||
let links: z.infer<typeof ProvideLinksToolSchema>['links'] = [];
|
||||
|
||||
@@ -297,7 +315,7 @@ const systemPrompt =
|
||||
|
||||
export function AISearch({ children }: { children: ReactNode }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const chat = useChat({
|
||||
const chat = useChat<InkeepUIMessage>({
|
||||
id: 'search',
|
||||
initialMessages: [{ id: 'system', role: 'system' as const, content: systemPrompt, parts: [] }],
|
||||
transport: new DefaultChatTransport({
|
||||
@@ -323,7 +341,7 @@ export function AISearchTrigger({
|
||||
data-state={open ? 'open' : 'closed'}
|
||||
className={cn(
|
||||
position === 'float' && [
|
||||
'fixed bottom-4 gap-3 w-24 end-[calc(--spacing(4)+var(--removed-body-scroll-bar-size,0px))] shadow-lg z-20 transition-[translate,opacity]',
|
||||
'fixed bottom-4 gap-3 w-24 inset-e-[calc(--spacing(4)+var(--removed-body-scroll-bar-size,0px))] shadow-lg z-20 transition-[translate,opacity]',
|
||||
open && 'translate-y-10 opacity-0',
|
||||
],
|
||||
className,
|
||||
|
||||
@@ -14,16 +14,16 @@ import {
|
||||
import { Loader2, MessageCircleIcon, RefreshCw, SearchIcon, Send, X } from 'lucide-react';
|
||||
import { cn } from '@/lib/cn';
|
||||
import { buttonVariants } from '@hanzo/docs-ui/components/ui/button';
|
||||
import { type UIMessage, useChat, type UseChatHelpers } from '@ai-sdk/react';
|
||||
import { useChat, type UseChatHelpers } from '@ai-sdk/react';
|
||||
import { DefaultChatTransport, type Tool, type UIToolInvocation } from 'ai';
|
||||
import { Markdown } from '../markdown';
|
||||
import { Presence } from '@radix-ui/react-presence';
|
||||
import type { SearchTool } from '@/lib/openrouter/server';
|
||||
import type { ChatUIMessage, SearchTool } from '@/lib/openrouter/server';
|
||||
|
||||
const Context = createContext<{
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
chat: UseChatHelpers<UIMessage>;
|
||||
chat: UseChatHelpers<ChatUIMessage>;
|
||||
} | null>(null);
|
||||
|
||||
export function AISearchPanelHeader({ className, ...props }: ComponentProps<'div'>) {
|
||||
@@ -110,7 +110,24 @@ export function AISearchInput(props: ComponentProps<'form'>) {
|
||||
const isLoading = status === 'streaming' || status === 'submitted';
|
||||
const onStart = (e?: SyntheticEvent) => {
|
||||
e?.preventDefault();
|
||||
void sendMessage({ text: input });
|
||||
const message = input.trim();
|
||||
if (message.length === 0) return;
|
||||
|
||||
void sendMessage({
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
type: 'data-client',
|
||||
data: {
|
||||
location: location.href,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: message,
|
||||
},
|
||||
],
|
||||
});
|
||||
setInput('');
|
||||
localStorage.removeItem(StorageKeyInput);
|
||||
};
|
||||
@@ -237,7 +254,7 @@ const roleName: Record<string, string> = {
|
||||
assistant: 'fumadocs',
|
||||
};
|
||||
|
||||
function Message({ message, ...props }: { message: UIMessage } & ComponentProps<'div'>) {
|
||||
function Message({ message, ...props }: { message: ChatUIMessage } & ComponentProps<'div'>) {
|
||||
let markdown = '';
|
||||
const searchCalls: UIToolInvocation<SearchTool>[] = [];
|
||||
|
||||
@@ -291,7 +308,7 @@ function Message({ message, ...props }: { message: UIMessage } & ComponentProps<
|
||||
|
||||
export function AISearch({ children }: { children: ReactNode }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const chat = useChat({
|
||||
const chat = useChat<ChatUIMessage>({
|
||||
id: 'search',
|
||||
transport: new DefaultChatTransport({
|
||||
api: '/api/chat',
|
||||
@@ -315,7 +332,7 @@ export function AISearchTrigger({
|
||||
data-state={open ? 'open' : 'closed'}
|
||||
className={cn(
|
||||
position === 'float' && [
|
||||
'fixed bottom-4 gap-3 w-24 end-[calc(--spacing(4)+var(--removed-body-scroll-bar-size,0px))] shadow-lg z-20 transition-[translate,opacity]',
|
||||
'fixed bottom-4 gap-3 w-24 inset-e-[calc(--spacing(4)+var(--removed-body-scroll-bar-size,0px))] shadow-lg z-20 transition-[translate,opacity]',
|
||||
open && 'translate-y-10 opacity-0',
|
||||
],
|
||||
className,
|
||||
|
||||
@@ -11,6 +11,15 @@ interface CustomDocument extends DocumentData {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type ChatUIMessage = UIMessage<
|
||||
never,
|
||||
{
|
||||
client: {
|
||||
location: string;
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
const searchServer = createSearchServer();
|
||||
|
||||
async function createSearchServer() {
|
||||
@@ -55,6 +64,7 @@ const openrouter = createOpenRouter({
|
||||
apiKey: process.env.OPENROUTER_API_KEY,
|
||||
});
|
||||
|
||||
/** System prompt, you can update it to provide more specific information */
|
||||
const systemPrompt = [
|
||||
'You are an AI assistant for a documentation site.',
|
||||
'Use the `search` tool to retrieve relevant docs context before answering when needed.',
|
||||
@@ -63,7 +73,7 @@ const systemPrompt = [
|
||||
].join('\n');
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const reqJson: { messages?: UIMessage[] } = await req.json();
|
||||
const reqJson = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: openrouter.chat(process.env.OPENROUTER_MODEL ?? 'anthropic/claude-3.5-sonnet'),
|
||||
@@ -73,7 +83,15 @@ export async function POST(req: Request) {
|
||||
},
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
...(await convertToModelMessages(reqJson.messages ?? [])),
|
||||
...(await convertToModelMessages<ChatUIMessage>(reqJson.messages ?? [], {
|
||||
convertDataPart(part) {
|
||||
if (part.type === 'data-client')
|
||||
return {
|
||||
type: 'text',
|
||||
text: `[Client Context: ${JSON.stringify(part.data)}]`,
|
||||
};
|
||||
},
|
||||
})),
|
||||
],
|
||||
toolChoice: 'auto',
|
||||
});
|
||||
@@ -81,6 +99,8 @@ export async function POST(req: Request) {
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
|
||||
export type SearchTool = typeof searchTool;
|
||||
|
||||
const searchTool = tool({
|
||||
description: 'Search the docs content and return raw JSON results.',
|
||||
inputSchema: z.object({
|
||||
@@ -92,5 +112,3 @@ const searchTool = tool({
|
||||
return await search.searchAsync(query, { limit, merge: true, enrich: true });
|
||||
},
|
||||
});
|
||||
|
||||
export type SearchTool = typeof searchTool;
|
||||
|
||||
Reference in New Issue
Block a user