new file: .dockerignore

new file:   .env.example
	new file:   Dockerfile
	new file:   app.py
	new file:   blueprints/__init__.py
	new file:   blueprints/auth.py
	new file:   blueprints/chat.py
	new file:   blueprints/context.py
	new file:   blueprints/documents.py
	new file:   blueprints/main.py
	new file:   config.py
	new file:   docker-compose.yml
	new file:   models/__init__.py
	new file:   models/chat_session.py
	new file:   models/document.py
	new file:   models/user.py
	new file:   requirements.txt
	new file:   services/__init__.py
	new file:   services/document_parser.py
	new file:   services/llm_service.py
	new file:   services/rag_service.py
	new file:   services/url_scraper.py
	new file:   static/css/style.css
	new file:   static/js/chat.js
	new file:   static/js/inline_chat.js
	new file:   static/js/main.js
	new file:   templates/base.html
	new file:   templates/document_view.html
	new file:   templates/index.html
	new file:   templates/login.html
	new file:   templates/register.html
This commit is contained in:
SimolZimol
2026-05-22 16:03:50 +02:00
commit 939cc13689
31 changed files with 2025 additions and 0 deletions

63
templates/base.html Normal file
View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}KI Context Tool{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
copilot: {
bg: '#1e1e2e',
sidebar: '#181825',
panel: '#24273a',
border: '#313244',
accent: '#89b4fa',
accentHover: '#74c7ec',
text: '#cdd6f4',
muted: '#6c7086',
user: '#313244',
assistant:'#1e1e2e',
success: '#a6e3a1',
danger: '#f38ba8',
warning: '#fab387',
}
}
}
}
}
</script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
{% block head %}{% endblock %}
</head>
<body class="bg-copilot-bg text-copilot-text min-h-screen flex flex-col">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div id="flash-container" class="fixed top-4 right-4 z-50 space-y-2">
{% for category, message in messages %}
<div class="flash-msg px-4 py-3 rounded-lg text-sm font-medium shadow-lg
{% if category == 'success' %}bg-copilot-success text-copilot-bg
{% elif category == 'danger' %}bg-copilot-danger text-copilot-bg
{% else %}bg-copilot-accent text-copilot-bg{% endif %}">
{{ message }}
</div>
{% endfor %}
</div>
<script>
setTimeout(() => {
document.getElementById('flash-container')?.remove();
}, 4000);
</script>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,65 @@
{% extends "base.html" %}
{% block title %}Document View — KI Context Tool{% endblock %}
{% block head %}
<!-- CodeMirror 6 via CDN (codemirror.net bundles) -->
<script src="https://unpkg.com/codemirror@6.0.1/dist/index.js" type="module" id="cm-loader"></script>
{% endblock %}
{% block content %}
<div class="flex flex-col h-screen overflow-hidden">
<!-- Topbar -->
<header class="h-10 border-b border-copilot-border bg-copilot-sidebar flex items-center gap-3 px-4 shrink-0">
<a href="{{ url_for('main.index') }}"
class="text-copilot-muted hover:text-copilot-accent transition text-sm flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" d="M15 19l-7-7 7-7"/>
</svg>
Back
</a>
<span class="text-copilot-border">|</span>
<span id="doc-name" class="text-sm text-copilot-text truncate flex-1"></span>
</header>
<!-- Editor area -->
<div class="flex-1 overflow-auto relative" id="editor-wrapper">
<div id="editor-mount" class="h-full font-mono text-sm"></div>
<!-- Inline chat popup (hidden by default) -->
<div id="inline-popup"
class="hidden absolute z-50 w-80 bg-copilot-panel border border-copilot-border rounded-xl shadow-2xl p-3"
style="top:0;left:0">
<p class="text-xs text-copilot-muted mb-2 font-semibold">Ask about selection</p>
<div id="inline-selection-preview"
class="text-xs text-copilot-muted bg-copilot-bg border border-copilot-border rounded px-2 py-1 mb-2 max-h-20 overflow-y-auto leading-relaxed">
</div>
<textarea id="inline-question" rows="2" placeholder="Your question…"
class="w-full bg-copilot-bg border border-copilot-border rounded-lg px-3 py-2 text-xs text-copilot-text
focus:outline-none focus:border-copilot-accent resize-none mb-2"></textarea>
<div class="flex gap-2">
<button id="inline-submit"
class="flex-1 bg-copilot-accent hover:bg-copilot-accentHover text-copilot-bg text-xs font-semibold py-1.5 rounded-lg transition">
Ask
</button>
<button id="inline-close"
class="text-xs text-copilot-muted hover:text-copilot-danger py-1.5 px-2 rounded-lg transition">
</button>
</div>
<!-- Answer area -->
<div id="inline-answer"
class="hidden mt-3 border-t border-copilot-border pt-3 text-xs text-copilot-text leading-relaxed max-h-48 overflow-y-auto whitespace-pre-wrap">
</div>
<p id="inline-loading" class="hidden text-xs text-copilot-muted animate-pulse mt-2">Thinking…</p>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
window.__DOC_ID__ = {{ doc_id }};
</script>
<script src="{{ url_for('static', filename='js/inline_chat.js') }}" type="module"></script>
{% endblock %}

