Skip to content
Open
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
17 changes: 15 additions & 2 deletions packages/app/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -723,12 +723,14 @@ export default function App() {
setTemplateDraftPrompt,
templateDraftScope,
setTemplateDraftScope,
templateDraftAutoRun,
setTemplateDraftAutoRun,
workspaceTemplates,
globalTemplates,
openTemplateModal,
saveTemplate,
deleteTemplate,
runTemplate,
applyTemplate,
loadWorkspaceTemplates,
} = templateState;

Expand Down Expand Up @@ -1474,8 +1476,15 @@ export default function App() {
applyThemeMode(isDark ? "dark" : "light");
});

// Listen for setPrompt events from template system (when autoRun is disabled)
const handleSetPrompt = (e: CustomEvent<string>) => {
setPrompt(e.detail);
};
window.addEventListener("openwork:setPrompt", handleSetPrompt as EventListener);

onCleanup(() => {
unsubscribeTheme();
window.removeEventListener("openwork:setPrompt", handleSetPrompt as EventListener);
});

createEffect(() => {
Expand Down Expand Up @@ -2090,7 +2099,7 @@ export default function App() {
setTemplateDraftScope(scope);
},
openTemplateModal,
runTemplate,
applyTemplate,
deleteTemplate,
refreshSkills: (options?: { force?: boolean }) => refreshSkills(options).catch(() => undefined),
refreshPlugins: (scopeOverride?: PluginScope) =>
Expand Down Expand Up @@ -2263,6 +2272,8 @@ export default function App() {
sessionStatus={selectedSessionStatus()}
renameSession={renameSessionTitle}
error={error()}
workspaceTemplates={workspaceTemplates()}
applyTemplate={applyTemplate}
/>
</Match>
<Match when={true}>
Expand Down Expand Up @@ -2332,12 +2343,14 @@ export default function App() {
description={templateDraftDescription()}
prompt={templateDraftPrompt()}
scope={templateDraftScope()}
autoRun={templateDraftAutoRun()}
onClose={() => setTemplateModalOpen(false)}
onSave={saveTemplate}
onTitleChange={setTemplateDraftTitle}
onDescriptionChange={setTemplateDraftDescription}
onPromptChange={setTemplateDraftPrompt}
onScopeChange={setTemplateDraftScope}
onAutoRunChange={setTemplateDraftAutoRun}
/>

<ReloadWorkspaceToast
Expand Down
21 changes: 21 additions & 0 deletions packages/app/src/app/components/template-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ export type TemplateModalProps = {
description: string;
prompt: string;
scope: "workspace" | "global";
autoRun: boolean;
onClose: () => void;
onSave: () => void;
onTitleChange: (value: string) => void;
onDescriptionChange: (value: string) => void;
onPromptChange: (value: string) => void;
onScopeChange: (value: "workspace" | "global") => void;
onAutoRunChange: (value: boolean) => void;
};

export default function TemplateModal(props: TemplateModalProps) {
Expand Down Expand Up @@ -88,6 +90,25 @@ export default function TemplateModal(props: TemplateModalProps) {
/>
<div class="mt-1 text-xs text-gray-10">{translate("templates.prompt_hint")}</div>
</label>

{/* Auto-run Toggle */}
<div class="flex items-center justify-between bg-gray-1 p-3 rounded-xl border border-gray-6 gap-3">
<div class="min-w-0">
<div class="text-sm text-gray-12">{translate("templates.auto_run_label")}</div>
<div class="text-xs text-gray-7">{translate("templates.auto_run_hint")}</div>
</div>
<button
type="button"
class={`px-3 py-1 rounded-full text-xs font-medium border transition-colors shrink-0 ${
props.autoRun
? "bg-gray-12/10 text-gray-12 border-gray-6/20"
: "text-gray-10 border-gray-6 hover:text-gray-12"
}`}
onClick={() => props.onAutoRunChange(!props.autoRun)}
>
{props.autoRun ? "On" : "Off"}
</button>
</div>
</div>

<div class="mt-6 flex justify-end gap-2">
Expand Down
8 changes: 4 additions & 4 deletions packages/app/src/app/pages/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export type DashboardViewProps = {
setTemplateDraftScope: (value: "workspace" | "global") => void;
openTemplateModal: () => void;
resetTemplateDraft?: (scope?: "workspace" | "global") => void;
runTemplate: (template: WorkspaceTemplate) => void;
applyTemplate: (template: WorkspaceTemplate) => void;
deleteTemplate: (templateId: string) => void;
refreshSkills: (options?: { force?: boolean }) => void;
refreshPlugins: (scopeOverride?: PluginScope) => void;
Expand Down Expand Up @@ -537,15 +537,15 @@ export default function DashboardView(props: DashboardViewProps) {
<For each={quickTemplates()}>
{(t) => (
<button
onClick={() => props.runTemplate(t)}
onClick={() => props.applyTemplate(t)}
class="group p-5 rounded-2xl bg-gray-2/30 border border-gray-6/50 hover:bg-gray-2 hover:border-gray-7 transition-all text-left"
>
<div class="w-10 h-10 rounded-full bg-gray-4 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
<FileText size={20} class="text-indigo-11" />
</div>
<h4 class="font-medium text-gray-12 mb-1">{t.title}</h4>
<p class="text-sm text-gray-10">
{t.description || "Run a saved workflow"}
{t.description || "Apply a saved workflow"}
</p>
</button>
)}
Expand Down Expand Up @@ -698,7 +698,7 @@ export default function DashboardView(props: DashboardViewProps) {
setTemplateDraftScope={props.setTemplateDraftScope}
openTemplateModal={props.openTemplateModal}
resetTemplateDraft={props.resetTemplateDraft}
runTemplate={props.runTemplate}
applyTemplate={props.applyTemplate}
deleteTemplate={props.deleteTemplate}
/>
</Match>
Expand Down
187 changes: 118 additions & 69 deletions packages/app/src/app/pages/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import type {
TodoItem,
View,
WorkspaceDisplay,
WorkspaceTemplate,
} from "../types";

import {
AlertTriangle,
ArrowRight,
FileText,
ChevronDown,
HardDrive,
Shield,
Expand Down Expand Up @@ -94,6 +96,8 @@ export type SessionViewProps = {
setSessionAgent: (sessionId: string, agent: string | null) => void;
saveSession: (sessionId: string) => Promise<string>;
sessionStatusById: Record<string, string>;
workspaceTemplates: WorkspaceTemplate[];
applyTemplate: (template: WorkspaceTemplate) => void;
};

export default function SessionView(props: SessionViewProps) {
Expand Down Expand Up @@ -429,7 +433,7 @@ export default function SessionView(props: SessionViewProps) {
}
setPrevArtifactCount(count);
});

createEffect(() => {
const files = props.workingFiles;
const count = files.length;
Expand Down Expand Up @@ -824,87 +828,132 @@ export default function SessionView(props: SessionViewProps) {
}
`}
</style>

<Show when={props.messages.length === 0}>
<div class="text-center py-20 space-y-4">
<div class="w-16 h-16 bg-gray-2 rounded-3xl mx-auto flex items-center justify-center border border-gray-6">
<Zap class="text-gray-7" />
<div class="flex flex-col h-full">
<div class="text-center py-20 space-y-4">
<div class="w-16 h-16 bg-gray-2 rounded-3xl mx-auto flex items-center justify-center border border-gray-6">
<Zap class="text-gray-7" />
</div>
<h3 class="text-xl font-medium">Ready to work</h3>
<p class="text-gray-10 text-sm max-w-xs mx-auto">
Describe a task. I'll show progress and ask for permissions when needed.
</p>
</div>
<h3 class="text-xl font-medium">Ready to work</h3>
<p class="text-gray-10 text-sm max-w-xs mx-auto">
Describe a task. I'll show progress and ask for permissions when needed.
</p>
</div>
</Show>

<MessageList
messages={props.messages}
artifacts={props.artifacts}
developerMode={props.developerMode}
showThinking={props.showThinking}
expandedStepIds={props.expandedStepIds}
setExpandedStepIds={props.setExpandedStepIds}
onOpenArtifact={handleOpenArtifact}
footer={
showRunIndicator() ? (
<div class="flex justify-start pl-2">
<div class="w-full max-w-[68ch] space-y-2">
<Show when={thinkingStatus()}>
<div class="rounded-xl border border-gray-6/70 bg-gray-2/40 px-3 py-2 text-xs text-gray-11">
{/* Spacer */}
<div class="flex-1" />

{/* Quick Start Templates */}
<Show when={props.workspaceTemplates.length > 0}>
<div class="mb-12 px-4 max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-11 uppercase tracking-wider">
Quick Start Templates
</h3>
<button
class="text-xs text-gray-10 hover:text-gray-12 transition-colors"
onClick={() => {
props.setView("dashboard");
props.setTab("templates");
}}
>
View all
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<For each={props.workspaceTemplates.slice(0, 3)}>
{(t) => (
<button
type="button"
class="w-full flex items-center justify-between gap-3 text-left"
onClick={() => setThinkingExpanded((prev) => !prev)}
aria-expanded={thinkingExpanded()}
onClick={() => props.applyTemplate(t)}
class="group p-5 rounded-2xl bg-gray-2/30 border border-gray-6/50 hover:bg-gray-2 hover:border-gray-7 transition-all text-left"
>
<div class="flex items-center gap-2 min-w-0">
<span class="text-[10px] uppercase tracking-wide text-gray-9">Thinking</span>
<span class="truncate text-gray-12">{thinkingStatus()}</span>
<div class="w-10 h-10 rounded-full bg-gray-4 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
<FileText size={20} class="text-indigo-11" />
</div>
<ChevronDown
size={12}
class={`text-gray-8 transition-transform ${thinkingExpanded() ? "rotate-180" : ""}`}
/>
<h4 class="font-medium text-gray-12 mb-1">{t.title}</h4>
<p class="text-sm text-gray-10">
{t.description || "Run a saved workflow"}
</p>
</button>
<Show when={thinkingExpanded() && thinkingDetail()}>
{(detail) => (
<div class="mt-2 text-xs text-gray-11">
<div class="text-gray-12">{detail().title}</div>
<Show when={detail().detail}>
<div class="mt-1 whitespace-pre-wrap text-gray-10">{detail().detail}</div>
</Show>
)}
</For>
</div>
</div>
</Show>
</div>
</Show>

<Show when={props.messages.length > 0}>
<MessageList
messages={props.messages}
artifacts={props.artifacts}
developerMode={props.developerMode}
showThinking={props.showThinking}
expandedStepIds={props.expandedStepIds}
setExpandedStepIds={props.setExpandedStepIds}
onOpenArtifact={handleOpenArtifact}
footer={
showRunIndicator() ? (
<div class="flex justify-start pl-2">
<div class="w-full max-w-[68ch] space-y-2">
<Show when={thinkingStatus()}>
<div class="rounded-xl border border-gray-6/70 bg-gray-2/40 px-3 py-2 text-xs text-gray-11">
<button
type="button"
class="w-full flex items-center justify-between gap-3 text-left"
onClick={() => setThinkingExpanded((prev) => !prev)}
aria-expanded={thinkingExpanded()}
>
<div class="flex items-center gap-2 min-w-0">
<span class="text-[10px] uppercase tracking-wide text-gray-9">Thinking</span>
<span class="truncate text-gray-12">{thinkingStatus()}</span>
</div>
)}
</Show>
</div>
</Show>
<div
class={`w-full flex items-center justify-between gap-3 text-xs font-mono ${
runPhase() === "error" ? "text-red-11" : "text-gray-9"
}`}
role="status"
aria-live="polite"
>
<div class="flex items-center gap-2 min-w-0">
<Show
when={runPhase() !== "error"}
fallback={<AlertTriangle size={12} class="shrink-0" />}
>
<Zap size={12} class="shrink-0" />
</Show>
<span class="truncate">{runLine()}</span>
<ChevronDown
size={12}
class={`text-gray-8 transition-transform ${thinkingExpanded() ? "rotate-180" : ""}`}
/>
</button>
<Show when={thinkingExpanded() && thinkingDetail()}>
{(detail) => (
<div class="mt-2 text-xs text-gray-11">
<div class="text-gray-12">{detail().title}</div>
<Show when={detail().detail}>
<div class="mt-1 whitespace-pre-wrap text-gray-10">{detail().detail}</div>
</Show>
</div>
)}
</Show>
</div>
</Show>
<div
class={`w-full flex items-center justify-between gap-3 text-xs font-mono ${
runPhase() === "error" ? "text-red-11" : "text-gray-9"
}`}
role="status"
aria-live="polite"
>
<div class="flex items-center gap-2 min-w-0">
<Show
when={runPhase() !== "error"}
fallback={<AlertTriangle size={12} class="shrink-0" />}
>
<Zap size={12} class="shrink-0" />
</Show>
<span class="truncate">{runLine()}</span>
</div>
<span class="shrink-0">{runElapsedLabel()}</span>
</div>
<span class="shrink-0">{runElapsedLabel()}</span>
</div>
</div>
</div>
) : undefined
}
/>
) : undefined
}
/>
</Show>

<div ref={(el) => (messagesEndEl = el)} />
</div>

<Minimap containerRef={() => chatContainerEl} messages={props.messages} />

<Show when={artifactToast()}>
Expand All @@ -914,7 +963,7 @@ export default function SessionView(props: SessionViewProps) {
</Show>
</div>

<Composer
<Composer
prompt={props.prompt}
setPrompt={props.setPrompt}
busy={props.busy}
Expand Down
Loading