Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 023a8a202b | |||
| 82b77b0baa | |||
| f65aca0221 | |||
| fd8b183f51 | |||
| db4375ca8a | |||
| accab8e0cd | |||
| 6c39256008 | |||
| e48fda0a2a | |||
| 1f94effb43 | |||
| b14c052dcd | |||
| c067490a78 | |||
| 6a49f664db | |||
| 10733fab7d |
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
# ---- Base Node ----
|
# ---- Base Node ----
|
||||||
FROM node:19-alpine AS base
|
FROM node:20-alpine AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ COPY . .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ---- Production ----
|
# ---- Production ----
|
||||||
FROM node:19-alpine AS production
|
FROM node:20-alpine AS production
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=dependencies /app/node_modules ./node_modules
|
COPY --from=dependencies /app/node_modules ./node_modules
|
||||||
COPY --from=build /app/.next ./.next
|
COPY --from=build /app/.next ./.next
|
||||||
|
|||||||
+103
-1
@@ -1,4 +1,10 @@
|
|||||||
import { IconClearAll, IconSettings } from '@tabler/icons-react';
|
import {
|
||||||
|
IconClearAll,
|
||||||
|
IconSettings,
|
||||||
|
IconMarkdown,
|
||||||
|
IconPdf,
|
||||||
|
IconScreenshot,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
import {
|
import {
|
||||||
MutableRefObject,
|
MutableRefObject,
|
||||||
memo,
|
memo,
|
||||||
@@ -34,6 +40,11 @@ import { SystemPrompt } from './SystemPrompt';
|
|||||||
import { TemperatureSlider } from './Temperature';
|
import { TemperatureSlider } from './Temperature';
|
||||||
import { MemoizedChatMessage } from './MemoizedChatMessage';
|
import { MemoizedChatMessage } from './MemoizedChatMessage';
|
||||||
|
|
||||||
|
import {jsPDF} from "jspdf";
|
||||||
|
import html2canvas from "html2canvas";
|
||||||
|
|
||||||
|
import { toPng } from 'html-to-image';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
stopConversationRef: MutableRefObject<boolean>;
|
stopConversationRef: MutableRefObject<boolean>;
|
||||||
}
|
}
|
||||||
@@ -300,6 +311,79 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onMarkdown = () => {
|
||||||
|
if (!selectedConversation){
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let markdownContent = '';
|
||||||
|
|
||||||
|
selectedConversation.messages.forEach(obj => {
|
||||||
|
markdownContent += `## ${obj.role === "user" ? t('You') : t("AI")}\n\n${obj.content}\n\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const date = new Date().toLocaleString("default", { year: "numeric", month: "long", day: "numeric" })
|
||||||
|
const time = new Date().toLocaleTimeString("default", {hour12: true, hour: "numeric", minute: "numeric"})
|
||||||
|
|
||||||
|
markdownContent += `---\n`
|
||||||
|
markdownContent += `${t("Exported on")} ` + date + ` ${t("at")} ` + time + ".";
|
||||||
|
|
||||||
|
const markdownFile = new Blob([markdownContent], { type: 'text/markdown' });
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
|
||||||
|
downloadLink.href = URL.createObjectURL(markdownFile);
|
||||||
|
downloadLink.download = `${selectedConversation?.name || 'conversation'}.md`;
|
||||||
|
downloadLink.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPdf = () => {
|
||||||
|
if (chatContainerRef.current === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
chatContainerRef.current.classList.remove('max-h-full')
|
||||||
|
html2canvas(chatContainerRef.current).then((canvas) => {
|
||||||
|
if (chatContainerRef.current) {
|
||||||
|
chatContainerRef.current.classList.add('max-h-full')
|
||||||
|
}
|
||||||
|
const imgData = canvas.toDataURL('image/png');
|
||||||
|
const orientation = canvas.width > canvas.height ? "l" : "p";
|
||||||
|
const pixelRatio = window.devicePixelRatio > 2 ? window.devicePixelRatio : 2
|
||||||
|
const pdf = new jsPDF(
|
||||||
|
orientation,
|
||||||
|
"pt",
|
||||||
|
[canvas.width / pixelRatio, canvas.height / pixelRatio],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
var pdfWidth = pdf.internal.pageSize.getWidth();
|
||||||
|
var pdfHeight = pdf.internal.pageSize.getHeight();
|
||||||
|
pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, "", "FAST");
|
||||||
|
const title = `${selectedConversation?.name || 'conversation'}.pdf`
|
||||||
|
pdf.save(title)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onScreenshot = () => {
|
||||||
|
if (chatContainerRef.current === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chatContainerRef.current.classList.remove('max-h-full');
|
||||||
|
toPng(chatContainerRef.current, { cacheBust: true })
|
||||||
|
.then((dataUrl) => {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.download = `${selectedConversation?.name || 'conversation'}.png`;
|
||||||
|
link.href = dataUrl;
|
||||||
|
link.click();
|
||||||
|
if (chatContainerRef.current) {
|
||||||
|
chatContainerRef.current.classList.add('max-h-full');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const scrollDown = () => {
|
const scrollDown = () => {
|
||||||
if (autoScrollEnabled) {
|
if (autoScrollEnabled) {
|
||||||
messagesEndRef.current?.scrollIntoView(true);
|
messagesEndRef.current?.scrollIntoView(true);
|
||||||
@@ -454,6 +538,24 @@ export const Chat = memo(({ stopConversationRef }: Props) => {
|
|||||||
>
|
>
|
||||||
<IconClearAll size={18} />
|
<IconClearAll size={18} />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="ml-2 cursor-pointer hover:opacity-50"
|
||||||
|
onClick={onMarkdown}
|
||||||
|
>
|
||||||
|
<IconMarkdown size={18} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="ml-2 cursor-pointer hover:opacity-50"
|
||||||
|
onClick={onPdf}
|
||||||
|
>
|
||||||
|
<IconPdf size={18} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="ml-2 cursor-pointer hover:opacity-50"
|
||||||
|
onClick={onScreenshot}
|
||||||
|
>
|
||||||
|
<IconScreenshot size={18} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{showSettings && (
|
{showSettings && (
|
||||||
<div className="flex flex-col space-y-10 md:mx-auto md:max-w-xl md:gap-6 md:py-3 md:pt-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
|
<div className="flex flex-col space-y-10 md:mx-auto md:max-w-xl md:gap-6 md:py-3 md:pt-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
|
||||||
|
|||||||
@@ -15,7 +15,10 @@
|
|||||||
"@dqbd/tiktoken": "^1.0.2",
|
"@dqbd/tiktoken": "^1.0.2",
|
||||||
"@tabler/icons-react": "^2.9.0",
|
"@tabler/icons-react": "^2.9.0",
|
||||||
"eventsource-parser": "^0.1.0",
|
"eventsource-parser": "^0.1.0",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
|
"html-to-image": "^1.11.11",
|
||||||
"i18next": "^22.4.13",
|
"i18next": "^22.4.13",
|
||||||
|
"jspdf": "^2.5.1",
|
||||||
"next": "13.2.4",
|
"next": "13.2.4",
|
||||||
"next-i18next": "^13.2.2",
|
"next-i18next": "^13.2.2",
|
||||||
"openai": "^3.2.1",
|
"openai": "^3.2.1",
|
||||||
|
|||||||
+7
-1
@@ -54,7 +54,13 @@ const handler = async (req: Request): Promise<Response> => {
|
|||||||
|
|
||||||
const stream = await OpenAIStream(model, promptToSend, temperatureToUse, key, messagesToSend);
|
const stream = await OpenAIStream(model, promptToSend, temperatureToUse, key, messagesToSend);
|
||||||
|
|
||||||
return new Response(stream);
|
var resp = new Response(stream);
|
||||||
|
|
||||||
|
// let proxy services like nginx or argo tunnel know about pass the chunk immediately
|
||||||
|
// similar to nginx option `proxy_buffering off;`
|
||||||
|
resp.headers.set('Content-Type', 'text/event-stream');
|
||||||
|
|
||||||
|
return resp;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (error instanceof OpenAIError) {
|
if (error instanceof OpenAIError) {
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ export interface OpenAIModel {
|
|||||||
export enum OpenAIModelID {
|
export enum OpenAIModelID {
|
||||||
GPT_3_5 = 'gpt-3.5-turbo',
|
GPT_3_5 = 'gpt-3.5-turbo',
|
||||||
GPT_3_5_AZ = 'gpt-35-turbo',
|
GPT_3_5_AZ = 'gpt-35-turbo',
|
||||||
|
GPT_3_5_16K = 'gpt-3.5-turbo-16k',
|
||||||
GPT_4 = 'gpt-4',
|
GPT_4 = 'gpt-4',
|
||||||
GPT_4_32K = 'gpt-4-32k',
|
GPT_4_32K = 'gpt-4-32k',
|
||||||
|
GPT_4_TURBO = 'gpt-4-1106-preview'
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case the `DEFAULT_MODEL` environment variable is not set or set to an unsupported model
|
// in case the `DEFAULT_MODEL` environment variable is not set or set to an unsupported model
|
||||||
@@ -30,6 +32,12 @@ export const OpenAIModels: Record<OpenAIModelID, OpenAIModel> = {
|
|||||||
maxLength: 12000,
|
maxLength: 12000,
|
||||||
tokenLimit: 4000,
|
tokenLimit: 4000,
|
||||||
},
|
},
|
||||||
|
[OpenAIModelID.GPT_3_5_16K]: {
|
||||||
|
id: OpenAIModelID.GPT_3_5_16K,
|
||||||
|
name: 'GPT-3.5-16K',
|
||||||
|
maxLength: 96000,
|
||||||
|
tokenLimit: 32000,
|
||||||
|
},
|
||||||
[OpenAIModelID.GPT_4]: {
|
[OpenAIModelID.GPT_4]: {
|
||||||
id: OpenAIModelID.GPT_4,
|
id: OpenAIModelID.GPT_4,
|
||||||
name: 'GPT-4',
|
name: 'GPT-4',
|
||||||
@@ -42,4 +50,10 @@ export const OpenAIModels: Record<OpenAIModelID, OpenAIModel> = {
|
|||||||
maxLength: 96000,
|
maxLength: 96000,
|
||||||
tokenLimit: 32000,
|
tokenLimit: 32000,
|
||||||
},
|
},
|
||||||
|
[OpenAIModelID.GPT_4_TURBO]: {
|
||||||
|
id: OpenAIModelID.GPT_4_TURBO,
|
||||||
|
name: 'GPT-4-TURBO',
|
||||||
|
maxLength: 380000,
|
||||||
|
tokenLimit: 128000,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user