123
templates/index.html Normal file
View File

@@ -0,0 +1,123 @@
{% extends "base.html" %}
{% block title %}KI Context Tool{% endblock %}
{% block content %}
<div class="flex h-screen overflow-hidden">
<!-- ══════════════════════════════════════════════
LEFT SIDEBAR
══════════════════════════════════════════════ -->
<aside id="sidebar" class="w-72 min-w-[220px] bg-copilot-sidebar border-r border-copilot-border flex flex-col select-none shrink-0">
<!-- Logo / User -->
<div class="flex items-center justify-between px-4 py-3 border-b border-copilot-border">
<div class="flex items-center gap-2">
<svg class="w-6 h-6 text-copilot-accent shrink-0" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 14h-2V8h2zm0-8h-2V8h2z"/>
</svg>
<span class="font-semibold text-sm text-copilot-text truncate">KI Context Tool</span>
</div>
<a href="{{ url_for('auth.logout') }}" title="Logout"
class="text-copilot-muted hover:text-copilot-danger transition p-1 rounded">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h6a2 2 0 012 2v1"/>
</svg>
</a>
</div>
<!-- New Chat -->
<div class="px-3 py-2 border-b border-copilot-border">
<button id="btn-new-chat"
class="w-full flex items-center gap-2 px-3 py-2 rounded-lg bg-copilot-accent text-copilot-bg text-sm font-semibold hover:bg-copilot-accentHover transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
<path stroke-linecap="round" d="M12 4v16m8-8H4"/>
</svg>
New Chat
</button>
</div>
<!-- Chat history -->
<div class="px-3 py-2 border-b border-copilot-border">
<p class="text-xs font-semibold uppercase tracking-wider text-copilot-muted mb-2">Chat History</p>
<ul id="session-list" class="space-y-0.5 max-h-36 overflow-y-auto"></ul>
</div>
<!-- Documents section -->
<div class="px-3 py-2 border-b border-copilot-border flex-1 overflow-y-auto">
<div class="flex items-center justify-between mb-2">
<p class="text-xs font-semibold uppercase tracking-wider text-copilot-muted">Documents</p>
<label class="cursor-pointer text-copilot-accent hover:text-copilot-accentHover transition" title="Upload file">
<input id="file-input" type="file" accept=".pdf,.txt,.docx,.md" class="hidden" multiple />
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1M12 4v12m-4-4l4-4 4 4"/>
</svg>
</label>
</div>
<ul id="doc-list" class="space-y-0.5"></ul>
<p id="doc-uploading" class="hidden text-xs text-copilot-muted mt-1 animate-pulse">Uploading & indexing…</p>
</div>
<!-- URLs section -->
<div class="px-3 py-2 border-t border-copilot-border">
<p class="text-xs font-semibold uppercase tracking-wider text-copilot-muted mb-2">Web Sources</p>
<form id="url-form" class="flex gap-1 mb-2">
<input id="url-input" type="url" placeholder="https://…"
class="flex-1 bg-copilot-bg border border-copilot-border rounded px-2 py-1 text-xs text-copilot-text focus:outline-none focus:border-copilot-accent" />
<button type="submit"
class="bg-copilot-accent text-copilot-bg text-xs font-semibold px-2 py-1 rounded hover:bg-copilot-accentHover transition">
Add
</button>
</form>
<ul id="url-list" class="space-y-0.5 max-h-32 overflow-y-auto"></ul>
</div>
</aside>
<!-- ══════════════════════════════════════════════
MAIN AREA
══════════════════════════════════════════════ -->
<main class="flex-1 flex flex-col min-w-0 bg-copilot-bg">
<!-- Topbar -->
<header class="h-10 border-b border-copilot-border flex items-center justify-between px-4 shrink-0">
<span id="chat-title" class="text-sm text-copilot-muted truncate">Select or start a chat</span>
<span class="text-xs text-copilot-muted" id="context-badge"></span>
</header>
<!-- Messages -->
<div id="messages" class="flex-1 overflow-y-auto px-6 py-4 space-y-4"></div>
<!-- Empty state -->
<div id="empty-state" class="flex-1 flex flex-col items-center justify-center text-center pointer-events-none absolute inset-0 mt-10">
<svg class="w-16 h-16 text-copilot-border mb-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>
</svg>
<p class="text-copilot-muted text-sm">Upload documents or add URLs,<br>then start a new chat.</p>
</div>
<!-- Input area -->
<div class="border-t border-copilot-border px-4 py-3 bg-copilot-sidebar shrink-0">
<!-- Active context chips -->
<div id="context-chips" class="flex flex-wrap gap-1 mb-2"></div>
<form id="chat-form" class="flex gap-2 items-end">
<textarea id="chat-input" rows="1" placeholder="Ask a question about your documents…"
class="flex-1 bg-copilot-bg border border-copilot-border rounded-xl px-4 py-2.5 text-sm text-copilot-text
focus:outline-none focus:border-copilot-accent resize-none overflow-hidden transition
max-h-40 leading-relaxed"
style="height:42px"></textarea>
<button id="send-btn" type="submit"
class="bg-copilot-accent hover:bg-copilot-accentHover text-copilot-bg rounded-xl px-4 py-2.5 font-semibold text-sm
transition disabled:opacity-40 disabled:cursor-not-allowed shrink-0">
Send
</button>
</form>
</div>
</main>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/main.js') }}" type="module"></script>
{% endblock %}

