Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
KotGPT
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Michael Pastushkov
KotGPT
Commits
3f1bf550
Commit
3f1bf550
authored
Aug 18, 2025
by
Michael Pastushkov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
ecbc30ca
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
13 additions
and
87 deletions
+13
-87
static/www/1.html
+13
-87
No files found.
static/www/1.html
View file @
3f1bf550
...
...
@@ -7,6 +7,7 @@
<link
href=
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel=
"stylesheet"
/>
<style>
:root
{
/* tune these if footer/composer sizes change */
--footer-h
:
2.75rem
;
/* fixed footer height (approx) */
--composer-h
:
6.5rem
;
/* composer block height (approx) */
--bottom-pad
:
calc
(
var
(
--footer-h
)
+
var
(
--composer-h
)
+
1rem
);
...
...
@@ -24,7 +25,7 @@
main
{
flex
:
1
1
auto
;
min-height
:
0
;
/*
IMPORTANT:
allow flex child to actually scroll */
min-height
:
0
;
/* allow flex child to actually scroll */
overflow-y
:
auto
;
/* scrollable area */
position
:
relative
;
padding-bottom
:
var
(
--bottom-pad
);
/* room for composer + fixed footer */
...
...
@@ -70,30 +71,6 @@
max-width
:
520px
;
width
:
100%
;
height
:
auto
;
filter
:
drop-shadow
(
0
6px
16px
rgba
(
0
,
0
,
0
,
.15
));
opacity
:
.98
;
}
/* Scroll button: FIXED, toggled by .show */
#scrollBtn
{
position
:
fixed
;
right
:
1rem
;
bottom
:
calc
(
var
(
--footer-h
)
+
var
(
--composer-h
)
+
1rem
);
/* sits above composer + footer */
z-index
:
1100
;
display
:
none
;
/* hidden by default; JS toggles .show */
align-items
:
center
;
justify-content
:
center
;
border
:
none
;
border-radius
:
999px
;
width
:
40px
;
height
:
40px
;
background
:
rgba
(
13
,
110
,
253
,
.95
);
color
:
#fff
;
box-shadow
:
0
4px
14px
rgba
(
0
,
0
,
0
,
.18
);
cursor
:
pointer
;
transition
:
opacity
.18s
ease-in-out
,
transform
.12s
ease-in-out
;
opacity
:
.96
;
}
#scrollBtn
.show
{
display
:
inline-flex
;
}
#scrollBtn
:hover
{
opacity
:
1
;
transform
:
translateY
(
-1px
);
}
#scrollBtn
svg
{
width
:
22px
;
height
:
22px
;
pointer-events
:
none
;
}
</style>
</head>
<body>
...
...
@@ -115,14 +92,6 @@
<div
id=
"thread"
></div>
</main>
<!-- Scroll button: visibility toggled by JS -->
<button
id=
"scrollBtn"
type=
"button"
aria-label=
"Scroll down"
title=
"Scroll down"
>
<svg
viewBox=
"0 0 24 24"
fill=
"currentColor"
aria-hidden=
"true"
>
<!-- down chevron -->
<path
d=
"M12 16a1 1 0 0 1-.7-.29l-6-6a1 1 0 1 1 1.4-1.42L12 13.59l5.3-5.3a1 1 0 0 1 1.4 1.42l-6 6A1 1 0 0 1 12 16z"
/>
</svg>
</button>
<!-- Composer (sticks above fixed footer) -->
<div
class=
"composer py-3"
>
<div
class=
"container"
>
...
...
@@ -142,7 +111,7 @@
<script
src=
"https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js"
></script>
<script
src=
"https://cdn.jsdelivr.net/npm/dompurify@3.1.6/dist/purify.min.js"
></script>
<script>
// --- i18n setup
(unchanged)
---
// --- i18n setup ---
const
DEFAULT_LOCALE
=
'en'
;
const
SUPPORTED
=
[
'en'
,
'ru'
];
let
I18N
=
{};
let
CURRENT_LOCALE
=
detectLocale
();
let
isRunning
=
false
;
...
...
@@ -159,7 +128,6 @@
const
goBtn
=
document
.
getElementById
(
'goBtn'
);
if
(
goBtn
)
goBtn
.
textContent
=
t
(
'buttons.go'
,
'Go'
);
const
stopBtn
=
document
.
getElementById
(
'stopBtn'
);
if
(
stopBtn
)
stopBtn
.
textContent
=
t
(
'buttons.stop'
,
'Stop'
);
const
disclaimer
=
document
.
getElementById
(
'disclaimer'
);
if
(
disclaimer
)
disclaimer
.
textContent
=
t
(
'footer.disclaimer'
,
'Beware: Cat can make mistakes.'
);
const
sb
=
document
.
getElementById
(
'scrollBtn'
);
if
(
sb
){
sb
.
setAttribute
(
'aria-label'
,
t
(
'buttons.scrollDown'
,
'Scroll down'
));
sb
.
title
=
t
(
'buttons.scrollDown'
,
'Scroll down'
);
}
setWelcomeImage
();
}
...
...
@@ -171,8 +139,7 @@
const
goBtn
=
document
.
getElementById
(
'goBtn'
);
const
stopBtn
=
document
.
getElementById
(
'stopBtn'
);
const
welcomeLogo
=
document
.
getElementById
(
'welcomeLogo'
);
const
mainScroll
=
document
.
getElementById
(
'mainScroll'
);
// intended scroll container
const
scrollBtn
=
document
.
getElementById
(
'scrollBtn'
);
// the button we toggle & click
const
mainScroll
=
document
.
getElementById
(
'mainScroll'
);
let
firstRequestDone
=
false
;
let
controller
=
null
;
...
...
@@ -195,43 +162,11 @@
if
(
img
)
targetEl
.
innerHTML
=
''
;
}
// --- figure out what is actually scrolling right now ---
function
isOverflowing
(
el
){
return
(
el
.
scrollHeight
-
el
.
clientHeight
)
>
4
;
}
function
currentScrollEl
(){
// Prefer main if it overflows, otherwise fall back to the page scroller
if
(
mainScroll
&&
isOverflowing
(
mainScroll
))
return
mainScroll
;
return
document
.
scrollingElement
||
document
.
documentElement
;
}
function
atBottom
(
el
){
return
(
el
.
scrollTop
+
el
.
clientHeight
)
>=
(
el
.
scrollHeight
-
4
);
// --- bottom helpers ---
function
atBottom
(){
return
(
mainScroll
.
scrollTop
+
mainScroll
.
clientHeight
)
>=
(
mainScroll
.
scrollHeight
-
4
);
}
function
updateScrollIndicator
(){
if
(
!
scrollBtn
)
return
;
const
el
=
currentScrollEl
();
if
(
isOverflowing
(
el
)
&&
!
atBottom
(
el
))
scrollBtn
.
classList
.
add
(
'show'
);
else
scrollBtn
.
classList
.
remove
(
'show'
);
}
// Click: scroll the **actual** scroll container to bottom
if
(
scrollBtn
)
{
scrollBtn
.
addEventListener
(
'click'
,
()
=>
{
const
el
=
currentScrollEl
();
const
top
=
el
.
scrollHeight
;
if
(
el
===
document
.
scrollingElement
||
el
===
document
.
documentElement
||
el
===
document
.
body
)
{
window
.
scrollTo
({
top
,
behavior
:
'smooth'
});
}
else
{
el
.
scrollTo
({
top
,
behavior
:
'smooth'
});
}
setTimeout
(
updateScrollIndicator
,
400
);
});
}
// Keep indicator state in sync (listen to both container + window)
if
(
mainScroll
)
mainScroll
.
addEventListener
(
'scroll'
,
updateScrollIndicator
,
{
passive
:
true
});
window
.
addEventListener
(
'scroll'
,
updateScrollIndicator
,
{
passive
:
true
});
window
.
addEventListener
(
'resize'
,
updateScrollIndicator
);
// --- composer lock ---
function
setComposerLocked
(
locked
){
isRunning
=
locked
;
...
...
@@ -249,8 +184,7 @@
if
(
!
firstRequestDone
){
welcomeLogo
?.
remove
();
firstRequestDone
=
true
;
}
const
elBefore
=
currentScrollEl
();
const
auto
=
atBottom
(
elBefore
);
const
auto
=
atBottom
();
const
userMsg
=
document
.
createElement
(
'div'
);
userMsg
.
className
=
'message user-message'
;
...
...
@@ -263,7 +197,6 @@
thread
.
appendChild
(
assistantMsg
);
if
(
auto
)
assistantMsg
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'start'
});
updateScrollIndicator
();
inputText
.
value
=
''
;
controller
=
new
AbortController
();
...
...
@@ -289,14 +222,9 @@
rafPending
=
false
;
if
(
!
receivedAny
){
clearThinkingCatIfPresent
(
assistantMsg
);
receivedAny
=
true
;
}
renderMarkdown
(
assistantMsg
,
rawMarkdown
);
// If user was at bottom before streaming, keep pinned to bottom of the active scroller
if
(
auto
)
{
const
el
=
currentScrollEl
();
el
.
scrollTop
=
el
.
scrollHeight
;
if
(
auto
&&
atBottom
()){
mainScroll
.
scrollTop
=
mainScroll
.
scrollHeight
;
}
updateScrollIndicator
();
});
};
...
...
@@ -321,7 +249,6 @@
}
finally
{
setComposerLocked
(
false
);
controller
=
null
;
updateScrollIndicator
();
}
}
...
...
@@ -331,18 +258,17 @@
}
// events
document
.
getElementById
(
'promptForm'
)
.
addEventListener
(
'submit'
,
e
=>
{
e
.
preventDefault
();
if
(
goBtn
.
style
.
display
!==
'none'
)
start
();
});
document
.
getElementById
(
'inputText'
)
.
addEventListener
(
'keydown'
,
e
=>
{
promptForm
.
addEventListener
(
'submit'
,
e
=>
{
e
.
preventDefault
();
if
(
goBtn
.
style
.
display
!==
'none'
)
start
();
});
inputText
.
addEventListener
(
'keydown'
,
e
=>
{
if
(
e
.
key
===
'Enter'
&&
!
e
.
shiftKey
&&
goBtn
.
style
.
display
!==
'none'
){
e
.
preventDefault
();
start
();
}
});
document
.
getElementById
(
'stopBtn'
)
.
addEventListener
(
'click'
,
stop
);
stopBtn
.
addEventListener
(
'click'
,
stop
);
// init
window
.
addEventListener
(
'DOMContentLoaded'
,
async
()
=>
{
try
{
await
fetch
(
'/api/clear'
,{
method
:
'GET'
});
}
catch
(
_
){}
await
loadLocale
(
CURRENT_LOCALE
);
inputText
.
focus
();
updateScrollIndicator
();
// initial state
});
</script>
</body>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment