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