43
templates/login.html Normal file
View File

@@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block title %}Login — KI Context Tool{% endblock %}
{% block content %}
<div class="min-h-screen flex items-center justify-center">
<div class="w-full max-w-md bg-copilot-panel border border-copilot-border rounded-2xl p-8 shadow-2xl">
<div class="flex items-center gap-3 mb-8">
<svg class="w-8 h-8 text-copilot-accent" fill="currentColor" 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 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z"/>
</svg>
<h1 class="text-2xl font-bold text-copilot-text">KI Context Tool</h1>
</div>
<h2 class="text-lg font-semibold mb-6 text-copilot-muted">Sign in to your account</h2>
<form method="POST" class="space-y-5">
{{ form.hidden_tag() }}
<div>
<label class="block text-sm font-medium mb-1 text-copilot-muted">{{ form.email.label.text }}</label>
{{ form.email(class="w-full bg-copilot-bg border border-copilot-border rounded-lg px-4 py-2.5 text-copilot-text focus:outline-none focus:border-copilot-accent transition", placeholder="you@example.com") }}
{% for err in form.email.errors %}
<p class="text-copilot-danger text-xs mt-1">{{ err }}</p>
{% endfor %}
</div>
<div>
<label class="block text-sm font-medium mb-1 text-copilot-muted">{{ form.password.label.text }}</label>
{{ form.password(class="w-full bg-copilot-bg border border-copilot-border rounded-lg px-4 py-2.5 text-copilot-text focus:outline-none focus:border-copilot-accent transition", placeholder="••••••••") }}
{% for err in form.password.errors %}
<p class="text-copilot-danger text-xs mt-1">{{ err }}</p>
{% endfor %}
</div>
{{ form.submit(class="w-full bg-copilot-accent hover:bg-copilot-accentHover text-copilot-bg font-semibold py-2.5 rounded-lg transition cursor-pointer") }}
</form>
<p class="text-center text-copilot-muted text-sm mt-6">
No account?
<a href="{{ url_for('auth.register') }}" class="text-copilot-accent hover:underline">Register</a>
</p>
</div>
</div>
{% endblock %}

