Skip to content

Commit daf1211

Browse files
authored
feat: add /good, /bad, and /feedback slash commands (#896)
1 parent ed1adf2 commit daf1211

3 files changed

Lines changed: 72 additions & 2 deletions

File tree

apps/twig/src/renderer/features/message-editor/suggestions/getSuggestions.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ import Fuse, { type IFuseOptions } from "fuse.js";
55
import { useDraftStore } from "../stores/draftStore";
66
import type { CommandSuggestionItem, FileSuggestionItem } from "../types";
77

8+
const TWIG_COMMANDS: AvailableCommand[] = [
9+
{
10+
name: "good",
11+
description: "Capture positive feedback",
12+
input: { hint: "optional comment" },
13+
},
14+
{
15+
name: "bad",
16+
description: "Capture negative feedback",
17+
input: { hint: "optional comment" },
18+
},
19+
{
20+
name: "feedback",
21+
description: "Capture general feedback",
22+
input: { hint: "optional comment" },
23+
},
24+
];
25+
826
const COMMAND_LIMIT = 5;
927

1028
const COMMAND_FUSE_OPTIONS: IFuseOptions<AvailableCommand> = {
@@ -67,7 +85,7 @@ export function getCommandSuggestions(
6785
query: string,
6886
): CommandSuggestionItem[] {
6987
const taskId = useDraftStore.getState().contexts[sessionId]?.taskId;
70-
const commands = getAvailableCommandsForTask(taskId);
88+
const commands = [...TWIG_COMMANDS, ...getAvailableCommandsForTask(taskId)];
7189
const filtered = searchCommands(commands, query);
7290

7391
return filtered.map((cmd) => ({

apps/twig/src/renderer/features/task-detail/components/TaskLogsPanel.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ import { useDeleteTask } from "@features/tasks/hooks/useTasks";
1313
import { useWorkspaceStore } from "@features/workspace/stores/workspaceStore";
1414
import { useConnectivity } from "@hooks/useConnectivity";
1515
import { Box } from "@radix-ui/themes";
16+
import { track } from "@renderer/lib/analytics";
1617
import { logger } from "@renderer/lib/logger";
1718
import { useNavigationStore } from "@renderer/stores/navigationStore";
1819
import { trpcVanilla } from "@renderer/trpc/client";
1920
import type { Task } from "@shared/types";
2021
import { toast } from "@utils/toast";
2122
import { useCallback, useEffect, useRef } from "react";
23+
import { ANALYTICS_EVENTS, type FeedbackType } from "@/types/analytics";
2224

2325
const log = logger.scope("task-logs-panel");
2426

@@ -108,6 +110,30 @@ export function TaskLogsPanel({ taskId, task }: TaskLogsPanelProps) {
108110

109111
const handleSendPrompt = useCallback(
110112
async (text: string) => {
113+
const feedbackMatch = text.match(/^\/(good|bad|feedback)(?:\s+(.*))?$/);
114+
if (feedbackMatch) {
115+
const rawType = feedbackMatch[1];
116+
const feedbackType: FeedbackType =
117+
rawType === "feedback" ? "general" : (rawType as FeedbackType);
118+
const comment = feedbackMatch[2]?.trim() || undefined;
119+
track(ANALYTICS_EVENTS.TASK_FEEDBACK, {
120+
task_id: taskId,
121+
task_run_id: session?.taskRunId ?? task.latest_run?.id,
122+
log_url: session?.logUrl ?? task.latest_run?.log_url,
123+
event_count: events.length,
124+
feedback_type: feedbackType,
125+
feedback_comment: comment,
126+
});
127+
const label =
128+
feedbackType === "good"
129+
? "Positive"
130+
: feedbackType === "bad"
131+
? "Negative"
132+
: "General";
133+
toast.success(`${label} feedback captured`);
134+
return;
135+
}
136+
111137
try {
112138
markAsViewed(taskId);
113139

@@ -129,7 +155,16 @@ export function TaskLogsPanel({ taskId, task }: TaskLogsPanelProps) {
129155
log.error("Failed to send prompt", error);
130156
}
131157
},
132-
[taskId, markActivity, markAsViewed],
158+
[
159+
taskId,
160+
markActivity,
161+
markAsViewed,
162+
events.length,
163+
session?.logUrl,
164+
session?.taskRunId,
165+
task.latest_run?.id,
166+
task.latest_run?.log_url,
167+
],
133168
);
134169

135170
const handleCancelPrompt = useCallback(async () => {

apps/twig/src/types/analytics.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type GitActionType =
1111
| "publish"
1212
| "commit-push"
1313
| "create-pr";
14+
export type FeedbackType = "good" | "bad" | "general";
1415
export type FileOpenSource = "sidebar" | "agent-suggestion" | "search" | "diff";
1516
export type FileChangeType = "added" | "modified" | "deleted";
1617
export type StopReason = "user_cancelled" | "completed" | "error" | "timeout";
@@ -157,6 +158,16 @@ export interface FirstTaskCompletedProperties {
157158
duration_seconds: number;
158159
}
159160

161+
// Feedback events
162+
export interface TaskFeedbackProperties {
163+
task_id: string;
164+
task_run_id?: string;
165+
log_url?: string;
166+
event_count: number;
167+
feedback_type: FeedbackType;
168+
feedback_comment?: string;
169+
}
170+
160171
// Event names as constants
161172
export const ANALYTICS_EVENTS = {
162173
// App lifecycle
@@ -201,6 +212,9 @@ export const ANALYTICS_EVENTS = {
201212
// Settings events
202213
SETTING_CHANGED: "Setting changed",
203214

215+
// Feedback events
216+
TASK_FEEDBACK: "Task feedback",
217+
204218
// Error events
205219
TASK_CREATION_FAILED: "Task creation failed",
206220
AGENT_SESSION_ERROR: "Agent session error",
@@ -246,6 +260,9 @@ export type EventPropertyMap = {
246260
// Settings events
247261
[ANALYTICS_EVENTS.SETTING_CHANGED]: SettingChangedProperties;
248262

263+
// Feedback events
264+
[ANALYTICS_EVENTS.TASK_FEEDBACK]: TaskFeedbackProperties;
265+
249266
// Error events
250267
[ANALYTICS_EVENTS.TASK_CREATION_FAILED]: TaskCreationFailedProperties;
251268
[ANALYTICS_EVENTS.AGENT_SESSION_ERROR]: AgentSessionErrorProperties;

0 commit comments

Comments
 (0)