Align dev workflow with node-based electron-vite dev and tighten assistant markdown layout
This commit is contained in:
7979
package-lock.json
generated
Normal file
7979
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -11,15 +11,15 @@
|
|||||||
"build": "electron-vite build",
|
"build": "electron-vite build",
|
||||||
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.node.json",
|
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.node.json",
|
||||||
"preview": "electron-vite preview",
|
"preview": "electron-vite preview",
|
||||||
"build:binaries": "bun scripts/build.ts",
|
"build:binaries": "node scripts/build.ts",
|
||||||
"build:mac": "bun scripts/build.ts mac",
|
"build:mac": "node scripts/build.ts mac",
|
||||||
"build:mac-x64": "bun scripts/build.ts mac-x64",
|
"build:mac-x64": "node scripts/build.ts mac-x64",
|
||||||
"build:mac-arm64": "bun scripts/build.ts mac-arm64",
|
"build:mac-arm64": "node scripts/build.ts mac-arm64",
|
||||||
"build:win": "bun scripts/build.ts win",
|
"build:win": "node scripts/build.ts win",
|
||||||
"build:win-arm64": "bun scripts/build.ts win-arm64",
|
"build:win-arm64": "node scripts/build.ts win-arm64",
|
||||||
"build:linux": "bun scripts/build.ts linux",
|
"build:linux": "node scripts/build.ts linux",
|
||||||
"build:linux-arm64": "bun scripts/build.ts linux-arm64",
|
"build:linux-arm64": "node scripts/build.ts linux-arm64",
|
||||||
"build:all": "bun scripts/build.ts all",
|
"build:all": "node scripts/build.ts all",
|
||||||
"package:mac": "electron-builder --mac",
|
"package:mac": "electron-builder --mac",
|
||||||
"package:win": "electron-builder --win",
|
"package:win": "electron-builder --win",
|
||||||
"package:linux": "electron-builder --linux"
|
"package:linux": "electron-builder --linux"
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@opencode-ai/sdk": "0.15.13",
|
"@opencode-ai/sdk": "0.15.13",
|
||||||
"@solidjs/router": "^0.13.0",
|
"@solidjs/router": "^0.13.0",
|
||||||
|
"electron": "39.0.0",
|
||||||
"ignore": "7.0.5",
|
"ignore": "7.0.5",
|
||||||
"lucide-solid": "^0.300.0",
|
"lucide-solid": "^0.300.0",
|
||||||
"marked": "^12.0.0",
|
"marked": "^12.0.0",
|
||||||
@@ -36,9 +37,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "10.4.21",
|
"autoprefixer": "10.4.21",
|
||||||
"electron": "38.4.0",
|
|
||||||
"electron-builder": "^24.0.0",
|
"electron-builder": "^24.0.0",
|
||||||
"electron-vite": "^2.0.0",
|
"electron-vite": "4.0.1",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"tailwindcss": "3",
|
"tailwindcss": "3",
|
||||||
"typescript": "^5.3.0",
|
"typescript": "^5.3.0",
|
||||||
@@ -147,5 +147,6 @@
|
|||||||
"category": "Development",
|
"category": "Development",
|
||||||
"icon": "electron/resources/icon.png"
|
"icon": "electron/resources/icon.png"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
"private": true
|
||||||
|
}
|
||||||
40
scripts/dev.sh
Normal file → Executable file
40
scripts/dev.sh
Normal file → Executable file
@@ -1,24 +1,30 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Kill background processes on exit
|
set -euo pipefail
|
||||||
trap 'kill $(jobs -p) 2>/dev/null' EXIT
|
|
||||||
|
|
||||||
# Build main and preload in watch mode
|
# ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
NODE_ENV=development vite build --watch --mode development --config electron.vite.config.ts --outDir dist/main &
|
|
||||||
MAIN_PID=$!
|
|
||||||
|
|
||||||
NODE_ENV=development vite build --watch --mode development --ssr electron/preload/index.ts --outDir dist/preload &
|
if ! command -v node >/dev/null 2>&1; then
|
||||||
PRELOAD_PID=$!
|
echo "Node.js is required to run the development environment." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Start vite dev server for renderer
|
# Resolve the Electron binary via Node to avoid Bun resolution hiccups
|
||||||
NODE_ENV=development vite --config electron.vite.config.ts --mode development &
|
ELECTRON_EXEC_PATH="$(node -p "require('electron')")"
|
||||||
RENDERER_PID=$!
|
|
||||||
|
|
||||||
# Wait for builds to complete
|
if [[ -z "${ELECTRON_EXEC_PATH}" ]]; then
|
||||||
sleep 2
|
echo "Failed to resolve the Electron binary path." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Launch Electron
|
export NODE_ENV="${NODE_ENV:-development}"
|
||||||
NODE_ENV=development electron .
|
export ELECTRON_EXEC_PATH
|
||||||
|
|
||||||
# This will run when electron closes
|
# ELECTRON_VITE_BIN="$ROOT_DIR/node_modules/.bin/electron-vite"
|
||||||
wait
|
|
||||||
|
if [[ ! -x "${ELECTRON_VITE_BIN}" ]]; then
|
||||||
|
echo "electron-vite binary not found. Have you installed dependencies?" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "${ELECTRON_VITE_BIN}" dev "$@"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { TextPart } from "../types/message"
|
|||||||
interface MarkdownProps {
|
interface MarkdownProps {
|
||||||
part: TextPart
|
part: TextPart
|
||||||
isDark?: boolean
|
isDark?: boolean
|
||||||
|
size?: "base" | "sm" | "tight"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Markdown(props: MarkdownProps) {
|
export function Markdown(props: MarkdownProps) {
|
||||||
@@ -94,5 +95,17 @@ export function Markdown(props: MarkdownProps) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return <div ref={containerRef} class="prose prose-sm dark:prose-invert max-w-none" innerHTML={html()} />
|
const proseClass = () => {
|
||||||
|
const classes = ["prose", "dark:prose-invert", "max-w-none"]
|
||||||
|
|
||||||
|
if (props.size === "tight") {
|
||||||
|
classes.push("prose-sm", "prose-tight")
|
||||||
|
} else if (props.size === "sm") {
|
||||||
|
classes.push("prose-sm")
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div ref={containerRef} class={proseClass()} innerHTML={html()} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,32 +58,33 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const containerClass = () =>
|
||||||
|
isUser()
|
||||||
|
? "message-item-base bg-[var(--message-user-bg)] border-l-4 border-[var(--message-user-border)]"
|
||||||
|
: "message-item-base assistant-message bg-[var(--message-assistant-bg)] border-l-4 border-[var(--message-assistant-border)]"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div class={containerClass()}>
|
||||||
class={`message-item-base ${
|
<div class="flex justify-between items-center gap-2.5 pb-0.5">
|
||||||
isUser()
|
<span class="font-semibold text-xs text-[var(--text-muted)]">
|
||||||
? "bg-[var(--message-user-bg)] border-l-4 border-[var(--message-user-border)]"
|
|
||||||
: "bg-[var(--message-assistant-bg)] border-l-4 border-[var(--message-assistant-border)]"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div class="flex justify-between items-center gap-3">
|
|
||||||
<span class="font-semibold text-sm text-[var(--text-muted)]">
|
|
||||||
{isUser() ? "You" : "Assistant"}
|
{isUser() ? "You" : "Assistant"}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs text-[var(--text-muted)]">{timestamp()}</span>
|
<div class="flex items-center gap-2">
|
||||||
<Show when={isUser() && props.onRevert}>
|
<span class="text-[11px] text-[var(--text-muted)]">{timestamp()}</span>
|
||||||
<button
|
<Show when={isUser() && props.onRevert}>
|
||||||
class="bg-transparent border border-[var(--border-base)] text-[var(--text-muted)] cursor-pointer px-2 py-0.5 rounded text-base leading-none transition-all duration-200 flex items-center justify-center min-w-7 h-6 hover:bg-[var(--surface-hover)] hover:border-[var(--accent-primary)] hover:text-[var(--accent-primary)] active:scale-95"
|
<button
|
||||||
onClick={handleRevert}
|
class="bg-transparent border border-[var(--border-base)] text-[var(--text-muted)] cursor-pointer px-2 py-0.5 rounded text-sm leading-none transition-all duration-200 flex items-center justify-center min-w-7 h-6 hover:bg-[var(--surface-hover)] hover:border-[var(--accent-primary)] hover:text-[var(--accent-primary)] active:scale-95"
|
||||||
title="Revert to this message"
|
onClick={handleRevert}
|
||||||
aria-label="Revert to this message"
|
title="Revert to this message"
|
||||||
>
|
aria-label="Revert to this message"
|
||||||
↶
|
>
|
||||||
</button>
|
↶
|
||||||
</Show>
|
</button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pt-1.5 leading-relaxed whitespace-pre-wrap break-words">
|
<div class="pt-1 whitespace-pre-wrap break-words leading-[1.1]">
|
||||||
<Show when={props.isQueued && isUser()}>
|
<Show when={props.isQueued && isUser()}>
|
||||||
<div class="message-queued-badge">QUEUED</div>
|
<div class="message-queued-badge">QUEUED</div>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -98,7 +99,7 @@ export default function MessageItem(props: MessageItemProps) {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<For each={messageParts()}>{(part) => <MessagePart part={part} />}</For>
|
<For each={messageParts()}>{(part) => <MessagePart part={part} messageType={props.message.type} />}</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={props.message.status === "sending"}>
|
<Show when={props.message.status === "sending"}>
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ import { partHasRenderableText } from "../types/message"
|
|||||||
|
|
||||||
interface MessagePartProps {
|
interface MessagePartProps {
|
||||||
part: any
|
part: any
|
||||||
|
messageType?: "user" | "assistant"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MessagePart(props: MessagePartProps) {
|
export default function MessagePart(props: MessagePartProps) {
|
||||||
const { isDark } = useTheme()
|
const { isDark } = useTheme()
|
||||||
const partType = () => props.part?.type || ""
|
const partType = () => props.part?.type || ""
|
||||||
const reasoningId = () => `reasoning-${props.part?.id || ""}`
|
const reasoningId = () => `reasoning-${props.part?.id || ""}`
|
||||||
const isReasoningExpanded = () => isItemExpanded(reasoningId())
|
const isReasoningExpanded = () => isItemExpanded(reasoningId())
|
||||||
|
const isAssistantMessage = () => props.messageType === "assistant"
|
||||||
|
const textContainerClass = () => (isAssistantMessage() ? "message-text message-text-assistant" : "message-text")
|
||||||
|
|
||||||
function handleReasoningClick(e: Event) {
|
function handleReasoningClick(e: Event) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -25,8 +27,8 @@ export default function MessagePart(props: MessagePartProps) {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Match when={partType() === "text"}>
|
<Match when={partType() === "text"}>
|
||||||
<Show when={!props.part.synthetic && partHasRenderableText(props.part)}>
|
<Show when={!props.part.synthetic && partHasRenderableText(props.part)}>
|
||||||
<div class="message-text">
|
<div class={textContainerClass()}>
|
||||||
<Markdown part={props.part} isDark={isDark()} />
|
<Markdown part={props.part} isDark={isDark()} size={isAssistantMessage() ? "tight" : "base"} />
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -48,8 +50,8 @@ export default function MessagePart(props: MessagePartProps) {
|
|||||||
<span class="reasoning-label">Reasoning</span>
|
<span class="reasoning-label">Reasoning</span>
|
||||||
</div>
|
</div>
|
||||||
<Show when={isReasoningExpanded()}>
|
<Show when={isReasoningExpanded()}>
|
||||||
<div class="message-text mt-2">
|
<div class={`${textContainerClass()} mt-2`}>
|
||||||
<Markdown part={props.part} isDark={isDark()} />
|
<Markdown part={props.part} isDark={isDark()} size={isAssistantMessage() ? "tight" : "base"} />
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,6 +38,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="../main.tsx"></script>
|
<script type="module" src="./main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
1
src/renderer/main.tsx
Normal file
1
src/renderer/main.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import "../main.tsx"
|
||||||
@@ -118,6 +118,11 @@ button.button-primary {
|
|||||||
@apply flex flex-col gap-2 p-3 rounded-lg w-full;
|
@apply flex flex-col gap-2 p-3 rounded-lg w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assistant-message {
|
||||||
|
/* gap: 0.25rem; */
|
||||||
|
padding: 0.6rem 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Message state badges */
|
/* Message state badges */
|
||||||
.message-queued-badge {
|
.message-queued-badge {
|
||||||
@apply inline-block font-bold px-3 py-1 rounded mb-3 text-xs tracking-wide;
|
@apply inline-block font-bold px-3 py-1 rounded mb-3 text-xs tracking-wide;
|
||||||
@@ -606,6 +611,7 @@ button.button-primary {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.message-text pre {
|
.message-text pre {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
|||||||
@@ -214,4 +214,108 @@
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
font-size: var(--font-size-xs) !important;
|
font-size: var(--font-size-xs) !important;
|
||||||
line-height: var(--line-height-normal);
|
line-height: var(--line-height-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Assistant message compact overrides */
|
||||||
|
.message-text-assistant {
|
||||||
|
font-size: 13.5px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose {
|
||||||
|
font-size: 0.94rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.message-text-assistant .prose ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose ol {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose ul + * {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose ul + ol {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose ol + * {
|
||||||
|
margin-top: 14px;
|
||||||
|
line-height: 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose ol + a,
|
||||||
|
.message-text-assistant .prose ol + code,
|
||||||
|
.message-text-assistant .prose ol + img {
|
||||||
|
margin-top: 14px !important;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose li {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose li + li {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose li:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose li > br {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose li > br + a,
|
||||||
|
.message-text-assistant .prose li > br + code,
|
||||||
|
.message-text-assistant .prose li > br + img {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose h1 {
|
||||||
|
margin: 4px 0 2px;
|
||||||
|
font-size: 1.22em;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose h2 {
|
||||||
|
margin: 3px 0 2px;
|
||||||
|
font-size: 1.12em;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose h3 {
|
||||||
|
margin: 2px 0 2px;
|
||||||
|
font-size: 1.05em;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose blockquote {
|
||||||
|
margin: 6px 0 10px;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text-assistant .prose blockquote p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user