0.3.3 外网下载修复
This commit is contained in:
@@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { clamp, MIN_AGENT_STEPS, MAX_AGENT_STEPS } from "@/lib/constants";
|
||||
|
||||
type ChatMsg = { role: "user" | "assistant"; content: string };
|
||||
type ToolLog =
|
||||
@@ -201,14 +202,14 @@ export function AiAgentPanel() {
|
||||
<input
|
||||
className="w-[72px] rounded border px-2 py-1 text-xs"
|
||||
type="number"
|
||||
min={1}
|
||||
max={24}
|
||||
min={MIN_AGENT_STEPS}
|
||||
max={MAX_AGENT_STEPS}
|
||||
step={1}
|
||||
value={maxSteps}
|
||||
onChange={(e) => {
|
||||
const v = Number(e.target.value);
|
||||
if (!Number.isFinite(v)) return;
|
||||
setMaxSteps(Math.max(1, Math.min(24, Math.floor(v))));
|
||||
setMaxSteps(clamp(Math.floor(v), MIN_AGENT_STEPS, MAX_AGENT_STEPS));
|
||||
}}
|
||||
disabled={running}
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/components/ui/sheet";
|
||||
import { clamp, MIN_AGENT_STEPS, MAX_AGENT_STEPS } from "@/lib/constants";
|
||||
|
||||
type AgentMessage = { role: "user" | "assistant"; content: string };
|
||||
|
||||
@@ -79,8 +80,6 @@ type ChatSession = {
|
||||
|
||||
type PanelPage = "chat" | "tools" | "history" | "account" | "settings";
|
||||
|
||||
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
|
||||
|
||||
const generateId = () => {
|
||||
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
||||
return `sess_${Math.random().toString(16).slice(2, 10)}`;
|
||||
@@ -194,8 +193,8 @@ export function DocumentAiAgentPanel({
|
||||
const p = (window.localStorage.getItem("doc_ai_provider") || "").trim();
|
||||
const m = window.localStorage.getItem("doc_ai_model") || "";
|
||||
const parsed = Number(stepsRaw);
|
||||
if (Number.isFinite(parsed) && parsed >= 1) {
|
||||
setMaxSteps(Math.max(1, Math.min(24, Math.floor(parsed))));
|
||||
if (Number.isFinite(parsed) && parsed >= MIN_AGENT_STEPS) {
|
||||
setMaxSteps(clamp(Math.floor(parsed), MIN_AGENT_STEPS, MAX_AGENT_STEPS));
|
||||
}
|
||||
if (p === "local" || p === "online") setAiProvider(p);
|
||||
if (typeof m === "string") setAiModel(m);
|
||||
@@ -270,7 +269,7 @@ export function DocumentAiAgentPanel({
|
||||
// ignore
|
||||
}
|
||||
// 只在 documentId 变化时读取一次
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
}, [documentId]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -688,14 +687,14 @@ export function DocumentAiAgentPanel({
|
||||
<input
|
||||
className="w-[72px] rounded border px-2 py-1 text-xs"
|
||||
type="number"
|
||||
min={1}
|
||||
max={24}
|
||||
min={MIN_AGENT_STEPS}
|
||||
max={MAX_AGENT_STEPS}
|
||||
step={1}
|
||||
value={maxSteps}
|
||||
onChange={(e) => {
|
||||
const v = Number(e.target.value);
|
||||
if (!Number.isFinite(v)) return;
|
||||
setMaxSteps(Math.max(1, Math.min(24, Math.floor(v))));
|
||||
setMaxSteps(clamp(Math.floor(v), MIN_AGENT_STEPS, MAX_AGENT_STEPS));
|
||||
}}
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
@@ -23,11 +23,12 @@ import { CustomSideMenu } from "./menus/CustomSideMenu";
|
||||
import { CustomSlashMenu } from "./menus/CustomSlashMenu";
|
||||
import { MoveEmbedPickerHost } from "@/components/documents/move-embed-picker-host";
|
||||
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { DocumentToc, type TocEntry } from "@/components/editor/document-toc";
|
||||
import { DocumentToc, type TocEntry } from "@/components/editor/document-toc";
|
||||
import { useSearchPaletteStore } from "@/store/search-palette";
|
||||
import { useEditorBridgeStore } from "@/store/editor-bridge";
|
||||
import type { ReferenceTarget } from "@/types/search";
|
||||
import FullScreenTableEditor from "@/components/online-table/FullScreenTableEditor";
|
||||
import { clamp, MIN_BLOCK_LEVEL, MAX_BLOCK_LEVEL } from "@/lib/constants";
|
||||
import { ASSETS_CHANGED_EVENT, emitAssetsChanged } from "@/lib/events";
|
||||
|
||||
interface BlockNoteEditorProps {
|
||||
@@ -64,7 +65,7 @@ const buildHeadingToc = (blocks: Block<CustomBlockSchema>[]): TocEntry[] => {
|
||||
const walk = (targetBlocks: Block<CustomBlockSchema>[]) => {
|
||||
targetBlocks.forEach((block) => {
|
||||
if (block.type === "heading") {
|
||||
const level = Math.min(5, Math.max(1, Number(block.props.level) || 1));
|
||||
const level = clamp(Number(block.props.level) || 1, MIN_BLOCK_LEVEL, MAX_BLOCK_LEVEL);
|
||||
counters[level - 1] += 1;
|
||||
for (let i = level; i < counters.length; i += 1) {
|
||||
counters[i] = 0;
|
||||
|
||||
@@ -656,9 +656,6 @@ const MediaBlockContent = ({ block, editor }: any) => {
|
||||
>
|
||||
查看原文件
|
||||
</DropdownMenuItem>
|
||||
{assetType === "file" && isOfficeDoc && (
|
||||
<DropdownMenuItem onClick={() => void openWithOnlyOffice()}>使用 ONLYOFFICE 打开</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
void downloadAsset();
|
||||
|
||||
@@ -273,7 +273,7 @@ export function MindmapAiAgentPanel({
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
}, [mindmapId]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2502,8 +2502,8 @@ const MindmapBlockView = ({
|
||||
const handleImageConfirm = async () => {
|
||||
const url = imageUrl.trim();
|
||||
// 获取原始图片尺寸,如果没有则使用默认值
|
||||
let width = Number(imageWidth) || 0;
|
||||
let height = Number(imageHeight) || 0;
|
||||
const width = Number(imageWidth) || 0;
|
||||
const height = Number(imageHeight) || 0;
|
||||
|
||||
if (!url) {
|
||||
window.alert("请输入图片链接");
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { ImagePickerProvider } from "@/components/media/image-picker-context";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { DocumentAiAgentPanel } from "./DocumentAiAgentPanel";
|
||||
import { CONTENT_LOADING_DELAY_MS } from "@/lib/constants";
|
||||
|
||||
const BlockNoteEditor = dynamic(
|
||||
() => import("@/components/editor/blocknote-editor").then((mod) => mod.BlockNoteEditor),
|
||||
@@ -96,7 +97,7 @@ export function DocumentContent({
|
||||
}
|
||||
}, [documentId, editorBridge, openTableId, router]);
|
||||
|
||||
/* eslint-disable react-hooks/set-state-in-effect */
|
||||
|
||||
useEffect(() => {
|
||||
setPageTitle(title ?? "无标题");
|
||||
}, [title]);
|
||||
@@ -108,7 +109,7 @@ export function DocumentContent({
|
||||
useEffect(() => {
|
||||
setStats(initialStats ?? defaultStats);
|
||||
}, [initialStats]);
|
||||
/* eslint-enable react-hooks/set-state-in-effect */
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
@@ -128,12 +129,12 @@ export function DocumentContent({
|
||||
clearTimeout(contentLoadingTimerRef.current);
|
||||
contentLoadingTimerRef.current = null;
|
||||
}
|
||||
// 避免“秒闪”的加载提示:只有当加载超过短阈值时才显示提示
|
||||
// 避免"秒闪"的加载提示:只有当加载超过短阈值时才显示提示
|
||||
contentLoadingTimerRef.current = setTimeout(() => {
|
||||
if (!canceled) {
|
||||
setShowContentLoadingIndicator(true);
|
||||
}
|
||||
}, 200);
|
||||
}, CONTENT_LOADING_DELAY_MS);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/documents/content?documentId=${encodeURIComponent(documentId)}`, {
|
||||
|
||||
@@ -503,7 +503,7 @@ const FullScreenTableEditor: React.FC<FullScreenTableEditorProps> = ({ tableId,
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
console.log("[FullScreenTableEditor] init luckysheet", { tableId, sheets: options.data });
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Bot, Settings, Wrench, X } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { clamp } from "@/lib/constants";
|
||||
|
||||
type AgentMessage = { role: "user" | "assistant"; content: string };
|
||||
|
||||
@@ -35,8 +36,6 @@ const ONLINE_MODELS = [
|
||||
"gemini-3-flash-preview",
|
||||
] as const;
|
||||
|
||||
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
|
||||
|
||||
const isRecord = (v: unknown): v is Record<string, unknown> =>
|
||||
typeof v === "object" && v !== null && !Array.isArray(v);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useSelectedLayoutSegments } from "next/navigation";
|
||||
import { useAuthActions } from "@convex-dev/auth/react";
|
||||
import {
|
||||
ArrowRightLeft,
|
||||
ArrowUpRight,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
LayoutGrid,
|
||||
Library,
|
||||
Link as LinkIcon,
|
||||
LogOut,
|
||||
MoreHorizontal,
|
||||
PanelRightOpen,
|
||||
Plus,
|
||||
@@ -178,6 +180,7 @@ function SidebarContent({ initialData, sidebarQuery }: SidebarContentProps) {
|
||||
const isLoading = sidebarQuery.isLoading;
|
||||
const segments = useSelectedLayoutSegments();
|
||||
const router = useRouter();
|
||||
const { signOut } = useAuthActions();
|
||||
const activeId = segments?.[1] ?? "";
|
||||
const editorBridge = useEditorBridgeStore((state) => state.bridge);
|
||||
|
||||
@@ -186,6 +189,7 @@ function SidebarContent({ initialData, sidebarQuery }: SidebarContentProps) {
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => collectNodeIds(tree));
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
||||
const [workspaceMenuOpen, setWorkspaceMenuOpen] = useState(false);
|
||||
const [signingOut, setSigningOut] = useState(false);
|
||||
const [trashOpen, setTrashOpen] = useState(false);
|
||||
const [trashSearch, setTrashSearch] = useState("");
|
||||
const [trashTab, setTrashTab] = useState<"documents" | "assets">("documents");
|
||||
@@ -1729,6 +1733,26 @@ function SidebarContent({ initialData, sidebarQuery }: SidebarContentProps) {
|
||||
[sidebarData.activeWorkspaceId, sidebarQuery],
|
||||
);
|
||||
|
||||
const handleSignOut = useCallback(async () => {
|
||||
if (signingOut) {
|
||||
return;
|
||||
}
|
||||
if (!window.confirm("确认退出登录吗?")) {
|
||||
return;
|
||||
}
|
||||
setSigningOut(true);
|
||||
try {
|
||||
await signOut();
|
||||
setWorkspaceMenuOpen(false);
|
||||
router.replace("/auth");
|
||||
router.refresh();
|
||||
} catch (error: any) {
|
||||
window.alert(`退出登录失败:${error?.message ?? "请稍后再试"}`);
|
||||
} finally {
|
||||
setSigningOut(false);
|
||||
}
|
||||
}, [router, signOut, signingOut]);
|
||||
|
||||
const openContextMenu = useCallback((event: React.MouseEvent, node: DocumentNode) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -1826,6 +1850,17 @@ function SidebarContent({ initialData, sidebarQuery }: SidebarContentProps) {
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
<div className="border-t border-[#f1f1f1] p-1">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm text-red-600 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
onClick={() => void handleSignOut()}
|
||||
disabled={signingOut}
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
<span>{signingOut ? "正在退出..." : "退出登录"}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user