1525 lines
53 KiB
HTML
1525 lines
53 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Ollama Vision Chat Pro</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
:root {
|
||
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
--secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||
--success-color: #10b981;
|
||
--warning-color: #f59e0b;
|
||
--error-color: #ef4444;
|
||
--text-primary: #1f2937;
|
||
--text-secondary: #6b7280;
|
||
--bg-primary: #ffffff;
|
||
--bg-secondary: #f9fafb;
|
||
--border-color: #e5e7eb;
|
||
--glass-bg: rgba(255, 255, 255, 0.95);
|
||
--shadow-light: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||
--shadow-medium: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||
--shadow-heavy: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||
}
|
||
|
||
[data-theme="dark"] {
|
||
--text-primary: #f9fafb;
|
||
--text-secondary: #d1d5db;
|
||
--bg-primary: #111827;
|
||
--bg-secondary: #1f2937;
|
||
--border-color: #374151;
|
||
--glass-bg: rgba(17, 24, 39, 0.95);
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: var(--primary-gradient);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 10px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.container {
|
||
background: var(--glass-bg);
|
||
backdrop-filter: blur(20px);
|
||
border-radius: 24px;
|
||
box-shadow: var(--shadow-heavy);
|
||
width: 100%;
|
||
max-width: 1400px;
|
||
height: 95vh;
|
||
display: grid;
|
||
grid-template-columns: 1fr 420px;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
/* Video Section */
|
||
.video-section {
|
||
position: relative;
|
||
background: #000;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.video-container {
|
||
position: relative;
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
#videoElement {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.video-overlay {
|
||
position: absolute;
|
||
top: 20px;
|
||
left: 20px;
|
||
right: 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
z-index: 10;
|
||
gap: 15px;
|
||
}
|
||
|
||
.status-panel {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
color: white;
|
||
padding: 12px 16px;
|
||
border-radius: 16px;
|
||
font-size: 14px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
min-width: 200px;
|
||
}
|
||
|
||
.status-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.status-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--error-color);
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
.status-dot.connected { background: var(--success-color); }
|
||
.status-dot.warning { background: var(--warning-color); }
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.5; }
|
||
}
|
||
|
||
.model-info {
|
||
font-size: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.controls {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.control-row {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
padding: 8px;
|
||
border-radius: 16px;
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.control-btn {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border: none;
|
||
color: white;
|
||
padding: 10px;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
width: 40px;
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
position: relative;
|
||
}
|
||
|
||
.control-btn:hover {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.control-btn.active {
|
||
background: var(--success-color);
|
||
}
|
||
|
||
.control-btn.recording::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: -2px;
|
||
right: -2px;
|
||
width: 8px;
|
||
height: 8px;
|
||
background: #ff4444;
|
||
border-radius: 50%;
|
||
animation: pulse 1s infinite;
|
||
}
|
||
|
||
/* Enhanced Settings Panel */
|
||
.settings-panel {
|
||
position: absolute;
|
||
bottom: 20px;
|
||
left: 20px;
|
||
background: rgba(0, 0, 0, 0.9);
|
||
backdrop-filter: blur(15px);
|
||
color: white;
|
||
padding: 20px;
|
||
border-radius: 16px;
|
||
min-width: 320px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
display: none;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.settings-panel.show { display: block; }
|
||
|
||
.settings-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.settings-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
color: white;
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
opacity: 0.7;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.close-btn:hover { opacity: 1; }
|
||
|
||
.settings-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin-bottom: 12px;
|
||
color: #fff;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.setting-item {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.setting-item label {
|
||
display: block;
|
||
font-size: 13px;
|
||
margin-bottom: 6px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.setting-item input, .setting-item select {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
border-radius: 8px;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: white;
|
||
font-size: 14px;
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
.setting-item input:focus, .setting-item select:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
}
|
||
|
||
.setting-item input::placeholder {
|
||
color: rgba(255, 255, 255, 0.6);
|
||
}
|
||
|
||
.toggle-switch {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 50px;
|
||
height: 24px;
|
||
}
|
||
|
||
.toggle-switch input {
|
||
opacity: 0;
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
|
||
.slider {
|
||
position: absolute;
|
||
cursor: pointer;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
transition: .4s;
|
||
border-radius: 24px;
|
||
}
|
||
|
||
.slider:before {
|
||
position: absolute;
|
||
content: "";
|
||
height: 18px;
|
||
width: 18px;
|
||
left: 3px;
|
||
bottom: 3px;
|
||
background-color: white;
|
||
transition: .4s;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
input:checked + .slider {
|
||
background-color: var(--success-color);
|
||
}
|
||
|
||
input:checked + .slider:before {
|
||
transform: translateX(26px);
|
||
}
|
||
|
||
/* Chat Section */
|
||
.chat-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--bg-secondary);
|
||
position: relative;
|
||
}
|
||
|
||
.chat-header {
|
||
background: var(--bg-primary);
|
||
padding: 20px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.chat-title {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.chat-title h2 {
|
||
color: var(--text-primary);
|
||
margin-bottom: 4px;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.chat-title p {
|
||
color: var(--text-secondary);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.chat-controls {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.header-btn {
|
||
background: none;
|
||
border: 1px solid var(--border-color);
|
||
color: var(--text-secondary);
|
||
padding: 8px 12px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.header-btn:hover {
|
||
background: var(--bg-secondary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.messages-container {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
scroll-behavior: smooth;
|
||
}
|
||
|
||
.message {
|
||
max-width: 85%;
|
||
padding: 14px 18px;
|
||
border-radius: 20px;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
animation: slideIn 0.4s ease;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(15px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.message.user {
|
||
background: var(--primary-gradient);
|
||
color: white;
|
||
align-self: flex-end;
|
||
border-bottom-right-radius: 6px;
|
||
box-shadow: var(--shadow-light);
|
||
}
|
||
|
||
.message.assistant {
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
align-self: flex-start;
|
||
border: 1px solid var(--border-color);
|
||
border-bottom-left-radius: 6px;
|
||
box-shadow: var(--shadow-light);
|
||
}
|
||
|
||
.message.system {
|
||
background: #fef3c7;
|
||
color: #92400e;
|
||
align-self: center;
|
||
font-size: 12px;
|
||
border: 1px solid #fcd34d;
|
||
text-align: center;
|
||
max-width: 90%;
|
||
}
|
||
|
||
.message.error {
|
||
background: #fee2e2;
|
||
color: #dc2626;
|
||
border: 1px solid #fca5a5;
|
||
}
|
||
|
||
.message-time {
|
||
font-size: 11px;
|
||
opacity: 0.6;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.typing-indicator {
|
||
display: none;
|
||
padding: 14px 18px;
|
||
background: var(--bg-primary);
|
||
border-radius: 20px;
|
||
border-bottom-left-radius: 6px;
|
||
align-self: flex-start;
|
||
border: 1px solid var(--border-color);
|
||
}
|
||
|
||
.typing-indicator.show { display: block; }
|
||
|
||
.typing-dots {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
|
||
.typing-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--text-secondary);
|
||
animation: typingPulse 1.4s infinite ease-in-out;
|
||
}
|
||
|
||
.typing-dot:nth-child(1) { animation-delay: -0.32s; }
|
||
.typing-dot:nth-child(2) { animation-delay: -0.16s; }
|
||
|
||
@keyframes typingPulse {
|
||
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||
40% { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
/* Enhanced Input Section */
|
||
.input-container {
|
||
padding: 20px;
|
||
background: var(--bg-primary);
|
||
border-top: 1px solid var(--border-color);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.quick-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.quick-action {
|
||
background: var(--bg-secondary);
|
||
border: 1px solid var(--border-color);
|
||
color: var(--text-secondary);
|
||
padding: 6px 12px;
|
||
border-radius: 16px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.quick-action:hover {
|
||
background: var(--primary-gradient);
|
||
color: white;
|
||
border-color: transparent;
|
||
}
|
||
|
||
.input-row {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.input-wrapper {
|
||
flex: 1;
|
||
position: relative;
|
||
}
|
||
|
||
#messageInput {
|
||
width: 100%;
|
||
padding: 14px 50px 14px 16px;
|
||
border: 2px solid var(--border-color);
|
||
border-radius: 16px;
|
||
font-size: 14px;
|
||
resize: none;
|
||
max-height: 120px;
|
||
min-height: 48px;
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
transition: all 0.3s ease;
|
||
font-family: inherit;
|
||
}
|
||
|
||
#messageInput:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.input-actions {
|
||
position: absolute;
|
||
right: 8px;
|
||
bottom: 8px;
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
|
||
.input-btn {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-secondary);
|
||
padding: 6px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.input-btn:hover {
|
||
background: var(--bg-secondary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.send-btn {
|
||
background: var(--primary-gradient);
|
||
border: none;
|
||
color: white;
|
||
padding: 14px 20px;
|
||
border-radius: 16px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
white-space: nowrap;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.send-btn:hover:not(:disabled) {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
|
||
}
|
||
|
||
.send-btn:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
/* Theme Toggle */
|
||
.theme-toggle {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
background: var(--glass-bg);
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid var(--border-color);
|
||
color: var(--text-primary);
|
||
padding: 10px;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
width: 44px;
|
||
height: 44px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s ease;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.theme-toggle:hover {
|
||
transform: scale(1.1);
|
||
box-shadow: var(--shadow-medium);
|
||
}
|
||
|
||
/* Image Preview */
|
||
.image-preview {
|
||
position: absolute;
|
||
bottom: 80px;
|
||
right: 20px;
|
||
width: 150px;
|
||
height: 100px;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
display: none;
|
||
z-index: 5;
|
||
}
|
||
|
||
.image-preview.show { display: block; }
|
||
|
||
.image-preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.preview-label {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
color: white;
|
||
font-size: 10px;
|
||
padding: 4px 8px;
|
||
text-align: center;
|
||
}
|
||
|
||
/* Responsive Design */
|
||
@media (max-width: 1024px) {
|
||
.container {
|
||
grid-template-columns: 1fr;
|
||
grid-template-rows: 1fr 1fr;
|
||
max-width: 100%;
|
||
height: 98vh;
|
||
}
|
||
|
||
.video-overlay {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.status-panel {
|
||
min-width: auto;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
body { padding: 5px; }
|
||
|
||
.container {
|
||
border-radius: 16px;
|
||
height: 99vh;
|
||
}
|
||
|
||
.video-overlay {
|
||
top: 10px;
|
||
left: 10px;
|
||
right: 10px;
|
||
}
|
||
|
||
.settings-panel {
|
||
bottom: 10px;
|
||
left: 10px;
|
||
right: 10px;
|
||
min-width: auto;
|
||
}
|
||
|
||
.chat-header {
|
||
padding: 15px;
|
||
}
|
||
|
||
.messages-container {
|
||
padding: 15px;
|
||
}
|
||
|
||
.input-container {
|
||
padding: 15px;
|
||
}
|
||
|
||
.quick-actions {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* Scrollbar Styling */
|
||
.messages-container::-webkit-scrollbar,
|
||
.settings-panel::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
.messages-container::-webkit-scrollbar-track,
|
||
.settings-panel::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
.messages-container::-webkit-scrollbar-thumb,
|
||
.settings-panel::-webkit-scrollbar-thumb {
|
||
background: var(--border-color);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.messages-container::-webkit-scrollbar-thumb:hover,
|
||
.settings-panel::-webkit-scrollbar-thumb:hover {
|
||
background: var(--text-secondary);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<button class="theme-toggle" id="themeToggle">🌙</button>
|
||
|
||
<div class="container">
|
||
<div class="video-section">
|
||
<div class="video-container">
|
||
<video id="videoElement" autoplay muted playsinline></video>
|
||
<canvas id="captureCanvas" style="display: none;"></canvas>
|
||
|
||
<div class="video-overlay">
|
||
<div class="status-panel">
|
||
<div class="status-row">
|
||
<div class="status-dot" id="statusDot"></div>
|
||
<span id="statusText">Connecting...</span>
|
||
</div>
|
||
<div class="model-info" id="modelInfo">
|
||
Model: <span id="currentModel">Loading...</span>
|
||
</div>
|
||
<div class="model-info" id="performanceInfo">
|
||
Response time: <span id="responseTime">--</span>ms
|
||
</div>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<div class="control-row">
|
||
<button class="control-btn" id="settingsBtn" title="Settings">⚙️</button>
|
||
<button class="control-btn" id="captureBtn" title="Take Photo">📸</button>
|
||
<button class="control-btn" id="pauseBtn" title="Pause/Resume Auto-capture">⏸️</button>
|
||
</div>
|
||
<div class="control-row">
|
||
<button class="control-btn" id="clearBtn" title="Clear Chat">🗑️</button>
|
||
<button class="control-btn" id="exportBtn" title="Export Chat">💾</button>
|
||
<button class="control-btn" id="fullscreenBtn" title="Toggle Fullscreen">⛶</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="image-preview" id="imagePreview">
|
||
<img id="previewImg" alt="Last capture">
|
||
<div class="preview-label">Last Capture</div>
|
||
</div>
|
||
|
||
<div class="settings-panel" id="settingsPanel">
|
||
<div class="settings-header">
|
||
<div class="settings-title">Settings</div>
|
||
<button class="close-btn" id="closeSettings">×</button>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<div class="section-title">Connection</div>
|
||
<div class="setting-item">
|
||
<label>Ollama Server URL:</label>
|
||
<input type="text" id="ollamaUrl" value="http://localhost:11434" placeholder="http://localhost:11434">
|
||
</div>
|
||
<div class="setting-item">
|
||
<label>Vision Model:</label>
|
||
<select id="modelName">
|
||
<option value="llava">LLaVA (Default)</option>
|
||
<option value="llava:7b">LLaVA 7B</option>
|
||
<option value="llava:13b">LLaVA 13B</option>
|
||
<option value="bakllava">BakLLaVA</option>
|
||
<option value="custom">Custom Model</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-item" id="customModelItem" style="display: none;">
|
||
<label>Custom Model Name:</label>
|
||
<input type="text" id="customModel" placeholder="Enter model name">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<div class="section-title">Camera</div>
|
||
<div class="setting-item">
|
||
<label>Auto-capture Interval (seconds):</label>
|
||
<input type="range" id="captureInterval" min="5" max="60" value="10" step="5">
|
||
<div style="text-align: center; margin-top: 5px; font-size: 12px;">
|
||
<span id="intervalValue">10</span>s
|
||
</div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<label>Enable Auto-capture:</label>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="autoCapture" checked>
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-item">
|
||
<label>Image Quality:</label>
|
||
<select id="imageQuality">
|
||
<option value="0.6">Low (Fast)</option>
|
||
<option value="0.8">Medium (Default)</option>
|
||
<option value="1.0">High (Slow)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<div class="section-title">AI Behavior</div>
|
||
<div class="setting-item">
|
||
<label>System Prompt:</label>
|
||
<textarea id="systemPrompt" rows="3" placeholder="You are a helpful AI assistant that can see through the camera...">You are a helpful AI assistant with vision capabilities. You can see what the user is showing you through their camera. Be conversational, helpful, and provide detailed observations when asked about what you can see.</textarea>
|
||
</div>
|
||
<div class="setting-item">
|
||
<label>Context Memory:</label>
|
||
<select id="contextMemory">
|
||
<option value="3">Short (3 messages)</option>
|
||
<option value="10">Medium (10 messages)</option>
|
||
<option value="20">Long (20 messages)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chat-section">
|
||
<div class="chat-header">
|
||
<div class="chat-title">
|
||
<h2>Vision Chat Pro</h2>
|
||
<p>AI that can see and understand your world</p>
|
||
</div>
|
||
<div class="chat-controls">
|
||
<button class="header-btn" id="voiceBtn">🎤 Voice</button>
|
||
<button class="header-btn" id="modeBtn">💬 Chat</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="messages-container" id="messagesContainer">
|
||
<div class="message system">
|
||
🎉 Welcome to Vision Chat Pro! I can see through your camera and help with anything you need. Try asking me about what I can see, or just have a conversation!
|
||
</div>
|
||
</div>
|
||
|
||
<div class="typing-indicator" id="typingIndicator">
|
||
<div class="typing-dots">
|
||
<div class="typing-dot"></div>
|
||
<div class="typing-dot"></div>
|
||
<div class="typing-dot"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="input-container">
|
||
<div class="quick-actions" id="quickActions">
|
||
<button class="quick-action" data-prompt="What do you see in my camera?">👁️ What do you see?</button>
|
||
<button class="quick-action" data-prompt="Help me organize this space">🏠 Organize space</button>
|
||
<button class="quick-action" data-prompt="What colors are prominent in this image?">🎨 Describe colors</button>
|
||
<button class="quick-action" data-prompt="Can you read any text in this image?">📖 Read text</button>
|
||
<button class="quick-action" data-prompt="Give me feedback on this setup">💡 Get feedback</button>
|
||
</div>
|
||
|
||
<div class="input-row">
|
||
<div class="input-wrapper">
|
||
<textarea
|
||
id="messageInput"
|
||
placeholder="Ask me anything about what I can see, or just chat..."
|
||
rows="1"
|
||
></textarea>
|
||
<div class="input-actions">
|
||
<button class="input-btn" id="emojiBtn" title="Add emoji">😊</button>
|
||
<button class="input-btn" id="attachBtn" title="Attach image">📎</button>
|
||
</div>
|
||
</div>
|
||
<button class="send-btn" id="sendBtn">
|
||
<span>Send</span>
|
||
<span>📤</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
class EnhancedVisionChatApp {
|
||
constructor() {
|
||
this.video = document.getElementById('videoElement');
|
||
this.canvas = document.getElementById('captureCanvas');
|
||
this.ctx = this.canvas.getContext('2d');
|
||
this.messagesContainer = document.getElementById('messagesContainer');
|
||
this.messageInput = document.getElementById('messageInput');
|
||
this.sendBtn = document.getElementById('sendBtn');
|
||
this.statusDot = document.getElementById('statusDot');
|
||
this.statusText = document.getElementById('statusText');
|
||
this.typingIndicator = document.getElementById('typingIndicator');
|
||
|
||
// Settings
|
||
this.ollamaUrl = 'http://localhost:11434';
|
||
this.modelName = 'llava';
|
||
this.systemPrompt = 'You are a helpful AI assistant with vision capabilities.';
|
||
this.contextMemory = 10;
|
||
this.imageQuality = 0.8;
|
||
|
||
// State
|
||
this.isConnected = false;
|
||
this.captureIntervalId = null;
|
||
this.lastCaptureTime = 0;
|
||
this.currentImageData = null;
|
||
this.messageHistory = [];
|
||
this.availableModels = [];
|
||
this.isDarkTheme = false;
|
||
this.isRecording = false;
|
||
this.lastResponseTime = 0;
|
||
|
||
this.init();
|
||
}
|
||
|
||
async init() {
|
||
await this.setupCamera();
|
||
this.setupEventListeners();
|
||
this.loadSettings();
|
||
this.checkOllamaConnection();
|
||
this.startAutoCapture();
|
||
this.initializeTheme();
|
||
}
|
||
|
||
async setupCamera() {
|
||
try {
|
||
const stream = await navigator.mediaDevices.getUserMedia({
|
||
video: {
|
||
width: { ideal: 1920 },
|
||
height: { ideal: 1080 },
|
||
facingMode: 'user'
|
||
}
|
||
});
|
||
|
||
this.video.srcObject = stream;
|
||
|
||
this.video.addEventListener('loadedmetadata', () => {
|
||
this.canvas.width = this.video.videoWidth;
|
||
this.canvas.height = this.video.videoHeight;
|
||
});
|
||
|
||
this.addMessage('system', '📹 Camera connected successfully!');
|
||
} catch (error) {
|
||
console.error('Camera setup failed:', error);
|
||
this.addMessage('error', '❌ Failed to access camera. Please check permissions and try again.');
|
||
}
|
||
}
|
||
|
||
setupEventListeners() {
|
||
// Send message
|
||
this.sendBtn.addEventListener('click', () => this.sendMessage());
|
||
this.messageInput.addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
this.sendMessage();
|
||
}
|
||
});
|
||
|
||
// Auto-resize textarea
|
||
this.messageInput.addEventListener('input', () => {
|
||
this.messageInput.style.height = 'auto';
|
||
this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
|
||
});
|
||
|
||
// Quick actions
|
||
document.querySelectorAll('.quick-action').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
this.messageInput.value = btn.dataset.prompt;
|
||
this.messageInput.focus();
|
||
this.messageInput.style.height = 'auto';
|
||
this.messageInput.style.height = this.messageInput.scrollHeight + 'px';
|
||
});
|
||
});
|
||
|
||
// Control buttons
|
||
document.getElementById('captureBtn').addEventListener('click', () => {
|
||
this.captureFrame();
|
||
this.showImagePreview();
|
||
this.addMessage('system', '📸 Photo captured!');
|
||
});
|
||
|
||
document.getElementById('pauseBtn').addEventListener('click', () => {
|
||
this.toggleAutoCapture();
|
||
});
|
||
|
||
document.getElementById('settingsBtn').addEventListener('click', () => {
|
||
this.toggleSettings();
|
||
});
|
||
|
||
document.getElementById('closeSettings').addEventListener('click', () => {
|
||
this.hideSettings();
|
||
});
|
||
|
||
document.getElementById('clearBtn').addEventListener('click', () => {
|
||
this.clearChat();
|
||
});
|
||
|
||
document.getElementById('exportBtn').addEventListener('click', () => {
|
||
this.exportChat();
|
||
});
|
||
|
||
document.getElementById('fullscreenBtn').addEventListener('click', () => {
|
||
this.toggleFullscreen();
|
||
});
|
||
|
||
// Theme toggle
|
||
document.getElementById('themeToggle').addEventListener('click', () => {
|
||
this.toggleTheme();
|
||
});
|
||
|
||
// Settings handlers
|
||
document.getElementById('ollamaUrl').addEventListener('change', (e) => {
|
||
this.ollamaUrl = e.target.value;
|
||
this.saveSettings();
|
||
this.checkOllamaConnection();
|
||
});
|
||
|
||
document.getElementById('modelName').addEventListener('change', (e) => {
|
||
if (e.target.value === 'custom') {
|
||
document.getElementById('customModelItem').style.display = 'block';
|
||
} else {
|
||
document.getElementById('customModelItem').style.display = 'none';
|
||
this.modelName = e.target.value;
|
||
this.saveSettings();
|
||
this.updateModelInfo();
|
||
}
|
||
});
|
||
|
||
document.getElementById('customModel').addEventListener('change', (e) => {
|
||
this.modelName = e.target.value;
|
||
this.saveSettings();
|
||
this.updateModelInfo();
|
||
});
|
||
|
||
document.getElementById('autoCapture').addEventListener('change', (e) => {
|
||
if (e.target.checked) {
|
||
this.startAutoCapture();
|
||
} else {
|
||
this.stopAutoCapture();
|
||
}
|
||
this.saveSettings();
|
||
});
|
||
|
||
document.getElementById('captureInterval').addEventListener('input', (e) => {
|
||
document.getElementById('intervalValue').textContent = e.target.value;
|
||
this.stopAutoCapture();
|
||
if (document.getElementById('autoCapture').checked) {
|
||
this.startAutoCapture();
|
||
}
|
||
this.saveSettings();
|
||
});
|
||
|
||
document.getElementById('imageQuality').addEventListener('change', (e) => {
|
||
this.imageQuality = parseFloat(e.target.value);
|
||
this.saveSettings();
|
||
});
|
||
|
||
document.getElementById('systemPrompt').addEventListener('change', (e) => {
|
||
this.systemPrompt = e.target.value;
|
||
this.saveSettings();
|
||
});
|
||
|
||
document.getElementById('contextMemory').addEventListener('change', (e) => {
|
||
this.contextMemory = parseInt(e.target.value);
|
||
this.saveSettings();
|
||
});
|
||
|
||
// Header buttons
|
||
document.getElementById('voiceBtn').addEventListener('click', () => {
|
||
this.toggleVoiceMode();
|
||
});
|
||
|
||
document.getElementById('modeBtn').addEventListener('click', () => {
|
||
this.toggleChatMode();
|
||
});
|
||
}
|
||
|
||
async checkOllamaConnection() {
|
||
try {
|
||
this.setConnectionStatus('connecting');
|
||
const response = await fetch(`${this.ollamaUrl}/api/tags`);
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
this.availableModels = data.models || [];
|
||
this.setConnectionStatus('connected');
|
||
this.updateAvailableModels();
|
||
|
||
const hasCurrentModel = this.availableModels.some(model =>
|
||
model.name.includes(this.modelName)
|
||
);
|
||
|
||
if (!hasCurrentModel) {
|
||
this.addMessage('system', `⚠️ Model "${this.modelName}" not found. Available models: ${this.availableModels.map(m => m.name).join(', ')}`);
|
||
this.setConnectionStatus('warning');
|
||
}
|
||
|
||
this.updateModelInfo();
|
||
} else {
|
||
throw new Error('Connection failed');
|
||
}
|
||
} catch (error) {
|
||
console.error('Ollama connection failed:', error);
|
||
this.setConnectionStatus('disconnected');
|
||
this.addMessage('error', `❌ Failed to connect to Ollama at ${this.ollamaUrl}. Make sure Ollama is running.`);
|
||
}
|
||
}
|
||
|
||
setConnectionStatus(status) {
|
||
this.statusDot.className = 'status-dot';
|
||
|
||
switch (status) {
|
||
case 'connected':
|
||
this.statusDot.classList.add('connected');
|
||
this.statusText.textContent = 'Connected';
|
||
this.isConnected = true;
|
||
break;
|
||
case 'connecting':
|
||
this.statusText.textContent = 'Connecting...';
|
||
this.isConnected = false;
|
||
break;
|
||
case 'warning':
|
||
this.statusDot.classList.add('warning');
|
||
this.statusText.textContent = 'Model Warning';
|
||
this.isConnected = true;
|
||
break;
|
||
default:
|
||
this.statusText.textContent = 'Disconnected';
|
||
this.isConnected = false;
|
||
}
|
||
}
|
||
|
||
updateModelInfo() {
|
||
document.getElementById('currentModel').textContent = this.modelName;
|
||
}
|
||
|
||
updateAvailableModels() {
|
||
const select = document.getElementById('modelName');
|
||
const customOption = select.querySelector('option[value="custom"]');
|
||
|
||
// Clear existing model options (except predefined ones)
|
||
const predefinedValues = ['llava', 'llava:7b', 'llava:13b', 'bakllava', 'custom'];
|
||
Array.from(select.options).forEach(option => {
|
||
if (!predefinedValues.includes(option.value)) {
|
||
option.remove();
|
||
}
|
||
});
|
||
|
||
// Add available models
|
||
this.availableModels.forEach(model => {
|
||
if (!predefinedValues.includes(model.name)) {
|
||
const option = document.createElement('option');
|
||
option.value = model.name;
|
||
option.textContent = model.name;
|
||
select.insertBefore(option, customOption);
|
||
}
|
||
});
|
||
}
|
||
|
||
captureFrame() {
|
||
if (!this.video.videoWidth || !this.video.videoHeight) return null;
|
||
|
||
this.ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
||
const imageData = this.canvas.toDataURL('image/jpeg', this.imageQuality);
|
||
this.currentImageData = imageData.split(',')[1];
|
||
this.lastCaptureTime = Date.now();
|
||
|
||
return this.currentImageData;
|
||
}
|
||
|
||
showImagePreview() {
|
||
if (this.currentImageData) {
|
||
const preview = document.getElementById('imagePreview');
|
||
const img = document.getElementById('previewImg');
|
||
img.src = 'data:image/jpeg;base64,' + this.currentImageData;
|
||
preview.classList.add('show');
|
||
|
||
setTimeout(() => {
|
||
preview.classList.remove('show');
|
||
}, 3000);
|
||
}
|
||
}
|
||
|
||
startAutoCapture() {
|
||
this.stopAutoCapture();
|
||
const interval = parseInt(document.getElementById('captureInterval').value) * 1000;
|
||
|
||
this.captureIntervalId = setInterval(() => {
|
||
this.captureFrame();
|
||
}, interval);
|
||
|
||
// Capture first frame immediately
|
||
this.captureFrame();
|
||
|
||
// Update pause button
|
||
const pauseBtn = document.getElementById('pauseBtn');
|
||
pauseBtn.classList.add('active');
|
||
pauseBtn.innerHTML = '⏸️';
|
||
}
|
||
|
||
stopAutoCapture() {
|
||
if (this.captureIntervalId) {
|
||
clearInterval(this.captureIntervalId);
|
||
this.captureIntervalId = null;
|
||
}
|
||
|
||
// Update pause button
|
||
const pauseBtn = document.getElementById('pauseBtn');
|
||
pauseBtn.classList.remove('active');
|
||
pauseBtn.innerHTML = '▶️';
|
||
}
|
||
|
||
toggleAutoCapture() {
|
||
const autoCapture = document.getElementById('autoCapture');
|
||
autoCapture.checked = !autoCapture.checked;
|
||
|
||
if (autoCapture.checked) {
|
||
this.startAutoCapture();
|
||
this.addMessage('system', '▶️ Auto-capture resumed');
|
||
} else {
|
||
this.stopAutoCapture();
|
||
this.addMessage('system', '⏸️ Auto-capture paused');
|
||
}
|
||
|
||
this.saveSettings();
|
||
}
|
||
|
||
async sendMessage() {
|
||
const message = this.messageInput.value.trim();
|
||
if (!message) return;
|
||
|
||
if (!this.isConnected) {
|
||
this.addMessage('error', '❌ Not connected to Ollama. Please check the connection.');
|
||
return;
|
||
}
|
||
|
||
// Capture current frame if we don't have recent data
|
||
if (!this.currentImageData || (Date.now() - this.lastCaptureTime > 30000)) {
|
||
this.captureFrame();
|
||
}
|
||
|
||
this.addMessage('user', message);
|
||
this.messageInput.value = '';
|
||
this.messageInput.style.height = 'auto';
|
||
this.setSendingState(true);
|
||
this.showTypingIndicator();
|
||
|
||
const startTime = Date.now();
|
||
|
||
try {
|
||
const response = await this.sendToOllama(message, this.currentImageData);
|
||
this.lastResponseTime = Date.now() - startTime;
|
||
document.getElementById('responseTime').textContent = this.lastResponseTime;
|
||
|
||
this.addMessage('assistant', response);
|
||
this.updateMessageHistory(message, response);
|
||
} catch (error) {
|
||
console.error('Error sending message:', error);
|
||
this.addMessage('error', '❌ Error: ' + error.message);
|
||
} finally {
|
||
this.setSendingState(false);
|
||
this.hideTypingIndicator();
|
||
}
|
||
}
|
||
|
||
async sendToOllama(message, imageData) {
|
||
const payload = {
|
||
model: this.modelName,
|
||
prompt: this.buildContextualPrompt(message),
|
||
stream: false,
|
||
options: {
|
||
temperature: 0.7,
|
||
top_p: 0.9,
|
||
}
|
||
};
|
||
|
||
if (imageData) {
|
||
payload.images = [imageData];
|
||
}
|
||
|
||
const response = await fetch(`${this.ollamaUrl}/api/generate`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(payload)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
return data.response || 'No response received';
|
||
}
|
||
|
||
buildContextualPrompt(currentMessage) {
|
||
let prompt = this.systemPrompt + '\n\n';
|
||
|
||
// Add recent conversation history
|
||
const recentHistory = this.messageHistory.slice(-this.contextMemory);
|
||
if (recentHistory.length > 0) {
|
||
prompt += 'Recent conversation:\n';
|
||
recentHistory.forEach(({ user, assistant }) => {
|
||
prompt += `Human: ${user}\nAssistant: ${assistant}\n\n`;
|
||
});
|
||
}
|
||
|
||
prompt += `Human: ${currentMessage}\nAssistant:`;
|
||
return prompt;
|
||
}
|
||
|
||
updateMessageHistory(userMessage, assistantResponse) {
|
||
this.messageHistory.push({
|
||
user: userMessage,
|
||
assistant: assistantResponse,
|
||
timestamp: Date.now()
|
||
});
|
||
|
||
// Keep only recent messages based on context memory setting
|
||
if (this.messageHistory.length > this.contextMemory) {
|
||
this.messageHistory = this.messageHistory.slice(-this.contextMemory);
|
||
}
|
||
}
|
||
|
||
addMessage(type, content) {
|
||
const messageDiv = document.createElement('div');
|
||
messageDiv.className = `message ${type}`;
|
||
|
||
const contentDiv = document.createElement('div');
|
||
contentDiv.textContent = content;
|
||
messageDiv.appendChild(contentDiv);
|
||
|
||
if (type !== 'system' && type !== 'error') {
|
||
const timeDiv = document.createElement('div');
|
||
timeDiv.className = 'message-time';
|
||
timeDiv.textContent = new Date().toLocaleTimeString();
|
||
messageDiv.appendChild(timeDiv);
|
||
}
|
||
|
||
this.messagesContainer.appendChild(messageDiv);
|
||
this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
|
||
}
|
||
|
||
showTypingIndicator() {
|
||
this.typingIndicator.classList.add('show');
|
||
this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
|
||
}
|
||
|
||
hideTypingIndicator() {
|
||
this.typingIndicator.classList.remove('show');
|
||
}
|
||
|
||
setSendingState(sending) {
|
||
this.sendBtn.disabled = sending;
|
||
const btnText = this.sendBtn.querySelector('span');
|
||
const btnIcon = this.sendBtn.querySelector('span:last-child');
|
||
|
||
if (sending) {
|
||
btnText.textContent = 'Sending...';
|
||
btnIcon.textContent = '⏳';
|
||
} else {
|
||
btnText.textContent = 'Send';
|
||
btnIcon.textContent = '📤';
|
||
}
|
||
|
||
this.messageInput.disabled = sending;
|
||
}
|
||
|
||
toggleSettings() {
|
||
const panel = document.getElementById('settingsPanel');
|
||
panel.classList.toggle('show');
|
||
}
|
||
|
||
hideSettings() {
|
||
const panel = document.getElementById('settingsPanel');
|
||
panel.classList.remove('show');
|
||
}
|
||
|
||
clearChat() {
|
||
if (confirm('Are you sure you want to clear the chat history?')) {
|
||
this.messagesContainer.innerHTML = `
|
||
<div class="message system">
|
||
🔄 Chat cleared! I can still see through your camera and help with anything you need.
|
||
</div>
|
||
`;
|
||
this.messageHistory = [];
|
||
}
|
||
}
|
||
|
||
exportChat() {
|
||
const messages = Array.from(this.messagesContainer.children);
|
||
const chatData = {
|
||
timestamp: new Date().toISOString(),
|
||
messages: messages.map(msg => ({
|
||
type: msg.className.replace('message ', ''),
|
||
content: msg.textContent,
|
||
time: msg.querySelector('.message-time')?.textContent || ''
|
||
}))
|
||
};
|
||
|
||
const blob = new Blob([JSON.stringify(chatData, null, 2)], {
|
||
type: 'application/json'
|
||
});
|
||
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `vision-chat-${new Date().toISOString().slice(0, 10)}.json`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
|
||
this.addMessage('system', '💾 Chat exported successfully!');
|
||
}
|
||
|
||
toggleFullscreen() {
|
||
if (!document.fullscreenElement) {
|
||
document.documentElement.requestFullscreen();
|
||
} else {
|
||
document.exitFullscreen();
|
||
}
|
||
}
|
||
|
||
toggleTheme() {
|
||
this.isDarkTheme = !this.isDarkTheme;
|
||
document.documentElement.setAttribute('data-theme', this.isDarkTheme ? 'dark' : 'light');
|
||
document.getElementById('themeToggle').textContent = this.isDarkTheme ? '☀️' : '🌙';
|
||
this.saveSettings();
|
||
}
|
||
|
||
initializeTheme() {
|
||
const savedTheme = localStorage.getItem('visionChatTheme');
|
||
if (savedTheme === 'dark') {
|
||
this.toggleTheme();
|
||
}
|
||
}
|
||
|
||
toggleVoiceMode() {
|
||
// Placeholder for voice functionality
|
||
this.addMessage('system', '🎤 Voice mode coming soon!');
|
||
}
|
||
|
||
toggleChatMode() {
|
||
// Placeholder for different chat modes
|
||
this.addMessage('system', '💬 Chat mode options coming soon!');
|
||
}
|
||
|
||
saveSettings() {
|
||
const settings = {
|
||
ollamaUrl: this.ollamaUrl,
|
||
modelName: this.modelName,
|
||
systemPrompt: this.systemPrompt,
|
||
contextMemory: this.contextMemory,
|
||
imageQuality: this.imageQuality,
|
||
autoCapture: document.getElementById('autoCapture').checked,
|
||
captureInterval: document.getElementById('captureInterval').value,
|
||
theme: this.isDarkTheme ? 'dark' : 'light'
|
||
};
|
||
|
||
localStorage.setItem('visionChatSettings', JSON.stringify(settings));
|
||
}
|
||
|
||
loadSettings() {
|
||
const saved = localStorage.getItem('visionChatSettings');
|
||
if (saved) {
|
||
const settings = JSON.parse(saved);
|
||
|
||
this.ollamaUrl = settings.ollamaUrl || this.ollamaUrl;
|
||
this.modelName = settings.modelName || this.modelName;
|
||
this.systemPrompt = settings.systemPrompt || this.systemPrompt;
|
||
this.contextMemory = settings.contextMemory || this.contextMemory;
|
||
this.imageQuality = settings.imageQuality || this.imageQuality;
|
||
|
||
// Update UI elements
|
||
document.getElementById('ollamaUrl').value = this.ollamaUrl;
|
||
document.getElementById('modelName').value = this.modelName;
|
||
document.getElementById('systemPrompt').value = this.systemPrompt;
|
||
document.getElementById('contextMemory').value = this.contextMemory;
|
||
document.getElementById('imageQuality').value = this.imageQuality;
|
||
document.getElementById('autoCapture').checked = settings.autoCapture !== false;
|
||
document.getElementById('captureInterval').value = settings.captureInterval || 10;
|
||
document.getElementById('intervalValue').textContent = settings.captureInterval || 10;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Initialize the enhanced app
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
new EnhancedVisionChatApp();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |