Skip to content
Merged
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
Expand Up @@ -220,7 +220,9 @@ private String buildChatTask(String prompt) {
+ "It does not read as a request to build or change a chart/dashboard — it looks like a "
+ "greeting, small talk, or a question about what you can do. Reply briefly and naturally "
+ "in plain text (no HTML, no code block, no tool calls, no grounding, no SQL). If it's a "
+ "greeting, greet back and invite them to describe a dashboard. Keep it to 1-2 sentences.";
+ "greeting, greet back and invite them to describe a dashboard.\n\n"
+ "STYLE: Keep it to 1-2 short sentences. Lead with the answer. No filler phrases like "
+ "\"Great question!\" or restating what they asked. Just answer directly.";
}

// ── the task the agent runs ────────────────────────────────────────────
Expand Down Expand Up @@ -335,17 +337,25 @@ Output your FINAL message as ONLY fenced blocks (one ```dashboard-shell``` first
outside a fence, no tool calls after the last one.
Do NOT wrap the whole thing in a single ```html block — the shell and each widget are SEPARATE fences.

7. END with a ```dashboard-note``` fence: 1-3 sentences to the person who asked, in the same plain
7. END with a ```dashboard-note``` fence: a SHORT reply to the person who asked, in the same plain
business language as the two hard rules above (a note naming a table, a column, SQL, or the
connection id breaks the same security requirement the dashboard itself is bound by).
Say what THIS turn actually changed — not that a dashboard exists. Then, in the same note:
- State anything you could NOT do, could not verify, or chose to skip, and why. A build that
partly worked must say so. Never claim a number is correct because a query returned it.

REPLY STYLE — brevity is mandatory:
- Lead with what changed or what you built. No filler, no restating their request.
- Keep it to 1-3 short lines. Use markdown bullets when listing multiple items (requirements
met, status updates, or next steps). Never write long paragraphs.
- Skip phrases like "I've created...", "Here's what I did...", "As requested...". Just state
the facts: "Added revenue chart. Date range defaults to last 30 days."
- Only write more than 3 lines when explaining an error, a limitation you hit, or a breaking
change — and even then stay concise.

Content requirements (still apply):
- State anything you could NOT do, could not verify, or chose to skip, and why.
- If the user was correcting or disputing something (a wrong figure, a chart that didn't load),
say plainly whether it is now fixed, and what the value/behaviour is now versus what they
reported. If you could not reproduce or resolve their complaint, say THAT — do not answer a
correction with a description of what you built.
Write it as you would to a colleague: specific and short. Never open with "Done".""");
say plainly whether it is now fixed. If you could not resolve it, say THAT.
- Never claim a number is correct just because a query returned it.
- Never open with "Done" or "Great".""");
return sb.toString();
}

Expand Down
103 changes: 56 additions & 47 deletions src/components/layout/AppSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AGENTS_ENABLED, canAccessHomeSection, getConnectionAccessBadge, getConn
import { PERMISSIONS } from '@/lib/permissions'
import ManageConnectionsModal from '@/components/ManageConnectionsModal'
import SettingsModal from '@/components/SettingsModal'
import ProfileSwitch from './ProfileSwitch'
import { useAuth } from '@/hooks/useAuth'
import styles from './AppSidebar.module.css'

Expand All @@ -30,7 +31,7 @@ export default function AppSidebar() {
const [showConnectionDropdown, setShowConnectionDropdown] = useState(false)
const userMenuRef = useRef(null)
const connectionDropdownRef = useRef(null)
const { logout, role, username, impersonating, permissions, hasPermission } = useAuth()
const { logout, role, username, impersonating, permissions, hasPermission, canSwitchProfile } = useAuth()
// Connections (add/edit/delete a database) is administrative: the backend already
// refuses it to Developer and Data Engineer with 403, so hiding the button stops the
// UI offering a door that only leads to an error. Settings is likewise administrative
Expand Down Expand Up @@ -137,12 +138,13 @@ export default function AppSidebar() {
{/* Bottom: connections + profile */}
<div className={styles.divider} />
<div className={styles.bottom}>
{canSwitchProfile && <ProfileSwitch collapsed={collapsed} />}
<div className={styles.connectionSwitcher} ref={connectionDropdownRef}>
<button
className={styles.bottomItem}
onClick={() => setShowConnectionDropdown((v) => !v)}
title={collapsed ? connectionLabel : undefined}
disabled={isLoading || connections.length === 0}
disabled={isLoading || (connections.length === 0 && !canManageConnections)}
>
<Database size={15} className={styles.navIcon} />
<span className={`${styles.navLabel} ${collapsed ? styles.navLabelHidden : ''}`}>
Expand All @@ -154,62 +156,69 @@ export default function AppSidebar() {
{!collapsed && <ChevronDown size={14} className={styles.chevronIcon} />}
</button>

{showConnectionDropdown && !collapsed && connections.length > 0 && (
{showConnectionDropdown && !collapsed && (
<div className={styles.connectionDropdown}>
<div className={styles.dropdownLabel}>Connections</div>
{connections.map((conn) => (
<div
key={conn.id}
className={`${styles.dropdownRow} ${conn.id === connectionId ? styles.dropdownItemActive : ''}`}
>
{connections.length > 0 && (
<>
<div className={styles.dropdownLabel}>Connections</div>
{connections.map((conn) => (
<div
key={conn.id}
className={`${styles.dropdownRow} ${conn.id === connectionId ? styles.dropdownItemActive : ''}`}
>
<button
className={styles.dropdownItem}
onClick={() => {
changeConnection(conn.id)
setShowConnectionDropdown(false)
}}
>
<Database size={14} />
<span className={styles.dropdownItemName}>
{conn.connectionName}
{getConnectionAccessLabel(conn) ? ` · ${getConnectionAccessLabel(conn)}` : ''}
</span>
<span className={styles.dbTypeBadge}>{getConnectionAccessBadge(conn) || conn.dbType}</span>
{conn.id === connectionId && <Check size={13} className={styles.dropdownItemCheck} />}
</button>
<button
className={`${styles.dropdownPin} ${conn.pinned ? styles.dropdownPinActive : ''}`}
onClick={() =>
setConnectionPin.mutate({ connectionId: conn.id, pinned: !conn.pinned })
}
disabled={setConnectionPin.isPending}
aria-pressed={Boolean(conn.pinned)}
title={
conn.pinned
? 'Pinned as your default — DeepSQL opens on this connection. Click to unpin.'
: 'Pin as your default — DeepSQL will open on this connection every time you load it.'
}
>
<Pin size={13} />
</button>
</div>
))}
</>
)}
{canManageConnections && (
<>
{connections.length > 0 && <div className={styles.dropdownDivider} />}
<button
className={styles.dropdownItem}
className={styles.dropdownManage}
onClick={() => {
changeConnection(conn.id)
setShowConnectionDropdown(false)
setShowConnections(true)
}}
>
<Database size={14} />
<span className={styles.dropdownItemName}>
{conn.connectionName}
{getConnectionAccessLabel(conn) ? ` · ${getConnectionAccessLabel(conn)}` : ''}
</span>
<span className={styles.dbTypeBadge}>{getConnectionAccessBadge(conn) || conn.dbType}</span>
{conn.id === connectionId && <Check size={13} className={styles.dropdownItemCheck} />}
<Settings size={14} />
<span>Manage connections…</span>
</button>
<button
className={`${styles.dropdownPin} ${conn.pinned ? styles.dropdownPinActive : ''}`}
onClick={() =>
setConnectionPin.mutate({ connectionId: conn.id, pinned: !conn.pinned })
}
disabled={setConnectionPin.isPending}
aria-pressed={Boolean(conn.pinned)}
title={
conn.pinned
? 'Pinned as your default — DeepSQL opens on this connection. Click to unpin.'
: 'Pin as your default — DeepSQL will open on this connection every time you load it.'
}
>
<Pin size={13} />
</button>
</div>
))}
</>
)}
</div>
)}
</div>

{canManageConnections && (
<button
className={styles.bottomItem}
onClick={() => setShowConnections(true)}
title={collapsed ? 'Connections' : undefined}
>
<Settings size={15} className={styles.navIcon} />
<span className={`${styles.navLabel} ${collapsed ? styles.navLabelHidden : ''}`}>
Connections
</span>
</button>
)}
<div className={styles.userMenuWrap} ref={userMenuRef}>
<button
className={styles.bottomItem}
Expand Down
28 changes: 28 additions & 0 deletions src/components/layout/AppSidebar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,34 @@
color: #111827;
}

.dropdownDivider {
height: 1px;
background: #e5e7eb;
margin: 4px 8px;
}

.dropdownManage {
display: flex;
align-items: center;
gap: 8px;
width: calc(100% - 8px);
margin: 4px;
padding: 8px 10px;
border: none;
border-radius: 8px;
background: transparent;
font-size: 13px;
color: #6b7280;
cursor: pointer;
text-align: left;
transition: background 0.1s, color 0.1s;
}

.dropdownManage:hover {
background: #f3f4f6;
color: #111827;
}

/* ── User profile dropdown ── */
.userMenuWrap {
position: relative;
Expand Down
Loading
Loading