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
22 changes: 9 additions & 13 deletions src/components/deploy/DeployModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { invoke } from '@tauri-apps/api/core';
import { useDeployStore } from '../../stores/deployStore';
import { useProjectStore } from '../../stores/projectStore';
import { PROVIDERS, getProvider } from '../../lib/deployProviders';
import { getDatabase } from '../../db/database';
import type { DeployProvider } from '../../lib/deployProviders';

interface DeployResult {
Expand All @@ -16,6 +15,7 @@ export function DeployModal() {
const { status, error, deployUrl, modalOpen, closeModal, setStatus, setDeployUrl, loadToken, saveToken, reset } =
useDeployStore();
const currentProject = useProjectStore((s) => s.currentProject);
const updateDeployConfig = useProjectStore((s) => s.updateDeployConfig);
const [selectedProvider, setSelectedProvider] = useState<DeployProvider | null>(null);
const [tokenInput, setTokenInput] = useState('');
const [needsToken, setNeedsToken] = useState(false);
Expand Down Expand Up @@ -53,12 +53,12 @@ export function DeployModal() {
setNeedsToken(false);

try {
const existingConfig: { provider: string; site_id: string; url: string } | null =
currentProject.deploy_config
? (typeof currentProject.deploy_config === 'string'
? JSON.parse(currentProject.deploy_config as string)
: currentProject.deploy_config)
: null;
let existingConfig: { provider: string; site_id: string; url: string } | null = null;
try {
if (currentProject.deploy_config) {
existingConfig = JSON.parse(currentProject.deploy_config);
}
} catch { /* invalid JSON, treat as no config */ }
const existingSiteId =
existingConfig?.provider === provider ? existingConfig.site_id : undefined;

Expand Down Expand Up @@ -88,17 +88,13 @@ export function DeployModal() {
break;
}

// Save deploy config to project
const db = await getDatabase();
// Save deploy config to project (updates both DB and store)
const deployConfig = JSON.stringify({
provider: result.provider,
site_id: result.site_id,
url: result.url,
});
await db.execute(
'UPDATE projects SET deploy_config = ? WHERE id = ?',
[deployConfig, currentProject.id]
);
await updateDeployConfig(currentProject.id, deployConfig);

setDeployUrl(result.url);
setStatus('success');
Expand Down
10 changes: 6 additions & 4 deletions src/components/preview/PreviewFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export function PreviewFrame() {
const currentProject = useProjectStore((s) => s.currentProject);
const updateProjectHtml = useProjectStore((s) => s.updateProjectHtml);
const { editMode, viewport, selectSection } = useEditorStore();
const projectRef = useRef(currentProject);
projectRef.current = currentProject;

// Send edit mode changes to iframe
useEffect(() => {
Expand Down Expand Up @@ -43,14 +45,14 @@ export function PreviewFrame() {
break;

case 'offpage:wysiwyg-done':
if (currentProject) {
if (projectRef.current) {
const newSectionHtml = ensureSectionId(data.payload.outerHtml, data.payload.id);
const updatedHtml = replaceSectionInHtml(
currentProject.html,
projectRef.current.html,
data.payload.id,
newSectionHtml
);
updateProjectHtml(currentProject.id, updatedHtml);
updateProjectHtml(projectRef.current.id, updatedHtml);
}
break;

Expand All @@ -65,7 +67,7 @@ export function PreviewFrame() {
break;
}
},
[editMode, currentProject, selectSection, updateProjectHtml]
[editMode, selectSection, updateProjectHtml]
);

useEffect(() => {
Expand Down
26 changes: 16 additions & 10 deletions src/hooks/useAiStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,18 @@ export function useAiStream() {

setStreaming(true);

unlistenRef.current = await listen<AiChunk>('ai-chunk', (event) => {
const unlisten = await listen<AiChunk>('ai-chunk', (event) => {
if (!event.payload.done) {
appendToStream(event.payload.token);
}
});
unlistenRef.current = unlisten;

try {
const fullHtml = await invoke<string>('stream_generate', {
messages,
system_prompt: systemPrompt,
max_tokens: maxTokens ?? null,
systemPrompt,
maxTokens: maxTokens ?? null,
});

await finalizeStream(projectId);
Expand All @@ -61,8 +62,10 @@ export function useAiStream() {
setStreaming(false);
return null;
} finally {
unlistenRef.current?.();
unlistenRef.current = null;
unlisten();
if (unlistenRef.current === unlisten) {
unlistenRef.current = null;
}
}
},
[sidecarPort, sidecarStatus, setStreaming, appendToStream, finalizeStream, updateProjectHtml]
Expand All @@ -83,17 +86,18 @@ export function useAiStream() {

setStreaming(true);

unlistenRef.current = await listen<AiChunk>('ai-chunk', (event) => {
const unlisten = await listen<AiChunk>('ai-chunk', (event) => {
if (!event.payload.done) {
appendToStream(event.payload.token);
}
});
unlistenRef.current = unlisten;

try {
const newSectionHtml = await invoke<string>('stream_generate', {
messages: buildSectionEditMessages(sectionHtml, userPrompt),
system_prompt: SYSTEM_PROMPTS.editSection,
max_tokens: null,
systemPrompt: SYSTEM_PROMPTS.editSection,
maxTokens: null,
});

await finalizeStream(projectId);
Expand All @@ -108,8 +112,10 @@ export function useAiStream() {
setStreaming(false);
return null;
} finally {
unlistenRef.current?.();
unlistenRef.current = null;
unlisten();
if (unlistenRef.current === unlisten) {
unlistenRef.current = null;
}
}
},
[sidecarPort, sidecarStatus, setStreaming, appendToStream, finalizeStream, updateProjectHtml]
Expand Down
10 changes: 7 additions & 3 deletions src/pages/ProjectPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ export function ProjectPage() {
const currentProject = useProjectStore((s) => s.currentProject);
const loadProjectById = useProjectStore((s) => s.loadProjectById);
const createSnapshot = useProjectStore((s) => s.createSnapshot);
const { addMessage } = useChatStore();
const { addMessage, clearMessages } = useChatStore();
const streaming = useChatStore((s) => s.streaming);
const clearSelection = useEditorStore((s) => s.clearSelection);
const { clearSelection } = useEditorStore();
const { generate, generateSection } = useAiStream();
const initialPromptSent = useRef(false);

useEffect(() => {
if (id) {
// Reset state when switching projects
initialPromptSent.current = false;
clearMessages();
clearSelection();
loadProjectById(id);
}
}, [id, loadProjectById]);
}, [id, loadProjectById, clearMessages, clearSelection]);

// Auto-send template customization prompt from ?prompt= query param
useEffect(() => {
Expand Down
14 changes: 13 additions & 1 deletion src/stores/aiStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,35 @@ export const useAiStore = create<AiState>((set, get) => ({

let unlistenProgress: (() => void) | null = null;
let unlistenComplete: (() => void) | null = null;
let completeFired = false;

try {
unlistenProgress = await listen<DownloadProgress>('download-progress', (event) => {
set({ downloadProgress: event.payload });
});

unlistenComplete = await listen<string>('download-complete', async (event) => {
completeFired = true;
set({ isDownloading: false, downloadProgress: null });
unlistenProgress?.();
unlistenComplete?.();

// Auto-start sidecar with downloaded model
const modelPath = await get().getModelPath(event.payload);
await get().startSidecar(modelPath);
});

await invoke('download_model', { modelUrl, filename });

// If invoke resolved without download-complete event (model already existed),
// clean up listeners and auto-start sidecar directly
if (!completeFired) {
unlistenProgress?.();
unlistenComplete?.();
set({ isDownloading: false, downloadProgress: null });

const modelPath = await get().getModelPath(filename);
await get().startSidecar(modelPath);
}
} catch (error) {
unlistenProgress?.();
unlistenComplete?.();
Expand Down
32 changes: 26 additions & 6 deletions src/stores/projectStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface ProjectState {
createProject: (name: string, siteType: SiteType) => Promise<Project>;
setCurrentProject: (project: Project | null) => void;
updateProjectHtml: (id: string, html: string) => Promise<void>;
updateDeployConfig: (id: string, deployConfig: string) => Promise<void>;
deleteProject: (id: string) => Promise<void>;
createSnapshot: (projectId: string, html: string, description: string) => Promise<void>;
}
Expand All @@ -36,12 +37,17 @@ export const useProjectStore = create<ProjectState>((set, _get) => ({

loadProjectById: async (id: string) => {
set({ loading: true });
const db = await getDatabase();
const rows = await db.select<Project[]>(
'SELECT * FROM projects WHERE id = ?',
[id]
);
set({ currentProject: rows[0] ?? null, loading: false });
try {
const db = await getDatabase();
const rows = await db.select<Project[]>(
'SELECT * FROM projects WHERE id = ?',
[id]
);
set({ currentProject: rows[0] ?? null, loading: false });
} catch (error) {
console.error('Failed to load project:', error);
set({ loading: false });
}
},

createProject: async (name: string, siteType: SiteType) => {
Expand Down Expand Up @@ -88,6 +94,20 @@ export const useProjectStore = create<ProjectState>((set, _get) => ({
}));
},

updateDeployConfig: async (id: string, deployConfig: string) => {
const db = await getDatabase();
await db.execute(
'UPDATE projects SET deploy_config = ? WHERE id = ?',
[deployConfig, id]
);
set((state) => ({
currentProject:
state.currentProject?.id === id
? { ...state.currentProject, deploy_config: deployConfig }
: state.currentProject,
}));
},

deleteProject: async (id: string) => {
const db = await getDatabase();
await db.execute('DELETE FROM projects WHERE id = ?', [id]);
Expand Down
2 changes: 1 addition & 1 deletion src/types/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface Project {
html: string;
template_id: string | null;
site_type: SiteType;
deploy_config: DeployConfig | null;
deploy_config: string | null;
created_at: string;
updated_at: string;
}
Expand Down