Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
| 桌面框架 | <a href="https://go.dev/" target="_blank" rel="noreferrer">Go</a> / <a href="https://v3alpha.wails.io/" target="_blank" rel="noreferrer">Wails 3</a> / <a href="https://react.dev/" target="_blank" rel="noreferrer">React</a> |
| 本地存储 | <a href="https://www.sqlite.org/" target="_blank" rel="noreferrer">SQLite</a> / <a href="https://bun.uptrace.dev/" target="_blank" rel="noreferrer">bun</a> / <a href="https://github.com/asg017/sqlite-vec" target="_blank" rel="noreferrer">sqlite-vec</a> |
| 媒体处理 | <a href="https://github.com/yt-dlp/yt-dlp" target="_blank" rel="noreferrer">yt-dlp</a> / <a href="https://ffmpeg.org/" target="_blank" rel="noreferrer">FFmpeg</a> |
| 浏览器自动化 | <a href="https://playwright.dev/" target="_blank" rel="noreferrer">Playwright</a> |
| 浏览器自动化 | <a href="https://chromedevtools.github.io/devtools-protocol/" target="_blank" rel="noreferrer">Chrome DevTools Protocol</a> / <a href="https://github.com/chromedp/chromedp" target="_blank" rel="noreferrer">chromedp</a> |
| 渠道接入 | <a href="https://telegram.org/" target="_blank" rel="noreferrer">Telegram</a> / <a href="https://github.com/mymmrac/telego" target="_blank" rel="noreferrer">telego</a> |

正是这些项目与它们背后的维护者,让追创作能够在桌面、媒体处理、自动化与渠道接入之间建立起一条持续演进的工作链路。
Expand Down
2 changes: 1 addition & 1 deletion README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ DreamCreator is built on top of a number of excellent open-source projects and s
| Desktop Framework | <a href="https://go.dev/" target="_blank" rel="noreferrer">Go</a> / <a href="https://v3alpha.wails.io/" target="_blank" rel="noreferrer">Wails 3</a> / <a href="https://react.dev/" target="_blank" rel="noreferrer">React</a> |
| Local Storage | <a href="https://www.sqlite.org/" target="_blank" rel="noreferrer">SQLite</a> / <a href="https://bun.uptrace.dev/" target="_blank" rel="noreferrer">bun</a> / <a href="https://github.com/asg017/sqlite-vec" target="_blank" rel="noreferrer">sqlite-vec</a> |
| Media Processing | <a href="https://github.com/yt-dlp/yt-dlp" target="_blank" rel="noreferrer">yt-dlp</a> / <a href="https://ffmpeg.org/" target="_blank" rel="noreferrer">FFmpeg</a> |
| Browser Automation | <a href="https://playwright.dev/" target="_blank" rel="noreferrer">Playwright</a> |
| Browser Automation | <a href="https://chromedevtools.github.io/devtools-protocol/" target="_blank" rel="noreferrer">Chrome DevTools Protocol</a> / <a href="https://github.com/chromedp/chromedp" target="_blank" rel="noreferrer">chromedp</a> |
| Channel Access | <a href="https://telegram.org/" target="_blank" rel="noreferrer">Telegram</a> / <a href="https://github.com/mymmrac/telego" target="_blank" rel="noreferrer">telego</a> |

