diff --git a/admin/user.go b/admin/user.go
index d201b2f..9b03657 100644
--- a/admin/user.go
+++ b/admin/user.go
@@ -118,10 +118,20 @@ func passwordMigration(db *sql.DB, cache *redis.Client, id int64, password strin
if len(password) < 6 || len(password) > 36 {
return fmt.Errorf("password length must be between 6 and 36")
}
+ var username string
+ hash_passwd := utils.Sha2Encrypt(password)
+ err_u := db.QueryRow("SELECT username FROM auth WHERE id = ?", id).Scan(&username)
+ if err_u != nil {
+ return fmt.Errorf("failed to fetch username: %v", err_u)
+ }
+ cacheKey := fmt.Sprintf("nio:user:%s", username)
+ if err_u := cache.Del(context.Background(), cacheKey).Err(); err_u != nil {
+ return fmt.Errorf("failed to delete cache: %v", err_u)
+ }
_, err := globals.ExecDb(db, `
UPDATE auth SET password = ? WHERE id = ?
- `, utils.Sha2Encrypt(password), id)
+ `, hash_passwd, id)
cache.Del(context.Background(), fmt.Sprint("nio:user:root"))
diff --git a/app/package.json b/app/package.json
index 5850720..b00980c 100644
--- a/app/package.json
+++ b/app/package.json
@@ -46,7 +46,7 @@
"html-to-image": "^1.11.11",
"i18next": "^23.4.6",
"localforage": "^1.10.0",
- "lucide-react": "^0.309.0",
+ "lucide-react": "^0.320.0",
"match-sorter": "^6.3.1",
"mermaid": "^10.9.0",
"next-themes": "^0.2.1",
diff --git a/app/src/components/ExportAllMsgButton.tsx b/app/src/components/ExportAllMsgButton.tsx
new file mode 100644
index 0000000..6bc0ccd
--- /dev/null
+++ b/app/src/components/ExportAllMsgButton.tsx
@@ -0,0 +1,178 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "./ui/dialog.tsx";
+import { Image, MenuSquare, PanelRight, ClipboardPaste } from "lucide-react";
+import { useTranslation } from "react-i18next";
+import "@/assets/common/editor.less";
+import MarkdownExport from "./Markdown.tsx";
+import React, { useMemo, useState } from "react";
+import { Toggle } from "./ui/toggle.tsx";
+import { mobile } from "@/utils/device.ts";
+import { ChatAction } from "@/components/home/assemblies/ChatAction.tsx";
+import { cn } from "@/components/ui/lib/utils.ts";
+import { Message } from "@/api/types.tsx";
+import {
+ useMessages,
+} from "@/store/chat.ts";
+
+type ExportAllMsgButtonProps = {
+ maxLength?: number;
+
+ formatter?: (value: string) => string;
+ title?: string;
+
+ open?: boolean;
+ setOpen?: (open: boolean) => void;
+ children?: React.ReactNode;
+
+ submittable?: boolean;
+ onSubmit?: (value: string) => void;
+ closeOnSubmit?: boolean;
+};
+
+function ExportAllMsgButtonCall({
+ formatter,
+}: ExportAllMsgButtonProps) {
+ const { t } = useTranslation();
+ const [openPreview, setOpenPreview] = useState(!mobile);
+ const [openInput, setOpenInput] = useState(true);
+ const messages: Message[] = useMessages();
+ const jsonArray = messages.map(message => ({
+ role: message.role,
+ content: message.content
+ }));
+ const jsonString = `\`\`\`json\n${JSON.stringify(jsonArray, null, 4)}\n\`\`\``;
+
+ function convertToMarkdown(jsonArray: { role: any; content: any; }[]) {
+ return jsonArray.map(({ role, content }) => {
+ const roleText = role === 'user' ? 'û˵' : 'AI˵';
+ return `## ${roleText}\n\n${content}`;
+ }).join('\n\n');
+ }
+
+ const markdownString = convertToMarkdown(jsonArray);
+ const markdownValue = useMemo(() => {
+ return formatter ? formatter(markdownString) : markdownString;
+ }, [markdownString, formatter]);
+
+
+
+
+ return (
+
+
+
+
{
+ setOpenPreview(false);
+ setOpenInput(true);
+ }}
+ >
+
+
+
+
{
+ setOpenPreview(true);
+ setOpenInput(true);
+ }}
+ >
+
+
+
+
{
+ setOpenPreview(true);
+ setOpenInput(false);
+ }}
+ >
+
+
+
+
+
+ {openInput && (
+
+
+
+ )}
+ {openPreview && (
+
+ )}
+
+
+
+
+ );
+}
+
+function ExportAllMsgButton(props: ExportAllMsgButtonProps) {
+ const { t } = useTranslation();
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export default ExportAllMsgButton;
+
+export function JSONTransMarkdownProvider({ ...props }: ExportAllMsgButtonProps) {
+ return (
+ `\`\`\`markdown\n${value}\n\`\`\``}
+
+ />
+ );
+}
\ No newline at end of file
diff --git a/app/src/components/Markdown.tsx b/app/src/components/Markdown.tsx
index d3f0ee8..2da5523 100644
--- a/app/src/components/Markdown.tsx
+++ b/app/src/components/Markdown.tsx
@@ -96,6 +96,52 @@ function Markdown({
[processedContent, acceptHtml, codeStyle, className, loading],
);
}
+
+type MarkdownExportProps = {
+ children: string;
+ className?: string;
+ acceptHtml?: boolean;
+ codeStyle?: string;
+ loading?: boolean;
+};
+function MarkdownExport({
+ children,
+ acceptHtml,
+ codeStyle,
+ className,
+ loading,
+}: MarkdownExportProps) {
+ const processedContent = useMemo(() => {
+ let content = children;
+
+ // Inline math: replace \(...\) with $ ... $
+ content = content.replace(/\\\((.*?)\\\)/g, (_, equation) => `$ ${equation.trim()} $`);
+
+ // Block math: replace \[...\] with $$...$$ on separate lines
+ content = content.replace(
+ /\s*\\\[\s*([\s\S]*?)\s*\\\]\s*/g,
+ (_, equation) => `\n$$\n${equation.trim()}\n$$\n`
+ );
+
+ return content;
+ }, [children]);
+
+ return useMemo(
+ () => (
+
+ ),
+ [processedContent, acceptHtml, codeStyle, className, loading],
+ );
+}
+export { MarkdownExport };
+
+
type CodeMarkdownProps = MarkdownProps & {
filename: string;
};
diff --git a/app/src/components/home/ChatWrapper.tsx b/app/src/components/home/ChatWrapper.tsx
index 16ff105..c9232bd 100644
--- a/app/src/components/home/ChatWrapper.tsx
+++ b/app/src/components/home/ChatWrapper.tsx
@@ -35,6 +35,7 @@ import ScrollAction from "@/components/home/assemblies/ScrollAction.tsx";
import { cn } from "@/components/ui/lib/utils.ts";
import { goAuth } from "@/utils/app.ts";
import { getModelFromId } from "@/conf/model.ts";
+import { JSONTransMarkdownProvider } from "@/components/ExportAllMsgButton.tsx";
type InterfaceProps = {
scrollable: boolean;
@@ -180,6 +181,7 @@ function ChatWrapper() {
+
diff --git a/app/src/components/home/assemblies/ChatAction.tsx b/app/src/components/home/assemblies/ChatAction.tsx
index 4d80037..80f7742 100644
--- a/app/src/components/home/assemblies/ChatAction.tsx
+++ b/app/src/components/home/assemblies/ChatAction.tsx
@@ -108,6 +108,19 @@ export function SettingsAction() {
);
}
+export function ExportButtonAction() {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ return (
+ dispatch(openDialog())}
+ >
+
+
+ );
+}
export function MarketAction() {
const { t } = useTranslation();
diff --git a/app/src/resources/i18n/cn.json b/app/src/resources/i18n/cn.json
index 8dfd01b..4b82ec0 100644
--- a/app/src/resources/i18n/cn.json
+++ b/app/src/resources/i18n/cn.json
@@ -328,6 +328,11 @@
"update-success": "更新成功",
"update-success-prompt": "您已更新至最新版本。"
},
+ "export":{
+ "text": "导出文本",
+ "user_used": "用户自己导出",
+ "pdf": "导出成pdf"
+ },
"share": {
"title": "分享",
"share-conversation": "分享对话",