Skip to content

Commit 2dd3ea8

Browse files
committed
feat: add toolType as useAIChat feature, and add sql chat which can help user generate sql which easy to query
1 parent 2bf0c61 commit 2dd3ea8

File tree

12 files changed

+499
-237
lines changed

12 files changed

+499
-237
lines changed

src/client/components/CodeEditor/sql.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface SQLEditorProps {
3232
onExecuteLine?: (lineNumber: number, sql: string) => void | Promise<void>;
3333
enableRunButton?: boolean;
3434
executingLine?: number | null;
35+
onSelectSqlChange?: (lineNumber: number, sql: string) => void;
3536
}
3637

3738
export const SQLEditor: React.FC<SQLEditorProps> = React.memo((props) => {
@@ -41,11 +42,13 @@ export const SQLEditor: React.FC<SQLEditorProps> = React.memo((props) => {
4142
enableRunButton = false,
4243
onExecuteLine,
4344
executingLine = null,
45+
onSelectSqlChange,
4446
} = props;
4547
const colorScheme = useTheme();
4648
const theme = colorScheme === 'dark' ? 'vs-dark' : 'light';
4749
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
4850
const monacoRef = useRef<Monaco | null>(null);
51+
const lastSelectedLineRef = useRef<number | null>(null);
4952

5053
const registerSQLCompletions = useEvent((monaco: Monaco) => {
5154
return createSQLCompletionProvider(monaco, tables);
@@ -78,6 +81,36 @@ export const SQLEditor: React.FC<SQLEditorProps> = React.memo((props) => {
7881
executingLine
7982
);
8083
}
84+
85+
// Listen to cursor position changes
86+
if (onSelectSqlChange) {
87+
editor.onDidChangeCursorPosition((e) => {
88+
const lineNumber = e.position.lineNumber;
89+
90+
// Only trigger if line changed
91+
if (lastSelectedLineRef.current === lineNumber) {
92+
return;
93+
}
94+
lastSelectedLineRef.current = lineNumber;
95+
96+
const model = editor.getModel();
97+
if (!model) {
98+
return;
99+
}
100+
101+
const content = model.getValue();
102+
const statements = parseSQLStatements(content);
103+
104+
// Find the statement that contains the current line
105+
const statement = statements.find(
106+
(stmt) => stmt.startLine <= lineNumber && stmt.endLine >= lineNumber
107+
);
108+
109+
if (statement) {
110+
onSelectSqlChange(lineNumber, statement.sql);
111+
}
112+
});
113+
}
81114
});
82115

83116
const handleEditorWillMount = useEvent((monaco: Monaco) => {

src/client/components/ai/AIChatbot.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { LuCircleAlert } from 'react-icons/lu';
2+
import { LuCircleAlert, LuSparkles } from 'react-icons/lu';
33
import { SimpleContextUsage } from '../ai-elements/context';
44
import {
55
Conversation,
@@ -30,7 +30,7 @@ interface AIChatbotProps {
3030
input: string;
3131
setInput: (input: string) => void;
3232
placeholder?: string;
33-
usage?: LanguageModelUsage;
33+
usage?: LanguageModelUsage | null;
3434
isDisabled?: boolean;
3535
suggestions?: string[];
3636
alert?: React.ReactNode;
@@ -62,6 +62,24 @@ export const AIChatbot: React.FC<AIChatbotProps> = React.memo((props) => {
6262
<ConversationScrollButton />
6363
</Conversation>
6464

65+
{props.messages.length === 0 && (
66+
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-8 text-center">
67+
<div className="bg-primary/10 text-primary flex h-16 w-16 items-center justify-center rounded-full">
68+
<LuSparkles className="h-8 w-8" />
69+
</div>
70+
<div className="space-y-2">
71+
<h4 className="text-lg font-semibold">
72+
{t('Use AI to improve your work')}
73+
</h4>
74+
<p className="text-muted-foreground max-w-sm text-sm">
75+
{t(
76+
'Leverage AI to generate SQL queries, gain insights, and enhance your productivity. Describe your data analysis needs and let AI assist you effortlessly.'
77+
)}
78+
</p>
79+
</div>
80+
</div>
81+
)}
82+
6583
{props.error && (
6684
<div className="p-3">
6785
<Alert variant="destructive">

src/client/components/ai/AIResponseMessages.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AbstractChat, ChatStatus, UIDataTypes, UIMessage, UITools } from 'ai';
2-
import React from 'react';
2+
import React, { Fragment } from 'react';
33
import {
44
Message,
55
MessageAction,
@@ -31,8 +31,8 @@ export const AIResponseMessages: React.FC<AIResponseMessagesProps> = React.memo(
3131
const isLastMessage = messageIndex === messages.length - 1;
3232

3333
return (
34-
<>
35-
<Message from={message.role} key={message.id}>
34+
<Fragment key={message.id}>
35+
<Message from={message.role}>
3636
<MessageContent
3737
className={message.role === 'assistant' ? 'w-full' : 'w-auto'}
3838
>
@@ -71,7 +71,7 @@ export const AIResponseMessages: React.FC<AIResponseMessagesProps> = React.memo(
7171
</MessageAction>
7272
</MessageActions>
7373
)}
74-
</>
74+
</Fragment>
7575
);
7676
})}
7777

src/client/components/insights/warehouse/WarehouseAIChatPanel.tsx

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,67 @@
11
import React from 'react';
22
import { useTranslation } from '@i18next-toolkit/react';
3-
import { LuSparkles } from 'react-icons/lu';
3+
import { AIChatbot } from '@/components/ai/AIChatbot';
4+
import { useWarehouseSqlChat } from '@/hooks/useWarehouseSqlChat';
5+
import { AppRouterOutput } from '@/api/trpc';
6+
7+
type DatabaseType =
8+
AppRouterOutput['insights']['warehouse']['database']['list'][number];
49

510
interface WarehouseAIChatPanelProps {
611
workspaceId: string;
7-
databaseId: string;
12+
database: DatabaseType;
13+
currentSelectedSql: string | null;
814
onSQLGenerated?: (sql: string) => void;
915
}
1016

1117
export const WarehouseAIChatPanel: React.FC<WarehouseAIChatPanelProps> =
12-
React.memo(() => {
18+
React.memo((props) => {
1319
const { t } = useTranslation();
1420

21+
const {
22+
messages,
23+
status,
24+
input,
25+
setInput,
26+
usage,
27+
addToolResult,
28+
handleReset,
29+
handleSend,
30+
handleRegenerate,
31+
} = useWarehouseSqlChat({
32+
workspaceId: props.workspaceId,
33+
selectedScopes: [
34+
{
35+
type: 'database',
36+
id: props.database.id,
37+
name: props.database.name,
38+
},
39+
],
40+
currentSelectedSql: props.currentSelectedSql,
41+
onSQLGenerated: props.onSQLGenerated,
42+
});
43+
1544
return (
1645
<div className="flex h-full flex-col">
17-
{/* Header */}
1846
<div className="border-b px-4 py-3">
1947
<h3 className="font-semibold">{t('AI Assistant')}</h3>
2048
<p className="text-muted-foreground text-xs">
2149
{t('AI-powered SQL query generation')}
2250
</p>
2351
</div>
2452

25-
{/* Coming Soon Content */}
26-
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-8 text-center">
27-
<div className="bg-primary/10 text-primary flex h-16 w-16 items-center justify-center rounded-full">
28-
<LuSparkles className="h-8 w-8" />
29-
</div>
30-
<div className="space-y-2">
31-
<h4 className="text-lg font-semibold">{t('Coming Soon')}</h4>
32-
<p className="text-muted-foreground max-w-sm text-sm">
33-
{t(
34-
'AI-powered SQL query generation and assistance will be available soon. Stay tuned!'
35-
)}
36-
</p>
37-
</div>
38-
</div>
53+
<AIChatbot
54+
className="flex-1 overflow-hidden"
55+
messages={messages}
56+
status={status}
57+
input={input}
58+
setInput={setInput}
59+
usage={usage}
60+
onAddToolResult={addToolResult}
61+
onSubmit={handleSend}
62+
onReset={handleReset}
63+
onRegenerate={handleRegenerate}
64+
/>
3965
</div>
4066
);
4167
});

src/client/hooks/useAIChat.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useChat } from '@ai-sdk/react';
22
import {
33
ChatInit,
44
DefaultChatTransport,
5+
HttpChatTransportInitOptions,
56
LanguageModelUsage,
67
lastAssistantMessageIsCompleteWithToolCalls,
78
UIMessage,
@@ -20,6 +21,8 @@ interface UseAIChatOptions {
2021
*/
2122
apiEndpoint: string;
2223

24+
transportOptions?: Omit<HttpChatTransportInitOptions<UIMessage>, 'api'>;
25+
2326
/**
2427
* Custom tool call handler
2528
*/
@@ -53,6 +56,7 @@ interface UseAIChatOptions {
5356
*/
5457
export function useAIChat({
5558
apiEndpoint,
59+
transportOptions,
5660
onToolCall,
5761
initialMessages,
5862
onMessagesUpdate,
@@ -73,9 +77,10 @@ export function useAIChat({
7377
const transport = useMemo(
7478
() =>
7579
new DefaultChatTransport({
80+
...transportOptions,
7681
api: apiEndpoint,
7782
}),
78-
[apiEndpoint]
83+
[apiEndpoint, transportOptions]
7984
);
8085

8186
// Initialize chat with AI SDK

src/client/hooks/useWarehouseInsightChat.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type ChartBlock = InsightsTimeEventChartProps & {
2121
title: string;
2222
};
2323

24-
interface UseWarehouseAIChatOptions {
24+
interface UseWarehouseInsightChatOptions {
2525
workspaceId: string;
2626
selectedScopes: WarehouseScope[];
2727
isDisabled?: boolean;
@@ -36,7 +36,7 @@ export function useWarehouseInsightChat({
3636
workspaceId,
3737
selectedScopes,
3838
isDisabled = false,
39-
}: UseWarehouseAIChatOptions) {
39+
}: UseWarehouseInsightChatOptions) {
4040
const { t } = useTranslation();
4141
const [input, setInput] = useState('');
4242

@@ -82,6 +82,11 @@ export function useWarehouseInsightChat({
8282
regenerate: regenerateChat,
8383
} = useAIChat({
8484
apiEndpoint: `/api/insights/${workspaceId}/chat`,
85+
transportOptions: {
86+
body: {
87+
toolType: 'chart',
88+
},
89+
},
8590
initialMessages: session?.messages ?? [],
8691
onMessagesUpdate: (messages) => {
8792
// Save messages when AI response finishes

0 commit comments

Comments
 (0)