These projects, and the maintainers behind them, make it possible for DreamCreator to connect desktop workflows, media processing, automation, and channel access into one evolving system.
Expand Down
5 changes: 4 additions & 1 deletion frontend/scripts/audit-i18n.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ const englishStylePreservePhrases = [
"Apple",
"macOS",
"iOS",
"Chrome",
"Chromium",
"Edge",
"Brave",
"Chat Completions",
"OpenAI",
"OpenRouter",
"Bun",
"ClawHub",
"DreamCreator",
"Playwright",
"FFmpeg",
"FontGet",
"LibASS",
Expand Down
1 change: 1 addition & 0 deletions frontend/scripts/audit-structure.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const oversizeBaseline = {
"frontend/src/shared/contracts/library.ts": 1300,
"internal/application/channels/telegram/bot_service.go": 5000,
"internal/application/gateway/tools/browser_tools.go": 3150,
"internal/application/browsercdp/session.go": 3362,
"internal/application/library/service/service.go": 2750,
"internal/application/gateway/cron/scheduler.go": 2650,
"internal/application/memory/service/service.go": 2300,
Expand Down
8 changes: 1 addition & 7 deletions frontend/src/app/main/MainApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,12 +393,6 @@ export function MainApp() {
</div>
) : undefined;

const isAppUpdateAvailable =
updateInfo.status === "available" ||
updateInfo.status === "downloading" ||
updateInfo.status === "installing" ||
updateInfo.status === "ready_to_restart";

const isExternalToolsUpdateAvailable = useMemo(() => {
const tools = externalTools.data ?? [];
const updates = externalToolUpdates.data ?? [];
Expand Down Expand Up @@ -444,7 +438,7 @@ export function MainApp() {
onSelectThread={assistantUiEnabled ? handleSelectThread : undefined}
highlightThreadActive={assistantUiEnabled && visibleActiveTarget.type === "thread"}
showThreadList={assistantUiEnabled}
isAppUpdateAvailable={isAppUpdateAvailable}
appUpdateInfo={updateInfo}
isExternalToolsUpdateAvailable={isExternalToolsUpdateAvailable}
showTitleBarBorder={showTitleBarBorder}
contentScrollable={!isMainFamilyPage}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/settings/SettingsApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
useTestProxy,
useUpdateSettings,
} from "@/shared/query/settings";
import { useGatewayTools } from "@/shared/query/tools";
import { useI18n } from "@/shared/i18n";
import { useAssistantUiMode } from "@/shared/store/assistantUi";
import { useDebugMode } from "@/shared/store/debug";
Expand Down Expand Up @@ -167,6 +168,7 @@ export function SettingsApp() {
const { enabled: debugEnabled } = useDebugMode();
const { enabled: assistantUiEnabled } = useAssistantUiMode();
const isWindows = System.IsWindows();
useGatewayTools(assistantUiEnabled);

const [activeSection, setActiveSection] = React.useState<SettingsSectionId>("gateway");
const [memoryTab, setMemoryTab] = React.useState<MemoryTabId>("summary");
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/layout/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { Button } from "@/shared/ui/button";
import { AppSidebar } from "@/components/layout/AppSidebar";
import { TitleBar } from "@/components/layout/TitleBar";
import { WhatsNewDialog } from "@/features/update/WhatsNewDialog";
import { useI18n } from "@/shared/i18n";
import { MessageHost } from "@/shared/message/MessageHost";
import { cn } from "@/lib/utils";
Expand Down Expand Up @@ -43,7 +44,7 @@ export interface AppShellProps {
onSelectThread?: (threadId: string) => void;
highlightThreadActive?: boolean;
showThreadList?: boolean;
isAppUpdateAvailable?: boolean;
appUpdateInfo?: import("@/shared/store/update").UpdateInfo;
isExternalToolsUpdateAvailable?: boolean;
noticeUnreadCount?: number;
isNoticePanelOpen?: boolean;
Expand Down Expand Up @@ -75,7 +76,7 @@ function AppShellLayout({
onSelectThread,
highlightThreadActive,
showThreadList,
isAppUpdateAvailable,
appUpdateInfo,
isExternalToolsUpdateAvailable,
noticeUnreadCount,
isNoticePanelOpen,
Expand Down Expand Up @@ -184,7 +185,7 @@ function AppShellLayout({
onSelectThread={onSelectThread}
highlightThreadActive={highlightThreadActive}
showThreadList={showThreadList}
isAppUpdateAvailable={isAppUpdateAvailable}
appUpdateInfo={appUpdateInfo}
isExternalToolsUpdateAvailable={isExternalToolsUpdateAvailable}
noticeUnreadCount={noticeUnreadCount}
isNoticePanelOpen={isNoticePanelOpen}
Expand Down Expand Up @@ -216,6 +217,7 @@ function AppShellLayout({
/>
)}
<MessageHost />
<WhatsNewDialog activeWindow={activeWindow} autoOpen={activeWindow === "main"} />
<div
className={cn(
"flex min-h-0 flex-1 flex-col bg-transparent",
Expand Down
56 changes: 49 additions & 7 deletions frontend/src/components/layout/AppSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ import { SetupCenterDialog, SetupStatusSlot, useSetupCenter, useSetupStatus } fr
import { useI18n } from "@/shared/i18n";
import { messageBus } from "@/shared/message";
import { useCurrentUserProfile } from "@/shared/query/system";
import { useRestartToApply } from "@/shared/query/update";
import { cn } from "@/lib/utils";
import { hasPreparedUpdate, hasRemoteUpdate, type UpdateInfo } from "@/shared/store/update";
import { useAssistantUiMode } from "@/shared/store/assistantUi";
import { useSettingsStore } from "@/shared/store/settings";
import { useThreadStore } from "@/shared/store/threads";
Expand Down Expand Up @@ -96,7 +98,7 @@ export interface AppSidebarProps {
onSelectThread?: (threadId: string) => void;
highlightThreadActive?: boolean;
showThreadList?: boolean;
isAppUpdateAvailable?: boolean;
appUpdateInfo?: UpdateInfo;
isExternalToolsUpdateAvailable?: boolean;
noticeUnreadCount?: number;
isNoticePanelOpen?: boolean;
Expand Down Expand Up @@ -482,7 +484,7 @@ export function AppSidebar({
onSelectThread,
highlightThreadActive = true,
showThreadList = true,
isAppUpdateAvailable,
appUpdateInfo,
isExternalToolsUpdateAvailable,
noticeUnreadCount = 0,
isNoticePanelOpen = false,
Expand All @@ -495,14 +497,25 @@ export function AppSidebar({
const settingsLoading = useSettingsStore((state) => state.isLoading);
const { open: isSetupCenterOpen, setOpen: setSetupCenterOpen } = useSetupCenter();
const currentUserProfileQuery = useCurrentUserProfile();
const restartToApply = useRestartToApply();
const [search, setSearch] = React.useState("");
const deferredSearch = React.useDeferredValue(search);
const [isProductModeOpen, setIsProductModeOpen] = React.useState(false);
const loadMoreRef = React.useRef<HTMLDivElement | null>(null);
const setupAutoOpenInitializedRef = React.useRef(false);
const [renderLimit, setRenderLimit] = React.useState(200);

const hasUpdateMenu = Boolean(isAppUpdateAvailable || isExternalToolsUpdateAvailable);
const isAppUpdateReadyToRestart =
Boolean(appUpdateInfo) &&
appUpdateInfo?.status === "ready_to_restart" &&
hasPreparedUpdate(appUpdateInfo);
const isAppUpdateMenuVisible =
Boolean(appUpdateInfo) &&
(isAppUpdateReadyToRestart ||
(appUpdateInfo ? hasRemoteUpdate(appUpdateInfo) : false) ||
appUpdateInfo?.status === "downloading" ||
appUpdateInfo?.status === "installing");
const hasUpdateMenu = Boolean(isAppUpdateMenuVisible || isExternalToolsUpdateAvailable);
const normalizedSearch = deferredSearch.trim().toLowerCase();
const currentUserProfile = currentUserProfileQuery.data;
const currentUserName = resolveUserDisplayName(currentUserProfile);
Expand Down Expand Up @@ -676,16 +689,36 @@ export function AppSidebar({
onOpenSettings?.();
};

const handleRestartPreparedUpdate = React.useCallback(() => {
void restartToApply.mutateAsync().catch((error) => {
const message = error instanceof Error ? error.message : String(error);
messageBus.publishToast({
intent: "warning",
title: t("sidebar.footer.menu.restartAndUpdate"),
description: message,
});
});
}, [restartToApply, t]);

const updateMenuItems = React.useMemo(
() =>
[
isAppUpdateAvailable
isAppUpdateMenuVisible
? {
key: "app-update",
label: t("sidebar.footer.menu.appUpdate"),
label: isAppUpdateReadyToRestart
? t("sidebar.footer.menu.restartAndUpdate")
: t("sidebar.footer.menu.appUpdate"),
Icon: ArrowUpCircle,
iconClassName: "text-primary",
onSelect: () => handleSelectSettings("about"),
onSelect: () => {
if (isAppUpdateReadyToRestart) {
handleRestartPreparedUpdate();
return;
}
handleSelectSettings("about");
},
disabled: restartToApply.isPending,
}
: null,
isExternalToolsUpdateAvailable
Expand All @@ -695,10 +728,18 @@ export function AppSidebar({
Icon: Wrench,
iconClassName: "text-primary",
onSelect: () => handleSelectSettings("external-tools"),
disabled: false,
}
: null,
].filter((item): item is NonNullable<typeof item> => Boolean(item)),
[isAppUpdateAvailable, isExternalToolsUpdateAvailable, t]
[
handleRestartPreparedUpdate,
isAppUpdateMenuVisible,
isAppUpdateReadyToRestart,
isExternalToolsUpdateAvailable,
restartToApply.isPending,
t,
]
);

const handleSelectProductMode = (nextEnabled: boolean) => {
Expand Down Expand Up @@ -921,6 +962,7 @@ export function AppSidebar({
key={item.key}
className={footerDropdownItemClassName}
onSelect={item.onSelect}
disabled={item.disabled}
>
<div className={cn("flex h-4 w-4 shrink-0 items-center justify-center", item.iconClassName)}>
<item.Icon className={cn("h-4 w-4", item.iconClassName)} />
Expand Down
52 changes: 17 additions & 35 deletions frontend/src/features/chat/tool-ui/fallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import * as React from "react";
import type { ToolCallMessagePart, ToolCallMessagePartStatus } from "@assistant-ui/react";

import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
import { ApprovalCard } from "@/components/tool-ui/approval-card";
import { cn } from "@/lib/utils";
import { useI18n } from "@/shared/i18n";
import { messageBus } from "@/shared/message";
import { requestGateway } from "@/shared/realtime";
import { Button } from "@/shared/ui/button";
import { DASHBOARD_PANEL_SURFACE_CLASS } from "@/shared/ui/dashboard";

export type ToolUIFallbackCardProps = {
Expand Down Expand Up @@ -136,30 +136,24 @@ export function ToolUIFallbackCard({
resolvedStatus.type === "requires-action" && approvalID !== "" && approvalDecision === "";
const isCancelled =
resolvedStatus.type === "incomplete" && resolvedStatus.reason === "cancelled";
const approvalTitle = approval?.action?.trim() || t("chat.tools.approvalTool.title");
const approvalToolName = approval?.toolName?.trim() || resolvedToolName;
const approvalDescription = `${t("chat.tools.approvalTool.tool")}: ${approvalToolName}`;

const [open, setOpen] = React.useState(false);
const [pendingDecision, setPendingDecision] = React.useState<"approve" | "deny" | "">("");

React.useEffect(() => {
if (!requiresApprovalAction) {
setPendingDecision("");
}
}, [requiresApprovalAction, approvalID]);

const resolveApproval = React.useCallback(
async (decision: "approve" | "deny") => {
if (!approvalID || pendingDecision) {
if (!approvalID) {
return;
}
setPendingDecision(decision);
try {
await requestGateway("exec.approval.resolve", {
id: approvalID,
decision,
reason: decision === "approve" ? "approved by aui fallback" : "denied by aui fallback",
});
} catch (error) {
setPendingDecision("");
messageBus.publishToast({
intent: "danger",
title: t("chat.tools.approvalTool.resolveError"),
Expand All @@ -168,7 +162,7 @@ export function ToolUIFallbackCard({
});
}
},
[approvalID, pendingDecision, t]
[approvalID, t]
);

return (
Expand All @@ -182,29 +176,17 @@ export function ToolUIFallbackCard({
>
<ToolFallback.Trigger toolName={resolvedToolName} status={resolvedStatus} />
{requiresApprovalAction ? (
<div className="flex items-center justify-end gap-2 px-4 pt-2">
<Button
type="button"
size="sm"
variant="default"
disabled={pendingDecision !== ""}
onClick={() => {
void resolveApproval("approve");
}}
>
{t("chat.tools.approvalTool.approveAction")}
</Button>
<Button
type="button"
size="sm"
variant="outline"
disabled={pendingDecision !== ""}
onClick={() => {
void resolveApproval("deny");
}}
>
{t("chat.tools.approvalTool.denyAction")}
</Button>
<div className="px-4 pt-2">
<ApprovalCard
id={`exec-approval-${approvalID}`}
title={approvalTitle}
description={approvalDescription}
icon="shield-check"
confirmLabel={t("chat.tools.approvalTool.approveAction")}
cancelLabel={t("chat.tools.approvalTool.denyAction")}
onConfirm={() => resolveApproval("approve")}
onCancel={() => resolveApproval("deny")}
/>
</div>
) : null}
<ToolFallback.Content>
Expand Down
Loading
Loading