diff --git a/frontend/app/[locale]/(search)/producthunt/search-window-wapper.tsx b/frontend/app/[locale]/(search)/producthunt/search-window-wapper.tsx index aa4a90cc..70dc1489 100644 --- a/frontend/app/[locale]/(search)/producthunt/search-window-wapper.tsx +++ b/frontend/app/[locale]/(search)/producthunt/search-window-wapper.tsx @@ -2,7 +2,7 @@ import SearchBar from '@/components/search/search-bar'; import SearchWindow from '@/components/search/search-window'; -import { useSourceStore } from '@/lib/store'; +import { useSourceStore } from '@/lib/store/local-store'; import { SearchCategory, User } from '@/lib/types'; import React from 'react'; diff --git a/frontend/app/api/search/route.ts b/frontend/app/api/search/route.ts index 903ee277..ea955426 100644 --- a/frontend/app/api/search/route.ts +++ b/frontend/app/api/search/route.ts @@ -55,9 +55,24 @@ export async function POST(req: NextRequest) { { status: 429 }, ); } - let { model, source, messages, profile, isSearch } = await req.json(); + let { model, source, messages, profile, isSearch, questionLanguage, answerLanguage } = await req.json(); - console.log('model', model, 'source', source, 'messages', messages, 'userId', userId, 'isSearch', isSearch); + console.log( + 'model', + model, + 'source', + source, + 'messages', + messages, + 'userId', + userId, + 'isSearch', + isSearch, + 'questionLanguage', + questionLanguage, + 'answerLanguage', + answerLanguage, + ); if (isProModel(model) && !isPro) { return NextResponse.json( @@ -88,7 +103,7 @@ export async function POST(req: NextRequest) { break; } case SearchCategory.CHAT: { - await chat(messages, isPro, userId, profile, streamController(controller), model); + await chat(messages, isPro, userId, profile, streamController(controller), answerLanguage, model); break; } case SearchCategory.PRODUCT_HUNT: { @@ -104,7 +119,7 @@ export async function POST(req: NextRequest) { break; } default: { - await autoAnswer(messages, isPro, userId, profile, streamController(controller), model, source); + await autoAnswer(messages, isPro, userId, profile, streamController(controller), questionLanguage, answerLanguage, model, source); } } }, diff --git a/frontend/components/code/code-viewer.tsx b/frontend/components/code/code-viewer.tsx index b13a5bbd..3977d92d 100644 --- a/frontend/components/code/code-viewer.tsx +++ b/frontend/components/code/code-viewer.tsx @@ -7,7 +7,7 @@ import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/componen import { cn } from '@/lib/utils'; import { Tabs, TabsContent } from '@/components/ui/tabs'; import { useSigninModal } from '@/hooks/use-signin-modal'; -import { useUserStore } from '@/lib/store'; +import { useUserStore } from '@/lib/store/local-store'; import dynamic from 'next/dynamic'; const MonacoEditor = dynamic(() => import('@/components/code/editor'), { diff --git a/frontend/components/code/toolbar.tsx b/frontend/components/code/toolbar.tsx index 7cce68f0..8dc9b5fc 100644 --- a/frontend/components/code/toolbar.tsx +++ b/frontend/components/code/toolbar.tsx @@ -8,7 +8,7 @@ import { Camera, Check, ClipboardIcon, Monitor, Smartphone, Tablet } from 'lucid import { Icons } from '@/components/shared/icons'; import { useCallback, useState } from 'react'; import { useSigninModal } from '@/hooks/use-signin-modal'; -import { useUserStore } from '@/lib/store'; +import { useUserStore } from '@/lib/store/local-store'; export function CodeToolbar({ code, searchId, isReadOnly, resizablePanelRef, previewRef }) { const { hasCopied, copyToClipboard } = useCopyToClipboard(); diff --git a/frontend/components/index/file-uploader.tsx b/frontend/components/index/file-uploader.tsx index a02c6e67..b7b84229 100644 --- a/frontend/components/index/file-uploader.tsx +++ b/frontend/components/index/file-uploader.tsx @@ -11,7 +11,7 @@ import { useControllableState } from '@/hooks/use-controllable-state'; import { Button } from '@/components/ui/button'; import { Progress } from '@/components/ui/progress'; import { ScrollArea } from '@/components/ui/scroll-area'; -import { useUserStore } from '@/lib/store'; +import { useUserStore } from '@/lib/store/local-store'; import { useUpgradeModal } from '@/hooks/use-upgrade-modal'; import { useIndexModal } from '@/hooks/use-index-modal'; import { useTranslations } from 'next-intl'; diff --git a/frontend/components/index/index-web.tsx b/frontend/components/index/index-web.tsx index 97c74c94..b7a8ca8b 100644 --- a/frontend/components/index/index-web.tsx +++ b/frontend/components/index/index-web.tsx @@ -4,7 +4,7 @@ import { Button, buttonVariants } from '@/components//ui/button'; import { useState } from 'react'; import { toast } from 'sonner'; import { isValidUrl } from '@/lib/shared-utils'; -import { useUserStore } from '@/lib/store'; +import { useUserStore } from '@/lib/store/local-store'; import { useIndexModal } from '@/hooks/use-index-modal'; import { cn } from '@/lib/utils'; import { Textarea } from '@/components/ui/textarea'; diff --git a/frontend/components/layout/site-header.tsx b/frontend/components/layout/site-header.tsx index d71e0fbe..a53c362a 100644 --- a/frontend/components/layout/site-header.tsx +++ b/frontend/components/layout/site-header.tsx @@ -11,7 +11,7 @@ import { MarketingMenu } from '@/components/layout/mobile-menu'; import { User } from 'next-auth'; import { MainNavItem } from '@/types'; import { UserAccountNav } from '@/components/layout/user-account-nav'; -import { useUserStore } from '@/lib/store'; +import { useUserStore } from '@/lib/store/local-store'; import { siteConfig } from '@/config'; import Image from 'next/image'; diff --git a/frontend/components/layout/user-account-nav.tsx b/frontend/components/layout/user-account-nav.tsx index 384077f2..1e1979c2 100644 --- a/frontend/components/layout/user-account-nav.tsx +++ b/frontend/components/layout/user-account-nav.tsx @@ -6,7 +6,7 @@ import type { User } from 'next-auth'; import { signOut } from 'next-auth/react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { UserAvatar } from '@/components/shared/user-avatar'; -import { useUserStore } from '@/lib/store'; +import { useUserStore } from '@/lib/store/local-store'; import React from 'react'; interface UserAccountNavProps extends React.HTMLAttributes { diff --git a/frontend/components/profile.tsx b/frontend/components/profile.tsx index 0332d87e..ffcd64eb 100644 --- a/frontend/components/profile.tsx +++ b/frontend/components/profile.tsx @@ -5,7 +5,7 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } import TextareaAutosize from 'react-textarea-autosize'; import React, { useEffect, useState } from 'react'; import { Label } from '@/components/ui/label'; -import { useProfileStore } from '@/lib/store'; +import { useProfileStore } from '@/lib/store/local-store'; import { toast } from 'sonner'; export function CustomProfile() { diff --git a/frontend/components/search/language-selection.tsx b/frontend/components/search/language-selection.tsx new file mode 100644 index 00000000..c42ad3e5 --- /dev/null +++ b/frontend/components/search/language-selection.tsx @@ -0,0 +1,100 @@ +import * as React from 'react'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Languages } from 'lucide-react'; +import { useConfigStore } from '@/lib/store/local-store'; + +type Language = { + name: string; + value: string; +}; + +type LanguageSelectionProps = { + type: 'question' | 'answer'; + className?: string; +}; + +const LanguageItem: React.FC<{ language: Language }> = ({ language }) => ( + +
+ {language.name} +
+
+); + +export function LanguageSelection({ type, className }: LanguageSelectionProps) { + const languageMap: Record = { + auto: { + name: type === 'question' ? 'Default' : 'Auto', + value: 'auto', + }, + en: { + name: 'English', + value: 'en', + }, + zh: { + name: '中文', + value: 'zh', + }, + de: { + name: 'Deutsch', + value: 'de', + }, + fr: { + name: 'Français', + value: 'fr', + }, + es: { + name: 'Español', + value: 'es', + }, + ja: { + name: '日本語', + value: 'ja', + }, + ar: { + name: 'العربية', + value: 'ar', + }, + }; + + const { questionLanguage, answerLanguage, setQuestionLanguage, setAnswerLanguage } = useConfigStore(); + + const currentLanguage = type === 'question' ? questionLanguage : answerLanguage; + const setLanguage = type === 'question' ? setQuestionLanguage : setAnswerLanguage; + const selectedLanguage = languageMap[currentLanguage] ?? languageMap['auto']; + const label = type === 'question' ? 'Translate the Question to' : 'Answer Language'; + + return ( + + ); +} + +export function QuestionLanguageSelection({ className }: { className?: string }) { + return ; +} + +export function AnswerLanguageSelection({ className }: { className?: string }) { + return ; +} diff --git a/frontend/components/search/model-selection.tsx b/frontend/components/search/model-selection.tsx index 8302c8e6..90fd98f3 100644 --- a/frontend/components/search/model-selection.tsx +++ b/frontend/components/search/model-selection.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Box } from 'lucide-react'; -import { useModelStore, useUserStore } from '@/lib/store'; +import { useModelStore, useUserStore } from '@/lib/store/local-store'; import { useSigninModal } from '@/hooks/use-signin-modal'; import { Claude_35_Haiku, Claude_35_Sonnet, GPT_4o, GPT_4o_MIMI, O1_MIMI, O1_PREVIEW } from '@/lib/model'; import { isProUser, isPremiumUser } from '@/lib/shared-utils'; @@ -60,16 +60,9 @@ const ModelItem: React.FC<{ model: Model }> = ({ model }) => ( ); export function ModelSelection() { - const { model, setModel, initModel } = useModelStore(); + const { model, setModel } = useModelStore(); const selectedModel = modelMap[model] ?? modelMap[GPT_4o_MIMI]; - React.useEffect(() => { - const initialModel = initModel(); - if (initialModel && initialModel !== model) { - setModel(initialModel); - } - }, [model, initModel, setModel]); - const signInModal = useSigninModal(); const upgradeModal = useUpgradeModal(); const user = useUserStore((state) => state.user); diff --git a/frontend/components/search/search-bar.tsx b/frontend/components/search/search-bar.tsx index a3114824..d7e6aecb 100644 --- a/frontend/components/search/search-bar.tsx +++ b/frontend/components/search/search-bar.tsx @@ -2,12 +2,12 @@ import React, { KeyboardEvent, useMemo, useRef, useState } from 'react'; import { useSigninModal } from '@/hooks/use-signin-modal'; -import { SendHorizontal, FileTextIcon, Database, Image as ImageIcon, Link } from 'lucide-react'; +import { SendHorizontal, FileTextIcon, Image as ImageIcon, Link, Settings } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { ModelSelection } from '@/components/search/model-selection'; import { SourceSelection } from '@/components/search/source-selection'; import TextareaAutosize from 'react-textarea-autosize'; -import { configStore, useUIStore, useUserStore } from '@/lib/store'; +import { useConfigStore, useUIStore, useUserStore } from '@/lib/store/local-store'; import { toast } from 'sonner'; import { Icons } from '@/components/shared/icons'; import { type FileRejection, useDropzone } from 'react-dropzone'; @@ -22,6 +22,7 @@ import { Label } from '@/components/ui/label'; import { SearchType } from '@/lib/types'; import WebImageModal, { WebImageFile } from '@/components/modal/web-images-model'; import { isImageInputModel } from '@/lib/model'; +import { SearchSettingsDialog } from '@/components/search/search-settings'; interface Props { handleSearch: (key: string, attachments?: string[]) => void; @@ -85,7 +86,7 @@ const SearchBar: React.FC = ({ }; const checkImageInput = (attachments: string[]) => { - if (hasImageInput(attachments) && !isImageInputModel(configStore.getState().model)) { + if (hasImageInput(attachments) && !isImageInputModel(useConfigStore.getState().model)) { toast.error('Image input is not supported for this AI model, please switch to GPT-4o mini, GPT-4o, Claude 3.5 Sonnet AI models'); return true; } @@ -230,6 +231,7 @@ const SearchBar: React.FC = ({ loading: () => <>, }); + const [openSettingsDialog, setOpenSettingsDialog] = React.useState(false); const { isSearch, isShadcnUI, showMindMap, setIsSearch, setIsShadcnUI, setShowMindMap } = useUIStore(); return ( @@ -358,18 +360,17 @@ const SearchBar: React.FC = ({ )} {showModelSelection && } {showSourceSelection && } - {showModelSelection && ( -
- setShowMindMap(checked)} /> - -
- )} {showWebSearch && (
setIsSearch(checked)} />
)} +
setOpenSettingsDialog(true)}> + + +
+ {user && } diff --git a/frontend/components/search/search-message.tsx b/frontend/components/search/search-message.tsx index 75a0aa43..a300d5a1 100644 --- a/frontend/components/search/search-message.tsx +++ b/frontend/components/search/search-message.tsx @@ -12,7 +12,7 @@ import ExpandableSection from '@/components/search/expandable-section'; import MindMap from '@/components/search/mindmap'; import { useTranslations } from 'next-intl'; import UISection from '@/components/code/ui-section'; -import { useUIStore } from '@/lib/store'; +import { useUIStore } from '@/lib/store/local-store'; const SearchMessage = memo( (props: { diff --git a/frontend/components/search/search-settings.tsx b/frontend/components/search/search-settings.tsx new file mode 100644 index 00000000..218c372b --- /dev/null +++ b/frontend/components/search/search-settings.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; +import { AnswerLanguageSelection, QuestionLanguageSelection } from '@/components/search/language-selection'; +import { useUIStore } from '@/lib/store/local-store'; +import { BookA, BookKey, Map } from 'lucide-react'; + +interface SearchSettingsProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function SearchSettingsDialog({ open, onOpenChange }: SearchSettingsProps) { + const { showMindMap, setShowMindMap } = useUIStore(); + return ( + + + + MemFree Search Settings + +
+
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+
+
+
+ ); +} diff --git a/frontend/components/search/search-window.tsx b/frontend/components/search/search-window.tsx index 10fbd69e..d4d146a5 100644 --- a/frontend/components/search/search-window.tsx +++ b/frontend/components/search/search-window.tsx @@ -6,7 +6,7 @@ import SearchMessage from '@/components/search/search-message'; import { fetchEventSource } from '@microsoft/fetch-event-source'; import { useSearchParams } from 'next/navigation'; import { useSigninModal } from '@/hooks/use-signin-modal'; -import { configStore, useProfileStore, useUIStore } from '@/lib/store'; +import { useConfigStore, useProfileStore, useUIStore } from '@/lib/store/local-store'; import { ImageSource, Message, SearchType, TextSource, User, VideoSource } from '@/lib/types'; import { LoaderCircle } from 'lucide-react'; @@ -221,8 +221,10 @@ export default function SearchWindow({ id, initialMessages, user, isReadOnly = f 'Accept': 'text/event-stream', }, body: JSON.stringify({ - model: configStore.getState().model, - source: configStore.getState().source, + model: useConfigStore.getState().model, + source: useConfigStore.getState().source, + questionLanguage: useConfigStore.getState().questionLanguage, + answerLanguage: useConfigStore.getState().answerLanguage, profile: useProfileStore.getState().profile, isSearch: useUIStore.getState().isSearch, isShadcnUI: useUIStore.getState().isShadcnUI, diff --git a/frontend/components/search/source-selection.tsx b/frontend/components/search/source-selection.tsx index f14a3e01..1ee867c3 100644 --- a/frontend/components/search/source-selection.tsx +++ b/frontend/components/search/source-selection.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Globe } from 'lucide-react'; -import { useSourceStore, useUserStore } from '@/lib/store'; +import { useSourceStore, useUserStore } from '@/lib/store/local-store'; import { useSigninModal } from '@/hooks/use-signin-modal'; import { SearchCategory } from '@/lib/types'; import { useUpgradeModal } from '@/hooks/use-upgrade-modal'; @@ -37,23 +37,10 @@ export function SourceSelection() { name: t('Knowledge'), value: SearchCategory.KNOWLEDGE_BASE, }, - // [SearchCategory.UI]: { - // name: 'Generate UI', - // value: SearchCategory.UI, - // }, - // [SearchCategory.PRODUCT_HUNT]: { - // name: 'Product Hunt', - // value: SearchCategory.PRODUCT_HUNT, - // }, [SearchCategory.INDIE_MAKER]: { name: 'Indie Maker', value: SearchCategory.INDIE_MAKER, }, - // [SearchCategory.ACADEMIC]: { - // name: t('Academic'), - // flag: 'Pro', - // value: SearchCategory.ACADEMIC, - // }, [SearchCategory.TWEET]: { name: 'Twitter', flag: 'Pro', @@ -61,16 +48,9 @@ export function SourceSelection() { }, }; - const { source, setSource, initSource } = useSourceStore(); + const { source, setSource } = useSourceStore(); const selectedSource = sourceMap[source] ?? sourceMap[SearchCategory.ALL]; - React.useEffect(() => { - const initialSource = initSource(); - if (initialSource && initialSource !== source) { - setSource(initialSource); - } - }, [source, initSource, setSource]); - const signInModal = useSigninModal(); const upgradeModal = useUpgradeModal(); const user = useUserStore((state) => state.user); diff --git a/frontend/lib/llm/prompt.ts b/frontend/lib/llm/prompt.ts index 5c452806..d32328c5 100644 --- a/frontend/lib/llm/prompt.ts +++ b/frontend/lib/llm/prompt.ts @@ -177,14 +177,14 @@ If the user's question is a code-related issue or task, please abide by the foll 4. Before writing or suggesting code, perform a comprehensive code review of the existing code. 5. You should always provide complete, directly executable code, and do not omit part of the code. -Your answer MUST be written in the same language as the user QUESTION, For example, if the user QUESTION is written in chinese, your answer should be written in chinese too, if user's QUESTION is written in english, your answer should be written in english too. +%s Today's date is ${new Date().toISOString()}. `; export const AutoAnswerPrompt = ` # Assistant Background -You are an AI search engine who use the getInformation tool to give user accurate answers. +You are an AI search engine who use the searchWeb tool to give user accurate answers. But If the user's question is one of the following: 1. Greeting (unless the greeting contains a question after it) like Hi, Hello, How are you, etc. 2. Translation @@ -192,24 +192,13 @@ But If the user's question is one of the following: Please answer directly. if the user's question contains url link, please use the accessWebPage tool to get the url content. -When you use the getInformation tool, please rephrase the user's query appropriately to facilitate more accurate search: - -Example: -1. User question: What is a cat? -Rephrased: A cat - -2. User question: How does an A.C work? -Rephrased: A.C working - -2. User question: What is a car? How does it works? -Rephrased: Car working - +%s -If the User Profile is not empty, please use the information in the User Profile to give a more specific and personalized answer. +%s Your answer must follow the following rules: -1. Write an accurate, detailed, and comprehensive response to the user''s QUESTION based on context. +1. Write an accurate, detailed, and comprehensive response to the user''s question based on context. 2. Your answer must be as detailed and organized as possible, Prioritize the use of lists, tables, and quotes to organize output structures. 3. Your answer must be precise, of high-quality, and written by an expert using an unbiased and journalistic tone. 4. You MUST ADHERE to the following formatting instructions: @@ -227,7 +216,8 @@ If the user's question is a code-related issue or task, please abide by the foll 4. Before writing or suggesting code, perform a comprehensive code review of the existing code. 5. You should always provide complete, directly executable code, and do not omit part of the code. -Your answer MUST be written in the same language as the user QUESTION, For example, if the user QUESTION is written in chinese, your answer should be written in chinese too, if user's QUESTION is written in english, your answer should be written in english too. +%s + Today's date is ${new Date().toISOString()} `; diff --git a/frontend/lib/store.ts b/frontend/lib/store/local-store.ts similarity index 63% rename from frontend/lib/store.ts rename to frontend/lib/store/local-store.ts index beb1cb17..0290e26e 100644 --- a/frontend/lib/store.ts +++ b/frontend/lib/store/local-store.ts @@ -20,14 +20,14 @@ export const useProfileStore = create()( ), ); -type UIState = { +interface UIState { isSearch: boolean; isShadcnUI: boolean; showMindMap: boolean; setIsSearch: (isSearch: boolean) => void; setIsShadcnUI: (isShadcnUI: boolean) => void; setShowMindMap: (showMindMap: boolean) => void; -}; +} export const useUIStore = create()( persist( @@ -45,66 +45,53 @@ export const useUIStore = create()( ), ); -type UserState = { - user: User | null; - setUser: (user: User) => void; -}; - -export const useUserStore = create((set) => ({ - user: null, - setUser: (user: User) => set({ user }), -})); - -type ConfigState = { +interface ConfigState { model: string; source: string; - language: string; - colorScheme: 'light' | 'dark'; + questionLanguage: string; + answerLanguage: string; setModel: (model: string) => void; setSource: (source: string) => void; - initModel: () => string; - initSource: () => string; -}; + setQuestionLanguage: (language: string) => void; + setAnswerLanguage: (language: string) => void; +} -export const configStore = create()((set) => ({ - model: GPT_4o_MIMI, - source: 'all', - language: 'en', - colorScheme: 'light', - setModel: (model: string) => { - set({ model }); - localStorage.setItem('model', model); - }, - setSource: (source: string) => { - set({ source }); - localStorage.setItem('source', source); - }, - initModel: () => { - const model = localStorage.getItem('model'); - if (model) { - set({ model }); - } - return model || GPT_4o_MIMI; - }, - initSource() { - const source = localStorage.getItem('source'); - if (source) { - set({ source }); - } - return source || 'all'; - }, -})); +export const useConfigStore = create()( + persist( + (set) => ({ + model: GPT_4o_MIMI, + source: 'all', + questionLanguage: 'auto', + answerLanguage: 'auto', + setModel: (model: string) => set({ model }), + setSource: (source: string) => set({ source }), + setQuestionLanguage: (language: string) => set({ questionLanguage: language }), + setAnswerLanguage: (language: string) => set({ answerLanguage: language }), + }), + { + name: 'config-storage', + }, + ), +); export const useModelStore = () => - configStore((state) => ({ + useConfigStore((state) => ({ model: state.model, setModel: state.setModel, - initModel: state.initModel, })); export const useSourceStore = () => - configStore((state) => ({ + useConfigStore((state) => ({ source: state.source, setSource: state.setSource, - initSource: state.initSource, })); + +type UserState = { + user: User | null; + setUser: (user: User) => void; +}; + +export const useUserStore = create((set) => ({ + user: null, + setUser: (user: User) => set({ user }), +})); diff --git a/frontend/lib/tools/auto.ts b/frontend/lib/tools/auto.ts index 9caac7c6..51973aaf 100644 --- a/frontend/lib/tools/auto.ts +++ b/frontend/lib/tools/auto.ts @@ -16,6 +16,45 @@ import { searchRelevantContent } from '@/lib/tools/search'; import { ImageSource, Message as StoreMessage, SearchCategory, TextSource, VideoSource } from '@/lib/types'; import { streamText, tool } from 'ai'; import { z } from 'zod'; +import util from 'util'; + +const ProfilePrompt = `Please use the information in the User Profile to give a more specific and personalized answer: +\`\`\` +%s +\`\`\` +`; + +const WebSearchPromptWithTranslate = `When you use the searchWeb tool to search the internet, you must do this in two steps: + + 1. First rephrase the user's question appropriately to facilitate more accurate search: + + Example: + - User question: What is a cat? + - Rephrased: A cat + + - User question: How does an A.C work? + - Rephrased: A.C working + + - User question: What is a car? How does it works? + - Rephrased: Car working + + 2. then Translate rephrased question into %s language + + 3. finally Call the searchWeb tool with the translated user question as a parameter`; + +const WebSearchPrompt = `When you use the searchWeb tool to search the internet, please rephrase the user's question appropriately to facilitate more accurate search: + Example: + - User question: What is a cat? + - Rephrased: A cat + + - User question: How does an A.C work? + - Rephrased: A.C working + + - User question: What is a car? How does it works? + - Rephrased: Car working`; + +const AutoLanguagePrompt = `Your final answer MUST be written in the same language as the user question, For example, if the user question is written in chinese, your answer should be written in chinese too, if user's question is written in english, your answer should be written in english too.`; +const UserLanguagePrompt = `Your final answer MUST be written in %s language.`; export async function autoAnswer( messages: StoreMessage[], @@ -23,6 +62,8 @@ export async function autoAnswer( userId: string, profile?: string, onStream?: (...args: any[]) => void, + questionLanguage?: string, + answerLanguage?: string, model = GPT_4o_MIMI, source = SearchCategory.ALL, ) { @@ -34,11 +75,36 @@ export async function autoAnswer( let images: ImageSource[] = []; let videos: VideoSource[] = []; + let profileInstructions = ''; + if (profile) { + profileInstructions = util.format(ProfilePrompt, profile); + } + + console.log('questionLanguage', questionLanguage); + let searchWebInstructions = ''; + if (questionLanguage !== 'auto') { + searchWebInstructions = util.format(WebSearchPromptWithTranslate, questionLanguage); + } else { + searchWebInstructions = WebSearchPrompt; + } + + console.log('answerLanguage', answerLanguage); + let languageInstructions = ''; + if (answerLanguage !== 'auto') { + languageInstructions = util.format(UserLanguagePrompt, answerLanguage); + } else { + languageInstructions = AutoLanguagePrompt; + } + + const systemPrompt = util.format(AutoAnswerPrompt, searchWebInstructions, profileInstructions, languageInstructions); + console.log('system prompt', systemPrompt); + const userMessages = convertToCoreMessages(newMessages); const maxTokens = getMaxOutputToken(isPro, model); // const mexSteps = isPro ? 2 : 1; // const isContinued = isPro ? true : false; // console.log('auto answer', { maxTokens, mexSteps, isContinued }); + const result = streamText({ model: getLLM(model), maxSteps: 1, @@ -46,28 +112,20 @@ export async function autoAnswer( messages: [ { role: 'system', - content: AutoAnswerPrompt, + content: systemPrompt, experimental_providerMetadata: { anthropic: { cacheControl: { type: 'ephemeral' } }, }, }, - ...(profile - ? [ - { - role: 'system' as const, - content: `User Profile:\n${profile}`, - }, - ] - : []), ...userMessages, ], maxTokens: maxTokens, temperature: 0.1, tools: { - getInformation: tool({ - description: `get information from internet to answer user questions.`, + searchWeb: tool({ + description: `search web to answer user's question, rephrase and translate the question before calling this tool.`, parameters: z.object({ - question: z.string().describe('the users question'), + question: z.string().describe(`the user's question after rewriting and translating`), }), execute: async ({ question }) => searchRelevantContent(question, userId, source, onStream), }), @@ -95,10 +153,10 @@ export async function autoAnswer( let hasError = false; for await (const delta of result.fullStream) { switch (delta.type) { - case 'step-finish': { - console.log('step is continued', delta.isContinued, ' finish reason ', delta.finishReason); - break; - } + // case 'step-finish': { + // console.log('step is continued', delta.isContinued, ' finish reason ', delta.finishReason); + // break; + // } case 'text-delta': { if (delta.textDelta) { if (!hasAnswer) { @@ -123,7 +181,7 @@ export async function autoAnswer( ); break; case 'tool-result': - if (delta.toolName === 'getInformation') { + if (delta.toolName === 'searchWeb') { texts = texts.concat(delta.result.texts); images = images.concat(delta.result.images); console.log(`rewrite ${rewriteQuery} to ${delta.args.question}`); diff --git a/frontend/lib/tools/chat.ts b/frontend/lib/tools/chat.ts index 078453cc..0e1d7495 100644 --- a/frontend/lib/tools/chat.ts +++ b/frontend/lib/tools/chat.ts @@ -2,29 +2,41 @@ import 'server-only'; import { incSearchCount } from '@/lib/db'; import { convertToCoreMessages, getLLM, getMaxOutputToken } from '@/lib/llm/llm'; -import { ChatPrompt, DirectAnswerPrompt } from '@/lib/llm/prompt'; -import { getHistory, getHistoryMessages, streamResponse } from '@/lib/llm/utils'; +import { ChatPrompt } from '@/lib/llm/prompt'; +import { getHistoryMessages, streamResponse } from '@/lib/llm/utils'; import { logError } from '@/lib/log'; import { GPT_4o_MIMI } from '@/lib/model'; import { extractErrorMessage, saveMessages } from '@/lib/server-utils'; -import { ImageSource, Message as StoreMessage, SearchCategory, TextSource, VideoSource } from '@/lib/types'; -import { generateText, streamText } from 'ai'; +import { Message as StoreMessage, SearchCategory, TextSource, VideoSource } from '@/lib/types'; +import { streamText } from 'ai'; import util from 'util'; import { generateTitle } from '@/lib/tools/generate-title'; +const AutoLanguagePrompt = `Your answer MUST be written in the same language as the user question, For example, if the user QUESTION is written in chinese, your answer should be written in chinese too, if user's QUESTION is written in english, your answer should be written in english too.`; +const UserLanguagePrompt = `Your answer MUST be written in %s language.`; + export async function chat( messages: StoreMessage[], isPro: boolean, userId: string, profile?: string, onStream?: (...args: any[]) => void, + answerLanguage?: string, model = GPT_4o_MIMI, ) { try { const newMessages = getHistoryMessages(isPro, messages); const query = newMessages[newMessages.length - 1].content; - const prompt = util.format(ChatPrompt, profile); + console.log('answerLanguage', answerLanguage); + let languageInstructions = ''; + if (answerLanguage !== 'auto') { + languageInstructions = util.format(UserLanguagePrompt, answerLanguage); + } else { + languageInstructions = AutoLanguagePrompt; + } + + const prompt = util.format(ChatPrompt, profile, languageInstructions); console.log('chat prompt', prompt); const userMessages = convertToCoreMessages(newMessages); const maxTokens = getMaxOutputToken(isPro, model); diff --git a/frontend/lib/tools/search.ts b/frontend/lib/tools/search.ts index 0cf23898..c5bd76b1 100644 --- a/frontend/lib/tools/search.ts +++ b/frontend/lib/tools/search.ts @@ -10,7 +10,7 @@ export const searchRelevantContent = async (query: string, userId: string, sourc categories: [source], }; - // console.log('searchRelevantContent:', query, userId, source); + // qconsole.log('searchRelevantContent:', query, userId, source); if (userId && source === SearchCategory.ALL) { const vectorSearchPromise = getVectorSearch(userId).search(query);