Spaces:
Running
Running
ok so the user needs to have a little ai chat box when adding photos and data, as if the ai gives wrong info he needs to correct it and it needs to learn where it went wrong
Browse files- components/ai-chat.js +739 -0
- index.html +7 -4
components/ai-chat.js
ADDED
|
@@ -0,0 +1,739 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class AIChat extends HTMLElement {
|
| 2 |
+
constructor() {
|
| 3 |
+
super();
|
| 4 |
+
this.messages = [];
|
| 5 |
+
this.corrections = {};
|
| 6 |
+
this.learningData = JSON.parse(localStorage.getItem('ai_learning_data') || '{}');
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
connectedCallback() {
|
| 10 |
+
this.attachShadow({ mode: 'open' });
|
| 11 |
+
this.render();
|
| 12 |
+
this.setupEventListeners();
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
render() {
|
| 16 |
+
this.shadowRoot.innerHTML = `
|
| 17 |
+
<style>
|
| 18 |
+
:host {
|
| 19 |
+
display: block;
|
| 20 |
+
}
|
| 21 |
+
.chat-container {
|
| 22 |
+
background: #1e293b;
|
| 23 |
+
border: 1px solid #334155;
|
| 24 |
+
border-radius: 12px;
|
| 25 |
+
overflow: hidden;
|
| 26 |
+
display: flex;
|
| 27 |
+
flex-direction: column;
|
| 28 |
+
height: 400px;
|
| 29 |
+
}
|
| 30 |
+
.chat-header {
|
| 31 |
+
background: linear-gradient(135deg, #7c3aed20 0%, #06b6d420 100%);
|
| 32 |
+
padding: 12px 16px;
|
| 33 |
+
border-bottom: 1px solid #334155;
|
| 34 |
+
display: flex;
|
| 35 |
+
align-items: center;
|
| 36 |
+
gap: 10px;
|
| 37 |
+
}
|
| 38 |
+
.chat-header i {
|
| 39 |
+
color: #7c3aed;
|
| 40 |
+
}
|
| 41 |
+
.chat-header h3 {
|
| 42 |
+
margin: 0;
|
| 43 |
+
font-size: 14px;
|
| 44 |
+
font-weight: 600;
|
| 45 |
+
color: #e2e8f0;
|
| 46 |
+
}
|
| 47 |
+
.status-badge {
|
| 48 |
+
margin-left: auto;
|
| 49 |
+
font-size: 11px;
|
| 50 |
+
padding: 4px 10px;
|
| 51 |
+
border-radius: 9999px;
|
| 52 |
+
background: #22c55e20;
|
| 53 |
+
color: #22c55e;
|
| 54 |
+
}
|
| 55 |
+
.status-badge.thinking {
|
| 56 |
+
background: #f59e0b20;
|
| 57 |
+
color: #f59e0b;
|
| 58 |
+
animation: pulse 1.5s infinite;
|
| 59 |
+
}
|
| 60 |
+
@keyframes pulse {
|
| 61 |
+
0%, 100% { opacity: 1; }
|
| 62 |
+
50% { opacity: 0.6; }
|
| 63 |
+
}
|
| 64 |
+
.messages-area {
|
| 65 |
+
flex: 1;
|
| 66 |
+
overflow-y: auto;
|
| 67 |
+
padding: 16px;
|
| 68 |
+
display: flex;
|
| 69 |
+
flex-direction: column;
|
| 70 |
+
gap: 12px;
|
| 71 |
+
}
|
| 72 |
+
.message {
|
| 73 |
+
max-width: 85%;
|
| 74 |
+
padding: 12px 14px;
|
| 75 |
+
border-radius: 12px;
|
| 76 |
+
font-size: 13px;
|
| 77 |
+
line-height: 1.5;
|
| 78 |
+
position: relative;
|
| 79 |
+
}
|
| 80 |
+
.message.ai {
|
| 81 |
+
align-self: flex-start;
|
| 82 |
+
background: #0f172a;
|
| 83 |
+
border: 1px solid #334155;
|
| 84 |
+
color: #e2e8f0;
|
| 85 |
+
}
|
| 86 |
+
.message.user {
|
| 87 |
+
align-self: flex-end;
|
| 88 |
+
background: #7c3aed;
|
| 89 |
+
color: white;
|
| 90 |
+
}
|
| 91 |
+
.message.correction {
|
| 92 |
+
border-color: #f59e0b;
|
| 93 |
+
background: #f59e0b10;
|
| 94 |
+
}
|
| 95 |
+
.message-meta {
|
| 96 |
+
font-size: 10px;
|
| 97 |
+
opacity: 0.6;
|
| 98 |
+
margin-top: 6px;
|
| 99 |
+
}
|
| 100 |
+
.correction-form {
|
| 101 |
+
margin-top: 10px;
|
| 102 |
+
padding: 10px;
|
| 103 |
+
background: #0f172a;
|
| 104 |
+
border-radius: 8px;
|
| 105 |
+
border: 1px dashed #475569;
|
| 106 |
+
}
|
| 107 |
+
.correction-form label {
|
| 108 |
+
display: block;
|
| 109 |
+
font-size: 11px;
|
| 110 |
+
color: #94a3b8;
|
| 111 |
+
margin-bottom: 6px;
|
| 112 |
+
}
|
| 113 |
+
.correction-input {
|
| 114 |
+
width: 100%;
|
| 115 |
+
padding: 8px 10px;
|
| 116 |
+
background: #1e293b;
|
| 117 |
+
border: 1px solid #475569;
|
| 118 |
+
border-radius: 6px;
|
| 119 |
+
color: #e2e8f0;
|
| 120 |
+
font-size: 12px;
|
| 121 |
+
margin-bottom: 8px;
|
| 122 |
+
}
|
| 123 |
+
.correction-input:focus {
|
| 124 |
+
outline: none;
|
| 125 |
+
border-color: #f59e0b;
|
| 126 |
+
}
|
| 127 |
+
.correction-actions {
|
| 128 |
+
display: flex;
|
| 129 |
+
gap: 8px;
|
| 130 |
+
}
|
| 131 |
+
.btn-small {
|
| 132 |
+
padding: 6px 12px;
|
| 133 |
+
border-radius: 6px;
|
| 134 |
+
font-size: 11px;
|
| 135 |
+
cursor: pointer;
|
| 136 |
+
border: none;
|
| 137 |
+
transition: all 0.2s;
|
| 138 |
+
}
|
| 139 |
+
.btn-confirm {
|
| 140 |
+
background: #22c55e;
|
| 141 |
+
color: white;
|
| 142 |
+
}
|
| 143 |
+
.btn-confirm:hover {
|
| 144 |
+
background: #16a34a;
|
| 145 |
+
}
|
| 146 |
+
.btn-correct {
|
| 147 |
+
background: #f59e0b;
|
| 148 |
+
color: #0f172a;
|
| 149 |
+
}
|
| 150 |
+
.btn-correct:hover {
|
| 151 |
+
background: #d97706;
|
| 152 |
+
}
|
| 153 |
+
.field-correction {
|
| 154 |
+
display: flex;
|
| 155 |
+
align-items: center;
|
| 156 |
+
gap: 8px;
|
| 157 |
+
padding: 6px 10px;
|
| 158 |
+
background: #f59e0b15;
|
| 159 |
+
border-radius: 6px;
|
| 160 |
+
margin-top: 8px;
|
| 161 |
+
font-size: 11px;
|
| 162 |
+
}
|
| 163 |
+
.field-correction .field-name {
|
| 164 |
+
color: #f59e0b;
|
| 165 |
+
font-weight: 500;
|
| 166 |
+
}
|
| 167 |
+
.field-correction .arrow {
|
| 168 |
+
color: #64748b;
|
| 169 |
+
}
|
| 170 |
+
.field-correction .corrected {
|
| 171 |
+
color: #22c55e;
|
| 172 |
+
text-decoration: line-through;
|
| 173 |
+
opacity: 0.7;
|
| 174 |
+
}
|
| 175 |
+
.input-area {
|
| 176 |
+
padding: 12px 16px;
|
| 177 |
+
border-top: 1px solid #334155;
|
| 178 |
+
background: #0f172a;
|
| 179 |
+
}
|
| 180 |
+
.input-row {
|
| 181 |
+
display: flex;
|
| 182 |
+
gap: 10px;
|
| 183 |
+
}
|
| 184 |
+
.chat-input {
|
| 185 |
+
flex: 1;
|
| 186 |
+
padding: 10px 14px;
|
| 187 |
+
background: #1e293b;
|
| 188 |
+
border: 1px solid #475569;
|
| 189 |
+
border-radius: 8px;
|
| 190 |
+
color: #e2e8f0;
|
| 191 |
+
font-size: 13px;
|
| 192 |
+
}
|
| 193 |
+
.chat-input:focus {
|
| 194 |
+
outline: none;
|
| 195 |
+
border-color: #7c3aed;
|
| 196 |
+
}
|
| 197 |
+
.send-btn {
|
| 198 |
+
padding: 10px 16px;
|
| 199 |
+
background: #7c3aed;
|
| 200 |
+
border: none;
|
| 201 |
+
border-radius: 8px;
|
| 202 |
+
color: white;
|
| 203 |
+
cursor: pointer;
|
| 204 |
+
display: flex;
|
| 205 |
+
align-items: center;
|
| 206 |
+
justify-content: center;
|
| 207 |
+
transition: all 0.2s;
|
| 208 |
+
}
|
| 209 |
+
.send-btn:hover {
|
| 210 |
+
background: #6d28d9;
|
| 211 |
+
}
|
| 212 |
+
.send-btn:disabled {
|
| 213 |
+
opacity: 0.5;
|
| 214 |
+
cursor: not-allowed;
|
| 215 |
+
}
|
| 216 |
+
.quick-actions {
|
| 217 |
+
display: flex;
|
| 218 |
+
gap: 8px;
|
| 219 |
+
margin-top: 10px;
|
| 220 |
+
flex-wrap: wrap;
|
| 221 |
+
}
|
| 222 |
+
.quick-btn {
|
| 223 |
+
padding: 6px 12px;
|
| 224 |
+
background: #1e293b;
|
| 225 |
+
border: 1px solid #475569;
|
| 226 |
+
border-radius: 16px;
|
| 227 |
+
color: #94a3b8;
|
| 228 |
+
font-size: 11px;
|
| 229 |
+
cursor: pointer;
|
| 230 |
+
transition: all 0.2s;
|
| 231 |
+
}
|
| 232 |
+
.quick-btn:hover {
|
| 233 |
+
border-color: #7c3aed;
|
| 234 |
+
color: #7c3aed;
|
| 235 |
+
}
|
| 236 |
+
.detected-fields {
|
| 237 |
+
margin-top: 12px;
|
| 238 |
+
padding: 12px;
|
| 239 |
+
background: #0f172a;
|
| 240 |
+
border-radius: 8px;
|
| 241 |
+
border: 1px solid #334155;
|
| 242 |
+
}
|
| 243 |
+
.detected-fields h4 {
|
| 244 |
+
margin: 0 0 10px 0;
|
| 245 |
+
font-size: 12px;
|
| 246 |
+
color: #94a3b8;
|
| 247 |
+
font-weight: 500;
|
| 248 |
+
}
|
| 249 |
+
.field-grid {
|
| 250 |
+
display: grid;
|
| 251 |
+
grid-template-columns: repeat(2, 1fr);
|
| 252 |
+
gap: 8px;
|
| 253 |
+
}
|
| 254 |
+
.field-item {
|
| 255 |
+
display: flex;
|
| 256 |
+
flex-direction: column;
|
| 257 |
+
gap: 2px;
|
| 258 |
+
}
|
| 259 |
+
.field-label {
|
| 260 |
+
font-size: 10px;
|
| 261 |
+
color: #64748b;
|
| 262 |
+
text-transform: uppercase;
|
| 263 |
+
}
|
| 264 |
+
.field-value {
|
| 265 |
+
font-size: 12px;
|
| 266 |
+
color: #e2e8f0;
|
| 267 |
+
font-weight: 500;
|
| 268 |
+
}
|
| 269 |
+
.field-value.corrected {
|
| 270 |
+
color: #22c55e;
|
| 271 |
+
}
|
| 272 |
+
.field-value.wrong {
|
| 273 |
+
color: #ef4444;
|
| 274 |
+
text-decoration: line-through;
|
| 275 |
+
}
|
| 276 |
+
.confidence-indicator {
|
| 277 |
+
display: inline-flex;
|
| 278 |
+
align-items: center;
|
| 279 |
+
gap: 4px;
|
| 280 |
+
font-size: 10px;
|
| 281 |
+
padding: 2px 8px;
|
| 282 |
+
border-radius: 10px;
|
| 283 |
+
margin-left: 6px;
|
| 284 |
+
}
|
| 285 |
+
.confidence-high {
|
| 286 |
+
background: #22c55e20;
|
| 287 |
+
color: #22c55e;
|
| 288 |
+
}
|
| 289 |
+
.confidence-medium {
|
| 290 |
+
background: #f59e0b20;
|
| 291 |
+
color: #f59e0b;
|
| 292 |
+
}
|
| 293 |
+
.confidence-low {
|
| 294 |
+
background: #ef444420;
|
| 295 |
+
color: #ef4444;
|
| 296 |
+
}
|
| 297 |
+
.learned-badge {
|
| 298 |
+
display: inline-flex;
|
| 299 |
+
align-items: center;
|
| 300 |
+
gap: 4px;
|
| 301 |
+
font-size: 10px;
|
| 302 |
+
padding: 4px 10px;
|
| 303 |
+
border-radius: 10px;
|
| 304 |
+
background: #06b6d420;
|
| 305 |
+
color: #06b6d4;
|
| 306 |
+
margin-top: 8px;
|
| 307 |
+
}
|
| 308 |
+
.typing-indicator {
|
| 309 |
+
display: flex;
|
| 310 |
+
gap: 4px;
|
| 311 |
+
padding: 12px 14px;
|
| 312 |
+
}
|
| 313 |
+
.typing-indicator span {
|
| 314 |
+
width: 8px;
|
| 315 |
+
height: 8px;
|
| 316 |
+
background: #7c3aed;
|
| 317 |
+
border-radius: 50%;
|
| 318 |
+
animation: bounce 1.4s infinite ease-in-out both;
|
| 319 |
+
}
|
| 320 |
+
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
|
| 321 |
+
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
|
| 322 |
+
@keyframes bounce {
|
| 323 |
+
0%, 80%, 100% { transform: scale(0); }
|
| 324 |
+
40% { transform: scale(1); }
|
| 325 |
+
}
|
| 326 |
+
</style>
|
| 327 |
+
|
| 328 |
+
<div class="chat-container">
|
| 329 |
+
<div class="chat-header">
|
| 330 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
| 331 |
+
<h3>AI Assistant</h3>
|
| 332 |
+
<span class="status-badge" id="statusBadge">Ready</span>
|
| 333 |
+
</div>
|
| 334 |
+
|
| 335 |
+
<div class="messages-area" id="messagesArea">
|
| 336 |
+
<div class="message ai">
|
| 337 |
+
Hi! I'll analyze your record photos and help identify the details. If I get anything wrong, just correct me—I'll learn from it for next time!
|
| 338 |
+
</div>
|
| 339 |
+
</div>
|
| 340 |
+
|
| 341 |
+
<div class="input-area">
|
| 342 |
+
<div class="input-row">
|
| 343 |
+
<input type="text" class="chat-input" id="chatInput" placeholder="Ask a question or correct a field..." />
|
| 344 |
+
<button class="send-btn" id="sendBtn">
|
| 345 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
| 346 |
+
</button>
|
| 347 |
+
</div>
|
| 348 |
+
<div class="quick-actions" id="quickActions">
|
| 349 |
+
<button class="quick-btn" data-action="confirm-all">✓ All Correct</button>
|
| 350 |
+
<button class="quick-btn" data-action="wrong-artist">Wrong Artist</button>
|
| 351 |
+
<button class="quick-btn" data-action="wrong-title">Wrong Title</button>
|
| 352 |
+
<button class="quick-btn" data-action="wrong-year">Wrong Year</button>
|
| 353 |
+
<button class="quick-btn" data-action="wrong-cat">Wrong Catalogue</button>
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
</div>
|
| 357 |
+
`;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
setupEventListeners() {
|
| 361 |
+
const input = this.shadowRoot.getElementById('chatInput');
|
| 362 |
+
const sendBtn = this.shadowRoot.getElementById('sendBtn');
|
| 363 |
+
const quickActions = this.shadowRoot.getElementById('quickActions');
|
| 364 |
+
|
| 365 |
+
sendBtn.addEventListener('click', () => this.handleSend());
|
| 366 |
+
input.addEventListener('keypress', (e) => {
|
| 367 |
+
if (e.key === 'Enter') this.handleSend();
|
| 368 |
+
});
|
| 369 |
+
|
| 370 |
+
quickActions.addEventListener('click', (e) => {
|
| 371 |
+
if (e.target.classList.contains('quick-btn')) {
|
| 372 |
+
this.handleQuickAction(e.target.dataset.action);
|
| 373 |
+
}
|
| 374 |
+
});
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
handleSend() {
|
| 378 |
+
const input = this.shadowRoot.getElementById('chatInput');
|
| 379 |
+
const message = input.value.trim();
|
| 380 |
+
if (!message) return;
|
| 381 |
+
|
| 382 |
+
this.addMessage(message, 'user');
|
| 383 |
+
input.value = '';
|
| 384 |
+
|
| 385 |
+
// Process the message
|
| 386 |
+
this.processUserMessage(message);
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
handleQuickAction(action) {
|
| 390 |
+
switch(action) {
|
| 391 |
+
case 'confirm-all':
|
| 392 |
+
this.confirmAllFields();
|
| 393 |
+
break;
|
| 394 |
+
case 'wrong-artist':
|
| 395 |
+
this.requestCorrection('artist');
|
| 396 |
+
break;
|
| 397 |
+
case 'wrong-title':
|
| 398 |
+
this.requestCorrection('title');
|
| 399 |
+
break;
|
| 400 |
+
case 'wrong-year':
|
| 401 |
+
this.requestCorrection('year');
|
| 402 |
+
break;
|
| 403 |
+
case 'wrong-cat':
|
| 404 |
+
this.requestCorrection('catalogueNumber');
|
| 405 |
+
break;
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
addMessage(text, sender, options = {}) {
|
| 410 |
+
const messagesArea = this.shadowRoot.getElementById('messagesArea');
|
| 411 |
+
const messageDiv = document.createElement('div');
|
| 412 |
+
messageDiv.className = `message ${sender} ${options.isCorrection ? 'correction' : ''}`;
|
| 413 |
+
|
| 414 |
+
let content = text;
|
| 415 |
+
if (options.field && options.originalValue !== undefined) {
|
| 416 |
+
content += `
|
| 417 |
+
<div class="field-correction">
|
| 418 |
+
<span class="field-name">${options.field}:</span>
|
| 419 |
+
<span class="corrected">${options.originalValue}</span>
|
| 420 |
+
<span class="arrow">→</span>
|
| 421 |
+
<span>${options.newValue}</span>
|
| 422 |
+
</div>
|
| 423 |
+
`;
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
messageDiv.innerHTML = content;
|
| 427 |
+
if (options.meta) {
|
| 428 |
+
messageDiv.innerHTML += `<div class="message-meta">${options.meta}</div>`;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
messagesArea.appendChild(messageDiv);
|
| 432 |
+
messagesArea.scrollTop = messagesArea.scrollHeight;
|
| 433 |
+
|
| 434 |
+
this.messages.push({ sender, text, ...options });
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
showTyping() {
|
| 438 |
+
const messagesArea = this.shadowRoot.getElementById('messagesArea');
|
| 439 |
+
const typingDiv = document.createElement('div');
|
| 440 |
+
typingDiv.className = 'message ai typing-indicator';
|
| 441 |
+
typingDiv.id = 'typingIndicator';
|
| 442 |
+
typingDiv.innerHTML = '<span></span><span></span><span></span>';
|
| 443 |
+
messagesArea.appendChild(typingDiv);
|
| 444 |
+
messagesArea.scrollTop = messagesArea.scrollHeight;
|
| 445 |
+
|
| 446 |
+
this.setStatus('thinking');
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
hideTyping() {
|
| 450 |
+
const typing = this.shadowRoot.getElementById('typingIndicator');
|
| 451 |
+
if (typing) typing.remove();
|
| 452 |
+
this.setStatus('ready');
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
setStatus(status) {
|
| 456 |
+
const badge = this.shadowRoot.getElementById('statusBadge');
|
| 457 |
+
badge.className = `status-badge ${status === 'thinking' ? 'thinking' : ''}`;
|
| 458 |
+
badge.textContent = status === 'thinking' ? 'Analyzing...' : 'Ready';
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
// Called when OCR results come in
|
| 462 |
+
showDetectionResults(data) {
|
| 463 |
+
this.currentDetection = data;
|
| 464 |
+
this.corrections = {};
|
| 465 |
+
|
| 466 |
+
const messagesArea = this.shadowRoot.getElementById('messagesArea');
|
| 467 |
+
|
| 468 |
+
// Build detected fields display
|
| 469 |
+
const fieldsHtml = `
|
| 470 |
+
<div class="detected-fields">
|
| 471 |
+
<h4>Detected Information
|
| 472 |
+
<span class="confidence-indicator confidence-${data.confidence || 'medium'}">
|
| 473 |
+
${data.confidence || 'medium'} confidence
|
| 474 |
+
</span>
|
| 475 |
+
</h4>
|
| 476 |
+
<div class="field-grid">
|
| 477 |
+
${this.renderField('Artist', data.artist)}
|
| 478 |
+
${this.renderField('Title', data.title)}
|
| 479 |
+
${this.renderField('Year', data.year)}
|
| 480 |
+
${this.renderField('Catalogue #', data.catalogueNumber)}
|
| 481 |
+
${this.renderField('Label', data.label)}
|
| 482 |
+
${this.renderField('Country', data.country)}
|
| 483 |
+
${this.renderField('Format', data.format)}
|
| 484 |
+
${this.renderField('Genre', data.genre)}
|
| 485 |
+
</div>
|
| 486 |
+
${this.hasLearnedCorrections() ? `
|
| 487 |
+
<div class="learned-badge">
|
| 488 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
| 489 |
+
Applied ${Object.keys(this.getRelevantLearnings(data)).length} learned corrections
|
| 490 |
+
</div>
|
| 491 |
+
` : ''}
|
| 492 |
+
</div>
|
| 493 |
+
`;
|
| 494 |
+
|
| 495 |
+
const messageDiv = document.createElement('div');
|
| 496 |
+
messageDiv.className = 'message ai';
|
| 497 |
+
messageDiv.innerHTML = `
|
| 498 |
+
I've analyzed your photos! Here's what I found:
|
| 499 |
+
${fieldsHtml}
|
| 500 |
+
<p style="margin-top: 12px; font-size: 12px; color: #94a3b8;">
|
| 501 |
+
Please review these details. If anything looks wrong, click "Wrong [Field]" or type a correction like "The artist is actually..."
|
| 502 |
+
</p>
|
| 503 |
+
`;
|
| 504 |
+
|
| 505 |
+
messagesArea.appendChild(messageDiv);
|
| 506 |
+
messagesArea.scrollTop = messagesArea.scrollHeight;
|
| 507 |
+
|
| 508 |
+
// Apply any learned corrections automatically
|
| 509 |
+
this.applyLearnedCorrections(data);
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
renderField(label, value) {
|
| 513 |
+
const corrected = this.corrections[label.toLowerCase()];
|
| 514 |
+
const displayValue = corrected || value || '-';
|
| 515 |
+
const isCorrected = !!corrected;
|
| 516 |
+
|
| 517 |
+
return `
|
| 518 |
+
<div class="field-item">
|
| 519 |
+
<span class="field-label">${label}</span>
|
| 520 |
+
<span class="field-value ${isCorrected ? 'corrected' : ''} ${value === null ? 'wrong' : ''}">
|
| 521 |
+
${displayValue}
|
| 522 |
+
</span>
|
| 523 |
+
</div>
|
| 524 |
+
`;
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
requestCorrection(field) {
|
| 528 |
+
const currentValue = this.corrections[field] || this.currentDetection?.[field] || '';
|
| 529 |
+
|
| 530 |
+
this.addMessage(
|
| 531 |
+
`What's the correct ${field}?`,
|
| 532 |
+
'ai',
|
| 533 |
+
{ meta: 'Type the correct value below' }
|
| 534 |
+
);
|
| 535 |
+
|
| 536 |
+
// Store that we're waiting for this correction
|
| 537 |
+
this.pendingCorrection = field;
|
| 538 |
+
|
| 539 |
+
// Focus input
|
| 540 |
+
this.shadowRoot.getElementById('chatInput').focus();
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
processUserMessage(message) {
|
| 544 |
+
// Check if this is a correction response
|
| 545 |
+
if (this.pendingCorrection) {
|
| 546 |
+
this.applyCorrection(this.pendingCorrection, message);
|
| 547 |
+
this.pendingCorrection = null;
|
| 548 |
+
return;
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
// Check for natural language corrections
|
| 552 |
+
const correctionPatterns = [
|
| 553 |
+
{ regex: /(?:the\s+)?artist\s+(?:is|should be|was)\s+(.+)/i, field: 'artist' },
|
| 554 |
+
{ regex: /(?:the\s+)?title\s+(?:is|should be|was)\s+(.+)/i, field: 'title' },
|
| 555 |
+
{ regex: /(?:the\s+)?year\s+(?:is|should be|was)\s+(\d{4})/i, field: 'year' },
|
| 556 |
+
{ regex: /(?:the\s+)?catalog(?:ue)?\s*(?:#|number)?\s+(?:is|should be|was)\s+(.+)/i, field: 'catalogueNumber' },
|
| 557 |
+
{ regex: /(?:the\s+)?label\s+(?:is|should be|was)\s+(.+)/i, field: 'label' },
|
| 558 |
+
{ regex: /wrong\s+artist/i, field: 'artist', needsValue: true },
|
| 559 |
+
{ regex: /wrong\s+title/i, field: 'title', needsValue: true },
|
| 560 |
+
{ regex: /wrong\s+year/i, field: 'year', needsValue: true },
|
| 561 |
+
];
|
| 562 |
+
|
| 563 |
+
for (const pattern of correctionPatterns) {
|
| 564 |
+
const match = message.match(pattern.regex);
|
| 565 |
+
if (match) {
|
| 566 |
+
if (pattern.needsValue) {
|
| 567 |
+
this.requestCorrection(pattern.field);
|
| 568 |
+
} else {
|
| 569 |
+
this.applyCorrection(pattern.field, match[1].trim());
|
| 570 |
+
}
|
| 571 |
+
return;
|
| 572 |
+
}
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
// General question or confirmation
|
| 576 |
+
this.showTyping();
|
| 577 |
+
setTimeout(() => {
|
| 578 |
+
this.hideTyping();
|
| 579 |
+
this.addMessage(
|
| 580 |
+
"I understand. You can tell me about any other corrections needed, or click 'All Correct' if everything looks good!",
|
| 581 |
+
'ai'
|
| 582 |
+
);
|
| 583 |
+
}, 800);
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
applyCorrection(field, newValue) {
|
| 587 |
+
const originalValue = this.currentDetection?.[field];
|
| 588 |
+
this.corrections[field] = newValue;
|
| 589 |
+
|
| 590 |
+
// Store learning data
|
| 591 |
+
this.learnFromCorrection(field, originalValue, newValue);
|
| 592 |
+
|
| 593 |
+
this.addMessage(
|
| 594 |
+
`Thanks! I've corrected the ${field}.`,
|
| 595 |
+
'ai',
|
| 596 |
+
{
|
| 597 |
+
isCorrection: true,
|
| 598 |
+
field: field.charAt(0).toUpperCase() + field.slice(1),
|
| 599 |
+
originalValue,
|
| 600 |
+
newValue,
|
| 601 |
+
meta: 'Learning saved for future analyses'
|
| 602 |
+
}
|
| 603 |
+
);
|
| 604 |
+
|
| 605 |
+
// Update the displayed fields
|
| 606 |
+
this.updateDetectedFieldsDisplay();
|
| 607 |
+
|
| 608 |
+
// Dispatch event for parent to update form
|
| 609 |
+
this.dispatchEvent(new CustomEvent('field-corrected', {
|
| 610 |
+
detail: { field, value: newValue, originalValue },
|
| 611 |
+
bubbles: true,
|
| 612 |
+
composed: true
|
| 613 |
+
}));
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
learnFromCorrection(field, originalValue, correctedValue) {
|
| 617 |
+
if (!originalValue || !correctedValue) return;
|
| 618 |
+
|
| 619 |
+
// Create a learning key based on context
|
| 620 |
+
const context = this.buildContextKey();
|
| 621 |
+
const key = `${context}:${field}`;
|
| 622 |
+
|
| 623 |
+
if (!this.learningData[key]) {
|
| 624 |
+
this.learningData[key] = [];
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
this.learningData[key].push({
|
| 628 |
+
from: originalValue,
|
| 629 |
+
to: correctedValue,
|
| 630 |
+
timestamp: Date.now(),
|
| 631 |
+
detection: this.currentDetection
|
| 632 |
+
});
|
| 633 |
+
|
| 634 |
+
// Keep only last 50 corrections per key
|
| 635 |
+
if (this.learningData[key].length > 50) {
|
| 636 |
+
this.learningData[key] = this.learningData[key].slice(-50);
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
localStorage.setItem('ai_learning_data', JSON.stringify(this.learningData));
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
buildContextKey() {
|
| 643 |
+
// Create a fuzzy context key from available data
|
| 644 |
+
const d = this.currentDetection || {};
|
| 645 |
+
const artist = (d.artist || '').toLowerCase().replace(/[^\w]/g, '').slice(0, 10);
|
| 646 |
+
const title = (d.title || '').toLowerCase().replace(/[^\w]/g, '').slice(0, 10);
|
| 647 |
+
return `${artist}_${title}`;
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
getRelevantLearnings(currentDetection) {
|
| 651 |
+
const context = this.buildContextKey();
|
| 652 |
+
const learnings = {};
|
| 653 |
+
|
| 654 |
+
for (const [key, corrections] of Object.entries(this.learningData)) {
|
| 655 |
+
if (key.startsWith(context)) {
|
| 656 |
+
const field = key.split(':')[1];
|
| 657 |
+
// Get most common correction
|
| 658 |
+
const counts = {};
|
| 659 |
+
corrections.forEach(c => {
|
| 660 |
+
const k = `${c.from}->${c.to}`;
|
| 661 |
+
counts[k] = (counts[k] || 0) + 1;
|
| 662 |
+
});
|
| 663 |
+
const mostCommon = Object.entries(counts).sort((a, b) => b[1] - a[1])[0];
|
| 664 |
+
if (mostCommon && mostCommon[1] >= 2) { // Need at least 2 occurrences
|
| 665 |
+
const [from, to] = mostCommon[0].split('->');
|
| 666 |
+
learnings[field] = { from, to, confidence: mostCommon[1] };
|
| 667 |
+
}
|
| 668 |
+
}
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
return learnings;
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
hasLearnedCorrections() {
|
| 675 |
+
return Object.keys(this.getRelevantLearnings(this.currentDetection)).length > 0;
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
applyLearnedCorrections(data) {
|
| 679 |
+
const learnings = this.getRelevantLearnings(data);
|
| 680 |
+
|
| 681 |
+
for (const [field, learning] of Object.entries(learnings)) {
|
| 682 |
+
if (data[field] === learning.from) {
|
| 683 |
+
this.corrections[field] = learning.to;
|
| 684 |
+
console.log(`Applied learned correction: ${field} "${learning.from}" -> "${learning.to}"`);
|
| 685 |
+
}
|
| 686 |
+
}
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
updateDetectedFieldsDisplay() {
|
| 690 |
+
// Remove old detection display and show updated one
|
| 691 |
+
const messages = this.shadowRoot.querySelectorAll('.message.ai');
|
| 692 |
+
messages.forEach(m => {
|
| 693 |
+
if (m.querySelector('.detected-fields')) {
|
| 694 |
+
m.remove();
|
| 695 |
+
}
|
| 696 |
+
});
|
| 697 |
+
|
| 698 |
+
// Re-show with corrections applied
|
| 699 |
+
const correctedData = { ...this.currentDetection, ...this.corrections };
|
| 700 |
+
this.showDetectionResults(correctedData);
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
confirmAllFields() {
|
| 704 |
+
this.addMessage(
|
| 705 |
+
"Great! All information confirmed. I'll use these details for your listing.",
|
| 706 |
+
'ai',
|
| 707 |
+
{ meta: 'Proceeding with verified information' }
|
| 708 |
+
);
|
| 709 |
+
|
| 710 |
+
this.dispatchEvent(new CustomEvent('all-confirmed', {
|
| 711 |
+
detail: {
|
| 712 |
+
data: { ...this.currentDetection, ...this.corrections },
|
| 713 |
+
corrections: this.corrections
|
| 714 |
+
},
|
| 715 |
+
bubbles: true,
|
| 716 |
+
composed: true
|
| 717 |
+
}));
|
| 718 |
+
}
|
| 719 |
+
|
| 720 |
+
getCorrectedData() {
|
| 721 |
+
return { ...this.currentDetection, ...this.corrections };
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
clear() {
|
| 725 |
+
this.messages = [];
|
| 726 |
+
this.corrections = {};
|
| 727 |
+
this.currentDetection = null;
|
| 728 |
+
this.pendingCorrection = null;
|
| 729 |
+
|
| 730 |
+
const messagesArea = this.shadowRoot.getElementById('messagesArea');
|
| 731 |
+
messagesArea.innerHTML = `
|
| 732 |
+
<div class="message ai">
|
| 733 |
+
Hi! I'll analyze your record photos and help identify the details. If I get anything wrong, just correct me—I'll learn from it for next time!
|
| 734 |
+
</div>
|
| 735 |
+
`;
|
| 736 |
+
}
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
customElements.define('ai-chat', AIChat);
|
index.html
CHANGED
|
@@ -46,8 +46,7 @@
|
|
| 46 |
</div>
|
| 47 |
<!-- Main Content -->
|
| 48 |
<main class="pt-20 pb-12 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
|
| 49 |
-
|
| 50 |
-
<!-- Hero Section -->
|
| 51 |
<section class="mb-12 text-center">
|
| 52 |
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 border border-primary/30 mb-6">
|
| 53 |
<i data-feather="disc" class="w-4 h-4 text-primary"></i>
|
|
@@ -73,15 +72,18 @@
|
|
| 73 |
<section class="mb-12">
|
| 74 |
<div class="bg-surface-light rounded-2xl p-8 border border-gray-800">
|
| 75 |
<div class="flex flex-col md:flex-row gap-8">
|
| 76 |
-
|
| 77 |
<!-- Upload Zone -->
|
| 78 |
<div class="flex-1">
|
| 79 |
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
| 80 |
<i data-feather="upload-cloud" class="w-5 h-5 text-secondary"></i>
|
| 81 |
Upload Record Photos
|
| 82 |
</h2>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
<div id="dropZone" class="border-2 border-dashed border-gray-600 rounded-xl p-12 text-center transition-all hover:border-primary hover:bg-primary/5 cursor-pointer group relative">
|
| 84 |
-
|
| 85 |
<div class="w-12 h-12 border-4 border-primary/30 border-t-primary rounded-full animate-spin mb-3"></div>
|
| 86 |
<p class="text-sm text-gray-400 mb-4">Analyzing images with AI...</p>
|
| 87 |
<!-- Analysis Progress Bar -->
|
|
@@ -350,6 +352,7 @@
|
|
| 350 |
<script src="components/enhanced-ocr-service.js"></script>
|
| 351 |
<script src="components/discogs-service.js"></script>
|
| 352 |
<script src="components/deepseek-service.js"></script>
|
|
|
|
| 353 |
<!-- Main Script -->
|
| 354 |
<script src="script.js"></script>
|
| 355 |
<script>feather.replace();</script>
|
|
|
|
| 46 |
</div>
|
| 47 |
<!-- Main Content -->
|
| 48 |
<main class="pt-20 pb-12 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
|
| 49 |
+
<!-- Hero Section -->
|
|
|
|
| 50 |
<section class="mb-12 text-center">
|
| 51 |
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 border border-primary/30 mb-6">
|
| 52 |
<i data-feather="disc" class="w-4 h-4 text-primary"></i>
|
|
|
|
| 72 |
<section class="mb-12">
|
| 73 |
<div class="bg-surface-light rounded-2xl p-8 border border-gray-800">
|
| 74 |
<div class="flex flex-col md:flex-row gap-8">
|
|
|
|
| 75 |
<!-- Upload Zone -->
|
| 76 |
<div class="flex-1">
|
| 77 |
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
|
| 78 |
<i data-feather="upload-cloud" class="w-5 h-5 text-secondary"></i>
|
| 79 |
Upload Record Photos
|
| 80 |
</h2>
|
| 81 |
+
|
| 82 |
+
<!-- AI Chat Interface -->
|
| 83 |
+
<ai-chat id="aiChatBox" class="mb-4"></ai-chat>
|
| 84 |
+
|
| 85 |
<div id="dropZone" class="border-2 border-dashed border-gray-600 rounded-xl p-12 text-center transition-all hover:border-primary hover:bg-primary/5 cursor-pointer group relative">
|
| 86 |
+
<div id="uploadSpinner" class="absolute inset-0 bg-surface-light/90 rounded-xl flex flex-col items-center justify-center hidden z-10">
|
| 87 |
<div class="w-12 h-12 border-4 border-primary/30 border-t-primary rounded-full animate-spin mb-3"></div>
|
| 88 |
<p class="text-sm text-gray-400 mb-4">Analyzing images with AI...</p>
|
| 89 |
<!-- Analysis Progress Bar -->
|
|
|
|
| 352 |
<script src="components/enhanced-ocr-service.js"></script>
|
| 353 |
<script src="components/discogs-service.js"></script>
|
| 354 |
<script src="components/deepseek-service.js"></script>
|
| 355 |
+
<script src="components/ai-chat.js"></script>
|
| 356 |
<!-- Main Script -->
|
| 357 |
<script src="script.js"></script>
|
| 358 |
<script>feather.replace();</script>
|