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 searchRouter from './search';
|
||||||
import discoverRouter from './discover';
|
import discoverRouter from './discover';
|
||||||
import uploadsRouter from './uploads';
|
import uploadsRouter from './uploads';
|
||||||
|
import translateRouter from './translate';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
@ -20,5 +21,6 @@ router.use('/chats', chatsRouter);
|
||||||
router.use('/search', searchRouter);
|
router.use('/search', searchRouter);
|
||||||
router.use('/discover', discoverRouter);
|
router.use('/discover', discoverRouter);
|
||||||
router.use('/uploads', uploadsRouter);
|
router.use('/uploads', uploadsRouter);
|
||||||
|
router.use('/translate', translateRouter);
|
||||||
|
|
||||||
export default router;
|
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,
|
StopCircle,
|
||||||
Layers3,
|
Layers3,
|
||||||
Plus,
|
Plus,
|
||||||
|
Languages,
|
||||||
|
Loader2
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Markdown from 'markdown-to-jsx';
|
import Markdown from 'markdown-to-jsx';
|
||||||
import Copy from './MessageActions/Copy';
|
import Copy from './MessageActions/Copy';
|
||||||
|
|
@ -20,6 +22,7 @@ import SearchImages from './SearchImages';
|
||||||
import SearchVideos from './SearchVideos';
|
import SearchVideos from './SearchVideos';
|
||||||
import { useSpeech } from 'react-text-to-speech';
|
import { useSpeech } from 'react-text-to-speech';
|
||||||
import { SearchPDFs } from './SearchPDFs';
|
import { SearchPDFs } from './SearchPDFs';
|
||||||
|
import { Select } from './ui/Select';
|
||||||
|
|
||||||
const MessageBox = ({
|
const MessageBox = ({
|
||||||
message,
|
message,
|
||||||
|
|
@ -42,6 +45,8 @@ const MessageBox = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [parsedMessage, setParsedMessage] = useState(message.content);
|
const [parsedMessage, setParsedMessage] = useState(message.content);
|
||||||
const [speechMessage, setSpeechMessage] = useState(message.content);
|
const [speechMessage, setSpeechMessage] = useState(message.content);
|
||||||
|
const [translating, setTranslating] = useState(false);
|
||||||
|
const [currentLanguage, setCurrentLanguage] = useState('english');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const regex = /\[(\d+)\]/g;
|
const regex = /\[(\d+)\]/g;
|
||||||
|
|
@ -64,7 +69,33 @@ const MessageBox = ({
|
||||||
setParsedMessage(message.content);
|
setParsedMessage(message.content);
|
||||||
}, [message.content, message.sources, message.role]);
|
}, [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 });
|
const { speechStatus, start, stop } = useSpeech({ text: speechMessage });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -101,17 +132,36 @@ const MessageBox = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<div className="flex flex-row items-center space-x-2">
|
<div className="flex flex-row items-center justify-between">
|
||||||
<Disc3
|
<div className="flex flex-row items-center space-x-2">
|
||||||
className={cn(
|
<Disc3
|
||||||
'text-black dark:text-white',
|
className={cn(
|
||||||
isLast && loading ? 'animate-spin' : 'animate-none',
|
'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}
|
<div className="flex items-center space-x-2">
|
||||||
/>
|
<Languages size={16} className="text-black/70 dark:text-white/70" />
|
||||||
<h3 className="text-black dark:text-white font-medium text-xl">
|
<Select
|
||||||
Answer
|
value={currentLanguage}
|
||||||
</h3>
|
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>
|
</div>
|
||||||
<Markdown
|
<Markdown
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ export const Select = ({ className, options, ...restProps }: SelectProps) => {
|
||||||
<select
|
<select
|
||||||
{...restProps}
|
{...restProps}
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{options.map(({ label, value, disabled }) => {
|
{options.map(({ label, value, disabled }) => {
|
||||||
return (
|
return (
|
||||||
<option key={value} value={value} disabled={disabled}>
|
<option key={value} value={value} disabled={disabled} className="py-1">
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</option>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue