feat: add core functionalities for chat, translation, and discovery
- Implement chat management with database integration - Add translation API endpoint with language model support - Set up discover route for news aggregation from multiple sources - Configure Docker for development and production environments - Enhance model provider management system - Add suggestion generation functionality
This commit is contained in:
parent
4d438f06cd
commit
61273953e1
4 changed files with 125 additions and 13 deletions
|
|
@ -8,6 +8,7 @@ import chatsRouter from './chats';
|
|||
import searchRouter from './search';
|
||||
import discoverRouter from './discover';
|
||||
import uploadsRouter from './uploads';
|
||||
import translateRouter from './translate';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
@ -20,5 +21,6 @@ router.use('/chats', chatsRouter);
|
|||
router.use('/search', searchRouter);
|
||||
router.use('/discover', discoverRouter);
|
||||
router.use('/uploads', uploadsRouter);
|
||||
router.use('/translate', translateRouter);
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
60
src/routes/translate.ts
Normal file
60
src/routes/translate.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import express from 'express';
|
||||
import logger from '../utils/logger';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { getAvailableChatModelProviders } from '../lib/providers';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { HumanMessage } from '@langchain/core/messages';
|
||||
import {
|
||||
getCustomOpenaiApiKey,
|
||||
getCustomOpenaiApiUrl,
|
||||
getCustomOpenaiModelName,
|
||||
} from '../config';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
interface TranslateBody {
|
||||
text: string;
|
||||
targetLanguage: string;
|
||||
}
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const body: TranslateBody = req.body;
|
||||
if (!body.text || !body.targetLanguage) {
|
||||
return res.status(400).json({ message: 'Missing required fields' });
|
||||
}
|
||||
|
||||
const chatModelProviders = await getAvailableChatModelProviders();
|
||||
const provider = Object.keys(chatModelProviders)[0];
|
||||
const model = Object.keys(chatModelProviders[provider])[0];
|
||||
let llm: BaseChatModel | undefined;
|
||||
|
||||
if (provider === 'custom_openai') {
|
||||
llm = new ChatOpenAI({
|
||||
modelName: getCustomOpenaiModelName(),
|
||||
openAIApiKey: getCustomOpenaiApiKey(),
|
||||
configuration: {
|
||||
baseURL: getCustomOpenaiApiUrl(),
|
||||
},
|
||||
}) as unknown as BaseChatModel;
|
||||
} else {
|
||||
llm = chatModelProviders[provider][model].model as unknown as BaseChatModel;
|
||||
}
|
||||
|
||||
if (!llm) {
|
||||
return res.status(400).json({ message: 'No LLM model available' });
|
||||
}
|
||||
|
||||
const prompt = `Translate the following text to ${body.targetLanguage}. Maintain the exact same formatting, including any markdown or special characters:
|
||||
${body.text}`;
|
||||
|
||||
const response = await llm.invoke([new HumanMessage(prompt)]);
|
||||
|
||||
res.status(200).json({ translatedText: response.content });
|
||||
} catch (err: any) {
|
||||
logger.error(`Error in translation: ${err.message}`);
|
||||
res.status(500).json({ message: 'An error has occurred.' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -11,6 +11,8 @@ import {
|
|||
StopCircle,
|
||||
Layers3,
|
||||
Plus,
|
||||
Languages,
|
||||
Loader2
|
||||
} from 'lucide-react';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import Copy from './MessageActions/Copy';
|
||||
|
|
@ -20,6 +22,7 @@ import SearchImages from './SearchImages';
|
|||
import SearchVideos from './SearchVideos';
|
||||
import { useSpeech } from 'react-text-to-speech';
|
||||
import { SearchPDFs } from './SearchPDFs';
|
||||
import { Select } from './ui/Select';
|
||||
|
||||
const MessageBox = ({
|
||||
message,
|
||||
|
|
@ -42,6 +45,8 @@ const MessageBox = ({
|
|||
}) => {
|
||||
const [parsedMessage, setParsedMessage] = useState(message.content);
|
||||
const [speechMessage, setSpeechMessage] = useState(message.content);
|
||||
const [translating, setTranslating] = useState(false);
|
||||
const [currentLanguage, setCurrentLanguage] = useState('english');
|
||||
|
||||
useEffect(() => {
|
||||
const regex = /\[(\d+)\]/g;
|
||||
|
|
@ -64,6 +69,32 @@ const MessageBox = ({
|
|||
setParsedMessage(message.content);
|
||||
}, [message.content, message.sources, message.role]);
|
||||
|
||||
const handleTranslate = async (language: string) => {
|
||||
if (language === currentLanguage) return;
|
||||
setTranslating(true);
|
||||
try {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/translate`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: message.content,
|
||||
targetLanguage: language
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Translation failed');
|
||||
|
||||
const data = await response.json();
|
||||
setParsedMessage(data.translatedText);
|
||||
setCurrentLanguage(language);
|
||||
} catch (error) {
|
||||
console.error('Translation error:', error);
|
||||
} finally {
|
||||
setTranslating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const { speechStatus, start, stop } = useSpeech({ text: speechMessage });
|
||||
|
||||
|
|
@ -101,17 +132,36 @@ const MessageBox = ({
|
|||
</div>
|
||||
)}
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div className="flex flex-row items-center space-x-2">
|
||||
<Disc3
|
||||
className={cn(
|
||||
'text-black dark:text-white',
|
||||
isLast && loading ? 'animate-spin' : 'animate-none',
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center space-x-2">
|
||||
<Disc3
|
||||
className={cn(
|
||||
'text-black dark:text-white',
|
||||
isLast && loading ? 'animate-spin' : 'animate-none',
|
||||
)}
|
||||
size={20}
|
||||
/>
|
||||
<h3 className="text-black dark:text-white font-medium text-xl">
|
||||
Answer
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
{translating && (
|
||||
<Loader2 className="animate-spin text-black/70 dark:text-white/70" size={16} />
|
||||
)}
|
||||
size={20}
|
||||
/>
|
||||
<h3 className="text-black dark:text-white font-medium text-xl">
|
||||
Answer
|
||||
</h3>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Languages size={16} className="text-black/70 dark:text-white/70" />
|
||||
<Select
|
||||
value={currentLanguage}
|
||||
onChange={(e) => handleTranslate(e.target.value)}
|
||||
className="h-auto text-sm py-1 min-w-[120px]" // Adjusted height and padding
|
||||
options={[
|
||||
{ value: 'english', label: 'English' },
|
||||
{ value: 'russian', label: 'Russian' }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Markdown
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ export const Select = ({ className, options, ...restProps }: SelectProps) => {
|
|||
<select
|
||||
{...restProps}
|
||||
className={cn(
|
||||
'bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm',
|
||||
'bg-light-secondary dark:bg-dark-secondary px-3 py-1 flex items-center overflow-visible border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm min-w-[120px] leading-normal',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{options.map(({ label, value, disabled }) => {
|
||||
return (
|
||||
<option key={value} value={value} disabled={disabled}>
|
||||
<option key={value} value={value} disabled={disabled} className="py-1">
|
||||
{label}
|
||||
</option>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue