Skip to content

Commit c57c20c

Browse files
committed
fix: Update t o use database relational path
- Add userfriendly error messages - Add database path and LLM keys to settings
1 parent 1264cdf commit c57c20c

6 files changed

Lines changed: 266 additions & 36 deletions

File tree

support/assurance/qualityfolio/ai-chat/app/api/chat/route.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
stepCountIs,
55
createUIMessageStream,
66
} from "ai";
7+
import path from "path";
78
import { createMCPClient } from "@ai-sdk/mcp";
89
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
910
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
@@ -60,7 +61,7 @@ const open_model = createOpenAICompatible({
6061
const mcpClient = await createMCPClient({
6162
transport: new StdioClientTransport({
6263
command: "surveilr",
63-
args: ["mcp", "server", "-d", process.env.RSSD_PATH!],
64+
args: ["mcp", "server", "-d", path.resolve(process.cwd(), process.env.RSSD_PATH!)],
6465
}),
6566
});
6667

@@ -74,6 +75,26 @@ console.log(
7475
"-----------------------------------------------------------------------------",
7576
);
7677

78+
function getFriendlyErrorMessage(err: unknown): string {
79+
const error = err instanceof Error ? err : new Error(String(err));
80+
const message = error.message.toLowerCase();
81+
82+
if (message.includes("429") || message.includes("too many requests") || message.includes("rate limit")) {
83+
return "Rate limit reached. Please wait a moment before sending another message.";
84+
}
85+
if (message.includes("408") || message.includes("504") || message.includes("timeout") || message.includes("deadline exceeded")) {
86+
return "The request timed out. Please try again in a few moments.";
87+
}
88+
if (message.includes("401") || message.includes("403") || message.includes("auth") || message.includes("api key")) {
89+
return "Authentication failed. Please check your API configuration.";
90+
}
91+
if (message.includes("quota") || message.includes("insufficient")) {
92+
return "Quota exceeded. Please check your account limits.";
93+
}
94+
95+
return "An unexpected error occurred. Please try again.";
96+
}
97+
7798
export async function POST(req: Request) {
7899
try {
79100
const { messages, model } = await req.json();
@@ -135,7 +156,7 @@ Analysis & Recommendations:
135156
- When asked for recommendations: base them on actual data retrieved from the RSSD and supplement with testing best practices.
136157
- Never refuse to provide recommendations simply because you are a database tool — you are an AI analyst that uses the database as your data source.
137158
- If the data is insufficient to give a full recommendation, state what data was found and what additional data would help.
138-
159+
139160
Behavioral Rules:
140161
1. Always start with list_tables() on the FIRST turn of a conversation. On subsequent turns, reuse schema already discovered — do not re-run list_tables() or get_table_columns() for already-inspected tables.
141162
2. Never call get_schema() unless the user explicitly asks for full schema metadata. It is expensive (25k-80k tokens).
@@ -172,19 +193,19 @@ Anti-Patterns to Avoid:
172193
return result.toUIMessageStreamResponse({
173194
onError: (err) => {
174195
console.error("STREAM ERROR:", err);
175-
return err instanceof Error ? err.message : "An error occurred while processing your request.";
196+
return getFriendlyErrorMessage(err);
176197
},
177198
});
178199
} catch (err) {
179200
console.error("API ERROR:", err);
180-
const errorMessage =
181-
err instanceof Error ? err.message : "An unexpected server error occurred.";
201+
const friendlyMessage = getFriendlyErrorMessage(err);
182202
const stream = createUIMessageStream({
183203
execute: ({ writer }) => {
184-
writer.write({
204+
writer.write({
185205
type: "error",
186-
errorText: errorMessage,
206+
errorText: friendlyMessage,
187207
});
208+
console.error("CRITICAL API ERROR(FRIENDLY):", friendlyMessage);
188209
},
189210
});
190211
return new Response(stream, {

support/assurance/qualityfolio/ai-chat/components/assistant-ui/thread.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
AttachmentPrimitive,
1515
ActionBarMorePrimitive,
1616
SuggestionPrimitive,
17+
ErrorPrimitive,
1718
} from "@assistant-ui/react";
1819
import { MarkdownTextPrimitive } from "@assistant-ui/react-markdown";
1920
import remarkGfm from "remark-gfm";
@@ -344,9 +345,21 @@ const AssistantMessage: FC = () => {
344345

345346
const AssistantMessageError: FC = () => {
346347
return (
347-
<div className="flex items-start gap-2 mt-2 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2.5 text-sm text-destructive">
348-
<AlertCircleIcon className="size-4 mt-0.5 shrink-0" />
349-
<span>Something went wrong. The AI failed to respond — please try again.</span>
348+
<div className="flex flex-col gap-1.5 mt-3 rounded-2xl border border-destructive/30 bg-destructive/5 px-4 py-3.5 text-sm text-destructive shadow-sm animate-in fade-in slide-in-from-top-1 duration-300">
349+
<div className="flex items-center gap-2 font-semibold">
350+
<AlertCircleIcon className="size-4 shrink-0" />
351+
<span>Service Notice</span>
352+
</div>
353+
<div className="pl-6 opacity-90 leading-relaxed break-words font-medium">
354+
<ErrorPrimitive.Message />
355+
</div>
356+
<div className="pl-6 mt-1">
357+
<ActionBarPrimitive.Reload asChild>
358+
<button className="text-xs font-bold underline underline-offset-4 hover:text-destructive/80 transition-colors">
359+
Try again
360+
</button>
361+
</ActionBarPrimitive.Reload>
362+
</div>
350363
</div>
351364
);
352365
};

support/assurance/qualityfolio/ai-generator/js/settings.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,26 @@
225225
{ name: "destination_path", label: "Destination Path", type: "text", placeholder: "e.g. /home/user/markdown or ./docs/markdown", required: true },
226226
{ name: "is_default", label: "Set as Default", type: "checkbox", required: false }
227227
]
228+
},
229+
database_path: {
230+
label: "Database Path",
231+
addUrl: "/pages/settings/save_database_path.sql",
232+
editUrl: "/pages/settings/save_database_path.sql",
233+
deleteUrl: "/pages/settings/delete_database_path.sql",
234+
fields: [
235+
{ name: "database_path", label: "Database Path", type: "text", placeholder: "e.g. ../resource-surveillance.sqlite.db", required: true }
236+
]
237+
},
238+
llm_keys: {
239+
label: "LLM API Keys",
240+
addUrl: "/pages/settings/save_llm_keys.sql",
241+
editUrl: "/pages/settings/save_llm_keys.sql",
242+
fields: [
243+
{ name: "openai_api_key", label: "OpenAI API Key", type: "password", placeholder: "sk-...", required: false },
244+
{ name: "gemini_api_key", label: "Gemini API Key", type: "password", placeholder: "AIza...", required: false },
245+
{ name: "groq_api_key", label: "Groq API Key", type: "password", placeholder: "gsk_...", required: false },
246+
{ name: "anthropic_api_key", label: "Anthropic API Key", type: "password", placeholder: "sk-ant-...", required: false }
247+
]
228248
}
229249
};
230250

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-- /pages/settings/save_database_path.sql
2+
-- Updates DB path in path_settings table and .env files
3+
4+
-- ============================================================
5+
-- STEP 1: Upsert into database
6+
-- ============================================================
7+
UPDATE path_settings
8+
SET destination_path = :database_path,
9+
updated_at = CURRENT_TIMESTAMP
10+
WHERE setting_key = 'database_path';
11+
12+
INSERT OR IGNORE INTO path_settings (setting_key, destination_path, description, updated_at)
13+
VALUES (
14+
'database_path',
15+
:database_path,
16+
'Path to the Resource Surveillance SQLite database',
17+
CURRENT_TIMESTAMP
18+
);
19+
20+
-- ============================================================
21+
-- STEP 2: Update ai-chat/.env file
22+
-- ============================================================
23+
SELECT sqlpage.exec(
24+
'sh', '-c',
25+
'
26+
DB_PATH="' || REPLACE(REPLACE(COALESCE(:database_path, ''), '\', '\\'), '"', '\"') || '"
27+
28+
# Find ai-chat/.env relative to common project structures
29+
CHAT_ENV=""
30+
if [ -f "../ai-chat/.env" ]; then
31+
CHAT_ENV="../ai-chat/.env"
32+
elif [ -f "../../../ai-chat/.env" ]; then
33+
CHAT_ENV="../../../ai-chat/.env"
34+
fi
35+
36+
if [ -n "$CHAT_ENV" ]; then
37+
if grep -q "^RSSD_PATH=" "$CHAT_ENV" 2>/dev/null; then
38+
sed -i "s|^RSSD_PATH=.*|RSSD_PATH=\"$DB_PATH\"|" "$CHAT_ENV"
39+
else
40+
echo "RSSD_PATH=\"$DB_PATH\"" >> "$CHAT_ENV"
41+
fi
42+
fi
43+
'
44+
) AS exec_result;
45+
46+
-- ============================================================
47+
-- STEP 3: Redirect back
48+
-- ============================================================
49+
SELECT 'html' AS component,
50+
'<html><head>
51+
<meta http-equiv="refresh" content="0;url=/settings.sql?tab=database_path&success=Database%20path%20saved%20successfully" />
52+
</head><body></body></html>' AS html;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
-- /pages/settings/save_llm_keys.sql
2+
-- Saves LLM API keys to DB and root .env
3+
4+
-- ============================================================
5+
-- STEP 1: Save to database (app_settings)
6+
-- ============================================================
7+
DELETE FROM app_settings WHERE key IN ('openai_api_key', 'gemini_api_key', 'groq_api_key', 'anthropic_api_key');
8+
9+
INSERT INTO app_settings (key, value) VALUES
10+
('openai_api_key', COALESCE(:openai_api_key, '')),
11+
('gemini_api_key', COALESCE(:gemini_api_key, '')),
12+
('groq_api_key', COALESCE(:groq_api_key, '')),
13+
('anthropic_api_key', COALESCE(:anthropic_api_key, ''));
14+
15+
-- ============================================================
16+
-- STEP 2: Update root .env
17+
-- ============================================================
18+
SELECT sqlpage.exec(
19+
'sh', '-c',
20+
'
21+
# Find root .env
22+
ENV_FILE=""
23+
if [ -f "../../../.env" ]; then
24+
ENV_FILE="../../../.env"
25+
elif [ -f "../.env" ]; then
26+
ENV_FILE="../.env"
27+
fi
28+
29+
if [ -n "$ENV_FILE" ]; then
30+
update_env() {
31+
KEY=$1
32+
VAL=$2
33+
if grep -q "^$KEY=" "$ENV_FILE" 2>/dev/null; then
34+
sed -i "s|^$KEY=.*|$KEY=\"$VAL\"|" "$ENV_FILE"
35+
else
36+
echo "$KEY=\"$VAL\"" >> "$ENV_FILE"
37+
fi
38+
}
39+
40+
update_env "OPENAI_API_KEY" "' || REPLACE(COALESCE(:openai_api_key, ''), '"', '\"') || '"
41+
update_env "GEMINI_API_KEY" "' || REPLACE(COALESCE(:gemini_api_key, ''), '"', '\"') || '"
42+
update_env "GROQ_API_KEY" "' || REPLACE(COALESCE(:groq_api_key, ''), '"', '\"') || '"
43+
update_env "ANTHROPIC_API_KEY" "' || REPLACE(COALESCE(:anthropic_api_key, ''), '"', '\"') || '"
44+
fi
45+
'
46+
) AS exec_result;
47+
48+
-- ============================================================
49+
-- STEP 3: Redirect back
50+
-- ============================================================
51+
SELECT 'html' AS component,
52+
'<html><head>
53+
<meta http-equiv="refresh" content="0;url=/settings.sql?tab=llm_keys&success=API%20keys%20saved%20successfully" />
54+
</head><body></body></html>' AS html;

support/assurance/qualityfolio/ai-generator/settings.sql

Lines changed: 96 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
-- Settings page for managing master data - UI matching entries.sql design system
2+
3+
SET _scn_id_format = COALESCE((SELECT value FROM app_settings WHERE key = 'scn_id_format'), 'SCN-{NUM}');
4+
SET _req_id_format = COALESCE((SELECT value FROM app_settings WHERE key = 'req_id_format'), 'RQ-{NUM}');
5+
SET _openai_key = COALESCE((SELECT value FROM app_settings WHERE key = 'openai_api_key'), '');
6+
SET _gemini_key = COALESCE((SELECT value FROM app_settings WHERE key = 'gemini_api_key'), '');
7+
SET _groq_key = COALESCE((SELECT value FROM app_settings WHERE key = 'groq_api_key'), '');
8+
SET _anthropic_key = COALESCE((SELECT value FROM app_settings WHERE key = 'anthropic_api_key'), '');
9+
210
SELECT 'shell' AS component,
311
'Qualityfolio AI' AS title,
412
'logo.png' AS image,
@@ -213,6 +221,12 @@ SELECT 'html' AS component, '
213221
<a href="settings.sql?tab=markdown_paths" class="cfg-tab-btn ' || CASE WHEN $tab = 'markdown_paths' THEN 'active' ELSE '' END || '">
214222
Markdown Folder
215223
</a>
224+
<a href="settings.sql?tab=database_path" class="cfg-tab-btn ' || CASE WHEN $tab = 'database_path' THEN 'active' ELSE '' END || '">
225+
Database Path
226+
</a>
227+
<a href="settings.sql?tab=llm_keys" class="cfg-tab-btn ' || CASE WHEN $tab = 'llm_keys' THEN 'active' ELSE '' END || '">
228+
LLM API Keys
229+
</a>
216230
</div>
217231
</div>
218232
' AS html;
@@ -675,29 +689,85 @@ SELECT 'html' AS component, '
675689
' AS html
676690
WHERE $tab = 'id_formats';
677691

678-
SELECT 'html' AS component,'
679-
<div class="cfg-wrap" style="padding-top:0">
680-
<div class="cfg-card">
681-
<div class="cfg-card-title"> Markdown Destination Folder</div>
682-
<div class="cfg-card-desc">Set the folder where generated markdown files will be saved.</div>
683-
<form method="POST" action="/pages/settings/save_path_settings.sql" class="cfg-format-form">
684-
<div class="cfg-format-field">
685-
<label>Destination Path</label>
686-
<input type="text"
687-
name="artifact_destination"
688-
placeholder="e.g. /home/user/markdown or ./docs/output"
689-
value="' || COALESCE(destination_path, '') || '"
690-
autocomplete="off" />
691-
<span class="hint">Enter the foldername (e.g. outputs)</span>
692-
</div>
693-
<div class="cfg-footer-btns">
694-
<button type="submit" class="btn btn-primary qfg-tc-btn">💾 Save Folder Name</button>
695-
</div>
696-
</form>
697-
</div>
698-
</div>
699-
'
700-
AS html
701-
FROM path_settings
702-
WHERE setting_key = 'markdown_destination'
703-
AND $tab = 'markdown_paths';
692+
-- ============================================
693+
-- PATH SETTINGS TABS (Markdown / Database)
694+
-- ============================================
695+
SELECT 'html' AS component,
696+
CASE
697+
WHEN $tab = 'markdown_paths' THEN '
698+
<div class="cfg-wrap" style="padding-top:0">
699+
<div class="cfg-card">
700+
<div class="cfg-card-title"> Markdown Destination Folder</div>
701+
<div class="cfg-card-desc">Set the folder where generated markdown files will be saved.</div>
702+
<form method="POST" action="/pages/settings/save_path_settings.sql" class="cfg-format-form">
703+
<div class="cfg-format-field">
704+
<label>Destination Path</label>
705+
<input type="text"
706+
name="artifact_destination"
707+
placeholder="e.g. /home/user/markdown or ./docs/output"
708+
value="' || COALESCE((SELECT destination_path FROM path_settings WHERE setting_key = 'markdown_destination'), '') || '"
709+
autocomplete="off" />
710+
<span class="hint">Enter the foldername (e.g. outputs)</span>
711+
</div>
712+
<div class="cfg-footer-btns">
713+
<button type="submit" class="btn btn-primary qfg-tc-btn">💾 Save Folder Name</button>
714+
</div>
715+
</form>
716+
</div>
717+
</div>'
718+
WHEN $tab = 'database_path' THEN '
719+
<div class="cfg-wrap" style="padding-top:0">
720+
<div class="cfg-card">
721+
<div class="cfg-card-title"> Database Path (RSSD)</div>
722+
<div class="cfg-card-desc">Set the path to the Resource Surveillance SQLite database file. This is used by the AI Chat.</div>
723+
<form method="POST" action="/pages/settings/save_database_path.sql" class="cfg-format-form">
724+
<div class="cfg-format-field">
725+
<label>Database Path</label>
726+
<input type="text"
727+
name="database_path"
728+
placeholder="e.g. ../resource-surveillance.sqlite.db"
729+
value="' || COALESCE((SELECT destination_path FROM path_settings WHERE setting_key = 'database_path'), '') || '"
730+
autocomplete="off" />
731+
<span class="hint">Enter the path to the .db file (e.g. ../resource-surveillance.sqlite.db)</span>
732+
</div>
733+
<div class="cfg-footer-btns">
734+
<button type="submit" class="btn btn-primary qfg-tc-btn">💾 Save Database Path</button>
735+
</div>
736+
</form>
737+
</div>
738+
</div>'
739+
WHEN $tab = 'llm_keys' THEN '
740+
<div class="cfg-wrap" style="padding-top:0">
741+
<div class="cfg-card">
742+
<div class="cfg-card-title"> LLM API Keys</div>
743+
<div class="cfg-card-desc">Set your API keys for various LLM providers. These are saved to the root .env file.</div>
744+
<form method="POST" action="/pages/settings/save_llm_keys.sql" class="cfg-format-form">
745+
<div class="cfg-format-group">
746+
<div class="cfg-format-field">
747+
<label>OpenAI API Key</label>
748+
<input type="password" name="openai_api_key" placeholder="sk-..." value="' || $_openai_key || '" autocomplete="off" />
749+
</div>
750+
<div class="cfg-format-field">
751+
<label>Gemini API Key</label>
752+
<input type="password" name="gemini_api_key" placeholder="AIza..." value="' || $_gemini_key || '" autocomplete="off" />
753+
</div>
754+
</div>
755+
<div class="cfg-format-group">
756+
<div class="cfg-format-field">
757+
<label>Groq API Key</label>
758+
<input type="password" name="groq_api_key" placeholder="gsk_..." value="' || $_groq_key || '" autocomplete="off" />
759+
</div>
760+
<div class="cfg-format-field">
761+
<label>Anthropic API Key</label>
762+
<input type="password" name="anthropic_api_key" placeholder="sk-ant-..." value="' || $_anthropic_key || '" autocomplete="off" />
763+
</div>
764+
</div>
765+
<div class="cfg-footer-btns">
766+
<button type="submit" class="btn btn-primary qfg-tc-btn">💾 Save API Keys</button>
767+
</div>
768+
</form>
769+
</div>
770+
</div>'
771+
ELSE ''
772+
END AS html
773+
WHERE $tab IN ('markdown_paths', 'database_path', 'llm_keys');

0 commit comments

Comments
 (0)