59
templates/register.html Normal file
View File

@@ -0,0 +1,59 @@
{% extends "base.html" %}
{% block title %}Register — KI Context Tool{% endblock %}
{% block content %}
<div class="min-h-screen flex items-center justify-center">
<div class="w-full max-w-md bg-copilot-panel border border-copilot-border rounded-2xl p-8 shadow-2xl">
<div class="flex items-center gap-3 mb-8">
<svg class="w-8 h-8 text-copilot-accent" fill="currentColor" 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 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z"/>
</svg>
<h1 class="text-2xl font-bold text-copilot-text">KI Context Tool</h1>
</div>
<h2 class="text-lg font-semibold mb-6 text-copilot-muted">Create an account</h2>
<form method="POST" class="space-y-5">
{{ form.hidden_tag() }}
<div>
<label class="block text-sm font-medium mb-1 text-copilot-muted">{{ form.username.label.text }}</label>
{{ form.username(class="w-full bg-copilot-bg border border-copilot-border rounded-lg px-4 py-2.5 text-copilot-text focus:outline-none focus:border-copilot-accent transition", placeholder="johndoe") }}
{% for err in form.username.errors %}
<p class="text-copilot-danger text-xs mt-1">{{ err }}</p>
{% endfor %}
</div>
<div>
<label class="block text-sm font-medium mb-1 text-copilot-muted">{{ form.email.label.text }}</label>
{{ form.email(class="w-full bg-copilot-bg border border-copilot-border rounded-lg px-4 py-2.5 text-copilot-text focus:outline-none focus:border-copilot-accent transition", placeholder="you@example.com") }}
{% for err in form.email.errors %}
<p class="text-copilot-danger text-xs mt-1">{{ err }}</p>
{% endfor %}
</div>
<div>
<label class="block text-sm font-medium mb-1 text-copilot-muted">{{ form.password.label.text }}</label>
{{ form.password(class="w-full bg-copilot-bg border border-copilot-border rounded-lg px-4 py-2.5 text-copilot-text focus:outline-none focus:border-copilot-accent transition", placeholder="Min. 8 characters") }}
{% for err in form.password.errors %}
<p class="text-copilot-danger text-xs mt-1">{{ err }}</p>
{% endfor %}
</div>
<div>
<label class="block text-sm font-medium mb-1 text-copilot-muted">{{ form.confirm.label.text }}</label>
{{ form.confirm(class="w-full bg-copilot-bg border border-copilot-border rounded-lg px-4 py-2.5 text-copilot-text focus:outline-none focus:border-copilot-accent transition", placeholder="Repeat password") }}
{% for err in form.confirm.errors %}
<p class="text-copilot-danger text-xs mt-1">{{ err }}</p>
{% endfor %}
</div>
{{ form.submit(class="w-full bg-copilot-accent hover:bg-copilot-accentHover text-copilot-bg font-semibold py-2.5 rounded-lg transition cursor-pointer") }}
</form>
<p class="text-center text-copilot-muted text-sm mt-6">
Already have an account?
<a href="{{ url_for('auth.login') }}" class="text-copilot-accent hover:underline">Login</a>
</p>
</div>
</div>
{% endblock %}