Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useSetupStore } from "@features/setup/stores/setupStore";
import type { DiscoveredTask } from "@features/setup/types";
import {
CATEGORY_CONFIG,
FALLBACK_CATEGORY_CONFIG,
} from "@features/setup/utils/categoryConfig";
import { Chip } from "@posthog/quill";
import { Tooltip } from "@radix-ui/themes";
import { useState } from "react";

const COLLAPSED_LIMIT = 4;

interface SuggestedTaskChipsProps {
onSelect: (task: DiscoveredTask) => void;
}

export function SuggestedTaskChips({ onSelect }: SuggestedTaskChipsProps) {
const discoveredTasks = useSetupStore((s) => s.discoveredTasks);
const [expanded, setExpanded] = useState(false);

if (discoveredTasks.length === 0) return null;

const visible =
expanded || discoveredTasks.length <= COLLAPSED_LIMIT
? discoveredTasks
: discoveredTasks.slice(0, COLLAPSED_LIMIT);
const hiddenCount = discoveredTasks.length - visible.length;

return (
<div className="mt-3 flex flex-wrap gap-1.5">
{visible.map((task) => (
<SuggestedTaskChip key={task.id} task={task} onSelect={onSelect} />
))}
{hiddenCount > 0 && (
<Chip
size="sm"
onClick={() => setExpanded(true)}
className="cursor-pointer! whitespace-nowrap text-(--gray-11)"
>
+{hiddenCount} more
</Chip>
)}
</div>
);
}

