Improved popup again.
Browse files- chat-copil2-web.html +38 -30
chat-copil2-web.html
CHANGED
|
@@ -5,22 +5,22 @@
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
| 6 |
<title>Local Chat with Transformers.js</title>
|
| 7 |
<style>
|
| 8 |
-
body
|
| 9 |
-
header,
|
| 10 |
-
header
|
| 11 |
-
select,button,textarea,input
|
| 12 |
-
button
|
| 13 |
-
main
|
| 14 |
-
.chat
|
| 15 |
-
.msg
|
| 16 |
-
.msg.user
|
| 17 |
-
.bubble
|
| 18 |
-
.msg.user .bubble
|
| 19 |
-
.msg.assistant .bubble
|
| 20 |
-
.toast
|
| 21 |
-
.toast.show
|
| 22 |
-
.modal-backdrop
|
| 23 |
-
.modal
|
| 24 |
</style>
|
| 25 |
</head>
|
| 26 |
<body>
|
|
@@ -88,9 +88,7 @@ function showTokenModal(){
|
|
| 88 |
tokenModal.hidden=false;
|
| 89 |
tokenInput.value=localStorage.getItem('hf_token')||'';
|
| 90 |
}
|
| 91 |
-
function hideTokenModal(){
|
| 92 |
-
tokenModal.hidden=true;
|
| 93 |
-
}
|
| 94 |
function setToken(tok){
|
| 95 |
if(tok){ env.HF_TOKEN=tok; localStorage.setItem('hf_token',tok);}
|
| 96 |
else{ delete env.HF_TOKEN; localStorage.removeItem('hf_token');}
|
|
@@ -103,8 +101,8 @@ async function withAuthRetry(fn){
|
|
| 103 |
return await fn();
|
| 104 |
} catch(e){
|
| 105 |
if(isUnauthorizedError(e)){
|
| 106 |
-
showTokenModal();
|
| 107 |
return new Promise((resolve,reject)=>{
|
|
|
|
| 108 |
tokenModal.querySelector('#token-save').onclick=()=>{
|
| 109 |
const val=tokenInput.value.trim();
|
| 110 |
hideTokenModal();
|
|
@@ -135,9 +133,17 @@ async function generate(text,bubble){
|
|
| 135 |
const opts={max_new_tokens:128,temperature:0.7,top_p:0.9,repetition_penalty:1.1};
|
| 136 |
let streamObj; try{ streamObj=state.pipe(text,{...opts,stream:true}); }catch{}
|
| 137 |
if(isAsyncIterable(streamObj)){
|
| 138 |
-
let full='';
|
| 139 |
-
const
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
}
|
| 142 |
const out=await withAuthRetry(()=>state.pipe(text,{...opts,stream:false}));
|
| 143 |
const textOut=Array.isArray(out)?(out[0]?.generated_text||out[0]?.text): (out.generated_text||out.text)||'';
|
|
@@ -145,18 +151,20 @@ async function generate(text,bubble){
|
|
| 145 |
}
|
| 146 |
async function onSend(){
|
| 147 |
const val=inputEl.value.trim(); if(!val) return;
|
| 148 |
-
inputEl.value='';
|
| 149 |
-
addMessage('user',val);
|
| 150 |
-
const bub=addMessage('assistant','…');
|
| 151 |
try { await generate(val,bub); }
|
| 152 |
catch(e){ bub.textContent='Error: '+(e.message||e); }
|
| 153 |
}
|
| 154 |
sendBtn.onclick=onSend;
|
| 155 |
inputEl.onkeydown=e=>{ if(e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); onSend(); } };
|
| 156 |
-
document.getElementById('set-token').onclick=()=>{
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
addMessage('assistant','Hello! Fully local via Transformers.js. Choose model and send a message.');
|
| 162 |
</script>
|
|
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
| 6 |
<title>Local Chat with Transformers.js</title>
|
| 7 |
<style>
|
| 8 |
+
body{margin:0;font-family:system-ui,sans-serif;background:#0f1115;color:#e8eaf0;display:grid;grid-template-rows:auto 1fr auto;height:100vh;}
|
| 9 |
+
header,footer{background:#171a21;padding:8px 12px;border-bottom:1px solid #2a2f39;}
|
| 10 |
+
header{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
|
| 11 |
+
select,button,textarea,input{background:#11141a;color:#e8eaf0;border:1px solid #2a2f39;border-radius:6px;padding:6px 8px;font:inherit;}
|
| 12 |
+
button{cursor:pointer;}
|
| 13 |
+
main{overflow:auto;padding:10px;}
|
| 14 |
+
.chat{display:flex;flex-direction:column;gap:10px;max-width:900px;margin:auto;}
|
| 15 |
+
.msg{display:flex;}
|
| 16 |
+
.msg.user{justify-content:flex-end;}
|
| 17 |
+
.bubble{padding:8px 10px;border-radius:8px;max-width:80%;white-space:pre-wrap;word-break:break-word;}
|
| 18 |
+
.msg.user .bubble{background:#1b2433;}
|
| 19 |
+
.msg.assistant .bubble{background:#141923;}
|
| 20 |
+
.toast{position:fixed;bottom:12px;left:50%;transform:translateX(-50%);background:#11141a;padding:6px 10px;border:1px solid #2a2f39;border-radius:6px;opacity:0;transition:opacity .2s;}
|
| 21 |
+
.toast.show{opacity:1;}
|
| 22 |
+
.modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);display:grid;place-items:center;}
|
| 23 |
+
.modal{background:#171a21;padding:16px;border-radius:8px;width:min(400px,90%);}
|
| 24 |
</style>
|
| 25 |
</head>
|
| 26 |
<body>
|
|
|
|
| 88 |
tokenModal.hidden=false;
|
| 89 |
tokenInput.value=localStorage.getItem('hf_token')||'';
|
| 90 |
}
|
| 91 |
+
function hideTokenModal(){ tokenModal.hidden=true; }
|
|
|
|
|
|
|
| 92 |
function setToken(tok){
|
| 93 |
if(tok){ env.HF_TOKEN=tok; localStorage.setItem('hf_token',tok);}
|
| 94 |
else{ delete env.HF_TOKEN; localStorage.removeItem('hf_token');}
|
|
|
|
| 101 |
return await fn();
|
| 102 |
} catch(e){
|
| 103 |
if(isUnauthorizedError(e)){
|
|
|
|
| 104 |
return new Promise((resolve,reject)=>{
|
| 105 |
+
showTokenModal();
|
| 106 |
tokenModal.querySelector('#token-save').onclick=()=>{
|
| 107 |
const val=tokenInput.value.trim();
|
| 108 |
hideTokenModal();
|
|
|
|
| 133 |
const opts={max_new_tokens:128,temperature:0.7,top_p:0.9,repetition_penalty:1.1};
|
| 134 |
let streamObj; try{ streamObj=state.pipe(text,{...opts,stream:true}); }catch{}
|
| 135 |
if(isAsyncIterable(streamObj)){
|
| 136 |
+
let full=''; try{
|
| 137 |
+
for await (const out of streamObj){
|
| 138 |
+
const t=(out.token && out.token.text)||out.text||''; full+=t; bubble.textContent=full;
|
| 139 |
+
}
|
| 140 |
+
return;
|
| 141 |
+
}catch(e){
|
| 142 |
+
if(isUnauthorizedError(e)){
|
| 143 |
+
await withAuthRetry(()=>generate(text,bubble)); return;
|
| 144 |
+
}
|
| 145 |
+
throw e;
|
| 146 |
+
}
|
| 147 |
}
|
| 148 |
const out=await withAuthRetry(()=>state.pipe(text,{...opts,stream:false}));
|
| 149 |
const textOut=Array.isArray(out)?(out[0]?.generated_text||out[0]?.text): (out.generated_text||out.text)||'';
|
|
|
|
| 151 |
}
|
| 152 |
async function onSend(){
|
| 153 |
const val=inputEl.value.trim(); if(!val) return;
|
| 154 |
+
inputEl.value=''; addMessage('user',val); const bub=addMessage('assistant','…');
|
|
|
|
|
|
|
| 155 |
try { await generate(val,bub); }
|
| 156 |
catch(e){ bub.textContent='Error: '+(e.message||e); }
|
| 157 |
}
|
| 158 |
sendBtn.onclick=onSend;
|
| 159 |
inputEl.onkeydown=e=>{ if(e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); onSend(); } };
|
| 160 |
+
document.getElementById('set-token').onclick=()=>{
|
| 161 |
+
showTokenModal();
|
| 162 |
+
tokenModal.querySelector('#token-save').onclick=()=>{
|
| 163 |
+
const val=tokenInput.value.trim();
|
| 164 |
+
hideTokenModal(); setToken(val); showToast(val?'Token saved':'Token cleared');
|
| 165 |
+
};
|
| 166 |
+
tokenModal.querySelector('#token-cancel').onclick=()=>hideTokenModal();
|
| 167 |
+
};
|
| 168 |
|
| 169 |
addMessage('assistant','Hello! Fully local via Transformers.js. Choose model and send a message.');
|
| 170 |
</script>
|