Add global styles using Tailwind CSS with custom color variables for light and dark themes
Some checks failed
GBCI / build (push) Failing after 10m8s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-06-28 23:58:53 -03:00
parent 375d702b19
commit 21a8236516
11 changed files with 4871 additions and 94 deletions

View file

@ -240,7 +240,7 @@ const CalendarSidebar = ({ isCollapsed, categories, onCategoryToggle, onDateSel
</div> </div>
</div> </div>
<div className="grid grid-cols-7 gap-1 text-xs font-mono"> <div className="grid grid-cols-7 gap-1 text-xs font-mono">
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => ( {['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(day => (
<div key={day} className="text-center text-foreground font-medium p-1"> <div key={day} className="text-center text-foreground font-medium p-1">
{day} {day}
</div> </div>

View file

@ -5,7 +5,7 @@ import History from '@tiptap/extension-history';
import Bold from '@tiptap/extension-bold'; // Import the extension import Bold from '@tiptap/extension-bold'; // Import the extension
import Italic from '@tiptap/extension-italic'; // Import the extension import Italic from '@tiptap/extension-italic'; // Import the extension
import { useState, useRef } from 'react'; import { useState, useRef } from 'react';
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'; import { useEditor, EditorContent, BubbleMenu, AnyExtension } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit'; import StarterKit from '@tiptap/starter-kit';
import TextStyle from '@tiptap/extension-text-style'; import TextStyle from '@tiptap/extension-text-style';
import FontFamily from '@tiptap/extension-font-family'; import FontFamily from '@tiptap/extension-font-family';
@ -24,7 +24,9 @@ import {
Link as LinkIcon, Image as ImageIcon, Save, Table as TableIcon, Link as LinkIcon, Image as ImageIcon, Save, Table as TableIcon,
Type, Highlighter, Type, Highlighter,
ChevronDown, ChevronDown,
Undo, Redo, Copy Undo, Redo, Copy,
ItalicIcon,
BoldIcon
} from 'lucide-react'; } from 'lucide-react';
import './style.css'; import './style.css';
@ -81,8 +83,7 @@ export default function RibbonWordClone() {
const editor = useEditor({ const editor = useEditor({
extensions: [ extensions: [
// @ts-ignore StarterKit.configure({ history: false }) as unknown as AnyExtension,
StarterKit,
Bold, Bold,
Italic, Italic,
History, History,
@ -161,23 +162,20 @@ export default function RibbonWordClone() {
return null; return null;
} }
return ( return (
<div className="word-clone"> <div className="word-clone">
{/* Quick Access Toolbar */} {/* Quick Access Toolbar */}
<div className="quick-access"> <div className="quick-access">
<button className="quick-access-btn" onClick={() => editor.chain().focus().undo().run()}> <button className="quick-access-btn" onClick={() => editor.chain().focus().undo().run()}>
<Undo size={14} /> <Undo size={14} />
</button> </button>
<button className="quick-access-btn" onClick={() => editor.chain().focus().redo().run()}> <button className="quick-access-btn" onClick={() => editor.chain().focus().redo().run()}>
<Redo size={14} /> <Redo size={14} />
</button> </button>
<button className="quick-access-btn" onClick={saveDocument}>
<Save size={14} />
</button>
<div className="title-controls"> <div className="title-controls">
<input <input
type="text" type="text"
@ -284,13 +282,13 @@ export default function RibbonWordClone() {
</div> </div>
<div style={{ display: 'flex', gap: '2px' }}> <div style={{ display: 'flex', gap: '2px' }}>
<RibbonButton <RibbonButton
icon={Bold} icon={BoldIcon}
label="Bold" label="Bold"
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
isActive={editor.isActive('bold')} isActive={editor.isActive('bold')}
/> />
<RibbonButton <RibbonButton
icon={Italic} icon={ItalicIcon}
label="Italic" label="Italic"
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
isActive={editor.isActive('italic')} isActive={editor.isActive('italic')}
@ -449,13 +447,13 @@ export default function RibbonWordClone() {
<BubbleMenu editor={editor}> <BubbleMenu editor={editor}>
<div className="bubble-menu"> <div className="bubble-menu">
<RibbonButton <RibbonButton
icon={Bold} icon={BoldIcon}
label="Bold" label="Bold"
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
isActive={editor.isActive('bold')} isActive={editor.isActive('bold')}
/> />
<RibbonButton <RibbonButton
icon={Italic} icon={ItalicIcon}
label="Italic" label="Italic"
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
isActive={editor.isActive('italic')} isActive={editor.isActive('italic')}

View file

@ -1,11 +1,18 @@
import { Nav } from './client-nav'; import { Nav } from './client-nav';
import './globals.css' // This path is correct if the file is in your src/app directory import { ReactNode } from 'react';
import { ReactNode } from 'react'
import { ThemeProvider } from './theme-provider'; import { ThemeProvider } from './theme-provider';
export default function RootLayout({ children }: { children: ReactNode }) { export default function RootLayout({ children }: { children: ReactNode }) {
return ( return (
<html lang="en"> <html lang="en">
<head>
<title>General Bots</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="General Bots and Custo AI Models" />
<link rel="stylesheet" href="/output.css" />
<link rel="icon" href="/favicon.ico" />
</head>
<body className="flex flex-col min-h-screen"> <body className="flex flex-col min-h-screen">
<div> <div>
<ThemeProvider> <ThemeProvider>
@ -17,5 +24,5 @@ export default function RootLayout({ children }: { children: ReactNode }) {
</div> </div>
</body> </body>
</html> </html>
) );
} }

View file

@ -1,41 +1,63 @@
"use client"; "use client";
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
const AuthenticationScreen = () => { const AuthenticationScreen = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false); const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const router = useRouter();
const handleLogin = async () => { // ZITADEL configuration
const zitadelConfig = {
authority: 'https://your-zitadel-instance.com',
clientId: 'your-client-id',
redirectUri: typeof window !== 'undefined' ? window.location.origin : '',
scopes: ['openid', 'profile', 'email'],
};
const handleSocialLogin = (provider: string) => {
setIsLoading(true);
setError('');
try { try {
localStorage.setItem('authToken', 'dummy-token'); // In a real implementation, this would redirect to ZITADEL's auth endpoint
setIsAuthenticated(true); const authUrl = `${zitadelConfig.authority}/oauth/v2/authorize?` +
alert('Login Successful'); `client_id=${zitadelConfig.clientId}&` +
} catch (error) { `redirect_uri=${encodeURIComponent(zitadelConfig.redirectUri)}&` +
console.error('Login error:', error); `response_type=code&` +
alert('Login Error'); `scope=${encodeURIComponent(zitadelConfig.scopes.join(' '))}&` +
`provider=${provider}`;
window.location.href = authUrl;
} catch (err) {
setError('Failed to initiate login');
console.error('Login error:', err);
} finally {
setIsLoading(false);
} }
}; };
const handleLogout = async () => { const handleEmailLogin = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setError('');
try { try {
localStorage.removeItem('authToken'); // Mock implementation - in real app you would call your backend or ZITADEL directly
setIsAuthenticated(false); localStorage.setItem('authToken', 'dummy-token');
alert('Logout Successful'); router.push('/dashboard');
} catch (error) { } catch (err) {
console.error('Logout error:', error); setError('Login failed. Please check your credentials.');
alert('Logout Error'); console.error('Login error:', err);
} finally {
setIsLoading(false);
} }
}; };
return ( return (
<div className="auth-screen"> <div className="auth-screen">
<div className="auth-content"> <div className="auth-content">
<button
className="auth-login-button"
onClick={isAuthenticated ? handleLogout : handleLogin}
>
{isAuthenticated ? 'Logout' : 'Login'}
</button>
<div className="auth-left-panel"> <div className="auth-left-panel">
<div className="auth-logo"> <div className="auth-logo">
<h1>Welcome to General Bots Online</h1> <h1>Welcome to General Bots Online</h1>
@ -48,18 +70,398 @@ const AuthenticationScreen = () => {
<div className="auth-form-container"> <div className="auth-form-container">
<div className="auth-form-header"> <div className="auth-form-header">
<h2>Create an account</h2> <h2>Sign in to your account</h2>
<p>Enter your email below to create your account</p> <p>Choose your preferred login method</p>
</div> </div>
{error && (
<div className="auth-error">
{error}
</div>
)}
<div className="auth-social-buttons">
<button
className="auth-social-button google"
onClick={() => handleSocialLogin('google')}
disabled={isLoading}
>
<svg className="auth-social-icon" viewBox="0 0 24 24">
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
</svg>
Continue with Google
</button>
<button
className="auth-social-button microsoft"
onClick={() => handleSocialLogin('microsoft')}
disabled={isLoading}
>
<svg className="auth-social-icon" viewBox="0 0 23 23">
<path d="M0 0h11v11H0zM12 0h11v11H12zM0 12h11v11H0zM12 12h11v11H12z" fill="#F25022"/>
<path d="M12 0h11v11H12z" fill="#7FBA00"/>
<path d="M0 12h11v11H0z" fill="#00A4EF"/>
<path d="M12 12h11v11H12z" fill="#FFB900"/>
</svg>
Continue with Microsoft
</button>
<button
className="auth-social-button facebook"
onClick={() => handleSocialLogin('facebook')}
disabled={isLoading}
>
<svg className="auth-social-icon" viewBox="0 0 24 24">
<path d="M22 12c0-5.52-4.48-10-10-10S2 6.48 2 12c0 4.84 3.44 8.87 8 9.8V15H8v-3h2V9.5C10 7.57 11.57 6 13.5 6H16v3h-2c-.55 0-1 .45-1 1v2h3v3h-3v6.95c5.05-.5 9-4.76 9-9.95z" fill="#1877F2"/>
</svg>
Continue with Facebook
</button>
<button
className="auth-social-button pragmatismo"
onClick={() => handleSocialLogin('pragmatismo')}
disabled={isLoading}
>
<svg className="auth-social-icon" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z" fill="currentColor"/>
</svg>
Continue with Pragmatismo
</button>
</div>
<div className="auth-divider">
<span>OR</span>
</div>
<form onSubmit={handleEmailLogin} className="auth-form">
<div className="auth-form-group">
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
required
/>
</div>
<div className="auth-form-group">
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
/>
</div>
<div className="auth-form-options">
<div className="auth-remember-me">
<input type="checkbox" id="remember" />
<label htmlFor="remember">Remember me</label>
</div>
<a href="#" className="auth-forgot-password">Forgot password?</a>
</div>
<button
type="submit"
className="auth-submit-button"
disabled={isLoading}
>
{isLoading ? 'Signing in...' : 'Sign in with Email'}
</button>
</form>
<div className="auth-signup-link">
Don't have an account? <a href="#">Sign up</a>
</div>
<p className="auth-terms"> <p className="auth-terms">
By clicking continue, you agree to our Terms of Service and Privacy Policy. By continuing, you agree to our <a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>.
</p> </p>
</div> </div>
</div> </div>
<style jsx>{`
.auth-screen {
--background: hsl(var(--background));
--foreground: hsl(var(--foreground));
--card: hsl(var(--card));
--card-foreground: hsl(var(--card-foreground));
--primary: hsl(var(--primary));
--primary-foreground: hsl(var(--primary-foreground));
--secondary: hsl(var(--secondary));
--secondary-foreground: hsl(var(--secondary-foreground));
--muted: hsl(var(--muted));
--muted-foreground: hsl(var(--muted-foreground));
--accent: hsl(var(--accent));
--accent-foreground: hsl(var(--accent-foreground));
--destructive: hsl(var(--destructive));
--destructive-foreground: hsl(var(--destructive-foreground));
--border: hsl(var(--border));
--input: hsl(var(--input));
--ring: hsl(var(--ring));
--radius: var(--radius);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--background);
color: var(--foreground);
padding: 1rem;
}
.auth-content {
display: flex;
width: 100%;
max-width: 1200px;
background-color: var(--card);
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.auth-left-panel {
flex: 1;
padding: 4rem;
background: linear-gradient(135deg, var(--primary), var(--accent));
color: var(--primary-foreground);
display: flex;
flex-direction: column;
justify-content: space-between;
}
.auth-logo h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
.auth-quote {
font-style: italic;
margin-top: auto;
}
.auth-quote p:last-child {
text-align: right;
margin-top: 0.5rem;
}
.auth-form-container {
flex: 1;
padding: 4rem;
max-width: 500px;
}
.auth-form-header {
margin-bottom: 2rem;
text-align: center;
}
.auth-form-header h2 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.auth-form-header p {
color: var(--muted-foreground);
}
.auth-error {
background-color: var(--destructive);
color: var(--destructive-foreground);
padding: 0.75rem;
border-radius: var(--radius);
margin-bottom: 1rem;
text-align: center;
}
.auth-social-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.auth-social-button {
display: flex;
align-items: center;
justify-content: center;
padding: 0.75rem;
border-radius: var(--radius);
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: 1px solid var(--border);
background-color: var(--secondary);
color: var(--secondary-foreground);
}
.auth-social-button:hover {
background-color: var(--muted);
}
.auth-social-button:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.auth-social-icon {
width: 1.25rem;
height: 1.25rem;
margin-right: 0.5rem;
}
.auth-divider {
display: flex;
align-items: center;
margin: 1.5rem 0;
color: var(--muted-foreground);
}
.auth-divider::before,
.auth-divider::after {
content: "";
flex: 1;
border-bottom: 1px solid var(--border);
}
.auth-divider span {
padding: 0 1rem;
}
.auth-form {
margin-top: 1.5rem;
}
.auth-form-group {
margin-bottom: 1rem;
}
.auth-form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.auth-form-group input {
width: 100%;
padding: 0.75rem;
border-radius: var(--radius);
border: 1px solid var(--border);
background-color: var(--input);
color: var(--foreground);
}
.auth-form-group input:focus {
outline: none;
border-color: var(--ring);
box-shadow: 0 0 0 2px var(--ring);
}
.auth-form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin: 1rem 0;
}
.auth-remember-me {
display: flex;
align-items: center;
}
.auth-remember-me input {
margin-right: 0.5rem;
}
.auth-forgot-password {
color: var(--primary);
text-decoration: none;
}
.auth-forgot-password:hover {
text-decoration: underline;
}
.auth-submit-button {
width: 100%;
padding: 0.75rem;
border-radius: var(--radius);
background-color: var(--primary);
color: var(--primary-foreground);
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.auth-submit-button:hover {
background-color: color-mix(in srgb, var(--primary), black 10%);
}
.auth-submit-button:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.auth-signup-link {
text-align: center;
margin: 1.5rem 0;
color: var(--muted-foreground);
}
.auth-signup-link a {
color: var(--primary);
text-decoration: none;
}
.auth-signup-link a:hover {
text-decoration: underline;
}
.auth-terms {
text-align: center;
font-size: 0.875rem;
color: var(--muted-foreground);
}
.auth-terms a {
color: var(--primary);
text-decoration: none;
}
.auth-terms a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.auth-content {
flex-direction: column;
}
.auth-left-panel {
padding: 2rem;
}
.auth-form-container {
padding: 2rem;
max-width: 100%;
}
.auth-social-buttons {
grid-template-columns: 1fr;
}
}
`}</style>
</div> </div>
); );
}; };
export default AuthenticationScreen; export default AuthenticationScreen;

View file

@ -9,8 +9,12 @@ import TextAlign from '@tiptap/extension-text-align';
import Footer from '../footer' import Footer from '../footer'
import { import {
Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, Underline, AlignLeft, AlignCenter, AlignRight,
Link, Highlighter, Type} from 'lucide-react'; Link, Highlighter, Type,
BoldIcon,
ItalicIcon} from 'lucide-react';
import Bold from '@tiptap/extension-bold';
import Italic from '@tiptap/extension-italic';
const SimplePaperNote = () => { const SimplePaperNote = () => {
@ -19,13 +23,15 @@ const SimplePaperNote = () => {
const editor = useEditor({ const editor = useEditor({
extensions: [ extensions: [
StarterKit.extend() as unknown as AnyExtension, StarterKit.configure() as unknown as AnyExtension,
Bold,
Italic,
TextStyle, TextStyle,
Color, Color,
Highlight.configure({ multicolor: true }), Highlight.configure({ multicolor: true }),
TextAlign.configure({ TextAlign.configure({
types: ['heading', 'paragraph'], types: ['heading', 'paragraph'],
}), }) as unknown as AnyExtension,
], ],
content: ` content: `
@ -121,7 +127,7 @@ const SimplePaperNote = () => {
}`} }`}
title="Bold" title="Bold"
> >
<Bold className="h-4 w-4" /> <BoldIcon className="h-4 w-4" />
</button> </button>
<button <button
@ -130,7 +136,7 @@ const SimplePaperNote = () => {
}`} }`}
title="Italic" title="Italic"
> >
<Italic className="h-4 w-4" /> <ItalicIcon className="h-4 w-4" />
</button> </button>
<button <button

File diff suppressed because one or more lines are too long

View file

@ -205,7 +205,7 @@ export default function LabComponent() {
borderColor: 'var(--border)', borderColor: 'var(--border)',
backgroundColor: 'var(--background)', backgroundColor: 'var(--background)',
color: 'var(--foreground)', color: 'var(--foreground)',
}} }}
/> />
</div> </div>
@ -222,9 +222,8 @@ export default function LabComponent() {
<button <button
key={index} key={index}
onClick={() => setSelectedCategory(category.name)} onClick={() => setSelectedCategory(category.name)}
className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${ className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : ''
selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : '' }`}
}`}
style={{ style={{
borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent', borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent',
backgroundColor: selectedCategory === category.name ? 'var(--card)' : '', backgroundColor: selectedCategory === category.name ? 'var(--card)' : '',
@ -328,7 +327,7 @@ export default function LabComponent() {
borderColor: 'var(--border)', borderColor: 'var(--border)',
backgroundColor: 'var(--background)', backgroundColor: 'var(--background)',
color: 'var(--foreground)', color: 'var(--foreground)',
}} }}
/> />
</div> </div>
@ -441,7 +440,7 @@ export default function LabComponent() {
borderColor: 'var(--border)', borderColor: 'var(--border)',
backgroundColor: 'var(--background)', backgroundColor: 'var(--background)',
color: 'var(--foreground)', color: 'var(--foreground)',
}} }}
/> />
</div> </div>
@ -576,7 +575,7 @@ export default function LabComponent() {
borderColor: 'var(--border)', borderColor: 'var(--border)',
backgroundColor: 'var(--background)', backgroundColor: 'var(--background)',
color: 'var(--foreground)', color: 'var(--foreground)',
}} }}
/> />
</div> </div>
@ -601,9 +600,8 @@ export default function LabComponent() {
<button <button
key={index} key={index}
onClick={() => setSelectedCategory(category.name)} onClick={() => setSelectedCategory(category.name)}
className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${ className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : ''
selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : '' }`}
}`}
style={{ style={{
borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent', borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent',
backgroundColor: selectedCategory === category.name ? 'var(--card)' : '', backgroundColor: selectedCategory === category.name ? 'var(--card)' : '',
@ -652,9 +650,8 @@ export default function LabComponent() {
<IconComponent className="h-5 w-5" style={{ color: 'var(--chart-2)' }} /> <IconComponent className="h-5 w-5" style={{ color: 'var(--chart-2)' }} />
<h3 className="font-semibold">{server.name}</h3> <h3 className="font-semibold">{server.name}</h3>
</div> </div>
<span className={`text-xs px-2 py-1 rounded ${ <span className={`text-xs px-2 py-1 rounded ${server.type === 'Hosted' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800'
server.type === 'Hosted' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800' }`}>
}`}>
{server.type} {server.type}
</span> </span>
</div> </div>
@ -712,7 +709,7 @@ export default function LabComponent() {
borderColor: 'var(--border)', borderColor: 'var(--border)',
backgroundColor: 'var(--background)', backgroundColor: 'var(--background)',
color: 'var(--foreground)', color: 'var(--foreground)',
}} }}
/> />
</div> </div>
@ -734,9 +731,8 @@ export default function LabComponent() {
<button <button
key={index} key={index}
onClick={() => setSelectedCategory(category.name)} onClick={() => setSelectedCategory(category.name)}
className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${ className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : ''
selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : '' }`}
}`}
style={{ style={{
borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent', borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent',
backgroundColor: selectedCategory === category.name ? 'var(--card)' : '', backgroundColor: selectedCategory === category.name ? 'var(--card)' : '',
@ -817,6 +813,296 @@ export default function LabComponent() {
</div> </div>
); );
const renderModels = () => (
<div className="h-full flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
{/* Header */}
<div className="p-4 border-b" style={{ borderColor: 'var(--border)' }}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold flex items-center gap-2">
<Cpu className="h-6 w-6" style={{ color: 'var(--chart-5)' }} />
AI Models
</h2>
<div className="flex gap-2">
<button className="px-4 py-2 border rounded-lg transition-colors" style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
Documentation
</button>
<button className="px-4 py-2 text-white rounded-lg transition-colors" style={{ backgroundColor: 'var(--chart-5)', color: 'var(--chart-5-foreground)' }}>
Add Model
</button>
</div>
</div>
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4" style={{ color: 'var(--muted-foreground)' }} />
<input
type="text"
placeholder="Search AI models..."
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:border-transparent"
style={{
borderColor: 'var(--border)',
backgroundColor: 'var(--background)',
color: 'var(--foreground)',
}}
/>
</div>
</div>
<div className="flex-1 flex">
{/* Categories Sidebar */}
<div className="w-80 border-r p-4" style={{ borderColor: 'var(--border)', backgroundColor: 'var(--background)' }}>
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Providers</h3>
<div className="space-y-2">
{[
{ name: "OpenAI", count: 12, color: "--primary" },
{ name: "Anthropic", count: 8, color: "--accent" },
{ name: "Google", count: 6, color: "--secondary" },
{ name: "Meta", count: 5, color: "--chart-2" },
{ name: "Mistral", count: 4, color: "--chart-3" },
{ name: "Cohere", count: 3, color: "--chart-4" },
{ name: "AWS", count: 7, color: "--chart-5" },
{ name: "Azure", count: 5, color: "--primary" },
{ name: "Other", count: 9, color: "--destructive" }
].map((category, index) => (
<button
key={index}
onClick={() => setSelectedCategory(category.name)}
className={`w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card ${selectedCategory === category.name ? 'bg-card shadow-sm border-l-4' : ''
}`}
style={{
borderLeftColor: selectedCategory === category.name ? `var(${category.color})` : 'transparent',
backgroundColor: selectedCategory === category.name ? 'var(--card)' : '',
color: 'var(--card-foreground)'
}}
>
<div className="flex items-center gap-3">
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: `var(${category.color})` }}
></div>
<span className="font-medium">{category.name}</span>
</div>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{category.count}
</span>
</button>
))}
</div>
{/* Model Types */}
<div className="mt-8">
<h3 className="font-semibold mb-4" style={{ color: 'var(--muted-foreground)' }}>Model Types</h3>
<div className="space-y-2">
<button className="w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card" style={{ color: 'var(--card-foreground)' }}>
<span className="font-medium">Text Generation</span>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>32</span>
</button>
<button className="w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card" style={{ color: 'var(--card-foreground)' }}>
<span className="font-medium">Multimodal</span>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>15</span>
</button>
<button className="w-full text-left p-3 rounded-lg transition-colors flex items-center justify-between hover:bg-card" style={{ color: 'var(--card-foreground)' }}>
<span className="font-medium">Embeddings</span>
<span className="text-sm bg-gray-200 px-2 py-1 rounded-full" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>12</span>
</button>
</div>
</div>
</div>
{/* Content Area */}
<div className="flex-1 p-6 overflow-y-auto" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
<div className="space-y-4">
{[
{
name: "AI21",
description: "You can get started with AI21Labs' Jurassic family of models, as well as their task-specific models.",
provider: "AI21 Labs",
type: "Text Generation",
status: "Active"
},
{
name: "AlephAlpha",
description: "LangChain.js supports AlephAlpha's Luminous family of models. You'll need an AlephAlpha API key.",
provider: "Aleph Alpha",
type: "Text Generation",
status: "Active"
},
{
name: "Arcjet Redact",
description: "The Arcjet redact integration allows you to redact sensitive information from text.",
provider: "Arcjet",
type: "Text Processing",
status: "Beta"
},
{
name: "AWS SageMakerEndpoint",
description: "LangChain.js supports integration with AWS SageMaker-hosted endpoints for custom models.",
provider: "AWS",
type: "Custom Models",
status: "Active"
},
{
name: "Azure OpenAI",
description: "Azure OpenAI provides access to OpenAI models through Microsoft's Azure cloud platform.",
provider: "Microsoft",
type: "Text Generation",
status: "Active"
},
{
name: "Bedrock",
description: "Amazon Bedrock is a fully managed service that makes foundation models from AI21, Anthropic, and Amazon accessible via API.",
provider: "AWS",
type: "Text Generation",
status: "Active"
},
{
name: "ChromeAI",
description: "This feature is experimental and is subject to change. Provides browser-based AI capabilities.",
provider: "Google",
type: "Experimental",
status: "Preview"
},
{
name: "Cloudflare Workers AI",
description: "This will help you get started with Cloudflare Workers AI text generation models.",
provider: "Cloudflare",
type: "Text Generation",
status: "Active"
},
{
name: "Cohere",
description: "This will help you get started with Cohere completion models (LLMs) and embedding models.",
provider: "Cohere",
type: "Text Generation",
status: "Active"
},
{
name: "Deep Infra",
description: "LangChain supports LLMs hosted by Deep Infra through the DeepInfra wrapper.",
provider: "Deep Infra",
type: "Text Generation",
status: "Active"
},
{
name: "Fireworks",
description: "Fireworks AI is an AI inference platform to run open-source models at scale.",
provider: "Fireworks",
type: "Text Generation",
status: "Active"
},
{
name: "Friendli",
description: "Friendli enhances AI application performance and optimizes cost savings for LLM inference.",
provider: "Friendli",
type: "Optimization",
status: "Beta"
},
{
name: "Google Vertex AI",
description: "Google Vertex is a service that provides access to Google's foundation models.",
provider: "Google",
type: "Text Generation",
status: "Active"
},
{
name: "HuggingFaceInference",
description: "Here's an example of calling a HuggingFaceInference model as an LLM.",
provider: "Hugging Face",
type: "Text Generation",
status: "Active"
},
{
name: "IBM watsonx.ai",
description: "This will help you get started with IBM text completion models on watsonx.ai.",
provider: "IBM",
type: "Text Generation",
status: "Active"
},
{
name: "Llama CPP",
description: "Only available on Node.js. Provides access to locally run Llama models.",
provider: "Meta",
type: "Local Inference",
status: "Active"
},
{
name: "MistralAI",
description: "Mistral AI is a platform that offers hosting for their open-weight models.",
provider: "Mistral",
type: "Text Generation",
status: "Active"
},
{
name: "Ollama",
description: "This will help you get started with Ollama text completion models running locally.",
provider: "Ollama",
type: "Local Inference",
status: "Active"
},
{
name: "Replicate",
description: "Here's an example of calling a Replicate model as an LLM.",
provider: "Replicate",
type: "Text Generation",
status: "Active"
},
{
name: "Together AI",
description: "You are currently on a page documenting the use of Together AI models with LangChain.",
provider: "Together AI",
type: "Text Generation",
status: "Active"
},
{
name: "Writer",
description: "LangChain.js supports calling Writer LLMs for content generation.",
provider: "Writer",
type: "Text Generation",
status: "Active"
},
{
name: "YandexGPT",
description: "LangChain.js supports calling YandexGPT LLMs. Also supports qwen and deepseek models.",
provider: "Yandex",
type: "Text Generation",
status: "Active"
}
].map((model, index) => (
<div key={index} className="border rounded-lg p-4 transition-shadow" style={{ backgroundColor: 'var(--card)', color: 'var(--card-foreground)', borderColor: 'var(--border)' }}>
<div className="flex items-start justify-between mb-3">
<h3 className="font-semibold text-lg">{model.name}</h3>
<span className={`text-xs px-2 py-1 rounded ${model.status === 'Active' ? 'bg-green-100 text-green-800' :
model.status === 'Beta' ? 'bg-blue-100 text-blue-800' :
'bg-yellow-100 text-yellow-800'
}`}>
{model.status}
</span>
</div>
<p className="text-sm mb-3" style={{ color: 'var(--muted-foreground)' }}>
{model.description}
</p>
<div className="flex items-center justify-between">
<div className="flex gap-2">
<span className="text-xs px-2 py-1 rounded" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{model.provider}
</span>
<span className="text-xs px-2 py-1 rounded" style={{ backgroundColor: 'var(--muted)', color: 'var(--muted-foreground)' }}>
{model.type}
</span>
</div>
<button className="flex items-center gap-1 text-sm font-medium" style={{ color: 'var(--chart-5)' }}>
<ExternalLink className="h-3 w-3" />
View Details
</button>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
return ( return (
<div className="h-screen flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}> <div className="h-screen flex flex-col" style={{ backgroundColor: 'var(--background)', color: 'var(--foreground)' }}>
{/* Tab Navigation */} {/* Tab Navigation */}
@ -827,18 +1113,19 @@ export default function LabComponent() {
{ id: 'templates', label: 'Templates', icon: FileText, color: 'text-green-600' }, { id: 'templates', label: 'Templates', icon: FileText, color: 'text-green-600' },
{ id: 'news', label: 'News', icon: Newspaper, color: 'text-blue-600' }, { id: 'news', label: 'News', icon: Newspaper, color: 'text-blue-600' },
{ id: 'mcp', label: 'MCP Servers', icon: Server, color: 'text-purple-600' }, { id: 'mcp', label: 'MCP Servers', icon: Server, color: 'text-purple-600' },
{ id: 'llm-tools', label: 'LLM Tools', icon: Cpu, color: 'text-indigo-600' } { id: 'llm-tools', label: 'LLM Tools', icon: Cpu, color: 'text-indigo-600' },
{ id: 'models', label: 'Models', icon: Cpu, color: 'text-teal-600' }
].map((tab) => { ].map((tab) => {
const IconComponent = tab.icon; const IconComponent = tab.icon;
return ( return (
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`flex items-center gap-2 px-6 py-4 border-b-2 transition-colors font-medium ${ className={`flex items-center gap-2 px-6 py-4 border-b-2 transition-colors font-medium ${activeTab === tab.id
activeTab === tab.id
? `border-current ${tab.color} bg-card` ? `border-current ${tab.color} bg-card`
: 'border-transparent hover:bg-card' : 'border-transparent hover:bg-card'
}`} }`}
> >
<IconComponent className="h-4 w-4" /> <IconComponent className="h-4 w-4" />
{tab.label} {tab.label}
@ -855,7 +1142,10 @@ export default function LabComponent() {
{activeTab === 'news' && renderNews()} {activeTab === 'news' && renderNews()}
{activeTab === 'mcp' && renderMCPServers()} {activeTab === 'mcp' && renderMCPServers()}
{activeTab === 'llm-tools' && renderLLMTools()} {activeTab === 'llm-tools' && renderLLMTools()}
{activeTab === 'models' && renderModels()}
</div> </div>
</div> </div>
); );
} }

View file

@ -10,7 +10,7 @@ type Theme = {
} }
const themes: Theme[] = [ const themes: Theme[] = [
{ name: 'retrowave', label: 'RetroWave', cssFile: '/themes/retrowave.css' },
{ name: '3dbevel', label: '3dbevel', cssFile: '/themes/3dbevel.css' }, { name: '3dbevel', label: '3dbevel', cssFile: '/themes/3dbevel.css' },
{ name: 'arcadeflash', label: 'Arcadeflash', cssFile: '/themes/arcadeflash.css' }, { name: 'arcadeflash', label: 'Arcadeflash', cssFile: '/themes/arcadeflash.css' },
{ name: 'cyberpunk', label: 'Cyberpunk', cssFile: '/themes/cyberpunk.css' }, { name: 'cyberpunk', label: 'Cyberpunk', cssFile: '/themes/cyberpunk.css' },

View file

@ -1,19 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>General Bots</title>
<link href="/output.css" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

File diff suppressed because it is too large Load diff