function SuggestedTaskChip({
task,
onSelect,
}: {
task: DiscoveredTask;
onSelect: (task: DiscoveredTask) => void;
}) {
const config = CATEGORY_CONFIG[task.category] ?? FALLBACK_CATEGORY_CONFIG;
const TaskIcon = config.icon;

return (
<Tooltip content={task.description}>
<Chip
size="sm"
onClick={() => onSelect(task)}
className="max-w-[220px] cursor-pointer! gap-1.5 whitespace-nowrap pl-2"
>
<TaskIcon
size={14}
weight="duotone"
color={`var(--${config.color}-9)`}
/>
<span className="min-w-0 truncate">{task.title}</span>
</Chip>
</Tooltip>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { UnifiedModelSelector } from "@features/sessions/components/UnifiedModel
import { getCurrentModeFromConfigOptions } from "@features/sessions/stores/sessionStore";
import type { AgentAdapter } from "@features/settings/stores/settingsStore";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import { useSetupStore } from "@features/setup/stores/setupStore";
import type { DiscoveredTask } from "@features/setup/types";
import { useAutoFocusOnTyping } from "@hooks/useAutoFocusOnTyping";
import { useConnectivity } from "@hooks/useConnectivity";
import {
Expand All @@ -47,6 +49,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { usePreviewConfig } from "../hooks/usePreviewConfig";
import { useTaskCreation } from "../hooks/useTaskCreation";
import { CloudGithubMissingNotice } from "./CloudGithubMissingNotice";
import { SuggestedTaskChips } from "./SuggestedTaskChips";
import { type WorkspaceMode, WorkspaceModeSelect } from "./WorkspaceModeSelect";

interface TaskInputProps {
Expand Down Expand Up @@ -539,6 +542,15 @@ export function TaskInput({
editorRef.current?.setContent(text);
editorRef.current?.focus();
}, []);
const handleSelectSuggestion = useCallback(
async (task: DiscoveredTask) => {
const ok = await handleSubmit({
segments: [{ type: "text", text: task.prompt ?? task.title }],
});
if (ok) useSetupStore.getState().removeDiscoveredTask(task.id);
},
[handleSubmit],
);
const hasPendingDraft = useCallback(
() => !(editorRef.current?.isEmpty() ?? true),
[],
Expand Down Expand Up @@ -825,6 +837,7 @@ export function TaskInput({
<CloudGithubMissingNotice />
</div>
)}
<SuggestedTaskChips onSelect={handleSelectSuggestion} />
</Flex>
</Flex>
</Flex>
Expand Down
183 changes: 101 additions & 82 deletions apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTaskInputHistoryStore } from "@features/message-editor/stores/taskIn
import type { EditorHandle } from "@features/message-editor/types";
import {
contentToXml,
type EditorContent,
extractFilePaths,
} from "@features/message-editor/utils/content";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
Expand Down Expand Up @@ -48,7 +49,7 @@ interface UseTaskCreationOptions {
interface UseTaskCreationReturn {
isCreatingTask: boolean;
canSubmit: boolean;
handleSubmit: () => void;
handleSubmit: (contentOverride?: EditorContent) => Promise<boolean>;
}

function prepareTaskInput(
Expand Down Expand Up @@ -204,96 +205,114 @@ export function useTaskCreation({
!isCreatingTask &&
!editorIsEmpty;

const handleSubmit = useCallback(async () => {
const editor = editorRef.current;
if (!canSubmit || !editor) return;
const handleSubmit = useCallback(
async (contentOverride?: EditorContent): Promise<boolean> => {
const editor = editorRef.current;
const allowSubmit = contentOverride
? isAuthenticated &&
isOnline &&
hasRequiredPath &&
!isCreatingTask &&
!!editor
: canSubmit && !!editor;
if (!allowSubmit || !editor) return false;

setIsCreatingTask(true);
setIsCreatingTask(true);

try {
const content = editor.getContent();

const plainText = editor.getText()?.trim();
if (plainText) {
useTaskInputHistoryStore.getState().addPrompt(plainText);
}
try {
const content = contentOverride ?? editor.getContent();

const input = prepareTaskInput(content, {
selectedDirectory,
selectedRepository,
githubIntegrationId,
githubUserIntegrationId,
workspaceMode,
branch,
executionMode,
adapter,
model,
reasoningLevel,
environmentId,
sandboxEnvironmentId,
signalReportId,
});
if (!contentOverride) {
const plainText = editor.getText()?.trim();
if (plainText) {
useTaskInputHistoryStore.getState().addPrompt(plainText);
}
}

if (executionMode) {
useSettingsStore.getState().setLastUsedInitialTaskMode(executionMode);
}
const input = prepareTaskInput(content, {
selectedDirectory,
selectedRepository,
githubIntegrationId,
githubUserIntegrationId,
workspaceMode,
branch,
executionMode,
adapter,
model,
reasoningLevel,
environmentId,
sandboxEnvironmentId,
signalReportId,
});

const taskService = get<TaskService>(RENDERER_TOKENS.TaskService);
const result = await taskService.createTask(input, (output) => {
invalidateTasks(output.task);
if (signalReportId) {
clearTaskInputReportAssociation();
}
if (onTaskCreated) {
onTaskCreated(output.task);
} else {
navigateToTask(output.task);
if (executionMode) {
useSettingsStore.getState().setLastUsedInitialTaskMode(executionMode);
}
useTourStore.getState().completeTour(createFirstTaskTour.id);
editor.clear();
});

if (result.success) {
void trackTaskCreated(input, selectedDirectory);
}

if (!result.success) {
const title = getErrorTitle(result.failedStep);
toast.error(title, { description: result.error });
log.error("Task creation failed", {
failedStep: result.failedStep,
error: result.error,
const taskService = get<TaskService>(RENDERER_TOKENS.TaskService);
const result = await taskService.createTask(input, (output) => {
invalidateTasks(output.task);
if (signalReportId) {
clearTaskInputReportAssociation();
}
if (onTaskCreated) {
onTaskCreated(output.task);
} else {
navigateToTask(output.task);
}
useTourStore.getState().completeTour(createFirstTaskTour.id);
editor.clear();
});

if (result.success) {
void trackTaskCreated(input, selectedDirectory);
}

if (!result.success) {
const title = getErrorTitle(result.failedStep);
toast.error(title, { description: result.error });
log.error("Task creation failed", {
failedStep: result.failedStep,
error: result.error,
});
}
return result.success;
} catch (error) {
const description =
error instanceof Error ? error.message : "Unknown error";
toast.error("Failed to create task", { description });
log.error("Unexpected error during task creation", { error });
return false;
} finally {
setIsCreatingTask(false);
}
} catch (error) {
const description =
error instanceof Error ? error.message : "Unknown error";
toast.error("Failed to create task", { description });
log.error("Unexpected error during task creation", { error });
} finally {
setIsCreatingTask(false);
}
}, [
canSubmit,
editorRef,
selectedDirectory,
selectedRepository,
githubIntegrationId,
githubUserIntegrationId,
workspaceMode,
branch,
executionMode,
adapter,
model,
reasoningLevel,
environmentId,
sandboxEnvironmentId,
signalReportId,
clearTaskInputReportAssociation,
invalidateTasks,
navigateToTask,
onTaskCreated,
]);
},
[
canSubmit,
editorRef,
isAuthenticated,
isOnline,
hasRequiredPath,
isCreatingTask,
selectedDirectory,
selectedRepository,
githubIntegrationId,
githubUserIntegrationId,
workspaceMode,
branch,
executionMode,
adapter,
model,
reasoningLevel,
environmentId,
sandboxEnvironmentId,
signalReportId,
clearTaskInputReportAssociation,
invalidateTasks,
navigateToTask,
onTaskCreated,
],
);

return {
isCreatingTask,
Expand Down
Loading