improved UI for images and non images in chat
deci committed
Jun 23, 2025 at 17:06 UTC
135291b523de3bc5aba6370f54c007cc2ad81a1f
2 files changed
+287
-78
webui/index.css
+265
-49
@@ -777,13 +777,13 @@ pre {
777
flex-shrink: 0;
778
}
779
780
-/* Preview section */
780
+/* Preview section with unified tile system */
781
.preview-section {
782
- display: flex;
783
- flex-wrap: wrap;
784
- gap: 8px;
782
+ display: grid;
783
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
784
+ gap: 12px;
785
margin-bottom: 10px;
786
- padding: var(--spacing-xs);
786
+ padding: var(--spacing-sm);
787
transition: all 0.3s ease;
788
background-color: var(--color-input);
789
border-radius: 8px;
@@ -792,45 +792,115 @@ pre {
792
793
.preview-item {
794
position: relative;
795
- flex-shrink: 0;
795
+ width: 120px;
796
+ height: 120px;
797
+ background-color: var(--color-background);
798
+ border: 2px solid var(--color-border);
799
+ border-radius: 12px;
800
+ display: flex;
801
+ align-items: center;
802
+ justify-content: center;
803
+ overflow: hidden;
804
+ cursor: pointer;
805
+ transition: all 0.3s ease;
806
animation: fadeIn 0.3s ease;
807
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
808
+}
809
+
810
+.preview-item:hover {
811
+ border-color: var(--color-primary);
812
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
813
+ transform: translateY(-2px);
814
}
815
816
.preview-item.image-preview img {
800
- max-height: 100px;
817
+ width: 100%;
818
+ height: 100%;
819
object-fit: cover;
802
- border-radius: 8px;
803
- border: 1px solid var(--color-border-light);
820
+ border-radius: 10px;
821
+}
822
+
823
+.preview-item.file-preview {
824
+ flex-direction: column;
825
+ padding: 12px;
826
+ text-align: center;
827
+}
828
+
829
+.preview-item.file-preview .file-icon {
830
+ width: 48px;
831
+ height: 48px;
832
+ margin-bottom: 8px;
833
+ opacity: 0.8;
834
+}
835
+
836
+.preview-item.file-preview .file-info {
837
+ display: flex;
838
+ flex-direction: column;
839
+ gap: 4px;
840
+ align-items: center;
841
+}
842
+
843
+.preview-item.file-preview .filename {
844
+ font-size: 0.75rem;
845
+ color: var(--color-text);
846
+ word-break: break-word;
847
+ line-height: 1.2;
848
+ max-height: 2.4em;
849
+ overflow: hidden;
850
+ text-overflow: ellipsis;
851
+ display: -webkit-box;
852
+ -webkit-line-clamp: 2;
853
+ -webkit-box-orient: vertical;
854
+}
855
+
856
+.preview-item.file-preview .extension {
857
+ background: var(--color-primary);
858
+ color: var(--color-text);
859
+ padding: 2px 6px;
860
+ border-radius: 4px;
861
+ font-size: 0.7rem;
862
+ text-transform: uppercase;
863
+ white-space: nowrap;
864
}
865
866
.remove-attachment,
867
.remove-image {
868
position: absolute;
809
- top: -6px;
810
- right: -6px;
869
+ top: 4px;
870
+ right: 4px;
871
background-color: var(--color-accent);
872
color: white;
873
border: none;
874
border-radius: 50%;
815
- width: 20px;
816
- height: 20px;
875
+ width: 24px;
876
+ height: 24px;
877
cursor: pointer;
878
display: flex;
879
align-items: center;
880
justify-content: center;
821
- transition: background-color 0.2s ease, transform 0.1s ease-in-out;
822
- z-index: 1;
881
+ transition: all 0.2s ease;
882
+ z-index: 2;
883
+ opacity: 0;
884
+ transform: scale(0.8);
885
+}
886
+
887
+.preview-item:hover .remove-attachment,
888
+.attachment-item:hover .remove-attachment,
889
+.preview-item:hover .remove-image,
890
+.attachment-item:hover .remove-image {
891
+ opacity: 1;
892
+ transform: scale(1);
893
}
894
895
.remove-attachment:hover,
896
.remove-image:hover {
897
background-color: var(--color-accent-dark);
828
- transform: scale(1.1);
898
+ transform: scale(1.1) !important;
899
}
900
901
.remove-attachment:active,
902
.remove-image:active {
833
- transform: scale(0.9);
903
+ transform: scale(0.9) !important;
904
}
905
906
.image-error {
@@ -1066,66 +1136,141 @@ pre {
1136
opacity: 0.5;
1137
}
1138
1069
-/* Message attachments styles */
1139
+/* Message attachments with unified tile system */
1140
.attachments-container {
1071
- margin-top: 0.5em;
1072
- display: flex;
1073
- flex-direction: column;
1074
- gap: 0.5em;
1141
+ margin-top: 0.75em;
1142
+ display: grid;
1143
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
1144
+ gap: 12px;
1145
+ padding: var(--spacing-sm);
1146
+ background-color: var(--color-input);
1147
+ border-radius: 8px;
1148
+ max-width: 600px; /* Limits to ~5 columns at 120px each */
1149
+ overflow: visible;
1150
}
1151
1152
.attachment-item {
1153
+ position: relative;
1154
+ width: 120px;
1155
+ height: 120px;
1156
+ background-color: var(--color-background);
1157
+ border: 2px solid var(--color-border);
1158
+ border-radius: 12px;
1159
display: flex;
1160
align-items: center;
1080
- gap: 1em;
1081
- background: var(--color-background);
1082
- padding: 0.5em;
1083
- border-radius: 4px;
1084
- transition: background-color 0.2s ease;
1161
+ justify-content: center;
1162
+ overflow: hidden;
1163
+ cursor: pointer;
1164
+ transition: all 0.3s ease;
1165
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1166
}
1167
1168
.attachment-item:hover {
1088
- background: var(--color-secondary);
1169
+ border-color: var(--color-primary);
1170
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
1171
+ transform: translateY(-2px);
1172
+}
1173
+
1174
+/* Image attachment styling */
1175
+.attachment-item.image-type {
1176
+ padding: 0;
1177
+}
1178
+
1179
+.attachment-item.image-type .attachment-preview {
1180
+ width: 100%;
1181
+ height: 100%;
1182
+ object-fit: cover;
1183
+ border-radius: 10px;
1184
+}
1185
+
1186
+.attachment-item.image-type .image-badge {
1187
+ position: absolute;
1188
+ bottom: 6px;
1189
+ left: 50%;
1190
+ transform: translateX(-50%);
1191
+ background: rgba(0, 0, 0, 0.7);
1192
+ color: white;
1193
+ padding: 2px 8px;
1194
+ border-radius: 12px;
1195
+ font-size: 0.65rem;
1196
+ text-transform: uppercase;
1197
+ backdrop-filter: blur(4px);
1198
+ -webkit-backdrop-filter: blur(4px);
1199
}
1200
1201
+/* File attachment styling */
1202
.attachment-item.file-type {
1092
- background: var(--color-background);
1203
+ flex-direction: column;
1204
+ padding: 12px;
1205
+ text-align: center;
1206
}
1207
1095
-.attachment-item:hover {
1096
- background: var(--color-secondary);
1208
+.attachment-item.file-type .file-icon {
1209
+ width: 48px;
1210
+ height: 48px;
1211
+ margin-bottom: 8px;
1212
+ opacity: 0.8;
1213
}
1214
1099
-.attachment-preview {
1100
- max-width: 100px;
1101
- max-height: 100px;
1215
+.attachment-item.file-type .file-title {
1216
+ font-size: 0.75rem;
1217
+ color: var(--color-text);
1218
+ word-break: break-word;
1219
+ line-height: 1.2;
1220
+ max-height: 2.4em;
1221
+ overflow: hidden;
1222
+ text-overflow: ellipsis;
1223
+ display: -webkit-box;
1224
+ -webkit-line-clamp: 2;
1225
+ -webkit-box-orient: vertical;
1226
+ text-align: center;
1227
+ margin-bottom: 4px;
1228
+}
1229
+
1230
+.attachment-item.file-type .file-extension {
1231
+ background: var(--color-primary);
1232
+ color: var(--color-text);
1233
+ padding: 2px 6px;
1234
border-radius: 4px;
1103
- object-fit: contain;
1235
+ font-size: 0.7rem;
1236
+ text-transform: uppercase;
1237
+ white-space: nowrap;
1238
}
1239
1106
-.attachment-image .attachment-preview {
1107
- margin-right: 8px;
1240
+.attachment-preview {
1241
+ width: 100%;
1242
+ height: 100%;
1243
+ object-fit: cover;
1244
+ border-radius: 10px;
1245
}
1246
1110
-.attachment-info,
1111
-.file-info {
1112
- display: flex;
1113
- align-items: center;
1114
- gap: 8px;
1247
+.attachment-image .attachment-preview {
1248
+ margin-right: 0;
1249
}
1250
1251
+.attachment-info,
1252
.file-info {
1253
display: flex;
1254
+ flex-direction: column;
1255
+ gap: 4px;
1256
align-items: center;
1120
- gap: 0.5em;
1257
+ width: 100%;
1258
}
1259
1260
.attachment-name,
1261
.filename,
1262
.file-name {
1126
- font-size: 0.9em;
1263
+ font-size: 0.75rem;
1264
color: var(--color-text);
1265
word-break: break-word;
1266
+ line-height: 1.2;
1267
+ max-height: 2.4em;
1268
+ overflow: hidden;
1269
+ text-overflow: ellipsis;
1270
+ display: -webkit-box;
1271
+ -webkit-line-clamp: 2;
1272
+ -webkit-box-orient: vertical;
1273
+ text-align: center;
1274
}
1275
1276
.attachment-ext,
@@ -1135,7 +1280,7 @@ pre {
1280
color: var(--color-text);
1281
padding: 2px 6px;
1282
border-radius: 4px;
1138
- font-size: 0.8em;
1283
+ font-size: 0.7rem;
1284
text-transform: uppercase;
1285
white-space: nowrap;
1286
}
@@ -1169,17 +1314,24 @@ pre {
1314
}
1315
1316
.image-wrapper {
1172
- width: 100px;
1173
- height: 100px;
1317
+ width: 100%;
1318
+ height: 100%;
1319
display: flex;
1320
align-items: center;
1321
justify-content: center;
1322
+ overflow: hidden;
1323
+ border-radius: 10px;
1324
}
1325
1326
.file-preview {
1327
display: flex;
1328
+ flex-direction: column;
1329
align-items: center;
1182
- gap: 0.5em;
1330
+ gap: 8px;
1331
+ width: 100%;
1332
+ height: 100%;
1333
+ padding: 12px;
1334
+ text-align: center;
1335
}
1336
1337
.extension {
@@ -1187,7 +1339,7 @@ pre {
1339
color: var(--color-text);
1340
padding: 2px 6px;
1341
border-radius: 4px;
1190
- font-size: 0.8em;
1342
+ font-size: 0.7rem;
1343
text-transform: uppercase;
1344
}
1345
@@ -1731,6 +1883,70 @@ input:checked + .slider:before {
1883
.kvps-val {
1884
margin: 0 0 0.4rem 0;
1885
}
1886
+
1887
+ /* Responsive tiles for mobile */
1888
+ .preview-section {
1889
+ grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
1890
+ gap: 8px;
1891
+ padding: var(--spacing-xs);
1892
+ }
1893
+
1894
+ .attachments-container {
1895
+ grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
1896
+ gap: 8px;
1897
+ padding: var(--spacing-xs);
1898
+ max-width: 300px; /* Limits to ~3 columns at 90px each */
1899
+ }
1900
+
1901
+ .preview-item,
1902
+ .attachment-item {
1903
+ width: 90px;
1904
+ height: 90px;
1905
+ border-radius: 8px;
1906
+ }
1907
+
1908
+ .preview-item.file-preview,
1909
+ .attachment-item.file-type {
1910
+ padding: 8px;
1911
+ }
1912
+
1913
+ .preview-item.file-preview .file-icon,
1914
+ .attachment-item.file-type .file-icon {
1915
+ width: 32px;
1916
+ height: 32px;
1917
+ margin-bottom: 4px;
1918
+ }
1919
+
1920
+ .preview-item.file-preview .filename,
1921
+ .attachment-item.file-type .file-title,
1922
+ .attachment-name,
1923
+ .filename {
1924
+ font-size: 0.65rem;
1925
+ -webkit-line-clamp: 1;
1926
+ }
1927
+
1928
+ .preview-item.file-preview .extension,
1929
+ .attachment-item.file-type .file-extension,
1930
+ .attachment-item.image-type .image-badge,
1931
+ .attachment-ext,
1932
+ .extension {
1933
+ font-size: 0.6rem;
1934
+ padding: 1px 4px;
1935
+ }
1936
+
1937
+ .remove-attachment,
1938
+ .remove-image {
1939
+ width: 20px;
1940
+ height: 20px;
1941
+ top: 2px;
1942
+ right: 2px;
1943
+ }
1944
+
1945
+ .attachment-item.image-type .image-badge {
1946
+ bottom: 4px;
1947
+ font-size: 0.55rem;
1948
+ padding: 1px 6px;
1949
+ }
1950
}
1951
1952
@media (max-width: 640px) {
webui/js/messages.js
+22
-29
@@ -375,9 +375,8 @@ export function drawMessageUser(
375
}
376
377
if (displayInfo.isImage) {
378
- // Render as image with enhanced device sync support
379
- const imgWrapper = document.createElement("div");
380
- imgWrapper.classList.add("image-wrapper");
378
+ // Render as image tile with bottom badge
379
+ attachmentDiv.classList.add("image-type");
380
381
const img = document.createElement("img");
382
img.src = displayInfo.previewUrl;
@@ -386,44 +385,38 @@ export function drawMessageUser(
385
img.style.cursor = "pointer";
386
img.addEventListener('click', displayInfo.clickHandler);
387
389
- const fileInfo = document.createElement("div");
390
- fileInfo.classList.add("file-info");
391
- fileInfo.innerHTML = `
392
- <span class="filename">${displayInfo.filename}</span>
393
- <span class="extension">${displayInfo.extension}</span>
394
- `;
388
+ const imageBadge = document.createElement("div");
389
+ imageBadge.classList.add("image-badge");
390
+ imageBadge.textContent = displayInfo.extension;
391
396
- imgWrapper.appendChild(img);
397
- attachmentDiv.appendChild(imgWrapper);
398
- attachmentDiv.appendChild(fileInfo);
392
+ attachmentDiv.appendChild(img);
393
+ attachmentDiv.appendChild(imageBadge);
394
} else {
400
- // Render as file with icon support for device sync
395
+ // Render as file tile with title and icon
396
attachmentDiv.classList.add("file-type");
397
403
- // Create file preview with potential server-side icon
404
- const filePreview = document.createElement("div");
405
- filePreview.classList.add("file-preview");
406
-
407
- // If we have a preview URL (server icon), show it
398
+ // File icon
399
if (displayInfo.previewUrl && displayInfo.previewUrl !== displayInfo.filename) {
400
const iconImg = document.createElement("img");
401
iconImg.src = displayInfo.previewUrl;
402
iconImg.alt = `${displayInfo.extension} file`;
403
iconImg.classList.add("file-icon");
413
- iconImg.style.width = "24px";
414
- iconImg.style.height = "24px";
415
- iconImg.style.marginRight = "8px";
416
- filePreview.appendChild(iconImg);
404
+ attachmentDiv.appendChild(iconImg);
405
}
406
419
- const textInfo = document.createElement("div");
420
- textInfo.innerHTML = `
421
- <span class="filename">${displayInfo.filename}</span>
422
- <span class="extension">${displayInfo.extension}</span>
423
- `;
424
- filePreview.appendChild(textInfo);
407
+ // File title (filename without extension)
408
+ const fileTitle = document.createElement("div");
409
+ fileTitle.classList.add("file-title");
410
+ const nameWithoutExt = displayInfo.filename.replace(/\.[^/.]+$/, "");
411
+ fileTitle.textContent = nameWithoutExt;
412
+
413
+ // File extension badge
414
+ const fileExtension = document.createElement("div");
415
+ fileExtension.classList.add("file-extension");
416
+ fileExtension.textContent = displayInfo.extension;
417
426
- attachmentDiv.appendChild(filePreview);
418
+ attachmentDiv.appendChild(fileTitle);
419
+ attachmentDiv.appendChild(fileExtension);
420
}
421
422