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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

A extreme lightweight SQLite Web administration tool based on Rust (Axum) and Solid.js.

- 🐳 **Extreme Lightweight**: Docker image size is only **~6.5MB**.
- ⚡ **High Performance**: Runtime memory usage is only **~700KB**.
- 🐳 **Extreme Lightweight**: Docker image size is only **~10MB**.
- ⚡ **High Performance**: Runtime memory usage is only **~1MB**.
- 🤖 **AI Assistant**: Intelligently corrects wrong SQL, supports natural language to SQL conversion.

![screenshot](screenshot.png)
Expand Down Expand Up @@ -63,7 +63,7 @@ services:
| RUST_LOG | `rust_sqlite_webui=debug,tower_http=debug` | Logging level configuration. |
| OPENAI_API_KEY | - | OpenAI API key (required for AI assistant). |
| OPENAI_BASE_URL | `https://api.openai.com/v1` | OpenAI API base URL. |
| OPENAI_MODEL | `gpt-3.5-turbo` | OpenAI model name. |
| OPENAI_MODEL | `gpt-5.4-mini` | OpenAI model name. |

> **Tip**: For development, create a `.env` file in `backend/` directory to configure these variables.

Expand Down
6 changes: 3 additions & 3 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

基于 Rust (Axum) 和 Solid.js 的 **极致轻量** SQLite Web 管理工具。

- 🐳 **极致轻量**: Docker 镜像大小仅 **~6.5MB**。
- ⚡ **高性能**: 运行时内存占用仅 **~700KB**。
- 🐳 **极致轻量**: Docker 镜像大小仅 **~10MB**。
- ⚡ **高性能**: 运行时内存占用仅 **~1MB**。
- 🤖 **AI 辅助**: 智能纠正错误 SQL,支持自然语言转 SQL。

![截图](screenshot.png)
Expand Down Expand Up @@ -62,7 +62,7 @@ services:
| RUST_LOG | `rust_sqlite_webui=debug,tower_http=debug` | 日志级别配置。|
| OPENAI_API_KEY | - | OpenAI API 密钥(AI 辅助功能必需)。|
| OPENAI_BASE_URL | `https://api.openai.com/v1` | OpenAI API 地址。|
| OPENAI_MODEL | `gpt-3.5-turbo` | OpenAI 模型名称。|
| OPENAI_MODEL | `gpt-5.4-mini` | OpenAI 模型名称。|

> **提示**: 开发环境下可在 `backend/` 目录创建 `.env` 文件配置上述变量。

Expand Down
2 changes: 1 addition & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-sqlite-webui"
version = "0.2.0"
version = "0.2.1"
edition = "2024"

