Skip to content

Commit 07f7e85

Browse files
MarkShawn2020claude
andcommitted
feat(logo): 完善 Logo 管理功能
- Logo 持久化存储到 Application Support 目录 - 打开对话框时自动加载历史生成的 Logo - 关闭对话框不再清空已生成的 Logo - ProjectLogo 监听 logo-updated 事件自动同步刷新 - 更新 prompt 强调透明背景 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7e8d0cb commit 07f7e85

2 files changed

Lines changed: 62 additions & 12 deletions

File tree

src/views/Workspace/LogoManager.tsx

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ function generateLogoPrompt(projectName: string, projectType?: string): string {
3434

3535
return `Minimalist logo icon for "${name}" software project.
3636
Style: Warm academic, intellectual, high-end minimalist.
37-
Colors: Primary terracotta clay (#CC785C), warm off-white background, deep charcoal accents.
37+
Colors: Primary terracotta clay (#CC785C), deep charcoal accents.
38+
IMPORTANT: Transparent background (PNG with alpha channel).
3839
Shape: Simple geometric form, abstract symbol, clean lines.
3940
${projectType ? `Theme: ${projectType} tool/application.` : ""}
40-
No text, no gradients. Single iconic shape. Suitable for app icon and favicon.
41-
Professional, memorable, works at small sizes (32x32).`;
41+
No text, no gradients, no background. Single iconic shape on transparent background.
42+
Suitable for app icon and favicon. Professional, memorable, works at small sizes (32x32).`;
4243
}
4344

4445
export function LogoManager({ projectPath, embedded = false }: LogoManagerProps) {
@@ -61,6 +62,36 @@ export function LogoManager({ projectPath, embedded = false }: LogoManagerProps)
6162
// Auto-generated prompt based on project
6263
const autoPrompt = useMemo(() => generateLogoPrompt(projectName), [projectName]);
6364

65+
// Load previously generated logos from Application Support
66+
const loadGeneratedLogos = useCallback(async () => {
67+
try {
68+
const result = await invoke<string>("exec_shell_command", {
69+
command: `ls -t "$HOME/Library/Application Support/com.lovstudio.lovcode/generated-logos/"*.png 2>/dev/null | head -20`,
70+
cwd: projectPath,
71+
});
72+
const files = result.trim().split('\n').filter(f => f);
73+
if (files.length === 0) return;
74+
75+
const previews = await Promise.all(
76+
files.map(async (path) => {
77+
const base64 = await invoke<string>("read_file_base64", { path });
78+
return `data:image/png;base64,${base64}`;
79+
})
80+
);
81+
setGenPreviews(previews);
82+
if (previews.length > 0) setSelectedPreview(0);
83+
} catch {
84+
// No logos yet, that's fine
85+
}
86+
}, [projectPath]);
87+
88+
// Load generated logos when dialog opens
89+
useEffect(() => {
90+
if (showGenDialog && genPreviews.length === 0) {
91+
loadGeneratedLogos();
92+
}
93+
}, [showGenDialog, genPreviews.length, loadGeneratedLogos]);
94+
6495
// Load current logo and versions
6596
const loadLogoData = useCallback(async () => {
6697
try {
@@ -91,13 +122,17 @@ export function LogoManager({ projectPath, embedded = false }: LogoManagerProps)
91122
const escapedPrompt = prompt.replace(/'/g, "'\\''").replace(/\n/g, ' ');
92123
const scriptPath = "$HOME/.claude/plugins/marketplaces/lovstudio-plugins-official/skills/image-gen/gen_image.py";
93124

94-
// Generate 2 variants in parallel
125+
// Generate 2 variants in parallel, save to Application Support
95126
const generateOne = async (index: number): Promise<string> => {
96-
const outputPath = `/tmp/lovcode-logo-${Date.now()}-${index}.png`;
97-
await invoke<string>("exec_shell_command", {
98-
command: `python3 ${scriptPath} '${escapedPrompt}' -o '${outputPath}' -q high --no-open`,
127+
const timestamp = Date.now();
128+
const filename = `logo-${timestamp}-${index}.png`;
129+
// Get actual path from shell (expands $HOME)
130+
const result = await invoke<string>("exec_shell_command", {
131+
command: `dir="$HOME/Library/Application Support/com.lovstudio.lovcode/generated-logos" && mkdir -p "$dir" && python3 ${scriptPath} '${escapedPrompt}' -o "$dir/${filename}" -q high --no-open && echo "$dir/${filename}"`,
99132
cwd: projectPath,
100133
});
134+
// Extract the echoed path (last line of output)
135+
const outputPath = result.trim().split('\n').pop() || '';
101136
const base64 = await invoke<string>("read_file_base64", { path: outputPath });
102137
return `data:image/png;base64,${base64}`;
103138
};
@@ -127,10 +162,10 @@ export function LogoManager({ projectPath, embedded = false }: LogoManagerProps)
127162
});
128163

129164
setShowGenDialog(false);
130-
setGenPreviews([]);
131-
setSelectedPreview(null);
132-
setGenPrompt("");
133165
await loadLogoData();
166+
167+
// Notify ProjectLogo to refresh
168+
window.dispatchEvent(new CustomEvent("logo-updated", { detail: { projectPath } }));
134169
} catch (err) {
135170
setError(err instanceof Error ? err.message : String(err));
136171
}
@@ -152,6 +187,7 @@ export function LogoManager({ projectPath, embedded = false }: LogoManagerProps)
152187
targetFilename: `logo${selected.substring(selected.lastIndexOf("."))}`,
153188
});
154189
await loadLogoData();
190+
window.dispatchEvent(new CustomEvent("logo-updated", { detail: { projectPath } }));
155191
}
156192
} catch (err) {
157193
console.error("Failed to upload logo:", err);
@@ -166,6 +202,7 @@ export function LogoManager({ projectPath, embedded = false }: LogoManagerProps)
166202
logoPath: version.path,
167203
});
168204
await loadLogoData();
205+
window.dispatchEvent(new CustomEvent("logo-updated", { detail: { projectPath } }));
169206
} catch (err) {
170207
console.error("Failed to set logo:", err);
171208
}

src/views/Workspace/ProjectLogo.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from "react";
1+
import { useState, useEffect, useCallback } from "react";
22
import { invoke } from "@tauri-apps/api/core";
33
import { CubeIcon } from "@radix-ui/react-icons";
44

@@ -16,12 +16,25 @@ const sizeClasses = {
1616
export function ProjectLogo({ projectPath, size = "sm" }: ProjectLogoProps) {
1717
const [logoSrc, setLogoSrc] = useState<string | null>(null);
1818

19-
useEffect(() => {
19+
const loadLogo = useCallback(() => {
2020
invoke<string | null>("get_project_logo", { projectPath })
2121
.then(setLogoSrc)
2222
.catch(() => setLogoSrc(null));
2323
}, [projectPath]);
2424

25+
useEffect(() => {
26+
loadLogo();
27+
28+
// Listen for logo updates from LogoManager
29+
const handleLogoUpdate = (e: CustomEvent) => {
30+
if (e.detail?.projectPath === projectPath) {
31+
loadLogo();
32+
}
33+
};
34+
window.addEventListener("logo-updated", handleLogoUpdate as EventListener);
35+
return () => window.removeEventListener("logo-updated", handleLogoUpdate as EventListener);
36+
}, [projectPath, loadLogo]);
37+
2538
const classes = sizeClasses[size];
2639

2740
if (!logoSrc) {

0 commit comments

Comments
 (0)