ltcai 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/package.json +3 -3
- package/server.py +75 -5
- package/static/account.html +449 -0
- package/static/admin.html +289 -59
- package/static/{indexd.html → chat.html} +359 -142
- package/static/index.html +0 -270
package/static/index.html
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="ko">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Lattice AI | Local Hub</title>
|
|
7
|
-
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&family=Inter:wght@400;500&display=swap" rel="stylesheet">
|
|
8
|
-
<style>
|
|
9
|
-
:root {
|
|
10
|
-
--bg-color: #0f172a;
|
|
11
|
-
--glass-bg: rgba(30, 41, 59, 0.7);
|
|
12
|
-
--primary-accent: #6366f1;
|
|
13
|
-
--secondary-accent: #a855f7;
|
|
14
|
-
--text-main: #f8fafc;
|
|
15
|
-
--text-dim: #94a3b8;
|
|
16
|
-
--bot-msg-bg: rgba(51, 65, 85, 0.5);
|
|
17
|
-
--user-msg-bg: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
* {
|
|
21
|
-
margin: 0;
|
|
22
|
-
padding: 0;
|
|
23
|
-
box-sizing: border-box;
|
|
24
|
-
font-family: 'Inter', sans-serif;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
body {
|
|
28
|
-
background-color: var(--bg-color);
|
|
29
|
-
background-image:
|
|
30
|
-
radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.15) 0px, transparent 50%),
|
|
31
|
-
radial-gradient(at 100% 100%, rgba(168, 85, 247, 0.15) 0px, transparent 50%);
|
|
32
|
-
height: 100vh;
|
|
33
|
-
display: flex;
|
|
34
|
-
flex-direction: column;
|
|
35
|
-
color: var(--text-main);
|
|
36
|
-
overflow: hidden;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
header {
|
|
40
|
-
padding: 1.5rem 2rem;
|
|
41
|
-
background: var(--glass-bg);
|
|
42
|
-
backdrop-filter: blur(12px);
|
|
43
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
44
|
-
display: flex;
|
|
45
|
-
justify-content: space-between;
|
|
46
|
-
align-items: center;
|
|
47
|
-
z-index: 10;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.logo {
|
|
51
|
-
font-family: 'Outfit', sans-serif;
|
|
52
|
-
font-weight: 600;
|
|
53
|
-
font-size: 1.5rem;
|
|
54
|
-
background: linear-gradient(to right, #818cf8, #c084fc);
|
|
55
|
-
-webkit-background-clip: text;
|
|
56
|
-
-webkit-text-fill-color: transparent;
|
|
57
|
-
display: flex;
|
|
58
|
-
align-items: center;
|
|
59
|
-
gap: 0.5rem;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.status-badge {
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: 0.5rem;
|
|
66
|
-
font-size: 0.85rem;
|
|
67
|
-
padding: 0.4rem 0.8rem;
|
|
68
|
-
background: rgba(34, 197, 94, 0.1);
|
|
69
|
-
color: #4ade80;
|
|
70
|
-
border-radius: 20px;
|
|
71
|
-
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.status-dot {
|
|
75
|
-
width: 8px;
|
|
76
|
-
height: 8px;
|
|
77
|
-
background-color: #4ade80;
|
|
78
|
-
border-radius: 50%;
|
|
79
|
-
box-shadow: 0 0 8px #4ade80;
|
|
80
|
-
animation: pulse 2s infinite;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
@keyframes pulse {
|
|
84
|
-
0% { opacity: 1; }
|
|
85
|
-
50% { opacity: 0.4; }
|
|
86
|
-
100% { opacity: 1; }
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
#chat-container {
|
|
90
|
-
flex: 1;
|
|
91
|
-
overflow-y: auto;
|
|
92
|
-
padding: 2rem;
|
|
93
|
-
display: flex;
|
|
94
|
-
flex-direction: column;
|
|
95
|
-
gap: 1.5rem;
|
|
96
|
-
scroll-behavior: smooth;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.message {
|
|
100
|
-
max-width: 80%;
|
|
101
|
-
padding: 1rem 1.25rem;
|
|
102
|
-
border-radius: 1.25rem;
|
|
103
|
-
line-height: 1.6;
|
|
104
|
-
font-size: 0.95rem;
|
|
105
|
-
position: relative;
|
|
106
|
-
animation: slideUp 0.3s ease-out;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
@keyframes slideUp {
|
|
110
|
-
from { opacity: 0; transform: translateY(10px); }
|
|
111
|
-
to { opacity: 1; transform: translateY(0); }
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.user-message {
|
|
115
|
-
align-self: flex-end;
|
|
116
|
-
background: var(--user-msg-bg);
|
|
117
|
-
color: white;
|
|
118
|
-
border-bottom-right-radius: 0.25rem;
|
|
119
|
-
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.bot-message {
|
|
123
|
-
align-self: flex-start;
|
|
124
|
-
background: var(--bot-msg-bg);
|
|
125
|
-
backdrop-filter: blur(5px);
|
|
126
|
-
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
127
|
-
border-bottom-left-radius: 0.25rem;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.input-area {
|
|
131
|
-
padding: 2rem;
|
|
132
|
-
background: linear-gradient(to top, var(--bg-color), transparent);
|
|
133
|
-
position: relative;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.input-wrapper {
|
|
137
|
-
max-width: 900px;
|
|
138
|
-
margin: 0 auto;
|
|
139
|
-
background: var(--glass-bg);
|
|
140
|
-
backdrop-filter: blur(20px);
|
|
141
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
142
|
-
border-radius: 1.5rem;
|
|
143
|
-
padding: 0.75rem;
|
|
144
|
-
display: flex;
|
|
145
|
-
gap: 0.75rem;
|
|
146
|
-
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
147
|
-
transition: transform 0.2s, border-color 0.2s;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
textarea {
|
|
151
|
-
flex: 1;
|
|
152
|
-
background: transparent;
|
|
153
|
-
border: none;
|
|
154
|
-
color: white;
|
|
155
|
-
padding: 0.75rem;
|
|
156
|
-
resize: none;
|
|
157
|
-
outline: none;
|
|
158
|
-
font-size: 1rem;
|
|
159
|
-
max-height: 150px;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
#send-btn {
|
|
163
|
-
background: var(--primary-accent);
|
|
164
|
-
color: white;
|
|
165
|
-
border: none;
|
|
166
|
-
width: 45px;
|
|
167
|
-
height: 45px;
|
|
168
|
-
border-radius: 12px;
|
|
169
|
-
cursor: pointer;
|
|
170
|
-
display: flex;
|
|
171
|
-
align-items: center;
|
|
172
|
-
justify-content: center;
|
|
173
|
-
transition: all 0.2s;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
#send-btn:hover { background: var(--secondary-accent); transform: scale(1.05); }
|
|
177
|
-
|
|
178
|
-
.typing { display: flex; gap: 4px; padding: 0.5rem; }
|
|
179
|
-
.dot { width: 6px; height: 6px; background: var(--text-dim); border-radius: 50%; animation: bounce 1.4s infinite ease-in-out; }
|
|
180
|
-
.dot:nth-child(2) { animation-delay: 0.2s; }
|
|
181
|
-
.dot:nth-child(3) { animation-delay: 0.4s; }
|
|
182
|
-
|
|
183
|
-
@keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }
|
|
184
|
-
</style>
|
|
185
|
-
</head>
|
|
186
|
-
<body>
|
|
187
|
-
<header>
|
|
188
|
-
<div class="logo">
|
|
189
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a10 10 0 1 0 10 10H12V2z"></path><path d="M12 12L2.1 12.1"></path><path d="M12 12v10"></path><path d="M12 12l8.1-5.4"></path></svg>
|
|
190
|
-
Lattice AI | Premium
|
|
191
|
-
</div>
|
|
192
|
-
<div class="status-badge"><div class="status-dot"></div>Local Hub Active</div>
|
|
193
|
-
</header>
|
|
194
|
-
|
|
195
|
-
<div id="chat-container">
|
|
196
|
-
<div class="message bot-message">
|
|
197
|
-
✨ 안녕하세요! 제가 정성껏 만든 프리미엄 로컬 허브입니다. <br>
|
|
198
|
-
브라우저에서 직접 Gemma 4와 대화해 보세요!
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
|
|
202
|
-
<div class="input-area">
|
|
203
|
-
<div class="input-wrapper">
|
|
204
|
-
<textarea id="user-input" placeholder="메시지를 입력하세요..." rows="1"></textarea>
|
|
205
|
-
<button id="send-btn">
|
|
206
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
|
|
207
|
-
</button>
|
|
208
|
-
</div>
|
|
209
|
-
</div>
|
|
210
|
-
|
|
211
|
-
<script>
|
|
212
|
-
const chatContainer = document.getElementById('chat-container');
|
|
213
|
-
const userInput = document.getElementById('user-input');
|
|
214
|
-
const sendBtn = document.getElementById('send-btn');
|
|
215
|
-
|
|
216
|
-
userInput.addEventListener('input', () => {
|
|
217
|
-
userInput.style.height = 'auto';
|
|
218
|
-
userInput.style.height = userInput.scrollHeight + 'px';
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
async function sendMessage() {
|
|
222
|
-
const message = userInput.value.trim();
|
|
223
|
-
if (!message) return;
|
|
224
|
-
addMessage(message, 'user');
|
|
225
|
-
userInput.value = '';
|
|
226
|
-
userInput.style.height = 'auto';
|
|
227
|
-
const loadingMsg = addLoadingMessage();
|
|
228
|
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
229
|
-
try {
|
|
230
|
-
const response = await fetch('/chat', {
|
|
231
|
-
method: 'POST',
|
|
232
|
-
headers: { 'Content-Type': 'application/json' },
|
|
233
|
-
body: JSON.stringify({ message: message, stream: false })
|
|
234
|
-
});
|
|
235
|
-
const data = await response.json();
|
|
236
|
-
loadingMsg.remove();
|
|
237
|
-
addMessage(data.response, 'bot');
|
|
238
|
-
} catch (error) {
|
|
239
|
-
loadingMsg.remove();
|
|
240
|
-
addMessage('❌ 서버 연결 실패', 'bot');
|
|
241
|
-
}
|
|
242
|
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function addMessage(text, side) {
|
|
246
|
-
const div = document.createElement('div');
|
|
247
|
-
div.className = `message ${side}-message`;
|
|
248
|
-
div.innerHTML = text.replace(/\n/g, '<br>');
|
|
249
|
-
chatContainer.appendChild(div);
|
|
250
|
-
return div;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function addLoadingMessage() {
|
|
254
|
-
const div = document.createElement('div');
|
|
255
|
-
div.className = 'message bot-message typing';
|
|
256
|
-
div.innerHTML = '<div class="dot"></div><div class="dot"></div><div class="dot"></div>';
|
|
257
|
-
chatContainer.appendChild(div);
|
|
258
|
-
return div;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
sendBtn.addEventListener('click', sendMessage);
|
|
262
|
-
userInput.addEventListener('keydown', (e) => {
|
|
263
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
264
|
-
e.preventDefault();
|
|
265
|
-
sendMessage();
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
</script>
|
|
269
|
-
</body>
|
|
270
|
-
</html>
|