[dependencies]
Expand Down
38 changes: 32 additions & 6 deletions backend/src/handlers/ai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub async fn correct_sql(
) -> impl IntoResponse {
let api_key = std::env::var("OPENAI_API_KEY");
let base_url = std::env::var("OPENAI_BASE_URL").unwrap_or_else(|_| "https://api.openai.com/v1".to_string());
let model = std::env::var("OPENAI_MODEL").unwrap_or_else(|_| "gpt-3.5-turbo".to_string());
let model = std::env::var("OPENAI_MODEL").unwrap_or_else(|_| "gpt-5.4-mini".to_string());

if api_key.is_err() {
return Json(CorrectResponse {
Expand Down Expand Up @@ -55,14 +55,40 @@ pub async fn correct_sql(

let mut schema_str = String::new();
for (name, col_type, not_null, pk) in &columns {
schema_str.push_str(&format!(" {} {}", col_type, name));
schema_str.push_str(&format!(" {} {}",name, col_type));
if *pk { schema_str.push_str(" PRIMARY KEY"); }
if *not_null { schema_str.push_str(" NOT NULL"); }
schema_str.push('\n');
}
for (from, to_table, to_col) in &foreign_keys {
schema_str.push_str(&format!(" {} REFERENCES {}({})\n", from, to_table, to_col));
}
// try to fetch one sample row from the table
let sample_row = sqlx::query(&format!("SELECT * FROM {} LIMIT 1", payload.table))
.fetch_optional(pool)
.await
.ok()
.flatten();

let mut sample_str = String::new();
if let Some(row) = sample_row {
let mut parts: Vec<String> = Vec::new();
for (name, _col_type, _not_null, _pk) in &columns {
// try several common types and fall back to NULL
let val = row.try_get::<String, &str>(name)
.map(|s| s)
.or_else(|_| row.try_get::<i64, &str>(name).map(|v| v.to_string()))
.or_else(|_| row.try_get::<f64, &str>(name).map(|v| v.to_string()))
.unwrap_or_else(|_| "NULL".to_string());
parts.push(format!("{}={}", name, val));
}
sample_str = parts.join(", ");
}

// append sample row info to schema string for prompt clarity
if !sample_str.is_empty() {
schema_str.push_str(&format!("\nSample row: {}\n", sample_str));
}
schema_str
} else {
String::new()
Expand All @@ -71,11 +97,11 @@ pub async fn correct_sql(

let client = Client::new();
let prompt = format!(
"Correct the following SQL using SQLite dialect.\n\
"Correct/Translate/Complete the User Input to SQL using SQLite dialect.\n\
Table: {}\n\
Schema:\n{}\n\
SQL: {}\n\n\
if you have no idea just return a basic query that can run.",
User Input: {}\n\n\
If user intent is ambiguous, return a simple SELECT * FROM <table> LIMIT 10.",
payload.table, schema, payload.sql
);
tracing::debug!("AI API prompt: {}", prompt);
Expand All @@ -85,7 +111,7 @@ pub async fn correct_sql(
.json(&json!({
"model": model,
"messages": [
{"role": "system", "content": "You are a helpful SQL expert assistant. Always return raw SQL string without any format."},
{"role": "system", "content": "You are a SQL expert. Always reply a single raw SQL string without any format."},
{"role": "user", "content": prompt}
],
"reasoning_effort": "low"
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Component, createSignal, onMount, For, Show } from "solid-js";
import ThemeSwitch from "./ThemeSwitch";
import { fetchTables, resetStore, apiKey, setIsAuthModalOpen } from "../lib/store";
import { getDbFiles, connectDb } from "../lib/api";
import { getDbFiles, connectDb, getHealth } from "../lib/api";

const Navbar: Component = () => {
const [appVersion, setAppVersion] = createSignal("");
// DB Logic
const [dbFiles, setDbFiles] = createSignal<string[]>([]);
const [currentPath, setCurrentPath] = createSignal("");
Expand All @@ -15,6 +16,10 @@ const Navbar: Component = () => {
onMount(async () => {
// DB Auto-load Logic
await fetchDbFiles();

// fetch version from health endpoint
const h = await getHealth();
setAppVersion(h.version || "");
});

async function fetchDbFiles() {
Expand Down Expand Up @@ -143,7 +148,12 @@ const Navbar: Component = () => {
<div class="navbar bg-base-100 shadow-lg gap-4 px-4 justify-between">
<div class="navbar-start w-auto">
<img src="./logo.png" alt="SQLite WebUI" class="size-8 inline-block mr-2" />
<h1 class="text-primary text-xl font-bold hidden sm:inline-block">SQLite WebUI</h1>
<h1 class="text-primary text-xl font-bold hidden sm:inline-block">
SQLite WebUI
<Show when={appVersion()}>
<span class="text-sm text-base-content/60 ml-2">v{appVersion()}</span>
</Show>
</h1>
</div>
<div class="navbar-center flex-1 max-w-128 flex flex-col">
<div class="join w-full">
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ export const getDbFiles = async (apiKey: string, onUnauthorized?: () => void): P
throw new Error("Failed to fetch db files");
};

export interface HealthResult {
status: string;
version?: string;
}

export const getHealth = async (): Promise<HealthResult> => {
const res = await fetch("./api/health");
return await res.json();
};

export const connectDb = async (
path: string,
create: boolean,
Expand Down
Loading