fix: strict anti-hallucination default prompt
- Replace creative/warm tone with factual summarization - Explicitly forbid invention, assumption, or hallucination - Model must ONLY use information from provided entries - Acknowledge gaps rather than fill them
This commit is contained in:
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Strict anti-hallucination default prompt**: New users get a grounded, factual prompt that ONLY summarizes what's recorded - no invention, no embellishment
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-03-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **Task System**: AI journal generation now creates tasks that track:
|
- **Task System**: AI journal generation now creates tasks that track:
|
||||||
- Request sent to AI provider (full prompt + config)
|
- Request sent to AI provider (full prompt + config)
|
||||||
@@ -17,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- `GET /api/v1/tasks/:id` endpoint
|
- `GET /api/v1/tasks/:id` endpoint
|
||||||
- **Theme System**: Light/Dark/System theme toggle
|
- **Theme System**: Light/Dark/System theme toggle
|
||||||
- **Branding**: "DearDiary.io" logo in navbar
|
- **Branding**: "DearDiary.io" logo in navbar
|
||||||
|
- **Default user**: Auto-created via `DEFAULT_USER_EMAIL` and `DEFAULT_USER_PASSWORD` env vars
|
||||||
|
- `/login` route (was `/auth`)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Renamed project from "TotalRecall" to "DearDiary"**
|
- **Renamed project from "TotalRecall" to "DearDiary"**
|
||||||
|
|||||||
@@ -43,4 +43,6 @@ COPY nginx.conf /etc/nginx/http.d/default.conf
|
|||||||
RUN mkdir -p /data /run /app/nginx_tmp /var/lib/nginx/logs && chmod 777 /var/lib/nginx/logs /var/lib/nginx/tmp && chown -R bun:nodejs /data /app
|
RUN mkdir -p /data /run /app/nginx_tmp /var/lib/nginx/logs && chmod 777 /var/lib/nginx/logs /var/lib/nginx/tmp && chown -R bun:nodejs /data /app
|
||||||
|
|
||||||
# Start everything as root
|
# Start everything as root
|
||||||
CMD sh -c "nginx -g 'daemon off;' & bunx prisma db push --accept-data-loss & bun ./dist/index.js"
|
COPY start.sh /start.sh
|
||||||
|
RUN chmod +x /start.sh
|
||||||
|
CMD ["/start.sh"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Database connection (SQLite, PostgreSQL, or MySQL)
|
# Database connection (SQLite, PostgreSQL, or MySQL)
|
||||||
DATABASE_URL="file:./data/totalrecall.db"
|
DATABASE_URL="file:./data/deardiary.db"
|
||||||
|
|
||||||
# Media storage directory
|
# Media storage directory
|
||||||
MEDIA_DIR="./data/media"
|
MEDIA_DIR="./data/media"
|
||||||
@@ -13,8 +13,15 @@ PORT="3000"
|
|||||||
# CORS origin (use specific domain in production)
|
# CORS origin (use specific domain in production)
|
||||||
CORS_ORIGIN="*"
|
CORS_ORIGIN="*"
|
||||||
|
|
||||||
|
# Default user (auto-created on startup if doesn't exist)
|
||||||
|
DEFAULT_USER_EMAIL="admin@localhost"
|
||||||
|
DEFAULT_USER_PASSWORD="changeme123"
|
||||||
|
|
||||||
|
# Default journal prompt (strict anti-hallucination)
|
||||||
|
# JOURNAL_PROMPT="You are a factual diary summarizer. Your ONLY job is to summarize the entries provided to you - nothing more.\n\nCRITICAL RULES:\n1. ONLY use information explicitly stated in the entries below\n2. NEVER invent, assume, or hallucinate any detail not in the entries\n3. NEVER add activities, emotions, weather, or context not directly mentioned\n4. If something is unclear in the entries, simply state what IS clear\n5. Keep the summary grounded and factual - no embellishment\n6. Do not write in an overly creative or story-telling style\n7. Only reference what the user explicitly recorded"
|
||||||
|
|
||||||
# Example PostgreSQL connection:
|
# Example PostgreSQL connection:
|
||||||
# DATABASE_URL="postgresql://postgres:password@db:5432/totalrecall"
|
# DATABASE_URL="postgresql://postgres:password@db:5432/deardiary"
|
||||||
|
|
||||||
# Example MySQL connection:
|
# Example MySQL connection:
|
||||||
# DATABASE_URL="mysql://root:password@localhost:3306/totalrecall"
|
# DATABASE_URL="mysql://root:password@localhost:3306/deardiary"
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ model Settings {
|
|||||||
aiApiKey String?
|
aiApiKey String?
|
||||||
aiModel String @default("gpt-4")
|
aiModel String @default("gpt-4")
|
||||||
aiBaseUrl String?
|
aiBaseUrl String?
|
||||||
journalPrompt String @default("You are a thoughtful journal writer. Based on the entries provided, write a reflective journal entry for this day in a warm, personal tone.")
|
journalPrompt String @default("You are a factual diary summarizer. Your ONLY job is to summarize the entries provided to you - nothing more.\n\nCRITICAL RULES:\n1. ONLY use information explicitly stated in the entries below\n2. NEVER invent, assume, or hallucinate any detail not in the entries\n3. NEVER add activities, emotions, weather, or context not directly mentioned\n4. If something is unclear in the entries, simply state what IS clear\n5. Keep the summary grounded and factual - no embellishment\n6. Do not write in an overly creative or story-telling style\n7. Only reference what the user explicitly recorded\n\nStructure:\n- Start with what was recorded (meetings, tasks, activities)\n- Note any explicit feelings or observations mentioned\n- Keep it concise and factual\n- If there are gaps in the day, acknowledge only what was recorded")
|
||||||
language String @default("en")
|
language String @default("en")
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|||||||
@@ -579,8 +579,61 @@ app.onError((err, c) => {
|
|||||||
return c.json({ data: null, error: { code: 'INTERNAL_ERROR', message: 'Internal server error' } }, 500);
|
return c.json({ data: null, error: { code: 'INTERNAL_ERROR', message: 'Internal server error' } }, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function createDefaultUser() {
|
||||||
|
const defaultEmail = envVars.DEFAULT_USER_EMAIL;
|
||||||
|
const defaultPassword = envVars.DEFAULT_USER_PASSWORD;
|
||||||
|
|
||||||
|
if (!defaultEmail || !defaultPassword) {
|
||||||
|
console.log('No default user configured (set DEFAULT_USER_EMAIL and DEFAULT_USER_PASSWORD)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existing = await prisma.user.findUnique({ where: { email: defaultEmail } });
|
||||||
|
if (existing) {
|
||||||
|
console.log(`Default user '${defaultEmail}' already exists`);
|
||||||
|
|
||||||
|
const hasKey = await prisma.apiKey.findFirst({ where: { userId: existing.id } });
|
||||||
|
if (!hasKey) {
|
||||||
|
const apiKey = randomBytes(32).toString('hex');
|
||||||
|
const keyHash = createHash('sha256').update(apiKey).digest('hex');
|
||||||
|
await prisma.apiKey.create({
|
||||||
|
data: { userId: existing.id, keyHash, name: 'Default' }
|
||||||
|
});
|
||||||
|
console.log(`Created API key for default user`);
|
||||||
|
console.log(`API Key: ${apiKey}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordHash = await bcrypt.hash(defaultPassword, 12);
|
||||||
|
const user = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email: defaultEmail,
|
||||||
|
passwordHash,
|
||||||
|
settings: { create: {} }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiKey = randomBytes(32).toString('hex');
|
||||||
|
const keyHash = createHash('sha256').update(apiKey).digest('hex');
|
||||||
|
await prisma.apiKey.create({
|
||||||
|
data: { userId: user.id, keyHash, name: 'Default' }
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Created default user: ${defaultEmail}`);
|
||||||
|
console.log(`API Key: ${apiKey}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to create default user:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const port = parseInt(envVars.PORT || '3000', 10);
|
const port = parseInt(envVars.PORT || '3000', 10);
|
||||||
console.log(`Starting TotalRecall API on port ${port}`);
|
console.log(`Starting DearDiary API on port ${port}`);
|
||||||
|
|
||||||
|
createDefaultUser().then(() => {
|
||||||
|
console.log('Server ready');
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
port,
|
port,
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ services:
|
|||||||
- JWT_SECRET=${JWT_SECRET:-change-me-in-production}
|
- JWT_SECRET=${JWT_SECRET:-change-me-in-production}
|
||||||
- PORT=3000
|
- PORT=3000
|
||||||
- CORS_ORIGIN=${CORS_ORIGIN:-*}
|
- CORS_ORIGIN=${CORS_ORIGIN:-*}
|
||||||
|
- DEFAULT_USER_EMAIL=${DEFAULT_USER_EMAIL:-}
|
||||||
|
- DEFAULT_USER_PASSWORD=${DEFAULT_USER_PASSWORD:-}
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- deardiary_data:/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
@@ -22,3 +24,6 @@ services:
|
|||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
deardiary_data:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import History from './pages/History';
|
|||||||
import Day from './pages/Day';
|
import Day from './pages/Day';
|
||||||
import Journal from './pages/Journal';
|
import Journal from './pages/Journal';
|
||||||
import Settings from './pages/Settings';
|
import Settings from './pages/Settings';
|
||||||
|
import { useTheme } from './lib/ThemeContext';
|
||||||
|
|
||||||
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
||||||
@@ -25,29 +26,43 @@ function PrivateRoute({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
return <Navigate to="/auth" replace />;
|
return <Navigate to="/login" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Navbar() {
|
||||||
|
return (
|
||||||
|
<nav className="bg-slate-900 border-b border-slate-800 sticky top-0 z-50">
|
||||||
|
<div className="max-w-4xl mx-auto px-4 py-3 flex items-center justify-between">
|
||||||
|
<a href="/" className="flex items-center gap-2">
|
||||||
|
<span className="text-lg font-bold bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">
|
||||||
|
DearDiary.io
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div className="flex gap-6">
|
||||||
|
<a href="/" className="text-slate-300 hover:text-white transition">Today</a>
|
||||||
|
<a href="/history" className="text-slate-300 hover:text-white transition">History</a>
|
||||||
|
<a href="/settings" className="text-slate-300 hover:text-white transition">Settings</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const key = api.getApiKey();
|
const key = api.getApiKey();
|
||||||
setIsAuthenticated(!!key);
|
setIsAuthenticated(!!key);
|
||||||
setLoading(false);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleAuth = () => {
|
if (isAuthenticated === null) {
|
||||||
setIsAuthenticated(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-slate-950">
|
<div className={`min-h-screen flex items-center justify-center ${resolvedTheme === 'dark' ? 'bg-slate-950' : 'bg-white'}`}>
|
||||||
<div className="animate-pulse text-slate-400">Loading...</div>
|
<div className="animate-pulse text-slate-400">Loading...</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -55,26 +70,11 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div className="min-h-screen bg-slate-950 text-slate-100">
|
<div className={`min-h-screen ${resolvedTheme === 'dark' ? 'bg-slate-950 text-slate-100' : 'bg-white text-slate-900'}`}>
|
||||||
{isAuthenticated && (
|
{isAuthenticated ? <Navbar /> : null}
|
||||||
<nav className="bg-slate-900 border-b border-slate-800 sticky top-0 z-50">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 py-3 flex items-center justify-between">
|
|
||||||
<a href="/" className="flex items-center gap-2">
|
|
||||||
<span className="text-lg font-bold bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">
|
|
||||||
DearDiary.io
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<div className="flex gap-6">
|
|
||||||
<a href="/" className="text-slate-300 hover:text-white transition">Today</a>
|
|
||||||
<a href="/history" className="text-slate-300 hover:text-white transition">History</a>
|
|
||||||
<a href="/settings" className="text-slate-300 hover:text-white transition">Settings</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
)}
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/auth" element={
|
<Route path="/login" element={
|
||||||
isAuthenticated ? <Navigate to="/" replace /> : <Auth onAuth={handleAuth} />
|
isAuthenticated ? <Navigate to="/" replace /> : <Auth onAuth={() => setIsAuthenticated(true)} />
|
||||||
} />
|
} />
|
||||||
<Route path="/" element={
|
<Route path="/" element={
|
||||||
<PrivateRoute><Home /></PrivateRoute>
|
<PrivateRoute><Home /></PrivateRoute>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function SettingsPage() {
|
|||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
api.clearApiKey();
|
api.clearApiKey();
|
||||||
window.location.href = '/auth';
|
window.location.href = '/login';
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|||||||
Reference in New Issue
Block a user