Devlog Jan 15: AI Integration & Module Development
This week we made significant strides in AI integration and module development. We launched the standalone app with dynamic module addition, enhanced the AI chat experience with streaming support, and improved the overall developer experience.
Major Milestones
Standalone App Launch
We achieved a major milestone with the completion of standalone app launch (46f70f7):
Dynamic Module Addition:
// Dynamic Module Discovery
export async function discoverExternalModules() {
const externalModulesPath = process.env.EXTERNAL_MODULES_PATH || './external_modules';
if (!fs.existsSync(externalModulesPath)) {
return [];
}
const moduleDirs = await fs.readdir(externalModulesPath);
const modules = await Promise.all(
moduleDirs.map(async (dir) => {
const modulePath = path.join(externalModulesPath, dir);
const moduleManifestPath = path.join(modulePath, 'module.json');
if (!fs.existsSync(moduleManifestPath)) {
return null;
}
const manifest = JSON.parse(
await fs.readFile(moduleManifestPath, 'utf-8')
);
// Check if already installed
const existingModule = await prisma.module.findUnique({
where: { id: manifest.id }
});
if (existingModule) {
return null;
}
// Register module
return await prisma.module.create({
data: {
id: manifest.id,
name: manifest.name,
description: manifest.description,
version: manifest.version,
author: manifest.author,
installed: true,
path: modulePath,
config: manifest.config || {}
}
});
})
);
return modules.filter(m => m !== null);
}
// Standalone App Entry Point
import express from 'express';
import { discoverExternalModules } from './modules/discover';
async function startServer() {
const app = express();
// Discover external modules on startup
await discoverExternalModules();
// Load all modules
await loadModules();
// Start server
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`MoLOS server running on port ${port}`);
});
}
startServer().catch(console.error);
Enhanced AI Chat Experience
We significantly improved the AI chat workspace with streaming support and better styling (69469f8):
Streaming Response Implementation:
// AI Chat with Streaming
import { useEffect, useState } from 'react';
export function useAIStreaming(prompt: string, enabled: boolean) {
const [response, setResponse] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!enabled || !prompt) return;
let cancelled = false;
async function streamResponse() {
setIsStreaming(true);
setError(null);
setResponse('');
try {
const response = await fetch('/api/ai/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
if (!response.ok) {
throw new Error('AI request failed');
}
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (reader && !cancelled) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
setResponse(prev => prev + chunk);
}
} catch (err) {
setError(err as Error);
} finally {
setIsStreaming(false);
}
}
streamResponse();
return () => {
cancelled = true;
};
}, [prompt, enabled]);
return { response, isStreaming, error };
}
Styled Chat Components:
// Enhanced Chat Message Component
export function ChatMessage({ message, isStreaming }: ChatMessageProps) {
const [content, setContent] = useState(message.content);
useEffect(() => {
setContent(message.content);
}, [message.content]);
return (
<div className={`chat-message ${message.role}`}>
<div className="message-header">
<span className="role">
{message.role === 'assistant' ? 'The Allmighty Architect' : 'You'}
</span>
<span className="timestamp">
{new Date(message.timestamp).toLocaleTimeString()}
</span>
</div>
<div className="message-content">
{isStreaming ? (
<TypewriterEffect text={content} />
) : (
<Markdown>{content}</Markdown>
)}
</div>
{message.role === 'assistant' && !isStreaming && (
<div className="message-actions">
<button onClick={() => copyToClipboard(content)}>
<CopyIcon size={16} />
</button>
</div>
)}
</div>
);
}
Database Environment Variable Improvements
We improved database path handling with better ENV parsing (01ad02e):
// Database URL Parser
export function parseDatabaseUrl(url: string): DatabaseConfig {
if (url.startsWith('file:')) {
// SQLite file database
const filePath = url.replace('file:', '');
return {
type: 'sqlite',
url: filePath
};
}
if (url.startsWith('mysql:')) {
// MySQL database
const match = url.match(/mysql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
if (match) {
return {
type: 'mysql',
host: match[3],
port: parseInt(match[4]),
database: match[5],
username: match[1],
password: match[2]
};
}
}
if (url.startsWith('postgresql:')) {
// PostgreSQL database
const match = url.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
if (match) {
return {
type: 'postgresql',
host: match[3],
port: parseInt(match[4]),
database: match[5],
username: match[1],
password: match[2]
};
}
}
// Default to SQLite
return {
type: 'sqlite',
url: './database.db'
};
}
TUI Enhancements
Enhanced the Terminal User Interface with database admin tools (3a95688):
// Database Admin TUI Commands
export class DatabaseAdminTUI {
private screen: blessed.Widgets.Screen;
constructor() {
this.screen = blessed.screen({
smartCSR: true,
title: 'MoLOS Database Admin'
});
this.setupMenu();
this.render();
}
private setupMenu() {
const menu = blessed.listbar({
parent: this.screen,
keys: true,
mouse: true,
style: {
bg: 'blue',
fg: 'white'
},
commands: {
'View Tables': () => this.viewTables(),
'Run Query': () => this.runQuery(),
'Backup': () => this.backupDatabase(),
'Migrations': () => this.manageMigrations()
}
});
this.screen.append(menu);
}
private async viewTables() {
const tables = await prisma.$queryRaw`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = DATABASE()
`;
const tableList = blessed.list({
parent: this.screen,
label: 'Database Tables',
keys: true,
mouse: true,
items: tables.map((t: any) => t.table_name)
});
tableList.on('select', async (item) => {
await this.viewTableData(item.getText());
});
this.screen.append(tableList);
this.screen.render();
}
private async runQuery() {
const queryBox = blessed.textbox({
parent: this.screen,
label: 'Enter SQL Query:',
input: true,
keys: true,
mouse: true,
style: {
bg: 'white',
fg: 'black'
}
});
const executeBtn = blessed.button({
parent: this.screen,
content: 'Execute',
keys: true,
mouse: true,
style: {
bg: 'green',
fg: 'white'
}
});
executeBtn.on('press', async () => {
const query = queryBox.getValue();
try {
const results = await prisma.$queryRawUnsafe(query);
this.displayResults(results);
} catch (error) {
this.displayError(error.message);
}
});
this.screen.append(queryBox);
this.screen.append(executeBtn);
this.screen.render();
}
private displayResults(results: any[]) {
const resultsBox = blessed.box({
parent: this.screen,
label: 'Query Results',
content: JSON.stringify(results, null, 2),
scrollable: true,
alwaysScroll: true,
keys: true,
mouse: true
});
this.screen.append(resultsBox);
this.screen.render();
}
}
Layout and Styling Improvements
We enhanced the root layout to give module developers more freedom (b5f0228):
// Flexible Root Layout
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>MoLOS</title>
</head>
<body>
<div className="molos-container">
{children}
</div>
<style>{`
.molos-container {
/* Padding and size are entirely based on module's developer
* and their own CSS - giving full control to modules */
min-height: 100vh;
}
`}</style>
</body>
</html>
);
}
Improved Navigation and General Layout
Finished navigation and general layout implementation (197b145, ac9f642):
// Navigation Component
export function Navigation({ activeModule }: { activeModule?: string }) {
const [modules, setModules] = useState<Module[]>([]);
useEffect(() => {
fetch('/api/modules')
.then(res => res.json())
.then(setModules);
}, []);
return (
<nav className="navigation">
<div className="nav-header">
<h1>MoLOS</h1>
</div>
<ul className="nav-links">
<li>
<Link href="/" className={activeModule === 'dashboard' ? 'active' : ''}>
<HomeIcon size={18} />
Dashboard
</Link>
</li>
{modules.map(module => (
<li key={module.id}>
<Link
href={`/modules/${module.id}`}
className={activeModule === module.id ? 'active' : ''}
>
<ModuleIcon size={18} />
{module.name}
</Link>
</li>
))}
</ul>
<div className="nav-footer">
<Link href="/settings">
<SettingsIcon size={18} />
Settings
</Link>
</div>
</nav>
);
}
Responsive Layout:
// Responsive Layout Wrapper
export function ResponsiveLayout({ children }: { children: React.ReactNode }) {
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth < 768);
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className={`responsive-layout ${isMobile ? 'mobile' : 'desktop'}`}>
<Navigation />
<main className="main-content">
{children}
</main>
</div>
);
}
Bug Fixes
- Fixed layout max width issues (
18c0501) - Fixed npm run check errors (
3ec6a97) - Removed border on rail scrollbar (
291f3d1) - Prettified codebase for better readability (
bdc6643)
Code Quality
- Fixed all npm run check errors (
3ec6a97) - Normalized database path handling across configs (
fcfd50f) - Updated .gitignore for better exclusion patterns (
9dea092)
What's Next
Next week we'll focus on:
- Database Improvements: Enhanced database features and optimizations
- Module Synchronization: Better sync mechanisms for external modules
- Dashboard Enhancements: Improved dashboard with more features
- Telegram Integration: Initial work on Telegram bot integration
The standalone app is now fully functional with dynamic module discovery. The AI integration is becoming more robust with streaming support. Stay tuned for more updates!
Related Commits:
