diff --git a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/chatbot-page.client.tsx b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/chatbot-page.client.tsx
index 8cc6b05..35f08bc 100644
--- a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/chatbot-page.client.tsx
+++ b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/chatbot-page.client.tsx
@@ -97,11 +97,13 @@ export default function ChatBotPageContent({
};
+ const hasSessions = chat.sessionsForDisplay.length > 0;
+
return (
{compactMode ? (
- {sessionSelector}
+ {hasSessions && sessionSelector}
) : (
- sessionSelector
+ hasSessions && sessionSelector
)}
diff --git a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/components/empty.tsx b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/components/empty.tsx
index 60c48ba..d22e65a 100644
--- a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/components/empty.tsx
+++ b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/components/empty.tsx
@@ -1,8 +1,9 @@
'use client';
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
import { useAtom } from 'jotai';
import { useTranslations } from 'next-intl';
+import { useParams } from 'next/navigation';
import { Sparkles } from 'lucide-react';
import {
@@ -17,8 +18,10 @@ import { Suggestions, Suggestion } from '@/components/ai-elements/suggestion';
import { activeDatabaseAtom } from '@/shared/stores/app.store';
import { useDatabases } from '@/hooks/use-databases';
import { useTables } from '@/hooks/use-tables';
+import { useSchema } from '@/hooks/use-schema';
import { DatabaseSelect } from '../../../components/sql-console-sidebar/database-select';
import { TableMentionTextarea } from '../thread/table-mention-textarea';
+import { generateSuggestions } from './suggestion-rules';
type ChatWelcomeProps = {
onSend: (text: string) => void;
@@ -33,6 +36,17 @@ export default function ChatWelcome({ onSend, disabled = false }: ChatWelcomePro
const [activeDatabase, setActiveDatabase] = useAtom(activeDatabaseAtom);
const { databases } = useDatabases();
const { tables } = useTables(activeDatabase);
+ const params = useParams();
+ const connectionId = params.connectionId as string | undefined;
+ const { schema } = useSchema(connectionId);
+
+ const suggestions = useMemo(() => {
+ const fallbacks = SUGGESTION_KEYS.map((key) => t(`Welcome.Suggestions.${key}`));
+ if (!schema?.databases || !activeDatabase) return fallbacks;
+ const db = schema.databases.find((d) => d.name === activeDatabase);
+ if (!db?.tables?.length) return fallbacks;
+ return generateSuggestions(db.tables, fallbacks, 4);
+ }, [schema, activeDatabase, t]);
const handleSubmit = (message: PromptInputMessage) => {
const text = message.text?.trim();
@@ -64,10 +78,10 @@ export default function ChatWelcome({ onSend, disabled = false }: ChatWelcomePro
- {SUGGESTION_KEYS.map((key) => (
+ {suggestions.map((text, i) => (
@@ -87,11 +101,12 @@ export default function ChatWelcome({ onSend, disabled = false }: ChatWelcomePro
/>
-
t.name ?? t)}>
+ t.name ?? t)} autoFocus>
setInput(e.target.value)}
+ autoFocus
className="min-h-18 w-full resize-none border-0 bg-transparent text-sm focus-visible:outline-none focus-visible:ring-0"
/>
diff --git a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/components/suggestion-rules.ts b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/components/suggestion-rules.ts
new file mode 100644
index 0000000..daccb6d
--- /dev/null
+++ b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/components/suggestion-rules.ts
@@ -0,0 +1,96 @@
+import type { TableSchema } from '@/shared/stores/schema.store';
+
+type SuggestedQuestion = {
+ text: string;
+ priority: number;
+};
+
+type TableRule = {
+ pattern: RegExp;
+ template: (table: string) => string;
+};
+
+type ColumnRule = {
+ pattern: RegExp;
+ template: (table: string, column: string) => string;
+};
+
+const TABLE_RULES: TableRule[] = [
+ { pattern: /order/i, template: (t) => `Show daily order trends from ${t}` },
+ { pattern: /user|customer/i, template: (t) => `Top 10 users by activity from ${t}` },
+ { pattern: /log|event/i, template: (t) => `Error logs in the last 24 hours from ${t}` },
+ { pattern: /product|item/i, template: (t) => `Most popular products from ${t}` },
+ { pattern: /payment|transaction/i, template: (t) => `Payment summary from ${t}` },
+];
+
+const COLUMN_RULES: ColumnRule[] = [
+ {
+ pattern: /^(created_at|updated_at|timestamp|date|time|datetime|created|updated)$/i,
+ template: (t) => `Trend of ${t} in the last 7 days`,
+ },
+ {
+ pattern: /^(amount|price|revenue|total|cost|salary|balance)$/i,
+ template: (t, c) => `Top 10 records from ${t} by ${c}`,
+ },
+ {
+ pattern: /^(status|state|type|category)$/i,
+ template: (t, c) => `Breakdown of ${t} by ${c}`,
+ },
+];
+
+function getTableBaseName(name: string): string {
+ const parts = name.split('.');
+ return parts[parts.length - 1];
+}
+
+export function generateSuggestions(
+ tables: TableSchema[],
+ fallbacks: string[],
+ limit = 4,
+): string[] {
+ const suggestions: SuggestedQuestion[] = [];
+ const usedTables = new Set();
+
+ for (const table of tables) {
+ const baseName = getTableBaseName(table.name);
+
+ for (const rule of TABLE_RULES) {
+ if (rule.pattern.test(baseName) && !usedTables.has(table.name)) {
+ suggestions.push({ text: rule.template(baseName), priority: 1 });
+ usedTables.add(table.name);
+ break;
+ }
+ }
+ }
+
+ for (const table of tables) {
+ if (usedTables.has(table.name)) continue;
+ const baseName = getTableBaseName(table.name);
+
+ for (const column of table.columns) {
+ if (usedTables.has(table.name)) break;
+
+ for (const rule of COLUMN_RULES) {
+ if (rule.pattern.test(column)) {
+ suggestions.push({ text: rule.template(baseName, column), priority: 2 });
+ usedTables.add(table.name);
+ break;
+ }
+ }
+ }
+ }
+
+ suggestions.sort((a, b) => a.priority - b.priority);
+ const result = suggestions.slice(0, limit).map((s) => s.text);
+
+ if (result.length < limit) {
+ for (const fb of fallbacks) {
+ if (result.length >= limit) break;
+ if (!result.includes(fb)) {
+ result.push(fb);
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/thread/table-mention-textarea.tsx b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/thread/table-mention-textarea.tsx
index fcb3b56..fee6d0c 100644
--- a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/thread/table-mention-textarea.tsx
+++ b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/thread/table-mention-textarea.tsx
@@ -14,10 +14,17 @@ type TableMentionTextareaProps = {
onChange: (value: string) => void;
tables: string[];
children: any;
+ autoFocus?: boolean;
} & Omit, 'value' | 'onChange'>;
-export function TableMentionTextarea({ value, onChange, tables, children }: TableMentionTextareaProps) {
+export function TableMentionTextarea({ value, onChange, tables, children, autoFocus }: TableMentionTextareaProps) {
const textareaRef = React.useRef(null);
+
+ React.useEffect(() => {
+ if (autoFocus && textareaRef.current) {
+ textareaRef.current.focus();
+ }
+ }, []);
const t = useTranslations('Chatbot');
const [open, setOpen] = React.useState(false);
diff --git a/apps/web/public/locales/en.json b/apps/web/public/locales/en.json
index e83d187..2468db0 100644
--- a/apps/web/public/locales/en.json
+++ b/apps/web/public/locales/en.json
@@ -763,7 +763,8 @@
"Input": {
"GlobalPlaceholder": "@ mention tables to ask about the database, Shift + Enter for a new line",
"CopilotPlaceholder": "Ask about this SQL / let AI fix or rewrite it, Shift + Enter for a new line",
- "SentWithAttachments": "Sent with attachments"
+ "SentWithAttachments": "Sent with attachments",
+ "WelcomePlaceholder": "Ask about your data… Try: \"Top 10 users by revenue last week\""
},
"Sessions": {
"Title": "Sessions",