diff --git a/TODO.md b/TODO.md index e8e87a638ab630dcfd54c0743c6c7b0d0f3c6af9..ee0f104d682f4ab5e21578ed963f217969cd3c14 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,11 @@ -- [ ] Folder picker for new projects -- [ ] Projects should be persisted so they're still visible after closing -- [ ] Remove model selection from project creation -- [ ] First message vanishes when thread is created?? Awful ux -- [ ] Show more clearly when thinking is happening -- [ ] Enable react compiler +- [x] Folder picker for new projects +- [x] Projects should be persisted so they're still visible after closing +- [x] Remove model selection from project creation +- [x] First message vanishes when thread is created?? Awful ux +- [x] Show more clearly when thinking is happening +- [x] Enable react compiler ## Bigger overhauls -- [ ] Markdown rendering -- [ ] Code block rendering +- [x] Markdown rendering +- [x] Code block rendering diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index b86e04cd89ad9069c532b9fa12c784507c46bae5..03fc88d050b51b3723ea1a73df37f10f213a78e9 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -3,7 +3,7 @@ fixPath(); import { spawn } from "node:child_process"; import path from "node:path"; -import { BrowserWindow, app, ipcMain, session } from "electron"; +import { BrowserWindow, app, dialog, ipcMain, session } from "electron"; import { IPC_CHANNELS, @@ -78,6 +78,21 @@ function registerIpcHandlers(): void { return todoStore.remove(todoIdSchema.parse(id)); }); + ipcMain.handle(IPC_CHANNELS.dialogPickFolder, async () => { + const owner = + BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0]; + const result = owner + ? await dialog.showOpenDialog(owner, { + properties: ["openDirectory", "createDirectory"], + }) + : await dialog.showOpenDialog({ + properties: ["openDirectory", "createDirectory"], + }); + + if (result.canceled) return null; + return result.filePaths[0] ?? null; + }); + // Terminal handlers ipcMain.handle(IPC_CHANNELS.terminalRun, async (_event, payload: unknown) => { return runTerminalCommand(terminalCommandInputSchema.parse(payload)); diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index 9c44b6ef95241ddcde451fc9694bd5aaf2de0572..b72be5e13dd0c16c89491d5c7053467876b799c9 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -9,6 +9,9 @@ const nativeApi: NativeApi = { toggle: (id) => ipcRenderer.invoke(IPC_CHANNELS.todosToggle, id), remove: (id) => ipcRenderer.invoke(IPC_CHANNELS.todosRemove, id), }, + dialogs: { + pickFolder: () => ipcRenderer.invoke(IPC_CHANNELS.dialogPickFolder), + }, terminal: { run: (input) => ipcRenderer.invoke(IPC_CHANNELS.terminalRun, input), }, diff --git a/apps/renderer/package.json b/apps/renderer/package.json index 0306dc912edacdafb32875c6dddabe3c58c19e68..05b67881e5595994ca6c77ccf8508e144d63dfe4 100644 --- a/apps/renderer/package.json +++ b/apps/renderer/package.json @@ -13,8 +13,12 @@ }, "dependencies": { "@acme/contracts": "workspace:*", + "highlight.js": "^11.11.1", + "react-markdown": "^10.1.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "rehype-highlight": "^7.0.2", + "remark-gfm": "^4.0.1" }, "devDependencies": { "@tailwindcss/vite": "^4.0.0", diff --git a/apps/renderer/src/components/ChatMarkdown.tsx b/apps/renderer/src/components/ChatMarkdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d43ebef94a083ecc1cd4237c4873aae49997fe79 --- /dev/null +++ b/apps/renderer/src/components/ChatMarkdown.tsx @@ -0,0 +1,30 @@ +import type { Components } from "react-markdown"; +import ReactMarkdown from "react-markdown"; +import rehypeHighlight from "rehype-highlight"; +import remarkGfm from "remark-gfm"; + +interface ChatMarkdownProps { + text: string; +} + +const markdownComponents: Components = { + a({ node: _node, ...props }) { + return ; + }, +}; + +export default function ChatMarkdown({ text }: ChatMarkdownProps) { + return ( +
- {msg.text || (msg.streaming ? "" : "(empty response)")}
-
+