Skip to content

Commit 07988a0

Browse files
committed
The AI updates the time filter
1 parent 748bc6a commit 07988a0

File tree

6 files changed

+200
-24
lines changed

6 files changed

+200
-24
lines changed

apps/webapp/app/components/code/AIQueryInput.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,25 @@ import { useOrganization } from "~/hooks/useOrganizations";
2020
import { useProject } from "~/hooks/useProject";
2121
import { cn } from "~/utils/cn";
2222

23+
interface AITimeFilter {
24+
period?: string;
25+
from?: string;
26+
to?: string;
27+
}
28+
2329
type StreamEventType =
2430
| { type: "thinking"; content: string }
2531
| { type: "tool_call"; tool: string; args: unknown }
26-
| { type: "result"; success: true; query: string }
32+
| { type: "time_filter"; filter: AITimeFilter }
33+
| { type: "result"; success: true; query: string; timeFilter?: AITimeFilter }
2734
| { type: "result"; success: false; error: string };
2835

2936
export type AIQueryMode = "new" | "edit";
3037

3138
interface AIQueryInputProps {
3239
onQueryGenerated: (query: string) => void;
40+
/** Called when the AI sets a time filter - updates URL search params */
41+
onTimeFilterChange?: (filter: AITimeFilter) => void;
3342
/** Set this to a prompt to auto-populate and immediately submit */
3443
autoSubmitPrompt?: string;
3544
/** Change this to force re-submission even if prompt is the same */
@@ -40,6 +49,7 @@ interface AIQueryInputProps {
4049

4150
export function AIQueryInput({
4251
onQueryGenerated,
52+
onTimeFilterChange,
4353
autoSubmitPrompt,
4454
autoSubmitKey,
4555
getCurrentQuery,
@@ -174,10 +184,22 @@ export function AIQueryInput({
174184
setThinking((prev) => prev + event.content);
175185
break;
176186
case "tool_call":
177-
setThinking((prev) => prev + `\nValidating query...\n`);
187+
if (event.tool === "setTimeFilter") {
188+
setThinking((prev) => prev + `\nSetting time filter...\n`);
189+
} else {
190+
setThinking((prev) => prev + `\nValidating query...\n`);
191+
}
192+
break;
193+
case "time_filter":
194+
// Apply time filter immediately when the AI sets it
195+
onTimeFilterChange?.(event.filter);
178196
break;
179197
case "result":
180198
if (event.success) {
199+
// Apply time filter if included in result (backup in case time_filter event was missed)
200+
if (event.timeFilter) {
201+
onTimeFilterChange?.(event.timeFilter);
202+
}
181203
onQueryGenerated(event.query);
182204
setPrompt("");
183205
setLastResult("success");
@@ -189,7 +211,7 @@ export function AIQueryInput({
189211
break;
190212
}
191213
},
192-
[onQueryGenerated]
214+
[onQueryGenerated, onTimeFilterChange]
193215
);
194216

195217
const handleSubmit = useCallback(

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/AITabContent.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@ import { useState } from "react";
22
import { AIQueryInput } from "~/components/code/AIQueryInput";
33
import { Header3 } from "~/components/primitives/Headers";
44

5+
interface AITimeFilter {
6+
period?: string;
7+
from?: string;
8+
to?: string;
9+
}
10+
511
export function AITabContent({
612
onQueryGenerated,
13+
onTimeFilterChange,
714
getCurrentQuery,
815
aiFixRequest,
916
}: {
1017
onQueryGenerated: (query: string) => void;
18+
onTimeFilterChange?: (filter: AITimeFilter) => void;
1119
getCurrentQuery: () => string;
1220
aiFixRequest: { prompt: string; key: number } | null;
1321
}) {
@@ -31,6 +39,7 @@ export function AITabContent({
3139
<div className="space-y-2">
3240
<AIQueryInput
3341
onQueryGenerated={onQueryGenerated}
42+
onTimeFilterChange={onTimeFilterChange}
3443
autoSubmitPrompt={activeRequest?.prompt}
3544
autoSubmitKey={activeRequest?.key}
3645
getCurrentQuery={getCurrentQuery}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/QueryHelpSidebar.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,24 @@ import { ExamplesContent } from "./ExamplesContent";
1111
import { TableSchemaContent } from "./TableSchemaContent";
1212
import { TRQLGuideContent } from "./TRQLGuideContent";
1313

14+
interface AITimeFilter {
15+
period?: string;
16+
from?: string;
17+
to?: string;
18+
}
19+
1420
export function QueryHelpSidebar({
1521
onTryExample,
1622
onQueryGenerated,
23+
onTimeFilterChange,
1724
getCurrentQuery,
1825
activeTab,
1926
onTabChange,
2027
aiFixRequest,
2128
}: {
2229
onTryExample: (query: string, scope: QueryScope) => void;
2330
onQueryGenerated: (query: string) => void;
31+
onTimeFilterChange?: (filter: AITimeFilter) => void;
2432
getCurrentQuery: () => string;
2533
activeTab: string;
2634
onTabChange: (tab: string) => void;
@@ -55,6 +63,7 @@ export function QueryHelpSidebar({
5563
>
5664
<AITabContent
5765
onQueryGenerated={onQueryGenerated}
66+
onTimeFilterChange={onTimeFilterChange}
5867
getCurrentQuery={getCurrentQuery}
5968
aiFixRequest={aiFixRequest}
6069
/>

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,10 +464,17 @@ const QueryEditorForm = forwardRef<
464464
);
465465
});
466466

467+
interface AITimeFilter {
468+
period?: string;
469+
from?: string;
470+
to?: string;
471+
}
472+
467473
export default function Page() {
468474
const { defaultQuery, history, isAdmin } = useTypedLoaderData<typeof loader>();
469475
const results = useTypedActionData<typeof action>();
470476
const navigation = useNavigation();
477+
const { replace: replaceSearchParams } = useSearchParams();
471478

472479
// Use most recent history item if available, otherwise fall back to defaults
473480
const initialQuery = history.length > 0 ? history[0].query : defaultQuery;
@@ -488,6 +495,21 @@ export default function Page() {
488495
}));
489496
}, []);
490497

498+
// Handle time filter changes from AI
499+
const handleTimeFilterChange = useCallback(
500+
(filter: AITimeFilter) => {
501+
replaceSearchParams({
502+
period: filter.period,
503+
from: filter.from,
504+
to: filter.to,
505+
// Clear cursor/direction when time filter changes
506+
cursor: undefined,
507+
direction: undefined,
508+
});
509+
},
510+
[replaceSearchParams]
511+
);
512+
491513
const isLoading = (navigation.state === "submitting" || navigation.state === "loading") && navigation.formMethod === "POST";
492514

493515

@@ -702,6 +724,7 @@ export default function Page() {
702724
const formatted = autoFormatSQL(query);
703725
editorRef.current?.setQuery(formatted);
704726
}}
727+
onTimeFilterChange={handleTimeFilterChange}
705728
getCurrentQuery={() => editorRef.current?.getQuery() ?? ""}
706729
activeTab={sidebarTab}
707730
onTabChange={setSidebarTab}

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query.ai-generate.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { findProjectBySlug } from "~/models/project.server";
66
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
77
import { requireUserId } from "~/services/session.server";
88
import { EnvironmentParamSchema } from "~/utils/pathBuilder";
9-
import { AIQueryService } from "~/v3/services/aiQueryService.server";
9+
import { AIQueryService, type AITimeFilter } from "~/v3/services/aiQueryService.server";
1010
import { querySchemas } from "~/v3/querySchemas";
1111

1212
const RequestSchema = z.object({
@@ -102,6 +102,8 @@ export async function action({ request, params }: ActionFunctionArgs) {
102102
success?: boolean;
103103
query?: string;
104104
error?: string;
105+
filter?: AITimeFilter;
106+
timeFilter?: AITimeFilter;
105107
}) => {
106108
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
107109
};
@@ -122,6 +124,19 @@ export async function action({ request, params }: ActionFunctionArgs) {
122124
tool: part.toolName,
123125
args: part.args,
124126
});
127+
128+
// If it's a setTimeFilter call, emit the time_filter event immediately
129+
if (part.toolName === "setTimeFilter") {
130+
const args = part.args as { period?: string; from?: string; to?: string };
131+
sendEvent({
132+
type: "time_filter",
133+
filter: {
134+
period: args.period,
135+
from: args.from,
136+
to: args.to,
137+
},
138+
});
139+
}
125140
break;
126141
}
127142
case "error": {
@@ -136,12 +151,14 @@ export async function action({ request, params }: ActionFunctionArgs) {
136151
// Extract query from the final text
137152
const finalText = await result.text;
138153
const query = extractQueryFromText(finalText);
154+
const timeFilter = service.getPendingTimeFilter();
139155

140156
if (query) {
141157
sendEvent({
142158
type: "result",
143159
success: true,
144160
query,
161+
timeFilter,
145162
});
146163
} else if (
147164
finalText.toLowerCase().includes("cannot") ||

0 commit comments

Comments
 (0)