Skip to content

Commit

Permalink
Support translating the search question when web search
Browse files Browse the repository at this point in the history
  • Loading branch information
ahaapple committed Dec 20, 2024
1 parent b6681e2 commit 7367d64
Show file tree
Hide file tree
Showing 21 changed files with 337 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
23 changes: 19 additions & 4 deletions frontend/app/api/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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: {
Expand All @@ -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);
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/code/code-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'), {
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/code/toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/index/file-uploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/index/index-web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/layout/site-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion frontend/components/layout/user-account-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement> {
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
100 changes: 100 additions & 0 deletions frontend/components/search/language-selection.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<SelectItem key={language.value} value={language.value} className="w-full p-2 block">
<div className="flex w-full justify-between">
<span className="text-md mr-2">{language.name}</span>
</div>
</SelectItem>
);

export function LanguageSelection({ type, className }: LanguageSelectionProps) {
const languageMap: Record<string, Language> = {
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 (
<Select
key={currentLanguage}
value={currentLanguage}
onValueChange={(value) => {
if (value && value !== currentLanguage) {
setLanguage(value);
}
}}
>
<SelectTrigger aria-label={label} className={`focus:ring-0 border-none outline-none ${className}`}>
<SelectValue>
<div className="flex items-center space-x-1">
<Languages className="h-4 w-4" />
<span className="font-semibold">{selectedLanguage.name}</span>
</div>
</SelectValue>
</SelectTrigger>
<SelectContent className="w-full">
{Object.values(languageMap).map((item) => (
<LanguageItem key={item.value} language={item} />
))}
</SelectContent>
</Select>
);
}

export function QuestionLanguageSelection({ className }: { className?: string }) {
return <LanguageSelection type="question" className={className} />;
}

export function AnswerLanguageSelection({ className }: { className?: string }) {
return <LanguageSelection type="answer" className={className} />;
}
11 changes: 2 additions & 9 deletions frontend/components/search/model-selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
19 changes: 10 additions & 9 deletions frontend/components/search/search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -85,7 +86,7 @@ const SearchBar: React.FC<Props> = ({
};

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;
}
Expand Down Expand Up @@ -230,6 +231,7 @@ const SearchBar: React.FC<Props> = ({
loading: () => <></>,
});

const [openSettingsDialog, setOpenSettingsDialog] = React.useState(false);
const { isSearch, isShadcnUI, showMindMap, setIsSearch, setIsShadcnUI, setShowMindMap } = useUIStore();

return (
Expand Down Expand Up @@ -358,18 +360,17 @@ const SearchBar: React.FC<Props> = ({
)}
{showModelSelection && <ModelSelection />}
{showSourceSelection && <SourceSelection />}
{showModelSelection && (
<div className="flex items-center space-x-2 mb-1">
<Switch id="mindmap" checked={showMindMap} onCheckedChange={(checked) => setShowMindMap(checked)} />
<Label htmlFor="mindmap">Show MindMap</Label>
</div>
)}
{showWebSearch && (
<div className="flex items-center space-x-2 mb-1">
<Switch id="search" checked={isSearch} onCheckedChange={(checked) => setIsSearch(checked)} />
<Label htmlFor="search">Web Search</Label>
</div>
)}
<div className="flex items-center space-x-2 mb-1 cursor-pointer hover:text-primary" onClick={() => setOpenSettingsDialog(true)}>
<Settings />
<Label>More Search Settings</Label>
</div>
<SearchSettingsDialog open={openSettingsDialog} onOpenChange={setOpenSettingsDialog} />
</div>
{user && <IndexModal />}
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/search/search-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
53 changes: 53 additions & 0 deletions frontend/components/search/search-settings.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-xl mx-auto">
<DialogHeader>
<DialogTitle>MemFree Search Settings</DialogTitle>
</DialogHeader>
<div className="space-y-8 py-4">
<div className="flex flex-col space-y-2">
<div className="flex items-center gap-2">
<BookA className="h-4 w-4" />
<Label className="text-sm font-bold">Translate question to target language when web search </Label>
</div>
<QuestionLanguageSelection className="w-1/3" />
</div>

<div className="flex flex-col space-y-2">
<div className="flex items-center gap-2">
<BookKey className="h-4 w-4" />
<Label className="text-sm font-bold">Answer Language</Label>
</div>
<AnswerLanguageSelection className="w-1/3" />
</div>

<div className="flex flex-col space-y-4">
<div className="flex items-center gap-2">
<Map className="h-4 w-4" />
<Label className="text-sm font-bold">Mind Map</Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="mindmap" checked={showMindMap} onCheckedChange={setShowMindMap} />
<Label htmlFor="mindmap">Show Mind Map</Label>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}
8 changes: 5 additions & 3 deletions frontend/components/search/search-window.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 7367d64

Please sign in to comment.