mindroot 7.7.0__py3-none-any.whl → 8.2.0__py3-none-any.whl
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.
Potentially problematic release.
This version of mindroot might be problematic. Click here for more details.
- mindroot/coreplugins/admin/plugin_manager.py +126 -3
- mindroot/coreplugins/admin/plugin_manager_backup.py +615 -0
- mindroot/coreplugins/admin/router.py +3 -1
- mindroot/coreplugins/admin/server_router.py +8 -1
- mindroot/coreplugins/admin/static/js/plugin-advanced-install.js +83 -12
- mindroot/coreplugins/admin/static/js/plugin-index-browser.js +138 -10
- mindroot/coreplugins/admin/static/js/plugin-install-dialog.js +345 -0
- mindroot/coreplugins/admin/static/js/server-control.js +68 -6
- mindroot/coreplugins/agent/agent.py +4 -0
- mindroot/coreplugins/chat/models.py +0 -1
- mindroot/coreplugins/chat/router.py +31 -0
- mindroot/coreplugins/chat/services.py +24 -0
- mindroot/coreplugins/chat/static/css/dark.css +35 -0
- mindroot/coreplugins/chat/static/css/default.css +35 -0
- mindroot/coreplugins/chat/static/js/chatform.js +185 -0
- mindroot/coreplugins/env_manager/__init__.py +3 -0
- mindroot/coreplugins/env_manager/inject/admin.jinja2 +16 -0
- mindroot/coreplugins/env_manager/mod.py +228 -0
- mindroot/coreplugins/env_manager/router.py +40 -0
- mindroot/coreplugins/env_manager/static/css/env-manager.css +263 -0
- mindroot/coreplugins/env_manager/static/js/env-manager.js +380 -0
- mindroot/coreplugins/home/router.py +33 -2
- mindroot/coreplugins/home/static/css/enhanced.css +111 -5
- mindroot/coreplugins/home/templates/home.jinja2 +7 -4
- mindroot/lib/chatlog.py +5 -1
- mindroot/lib/streamcmd.py +139 -0
- mindroot/lib/templates.py +13 -2
- mindroot/server.py +12 -25
- mindroot-8.2.0.dist-info/METADATA +15 -0
- {mindroot-7.7.0.dist-info → mindroot-8.2.0.dist-info}/RECORD +34 -25
- {mindroot-7.7.0.dist-info → mindroot-8.2.0.dist-info}/WHEEL +1 -1
- mindroot-7.7.0.dist-info/METADATA +0 -310
- {mindroot-7.7.0.dist-info → mindroot-8.2.0.dist-info}/entry_points.txt +0 -0
- {mindroot-7.7.0.dist-info → mindroot-8.2.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-7.7.0.dist-info → mindroot-8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,8 @@ class ServerControl extends BaseEl {
|
|
|
5
5
|
static properties = {
|
|
6
6
|
status: { type: String },
|
|
7
7
|
loading: { type: Boolean },
|
|
8
|
-
method: { type: String }
|
|
8
|
+
method: { type: String },
|
|
9
|
+
monitorActive: { type: Boolean }
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
static styles = css`
|
|
@@ -96,6 +97,11 @@ class ServerControl extends BaseEl {
|
|
|
96
97
|
margin-left: 0.5rem;
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
.monitor-status {
|
|
101
|
+
font-style: italic;
|
|
102
|
+
margin-left: 0.5rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
@keyframes spin {
|
|
100
106
|
100% { transform: rotate(360deg); }
|
|
101
107
|
}
|
|
@@ -110,6 +116,56 @@ class ServerControl extends BaseEl {
|
|
|
110
116
|
this.status = '';
|
|
111
117
|
this.loading = false;
|
|
112
118
|
this.method = '';
|
|
119
|
+
this.monitorActive = false;
|
|
120
|
+
this.monitorIntervalId = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Function to check if server is online
|
|
124
|
+
async checkServerStatus() {
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetch('/admin/server/ping', {
|
|
127
|
+
method: 'GET',
|
|
128
|
+
cache: 'no-store',
|
|
129
|
+
headers: { 'Cache-Control': 'no-cache' }
|
|
130
|
+
});
|
|
131
|
+
return response.ok;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Start monitoring server status
|
|
138
|
+
startServerMonitor() {
|
|
139
|
+
if (this.monitorActive) return; // Don't start if already monitoring
|
|
140
|
+
|
|
141
|
+
this.monitorActive = true;
|
|
142
|
+
let checkCount = 0;
|
|
143
|
+
const maxChecks = 120; // Maximum 2 minutes of checking
|
|
144
|
+
|
|
145
|
+
this.monitorIntervalId = setInterval(async () => {
|
|
146
|
+
checkCount++;
|
|
147
|
+
this.status = `Checking server status${'.'.repeat(checkCount % 4)} (${checkCount}s)`;
|
|
148
|
+
|
|
149
|
+
const isOnline = await this.checkServerStatus();
|
|
150
|
+
|
|
151
|
+
if (isOnline) {
|
|
152
|
+
this.stopMonitoring();
|
|
153
|
+
this.status = 'Server is back online! Refreshing page...';
|
|
154
|
+
setTimeout(() => window.location.reload(), 1000);
|
|
155
|
+
} else if (checkCount >= maxChecks) {
|
|
156
|
+
this.stopMonitoring();
|
|
157
|
+
this.status = 'Server did not come back online after 2 minutes. Please refresh manually.';
|
|
158
|
+
}
|
|
159
|
+
}, 1000);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Stop monitoring
|
|
163
|
+
stopMonitoring() {
|
|
164
|
+
if (this.monitorIntervalId) {
|
|
165
|
+
clearInterval(this.monitorIntervalId);
|
|
166
|
+
this.monitorIntervalId = null;
|
|
167
|
+
}
|
|
168
|
+
this.monitorActive = false;
|
|
113
169
|
}
|
|
114
170
|
|
|
115
171
|
async handleRestart() {
|
|
@@ -128,12 +184,9 @@ class ServerControl extends BaseEl {
|
|
|
128
184
|
this.status = result.message;
|
|
129
185
|
this.method = result.method;
|
|
130
186
|
|
|
131
|
-
//
|
|
187
|
+
// Start monitoring server status
|
|
132
188
|
if (result.method === 'pm2' || result.method === 'mindroot') {
|
|
133
|
-
|
|
134
|
-
this.status = 'Attempting to reconnect...';
|
|
135
|
-
window.location.reload();
|
|
136
|
-
}, 5000);
|
|
189
|
+
this.startServerMonitor();
|
|
137
190
|
}
|
|
138
191
|
} else {
|
|
139
192
|
throw new Error(`${result.message}${result.method ? ` (${result.method})` : ''}`);
|
|
@@ -146,6 +199,12 @@ class ServerControl extends BaseEl {
|
|
|
146
199
|
}
|
|
147
200
|
}
|
|
148
201
|
|
|
202
|
+
disconnectedCallback() {
|
|
203
|
+
super.disconnectedCallback();
|
|
204
|
+
// Clean up interval when component is removed
|
|
205
|
+
this.stopMonitoring();
|
|
206
|
+
}
|
|
207
|
+
|
|
149
208
|
async handleStop() {
|
|
150
209
|
if (!confirm('Are you sure you want to stop the server? You will need to restart it manually unless using PM2.')) return;
|
|
151
210
|
|
|
@@ -200,7 +259,10 @@ class ServerControl extends BaseEl {
|
|
|
200
259
|
<span class="material-icons">${this.method === 'pm2' ? 'cloud_queue' : 'terminal'}</span>
|
|
201
260
|
${this.method}
|
|
202
261
|
</span>
|
|
262
|
+
${this.monitorActive ? html`
|
|
263
|
+
<span class="monitor-status">Monitoring server status...</span>
|
|
203
264
|
` : ''}
|
|
265
|
+
` : ''}
|
|
204
266
|
</div>
|
|
205
267
|
` : ''}
|
|
206
268
|
</div>
|
|
@@ -275,6 +275,10 @@ class Agent:
|
|
|
275
275
|
if buffer[0] == '{':
|
|
276
276
|
buffer = "[" + buffer
|
|
277
277
|
|
|
278
|
+
# happened with Qwen 3 for some reason
|
|
279
|
+
buffer = buffer.replace('}] <>\n\n[{','}, {')
|
|
280
|
+
buffer = buffer.replace('}] <>\n[{','}, {')
|
|
281
|
+
|
|
278
282
|
commands, partial_cmd = parse_streaming_commands(buffer)
|
|
279
283
|
|
|
280
284
|
if isinstance(commands, int):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from fastapi import APIRouter, HTTPException, Request, Response
|
|
2
2
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
3
|
+
from fastapi import File, UploadFile, Form
|
|
3
4
|
from sse_starlette.sse import EventSourceResponse
|
|
4
5
|
from .models import MessageParts
|
|
5
6
|
from lib.providers.services import service, service_manager
|
|
@@ -15,6 +16,8 @@ from lib.providers.services import service, service_manager
|
|
|
15
16
|
from lib.providers.commands import command_manager
|
|
16
17
|
from lib.utils.debug import debug_box
|
|
17
18
|
from lib.session_files import load_session_data, save_session_data
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
18
21
|
from pydantic import BaseModel
|
|
19
22
|
|
|
20
23
|
router = APIRouter()
|
|
@@ -189,3 +192,31 @@ async def run_task_route(request: Request, agent_name: str, task_request: TaskRe
|
|
|
189
192
|
return {"status": "ok", "results": task_result, "full_results": full_results, "log_id": log_id}
|
|
190
193
|
|
|
191
194
|
|
|
195
|
+
@router.post("/chat/{log_id}/upload")
|
|
196
|
+
async def upload_file(request: Request, log_id: str, file: UploadFile = File(...)):
|
|
197
|
+
"""
|
|
198
|
+
Upload a file and store it in a user-specific directory.
|
|
199
|
+
Returns the file path that can be used in messages.
|
|
200
|
+
"""
|
|
201
|
+
user = request.state.user.username
|
|
202
|
+
|
|
203
|
+
# Create user uploads directory if it doesn't exist
|
|
204
|
+
user_upload_dir = f"data/users/{user}/uploads/{log_id}"
|
|
205
|
+
os.makedirs(user_upload_dir, exist_ok=True)
|
|
206
|
+
|
|
207
|
+
# Generate a safe filename to prevent path traversal
|
|
208
|
+
filename = os.path.basename(file.filename)
|
|
209
|
+
file_path = os.path.join(user_upload_dir, filename)
|
|
210
|
+
|
|
211
|
+
# Save the file
|
|
212
|
+
with open(file_path, "wb") as buffer:
|
|
213
|
+
shutil.copyfileobj(file.file, buffer)
|
|
214
|
+
|
|
215
|
+
# Return the file information
|
|
216
|
+
return {
|
|
217
|
+
"status": "ok",
|
|
218
|
+
"filename": filename,
|
|
219
|
+
"path": file_path,
|
|
220
|
+
"mime_type": file.content_type
|
|
221
|
+
}
|
|
222
|
+
|
|
@@ -235,6 +235,10 @@ async def send_message_to_agent(session_id: str, message: str | List[MessagePart
|
|
|
235
235
|
img = dataurl_to_pil(part['data'])
|
|
236
236
|
img_msg = await context.format_image_message(img)
|
|
237
237
|
new_parts.append(img_msg)
|
|
238
|
+
elif part['type'] == 'text' and '[UPLOADED FILE]' in part['text']:
|
|
239
|
+
# Ensure we don't duplicate file entries
|
|
240
|
+
if not any('[UPLOADED FILE]' in p.get('text', '') for p in new_parts):
|
|
241
|
+
new_parts.append(part)
|
|
238
242
|
else:
|
|
239
243
|
new_parts.append(part)
|
|
240
244
|
msg_to_add= {"role": "user", "content": new_parts }
|
|
@@ -248,6 +252,10 @@ async def send_message_to_agent(session_id: str, message: str | List[MessagePart
|
|
|
248
252
|
results = []
|
|
249
253
|
full_results = []
|
|
250
254
|
|
|
255
|
+
invalid = "ERROR, invalid response format."
|
|
256
|
+
|
|
257
|
+
consecutive_parse_errors = 0
|
|
258
|
+
|
|
251
259
|
while continue_processing and iterations < max_iterations:
|
|
252
260
|
iterations += 1
|
|
253
261
|
continue_processing = False
|
|
@@ -256,7 +264,23 @@ async def send_message_to_agent(session_id: str, message: str | List[MessagePart
|
|
|
256
264
|
if 'llm' in context.data:
|
|
257
265
|
context.current_model = context.data['llm']
|
|
258
266
|
|
|
267
|
+
parse_error = False
|
|
268
|
+
|
|
259
269
|
results, full_cmds = await agent_.chat_commands(context.current_model, context, messages=context.chat_log.get_recent())
|
|
270
|
+
for result in results:
|
|
271
|
+
if result['cmd'] == 'UNKNOWN':
|
|
272
|
+
consecutive_parse_errors += 1
|
|
273
|
+
parse_error = True
|
|
274
|
+
|
|
275
|
+
if not parse_error:
|
|
276
|
+
consecutive_parse_errors = 0
|
|
277
|
+
|
|
278
|
+
if consecutive_parse_errors > 6:
|
|
279
|
+
raise Exception("Too many consecutive parse errors, stopping processing.")
|
|
280
|
+
|
|
281
|
+
elif consecutive_parse_errors > 3:
|
|
282
|
+
results.append({"cmd": "UNKNOWN", "args": { "SYSTEM WARNING: Issue valid command list or task processing will be halted. Simplify output."}})
|
|
283
|
+
|
|
260
284
|
try:
|
|
261
285
|
tmp_data3 = { "results": full_cmds }
|
|
262
286
|
tmp_data3 = await pipeline_manager.process_results(tmp_data3, context=context)
|
|
@@ -961,6 +961,41 @@ tbody tr {
|
|
|
961
961
|
width: 32px;
|
|
962
962
|
}
|
|
963
963
|
|
|
964
|
+
/* File attachment styles */
|
|
965
|
+
.file-attachment {
|
|
966
|
+
display: inline-flex;
|
|
967
|
+
align-items: center;
|
|
968
|
+
background: rgba(30, 30, 50, 0.6);
|
|
969
|
+
border-radius: 8px;
|
|
970
|
+
padding: 8px 12px;
|
|
971
|
+
margin: 5px 0;
|
|
972
|
+
max-width: 100%;
|
|
973
|
+
overflow: hidden;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.file-attachment a {
|
|
977
|
+
display: flex;
|
|
978
|
+
align-items: center;
|
|
979
|
+
color: #f0f0f0;
|
|
980
|
+
text-decoration: none;
|
|
981
|
+
overflow: hidden;
|
|
982
|
+
text-overflow: ellipsis;
|
|
983
|
+
white-space: nowrap;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.file-attachment svg {
|
|
987
|
+
margin-right: 8px;
|
|
988
|
+
flex-shrink: 0;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.file-attachment:hover {
|
|
992
|
+
background: rgba(40, 40, 70, 0.8);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
.file-attachment a:hover {
|
|
996
|
+
text-decoration: underline;
|
|
997
|
+
}
|
|
998
|
+
|
|
964
999
|
.left-side {
|
|
965
1000
|
overflow-y: auto;
|
|
966
1001
|
}
|
|
@@ -961,6 +961,41 @@ tbody tr {
|
|
|
961
961
|
width: 32px;
|
|
962
962
|
}
|
|
963
963
|
|
|
964
|
+
/* File attachment styles */
|
|
965
|
+
.file-attachment {
|
|
966
|
+
display: inline-flex;
|
|
967
|
+
align-items: center;
|
|
968
|
+
background: rgba(30, 30, 50, 0.6);
|
|
969
|
+
border-radius: 8px;
|
|
970
|
+
padding: 8px 12px;
|
|
971
|
+
margin: 5px 0;
|
|
972
|
+
max-width: 100%;
|
|
973
|
+
overflow: hidden;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.file-attachment a {
|
|
977
|
+
display: flex;
|
|
978
|
+
align-items: center;
|
|
979
|
+
color: #f0f0f0;
|
|
980
|
+
text-decoration: none;
|
|
981
|
+
overflow: hidden;
|
|
982
|
+
text-overflow: ellipsis;
|
|
983
|
+
white-space: nowrap;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.file-attachment svg {
|
|
987
|
+
margin-right: 8px;
|
|
988
|
+
flex-shrink: 0;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.file-attachment:hover {
|
|
992
|
+
background: rgba(40, 40, 70, 0.8);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
.file-attachment a:hover {
|
|
996
|
+
text-decoration: underline;
|
|
997
|
+
}
|
|
998
|
+
|
|
964
999
|
.left-side {
|
|
965
1000
|
overflow-y: auto;
|
|
966
1001
|
}
|
|
@@ -10,6 +10,7 @@ class ChatForm extends BaseEl {
|
|
|
10
10
|
static properties = {
|
|
11
11
|
sender: { type: String },
|
|
12
12
|
message: { type: String },
|
|
13
|
+
uploadedFiles: { type: Array },
|
|
13
14
|
taskid: { type: String },
|
|
14
15
|
autoSizeInput: { type: Boolean, attribute: 'auto-size-input', reflect: true }
|
|
15
16
|
}
|
|
@@ -109,6 +110,49 @@ class ChatForm extends BaseEl {
|
|
|
109
110
|
font-size: 18px;
|
|
110
111
|
padding: 0;
|
|
111
112
|
}
|
|
113
|
+
.file-preview-container {
|
|
114
|
+
display: none;
|
|
115
|
+
margin-bottom: 10px;
|
|
116
|
+
}
|
|
117
|
+
.file-preview-container.has-files {
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-wrap: wrap;
|
|
120
|
+
gap: 8px;
|
|
121
|
+
}
|
|
122
|
+
.file-item {
|
|
123
|
+
position: relative;
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
background: rgba(30, 30, 50, 0.6);
|
|
127
|
+
border-radius: 4px;
|
|
128
|
+
padding: 8px 12px;
|
|
129
|
+
margin-bottom: 5px;
|
|
130
|
+
}
|
|
131
|
+
.file-icon {
|
|
132
|
+
margin-right: 8px;
|
|
133
|
+
}
|
|
134
|
+
.file-name {
|
|
135
|
+
flex-grow: 1;
|
|
136
|
+
white-space: nowrap;
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
text-overflow: ellipsis;
|
|
139
|
+
max-width: 200px;
|
|
140
|
+
}
|
|
141
|
+
.remove-file {
|
|
142
|
+
position: relative;
|
|
143
|
+
width: 24px;
|
|
144
|
+
height: 24px;
|
|
145
|
+
background: rgba(0, 0, 0, 0.3);
|
|
146
|
+
color: white;
|
|
147
|
+
border: none;
|
|
148
|
+
border-radius: 50%;
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
font-size: 18px;
|
|
154
|
+
padding: 0;
|
|
155
|
+
}
|
|
112
156
|
.upload-container {
|
|
113
157
|
display: flex;
|
|
114
158
|
align-items: center;
|
|
@@ -123,12 +167,23 @@ class ChatForm extends BaseEl {
|
|
|
123
167
|
cursor: pointer;
|
|
124
168
|
color: inherit;
|
|
125
169
|
}
|
|
170
|
+
.file-upload-button {
|
|
171
|
+
background: none;
|
|
172
|
+
border: none;
|
|
173
|
+
padding: 0;
|
|
174
|
+
margin-left: 8px;
|
|
175
|
+
cursor: pointer;
|
|
176
|
+
color: inherit;
|
|
177
|
+
}
|
|
126
178
|
.upload-button:hover {
|
|
127
179
|
opacity: 0.8;
|
|
128
180
|
}
|
|
129
181
|
#imageUpload {
|
|
130
182
|
display: none;
|
|
131
183
|
}
|
|
184
|
+
#fileUpload {
|
|
185
|
+
display: none;
|
|
186
|
+
}
|
|
132
187
|
.loading {
|
|
133
188
|
opacity: 0.5;
|
|
134
189
|
pointer-events: none;
|
|
@@ -159,6 +214,7 @@ class ChatForm extends BaseEl {
|
|
|
159
214
|
this.message = ''
|
|
160
215
|
this.autoSizeInput = true
|
|
161
216
|
this.taskid = null
|
|
217
|
+
this.uploadedFiles = []
|
|
162
218
|
this.selectedImages = []
|
|
163
219
|
this.isLoading = false
|
|
164
220
|
}
|
|
@@ -181,6 +237,52 @@ class ChatForm extends BaseEl {
|
|
|
181
237
|
}
|
|
182
238
|
await this._processImage(file)
|
|
183
239
|
}
|
|
240
|
+
console.log('File upload complete, file preview container:', this.shadowRoot.querySelector('.file-preview-container'))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async _handleFileUpload(e) {
|
|
244
|
+
const files = e.target.files
|
|
245
|
+
console.log('File upload triggered with files:', files)
|
|
246
|
+
if (!files || files.length === 0) return
|
|
247
|
+
|
|
248
|
+
this.isLoading = true
|
|
249
|
+
this.requestUpdate()
|
|
250
|
+
|
|
251
|
+
for (let file of files) {
|
|
252
|
+
try {
|
|
253
|
+
console.log('Processing file:', file.name, file.type, file.size)
|
|
254
|
+
const formData = new FormData()
|
|
255
|
+
formData.append('file', file)
|
|
256
|
+
|
|
257
|
+
const response = await authenticatedFetch(`/chat/${window.log_id}/upload`, {
|
|
258
|
+
method: 'POST',
|
|
259
|
+
body: formData
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
const result = await response.json()
|
|
263
|
+
console.log('Upload result:', result)
|
|
264
|
+
if (result.status === 'ok') {
|
|
265
|
+
if (this.uploadedFiles.some(f => f.filename === result.filename)) {
|
|
266
|
+
console.log('File already uploaded:', result.filename)
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
this.uploadedFiles.push({
|
|
270
|
+
filename: result.filename,
|
|
271
|
+
path: result.path,
|
|
272
|
+
mime_type: result.mime_type
|
|
273
|
+
})
|
|
274
|
+
console.log('Updated uploadedFiles:', this.uploadedFiles)
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error('Error uploading file:', error)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this._updateFilePreviews()
|
|
282
|
+
this.isLoading = false
|
|
283
|
+
this.requestUpdate()
|
|
284
|
+
e.target.value = '' // Reset file input
|
|
285
|
+
console.log('File upload complete, file preview container:', this.shadowRoot.querySelector('.file-preview-container'))
|
|
184
286
|
}
|
|
185
287
|
|
|
186
288
|
_resizeTextarea() {
|
|
@@ -249,6 +351,47 @@ class ChatForm extends BaseEl {
|
|
|
249
351
|
}
|
|
250
352
|
}
|
|
251
353
|
|
|
354
|
+
_updateFilePreviews() {
|
|
355
|
+
console.log('Updating file previews with files:', this.uploadedFiles)
|
|
356
|
+
const container = this.shadowRoot ? this.shadowRoot.querySelector('.file-preview-container') : null
|
|
357
|
+
if (!container) {
|
|
358
|
+
console.error('File preview container not found!')
|
|
359
|
+
return
|
|
360
|
+
} else {
|
|
361
|
+
container.style.display = 'block' // Make sure the container is visible
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
container.innerHTML = ''
|
|
365
|
+
|
|
366
|
+
if (this.uploadedFiles.length > 0) {
|
|
367
|
+
container.classList.add('has-files')
|
|
368
|
+
this.uploadedFiles.forEach((file, index) => {
|
|
369
|
+
console.log('Creating file item for:', file.filename)
|
|
370
|
+
const fileItem = document.createElement('div')
|
|
371
|
+
fileItem.className = 'file-item'
|
|
372
|
+
fileItem.innerHTML = `
|
|
373
|
+
<div class="file-icon">
|
|
374
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
375
|
+
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
|
|
376
|
+
</svg>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="file-name">${file.filename}</div>
|
|
379
|
+
<button class="remove-file" data-index="${index}">×</button>
|
|
380
|
+
`
|
|
381
|
+
fileItem.querySelector('.remove-file').addEventListener('click', () => this._removeFile(index))
|
|
382
|
+
container.appendChild(fileItem)
|
|
383
|
+
})
|
|
384
|
+
} else {
|
|
385
|
+
container.style.display = 'none' // Hide the container when empty
|
|
386
|
+
container.classList.remove('has-files')
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
_removeFile(index) {
|
|
391
|
+
this.uploadedFiles.splice(index, 1)
|
|
392
|
+
this._updateFilePreviews()
|
|
393
|
+
}
|
|
394
|
+
|
|
252
395
|
_removeImage(index) {
|
|
253
396
|
this.selectedImages.splice(index, 1)
|
|
254
397
|
this._updateImagePreviews()
|
|
@@ -264,6 +407,24 @@ class ChatForm extends BaseEl {
|
|
|
264
407
|
|
|
265
408
|
firstUpdated() {
|
|
266
409
|
this.messageEl = this.shadowRoot.getElementById('inp_message');
|
|
410
|
+
this.fileUploadEl = this.shadowRoot.getElementById('fileUpload');
|
|
411
|
+
|
|
412
|
+
// Initialize file preview container
|
|
413
|
+
const filePreviewContainer = this.shadowRoot.querySelector('.file-preview-container');
|
|
414
|
+
if (!filePreviewContainer) {
|
|
415
|
+
console.error('File preview container not found in firstUpdated!');
|
|
416
|
+
} else {
|
|
417
|
+
console.log('File preview container initialized:', filePreviewContainer);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Check if file upload input is properly initialized
|
|
421
|
+
if (!this.fileUploadEl) {
|
|
422
|
+
console.error('File upload input not found!');
|
|
423
|
+
} else {
|
|
424
|
+
console.log('File upload input initialized:', this.fileUploadEl);
|
|
425
|
+
this.fileUploadEl.addEventListener('change', this._handleFileUpload.bind(this));
|
|
426
|
+
}
|
|
427
|
+
|
|
267
428
|
this.sendButton = this.shadowRoot.querySelector('.send_msg');
|
|
268
429
|
|
|
269
430
|
const observer = new MutationObserver(() => {
|
|
@@ -308,6 +469,18 @@ class ChatForm extends BaseEl {
|
|
|
308
469
|
})
|
|
309
470
|
}
|
|
310
471
|
|
|
472
|
+
// Only add one file entry to avoid duplication
|
|
473
|
+
if (this.uploadedFiles.length > 0) {
|
|
474
|
+
const file = this.uploadedFiles[0];
|
|
475
|
+
console.log('Adding file to message:', file);
|
|
476
|
+
|
|
477
|
+
// Create a single file entry for all uploaded files
|
|
478
|
+
messageContent.push({
|
|
479
|
+
type: 'text',
|
|
480
|
+
text: `[UPLOADED FILE] Path: ${file.path}\nFilename: ${file.filename}\nType: ${file.mime_type}`
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
311
484
|
for (let imageData of this.selectedImages) {
|
|
312
485
|
messageContent.push({
|
|
313
486
|
type: 'image',
|
|
@@ -327,7 +500,9 @@ class ChatForm extends BaseEl {
|
|
|
327
500
|
this.dispatch('addmessage', ev_)
|
|
328
501
|
this.messageEl.value = ''
|
|
329
502
|
this.selectedImages = []
|
|
503
|
+
this.uploadedFiles = []
|
|
330
504
|
this._updateImagePreviews()
|
|
505
|
+
this._updateFilePreviews()
|
|
331
506
|
this._resizeTextarea()
|
|
332
507
|
this.requestUpdate()
|
|
333
508
|
}
|
|
@@ -338,6 +513,7 @@ class ChatForm extends BaseEl {
|
|
|
338
513
|
|
|
339
514
|
<div class="chat-entry flex py-2 ${this.isLoading ? 'loading' : ''}">
|
|
340
515
|
<div class="message-container">
|
|
516
|
+
<div class="file-preview-container"></div>
|
|
341
517
|
<div class="image-preview-container"></div>
|
|
342
518
|
<div class="upload-container">
|
|
343
519
|
<label class="upload-button" title="Upload image">
|
|
@@ -348,6 +524,15 @@ class ChatForm extends BaseEl {
|
|
|
348
524
|
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5-7l-3 3.72L9 13l-3 4h12l-4-5z"/>
|
|
349
525
|
</svg>
|
|
350
526
|
</label>
|
|
527
|
+
<label class="file-upload-button" title="Upload file">
|
|
528
|
+
<input type="file" id="fileUpload"
|
|
529
|
+
@change=${this._handleFileUpload}
|
|
530
|
+
multiple>
|
|
531
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
|
|
532
|
+
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
|
|
533
|
+
<path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/>
|
|
534
|
+
</svg>
|
|
535
|
+
</label>
|
|
351
536
|
</div>
|
|
352
537
|
<textarea id="inp_message" class="message-input"
|
|
353
538
|
@keydown=${(e) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{% block head %}
|
|
2
|
+
<link rel="stylesheet" href="/env_manager/static/css/env-manager.css">
|
|
3
|
+
<script type="module" src="/env_manager/static/js/env-manager.js"></script>
|
|
4
|
+
{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<details>
|
|
8
|
+
<summary>
|
|
9
|
+
<span class="material-icons">settings_applications</span>
|
|
10
|
+
<span>Environment Variables</span>
|
|
11
|
+
</summary>
|
|
12
|
+
<div class="details-content">
|
|
13
|
+
<env-manager theme="dark" scope="local"></env-manager>
|
|
14
|
+
</div>
|
|
15
|
+
</details>
|
|
16
|
+
{% endblock %}
|