Skip to content

Commit 9ef3724

Browse files
authored
optimize: fix exporting subs and videos (#41)
* optimize release notes * fix export subtitles and videos * support fonts refresh * optimize i18n en * optimized large files * optimize chat composer
1 parent 3350e42 commit 9ef3724

64 files changed

Lines changed: 10061 additions & 3909 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/release-drafter.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,27 @@ change-title-escapes: '\<*_&'
1616

1717
header: |
1818
<!-- dreamapp-release-header:start -->
19-
## 当前版本
19+
## 当前版本 / Current Version
2020
`v$RESOLVED_VERSION`
2121
22-
## 下载地址
23-
- macOS Apple Silicon: [点击下载](https://github.com/arnoldhao/dreamcreator/releases/download/v$RESOLVED_VERSION/dreamcreator-macos-arm64-$RESOLVED_VERSION.zip)
24-
- macOS Intel: [点击下载](https://github.com/arnoldhao/dreamcreator/releases/download/v$RESOLVED_VERSION/dreamcreator-macos-x64-$RESOLVED_VERSION.zip)
25-
- Windows 安装版: [点击下载](https://github.com/arnoldhao/dreamcreator/releases/download/v$RESOLVED_VERSION/dreamcreator-windows-x64-$RESOLVED_VERSION-installer.exe)
26-
- Windows 便携版: [点击下载](https://github.com/arnoldhao/dreamcreator/releases/download/v$RESOLVED_VERSION/dreamcreator-windows-x64-$RESOLVED_VERSION.zip)
22+
## 下载地址 / Downloads
23+
- macOS Apple Silicon: [点击下载 / Download](https://github.com/arnoldhao/dreamcreator/releases/download/v$RESOLVED_VERSION/dreamcreator-macos-arm64-$RESOLVED_VERSION.zip)
24+
- macOS Intel: [点击下载 / Download](https://github.com/arnoldhao/dreamcreator/releases/download/v$RESOLVED_VERSION/dreamcreator-macos-x64-$RESOLVED_VERSION.zip)
25+
- Windows 安装版 / Installer: [点击下载 / Download](https://github.com/arnoldhao/dreamcreator/releases/download/v$RESOLVED_VERSION/dreamcreator-windows-x64-$RESOLVED_VERSION-installer.exe)
26+
- Windows 便携版 / Portable: [点击下载 / Download](https://github.com/arnoldhao/dreamcreator/releases/download/v$RESOLVED_VERSION/dreamcreator-windows-x64-$RESOLVED_VERSION.zip)
2727
28-
## macOS 首次运行说明
28+
## macOS 首次运行说明 / First Launch on macOS
2929
将 `DreamCreator.app` 移动到“应用程序”目录后,若提示“无法打开”或“已损坏”,请在终端执行:
30+
Move `DreamCreator.app` to the Applications folder. If macOS says the app can't be opened or is damaged, run:
3031
3132
`sudo xattr -rd com.apple.quarantine /Applications/DreamCreator.app`
33+
34+
---
3235
<!-- dreamapp-release-header:end -->
3336
3437
template: |
38+
## 版本变更 / Changelog
39+
3540
$CHANGES
3641
3742
version-resolver:

frontend/scripts/audit-i18n.mjs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@ const rootDir = process.cwd();
66
const strict = process.argv.includes("--strict");
77
const jsonOnly = process.argv.includes("--json");
88
const pruneUnused = process.argv.includes("--prune-unused");
9+
const fixEnglishStyle = process.argv.includes("--fix-english-style");
910

1011
const localeDir = path.join(rootDir, "src", "shared", "i18n", "locales");
1112
const sourceDir = path.join(rootDir, "src");
12-
const englishStyleSkipKeys = new Set(["app.name"]);
13+
const englishStyleSkipKeys = new Set([
14+
"app.name",
15+
"library.config.subtitleStyles.exportProfileStyleDocument",
16+
"library.workspace.header.videoEditing",
17+
"library.workspace.header.subtitleEditing",
18+
"library.workspace.header.speechToSubtitle",
19+
"library.workspace.dialogs.exportSubtitle.style",
20+
]);
1321
const englishStylePreservePhrases = [
1422
"Dream Creator",
1523
"Apple",
@@ -148,19 +156,12 @@ const englishTitleCaseLowerWords = new Set([
148156
"via",
149157
]);
150158
const englishSentenceBoundaryChars = new Set([".", "!", "?"]);
159+
// Apple HIG defines title-style capitalization for compact actionable controls like
160+
// buttons and menu items, but uses sentence-style capitalization for labels and most prose.
151161
const englishTitleStyleExplicitPatterns = [
152162
/^app\.settings\.title\./,
153-
/^settings\.gateway\.detailsPanel\.sections\./,
154-
/^settings\.gateway\.detailsPanel\.(contextTabs|httpTabs|talkTabs|heartbeatTabs)\./,
155-
/^settings\.gateway\.detailsPanel\.(gateway\.controlPlane|runtime\.(maxSteps|toolLoop\.(enabled|warn|critical|global|history|detectorGeneric|detectorPoll|detectorPingPong)|contextWarn|contextHard|compactionMode|compactionReserveTokens|compactionKeepRecent|compactionReserveFloor|compactionMaxHistoryShare|compactionMemoryFlushEnabled|compactionMemoryFlushSoft|compactionMemoryFlushPrompt|compactionMemoryFlushSystem)|queue\.(globalConcurrency|sessionConcurrency|laneMain|laneSubagent|laneCron)|cron\.(enabled|maxConcurrentRuns|sessionRetention|runLogMaxBytes|runLogKeepLines)|heartbeat\.(enabled|periodicEnabled|runSession|notificationCenter|openNotifications|triggerLabel|lastStatus\.label|lastNotice\.label|promptAppend|includeReasoning|suppressToolErrorWarnings|activeStart|activeEnd|activeTimezone|trigger|spec\.items|spec\.updatedAt)|subagents\.(maxDepth|maxChildren|maxConcurrent|model)|http\.(maxBodyBytes|maxUrlParts|files\.(urlAllowlist|allowedMimes|maxBytes|maxChars|maxRedirects|pdfMaxPages|pdfMaxPixels|pdfMinTextChars)|images\.(urlAllowlist|allowedMimes|maxBytes|maxRedirects))|channelHealth\.minutes|voice\.(enabled)|voiceWake\.(enabled)|talk\.(voiceAliases|outputFormat|apiKey|interruptOnSpeech)|tts\.(status|provider|voiceId|modelId|format)|voiceWakeTriggers\.label)$/,
156-
/^settings\.gateway\.(change3davatar\.pickTitle|change3dmotion\.pickTitle|changeName\.title|readiness\.(readyTitle|incompleteTitle)|model\.(agentTitle|embeddingTitle|imageTitle))$/,
157-
/^settings\.(memory\.summary\.title|usage\.overview\.title|general\.download\.dialogTitle|general\.proxy\.dialogTitle|general\.advanced\.menuBarVisibility\.label|calls\.skills\.sources\.emptyTitle|calls\.skills\.security\.groups\.(package_write|deps_write|config_write|source_write)\.label|tools\.browserControl\.ssrfSection|skills\.listTitle|connectors\.loginTitle|externalTools\.releaseNotesTitle|provider\.models\.manage\.title|provider\.models\.manage\.invalidTitle|provider\.custom\.title|provider\.delete\.title|about\.advanced\.unlockedTitle)$/,
158-
/^settings\.integration\.channels\.(config\.groups\.columns\.requireMention|reset\.button)$/,
159-
/^cron\.(runs\.detailTitle|dialog\.(deleteTitle|bulkDeleteTitle|editTitle|viewTitle|newTitle)|columns\.|overview\.chart\.title$)/,
160-
/^library\.(columns\.|tools\.optionalTitle|resources\.(libraryInfoTitle|fileInfoTitle|recordInfoTitle|currentRecordsTitle|deleteLibraryTitle)|download\.(title|inputTitle)|task\.(deleteFilesTitle|deleteSuccessTitle|deleteFailedTitle|bulkDeleteSuccessTitle|bulkDeleteFailedTitle|failedCheckTitle)|file\.(deleteFilesTitle|deleteSuccessTitle|deleteFailedTitle|bulkDeleteSuccessTitle|bulkDeleteFailedTitle)|rowMenu\.renameTitle|overview\.chart\.title|preview\.imageTitle|import\.(videoPickerTitle|subtitlePickerTitle|unsupportedTitle|dialog\.targetTitle)|config\.(saveFailedTitle|taskRuntime\.(translateTitle|proofreadTitle)|videoExportPresets\.(savedTitle|saveFailedTitle|deletedTitle|deleteFailedTitle)|subtitleStyles\.(allStylesTitle|createFormTitle|unsavedTitle|importFailedTitle|importGuideTitle|exportSucceededTitle|exportFailedTitle|previewInfoTitle|monoStyleSectionTitle|bilingualMetaSectionTitle|primarySourceTitle|secondarySourceTitle|primaryStyleTitle|secondaryStyleTitle|overviewTitle|defaultsTitle|deliveryReadinessTitle|fontManagementTitle|referencedFontsTitle|styleSourcesTitle|browseSourceFailedTitle|fontSourcesTitle|syncFontSourceSuccessTitle|syncFontSourceFailedTitle|installUserFontSuccessTitle|installUserFontFailedTitle|installMachineFontSuccessTitle|installMachineFontFailedTitle|importSourceItemSuccessTitle|importSourceItemFailedTitle))|workspace\.(emptyTitle|table\.(timelineTitle|editorTitle)|dialogs\.(exportVideo\.(subtitleHandlingTitle|trackMappingTitle)|languageTask\.(title|qaRealtimeTitle|issueBreakdownTitle|restoreOriginalTitle)|importSubtitle\.(title|normalizationTitle|guidelineInheritanceTitle))|preview\.placeholderTitle|waveform\.title|review\.(lockedTitle|applySuccessTitle|applyFailedTitle|discardSuccessTitle|discardFailedTitle)|notifications\.(translationReadyTitle|proofreadReadyTitle|qaReviewReadyTitle|saveFailedTitle|noSubtitleTrackTitle|originalRestoredTitle|restoreFailedTitle|noFileSelectedTitle|openFileFailedTitle|noVideoSelectedTitle|noPresetSelectedTitle|exportQueuedTitle|exportFailedTitle|moduleConfigUnavailableTitle|promptProfileSavedTitle|savePromptProfileFailedTitle|translationQueuedTitle|translationFailedTitle|proofreadQueuedTitle|proofreadFailedTitle|importProfileReadyTitle)))$/,
161-
/^(gateway\.logs\.title|debug\.(status\.title|channels\.title|message\.frontend\.(toastTitle|notificationTitle|dialogTitle)|message\.realtime\.notifyTitle)|chat\.composer\.attachDialogTitle|chat\.welcome\.entry\.items\.(assistant|providers|model|generic)\.action|chat\.tools\.approvalTool\.title|productMode\.options\.(full|download)\.(title|action)|notifications\.footer\.codes\.(appUpdate|externalToolsUpdate)\.title|notifications\.empty\.title)$/,
162163
];
163-
const englishTitleStyleHeuristicPattern = /(\.title$|Title$|\.label$|\.button$|\.action$|columns\.|Tabs\.|sections\.|Section$|dialogTitle$)/;
164+
const englishTitleStyleHeuristicPattern = /(\.button$|\.action$|columns\.|Tabs\.|sections\.|Section$|dialogTitle$|pickTitle$)/;
164165
const englishTitleStyleSentencePattern = /\?|\.{1,3}$|\b(is|are|was|were|am|can't|cannot|need|needs|shows?\s+up|please)\b/i;
165166
const zhGlossaryReplacements = [
166167
["External Tools", "外部工具"],
@@ -333,9 +334,6 @@ function shouldUseEnglishTitleCase(key, value) {
333334
if (englishTitleStyleSentencePattern.test(value)) {
334335
return false;
335336
}
336-
if (/^chat\.welcome\.entry\./.test(key) || /^notifications\.center\.codes\./.test(key) || /^productMode\.title$/.test(key)) {
337-
return false;
338-
}
339337
return true;
340338
}
341339

@@ -471,7 +469,24 @@ function filterLocaleTree(input, usedSet, prefix = "") {
471469
return output;
472470
}
473471

474-
const enSource = JSON.parse(fs.readFileSync(path.join(localeDir, "en.json"), "utf8"));
472+
function mapLocaleTree(input, mapper, prefix = "") {
473+
const output = {};
474+
for (const [key, value] of Object.entries(input)) {
475+
const nextKey = prefix ? `${prefix}.${key}` : key;
476+
if (value && typeof value === "object" && !Array.isArray(value)) {
477+
output[key] = mapLocaleTree(value, mapper, nextKey);
478+
continue;
479+
}
480+
output[key] = mapper(nextKey, String(value));
481+
}
482+
return output;
483+
}
484+
485+
let enSource = JSON.parse(fs.readFileSync(path.join(localeDir, "en.json"), "utf8"));
486+
if (fixEnglishStyle) {
487+
enSource = mapLocaleTree(enSource, normalizeEnglishLocaleValue);
488+
fs.writeFileSync(path.join(localeDir, "en.json"), `${JSON.stringify(enSource, null, 2)}\n`);
489+
}
475490
const zhSource = JSON.parse(fs.readFileSync(path.join(localeDir, "zh-CN.json"), "utf8"));
476491
const en = flatten(enSource);
477492
const zh = flatten(zhSource);

frontend/src/app/ws/client.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export class WebSocketClient {
175175
if (this.reconnectTimer || !this.shouldReconnect) {
176176
return;
177177
}
178-
this.onMetric?.("reconnect");
178+
this.emitMetric("reconnect");
179179
this.reconnectTimer = window.setTimeout(() => {
180180
this.reconnectTimer = null;
181181
this.connect();
@@ -235,35 +235,45 @@ export class WebSocketClient {
235235
if (seq > 0) {
236236
const lastSeq = this.topicSeq.get(realtimeEvent.topic) ?? 0;
237237
if (seq <= lastSeq) {
238-
this.onMetric?.("duplicate-drop");
238+
this.emitMetric("duplicate-drop");
239239
return;
240240
}
241241
this.topicSeq.set(realtimeEvent.topic, seq);
242242
}
243243

244244
if (realtimeEvent.replay) {
245-
this.onMetric?.("replay");
245+
this.emitMetric("replay");
246246
}
247247
if ((realtimeEvent.type ?? "").trim().toLowerCase() === "resync-required") {
248-
this.onMetric?.("resync-required");
248+
this.emitMetric("resync-required");
249249
}
250250

251-
this.onMessage?.(realtimeEvent);
251+
this.emitMessage(realtimeEvent);
252252

253253
const handlers = this.subscriptions.get(realtimeEvent.topic);
254254
if (!handlers || handlers.size === 0) {
255255
return;
256256
}
257257

258-
handlers.forEach((handler) => handler(realtimeEvent));
258+
handlers.forEach((handler) => {
259+
try {
260+
handler(realtimeEvent);
261+
} catch (error) {
262+
console.error("[ws] subscription handler failed", error);
263+
}
264+
});
259265
}
260266

261267
private setStatus(status: ConnectionStatus) {
262268
if (this.status === status) {
263269
return;
264270
}
265271
this.status = status;
266-
this.onStatusChange?.(status);
272+
try {
273+
this.onStatusChange?.(status);
274+
} catch (error) {
275+
console.error("[ws] status handler failed", error);
276+
}
267277
}
268278

269279
private clearPending(reason: Error) {
@@ -273,4 +283,20 @@ export class WebSocketClient {
273283
});
274284
this.pending.clear();
275285
}
286+
287+
private emitMetric(kind: "reconnect" | "replay" | "resync-required" | "duplicate-drop") {
288+
try {
289+
this.onMetric?.(kind);
290+
} catch (error) {
291+
console.error("[ws] metric handler failed", error);
292+
}
293+
}
294+
295+
private emitMessage(event: RealtimeEvent) {
296+
try {
297+
this.onMessage?.(event);
298+
} catch (error) {
299+
console.error("[ws] message handler failed", error);
300+
}
301+
}
276302
}

frontend/src/features/chat/components/ComposerBar.tsx

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ const assistantSelectLabel = (assistant: Assistant, fallback: string) => {
238238
return emoji ? `${emoji} ${name}` : name;
239239
};
240240

241+
const assistantSelectEmoji = (assistant: Assistant | null | undefined) =>
242+
assistant?.identity?.emoji?.trim() || "";
243+
241244
function ThemedProviderIcon({ icon, className }: { icon: string; className?: string }) {
242245
return (
243246
<span
@@ -532,6 +535,8 @@ function AssistantModelDropdown({
532535
const selectedAssistantLabel = selectedAssistant
533536
? assistantSelectLabel(selectedAssistant, t("chat.composer.untitledAssistant"))
534537
: t("chat.composer.selectAssistant");
538+
const selectedAssistantEmoji = assistantSelectEmoji(selectedAssistant);
539+
const selectedAssistantTriggerText = selectedAssistantEmoji || selectedAssistantLabel;
535540
return (
536541
<AuiSelectRoot
537542
value={assistantId}
@@ -542,10 +547,18 @@ function AssistantModelDropdown({
542547
variant="ghost"
543548
size="sm"
544549
title={selectedAssistantLabel}
545-
className="w-fit min-w-0 max-w-full focus-visible:ring-0 focus-visible:ring-offset-0"
550+
aria-label={selectedAssistantLabel}
551+
className="w-fit min-w-0 max-w-full focus-visible:ring-0 focus-visible:ring-offset-0 [&>svg]:hidden"
546552
style={ASSISTANT_TRIGGER_MAX_WIDTH_STYLE}
547553
>
548-
<span className="min-w-0 truncate">{selectedAssistantLabel}</span>
554+
<span
555+
className={cn(
556+
"min-w-0 truncate",
557+
selectedAssistantEmoji ? "text-base leading-none" : undefined
558+
)}
559+
>
560+
{selectedAssistantTriggerText}
561+
</span>
549562
</AuiSelectTrigger>
550563
<AuiSelectContent align="center" className="max-w-[320px]">
551564
{assistants.length === 0 ? (
@@ -577,7 +590,7 @@ function AssistantModelDropdown({
577590
variant="ghost"
578591
size="sm"
579592
title={selectedModelLabel}
580-
className="w-fit min-w-0 max-w-full focus-visible:ring-0 focus-visible:ring-offset-0"
593+
className="w-fit min-w-0 max-w-full focus-visible:ring-0 focus-visible:ring-offset-0 [&>svg]:hidden"
581594
style={MODEL_TRIGGER_MAX_WIDTH_STYLE}
582595
disabled={disabled || !selectedAssistant || updateAssistant.isPending}
583596
>
@@ -623,13 +636,13 @@ function PermissionModeSelector({
623636
variant="ghost"
624637
size="sm"
625638
title={title}
626-
className="w-fit min-w-0"
639+
aria-label={title}
640+
className="w-fit min-w-0 [&>svg]:hidden"
627641
style={PERMISSION_TRIGGER_MAX_WIDTH_STYLE}
628642
>
629-
<div className="flex min-w-0 items-center gap-1.5 whitespace-nowrap">
643+
<span className="inline-flex items-center justify-center">
630644
<selected.Icon className="size-3.5 text-muted-foreground" />
631-
<span className="truncate">{selected.label}</span>
632-
</div>
645+
</span>
633646
</AuiSelectTrigger>
634647
<AuiSelectContent align="center" className="max-w-[260px]">
635648
{options.map((option) => (
@@ -976,17 +989,6 @@ export function ComposerBar({
976989
disabled={isRunning}
977990
mode="assistant"
978991
/>
979-
<AssistantModelDropdown
980-
assistants={assistants}
981-
assistantId={assistantId}
982-
selectedAssistant={selectedAssistant}
983-
setAssistantId={setAssistantId}
984-
modelGroups={modelGroups}
985-
agentProviderId={agentProviderId}
986-
agentModelName={agentModelName}
987-
disabled={isRunning}
988-
mode="model"
989-
/>
990992
<div className="shrink-0">
991993
<PermissionModeSelector
992994
value={execPermissionMode}
@@ -1029,6 +1031,17 @@ export function ComposerBar({
10291031
</TooltipProvider>
10301032
</div>
10311033
) : null}
1034+
<AssistantModelDropdown
1035+
assistants={assistants}
1036+
assistantId={assistantId}
1037+
selectedAssistant={selectedAssistant}
1038+
setAssistantId={setAssistantId}
1039+
modelGroups={modelGroups}
1040+
agentProviderId={agentProviderId}
1041+
agentModelName={agentModelName}
1042+
disabled={isRunning}
1043+
mode="model"
1044+
/>
10321045
</div>
10331046

10341047
<div className="flex shrink-0 items-center gap-2">

0 commit comments

Comments
 (0)