Skip to content

Commit 9eb2124

Browse files
committed
Merge branch 'next' into release
2 parents 465190b + 45bc76d commit 9eb2124

File tree

4 files changed

+329
-7
lines changed

4 files changed

+329
-7
lines changed

assets/debug-inputs.html

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Web Input Debugger</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
padding: 20px;
11+
background: #f0f0f0;
12+
}
13+
.key-container {
14+
display: grid;
15+
grid-template-columns: repeat(3, 60px);
16+
gap: 5px;
17+
margin: 20px 0;
18+
}
19+
.key {
20+
width: 60px;
21+
height: 60px;
22+
border: 2px solid #333;
23+
display: flex;
24+
align-items: center;
25+
justify-content: center;
26+
font-weight: bold;
27+
background: white;
28+
position: relative;
29+
user-select: none;
30+
}
31+
.key.pressed {
32+
background: #90EE90;
33+
}
34+
.key .duration {
35+
position: absolute;
36+
bottom: 2px;
37+
font-size: 10px;
38+
}
39+
.key .count {
40+
position: absolute;
41+
top: 2px;
42+
right: 2px;
43+
font-size: 10px;
44+
}
45+
.controls {
46+
margin: 20px 0;
47+
padding: 10px;
48+
background: white;
49+
border-radius: 5px;
50+
}
51+
.wasd-container {
52+
position: relative;
53+
width: 190px;
54+
height: 130px;
55+
}
56+
#KeyW {
57+
position: absolute;
58+
left: 65px;
59+
top: 0;
60+
}
61+
#KeyA {
62+
position: absolute;
63+
left: 0;
64+
top: 65px;
65+
}
66+
#KeyS {
67+
position: absolute;
68+
left: 65px;
69+
top: 65px;
70+
}
71+
#KeyD {
72+
position: absolute;
73+
left: 130px;
74+
top: 65px;
75+
}
76+
.space-container {
77+
margin-top: 20px;
78+
}
79+
#Space {
80+
width: 190px;
81+
}
82+
</style>
83+
</head>
84+
<body>
85+
<div class="controls">
86+
<label>
87+
<input type="checkbox" id="repeatMode"> Use keydown repeat mode (auto key-up after 150ms of no repeat)
88+
</label>
89+
</div>
90+
91+
<div class="wasd-container">
92+
<div id="KeyW" class="key" data-code="KeyW">W</div>
93+
<div id="KeyA" class="key" data-code="KeyA">A</div>
94+
<div id="KeyS" class="key" data-code="KeyS">S</div>
95+
<div id="KeyD" class="key" data-code="KeyD">D</div>
96+
</div>
97+
98+
<div class="key-container">
99+
<div id="ControlLeft" class="key" data-code="ControlLeft">Ctrl</div>
100+
</div>
101+
102+
<div class="space-container">
103+
<div id="Space" class="key" data-code="Space">Space</div>
104+
</div>
105+
106+
<script>
107+
const keys = {};
108+
const keyStats = {};
109+
const pressStartTimes = {};
110+
const keyTimeouts = {};
111+
112+
function initKeyStats(code) {
113+
if (!keyStats[code]) {
114+
keyStats[code] = {
115+
pressCount: 0,
116+
duration: 0,
117+
startTime: 0
118+
};
119+
}
120+
}
121+
122+
function updateKeyVisuals(code) {
123+
const element = document.getElementById(code);
124+
if (!element) return;
125+
126+
const stats = keyStats[code];
127+
if (keys[code]) {
128+
element.classList.add('pressed');
129+
const currentDuration = ((Date.now() - stats.startTime) / 1000).toFixed(1);
130+
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="duration">${currentDuration}s</span><span class="count">${stats.pressCount}</span>`;
131+
} else {
132+
element.classList.remove('pressed');
133+
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="count">${stats.pressCount}</span>`;
134+
}
135+
}
136+
137+
function releaseKey(code) {
138+
keys[code] = false;
139+
if (pressStartTimes[code]) {
140+
keyStats[code].duration += (Date.now() - pressStartTimes[code]) / 1000;
141+
delete pressStartTimes[code];
142+
}
143+
updateKeyVisuals(code);
144+
}
145+
146+
function handleKeyDown(event) {
147+
const code = event.code;
148+
const isRepeatMode = document.getElementById('repeatMode').checked;
149+
150+
initKeyStats(code);
151+
152+
// Clear any existing timeout for this key
153+
if (keyTimeouts[code]) {
154+
clearTimeout(keyTimeouts[code]);
155+
delete keyTimeouts[code];
156+
}
157+
158+
if (isRepeatMode) {
159+
// In repeat mode, always handle the keydown
160+
if (!keys[code] || event.repeat) {
161+
keys[code] = true;
162+
if (!event.repeat) {
163+
// Only increment count on initial press, not repeats
164+
keyStats[code].pressCount++;
165+
keyStats[code].startTime = Date.now();
166+
pressStartTimes[code] = Date.now();
167+
}
168+
}
169+
170+
// Set timeout to release key if no repeat events come
171+
keyTimeouts[code] = setTimeout(() => {
172+
releaseKey(code);
173+
}, 150);
174+
} else {
175+
// In normal mode, only handle keydown if key is not already pressed
176+
if (!keys[code]) {
177+
keys[code] = true;
178+
keyStats[code].pressCount++;
179+
keyStats[code].startTime = Date.now();
180+
pressStartTimes[code] = Date.now();
181+
}
182+
}
183+
184+
updateKeyVisuals(code);
185+
event.preventDefault();
186+
}
187+
188+
function handleKeyUp(event) {
189+
const code = event.code;
190+
const isRepeatMode = document.getElementById('repeatMode').checked;
191+
192+
if (!isRepeatMode) {
193+
releaseKey(code);
194+
}
195+
196+
event.preventDefault();
197+
}
198+
199+
// Initialize all monitored keys
200+
const monitoredKeys = ['KeyW', 'KeyA', 'KeyS', 'KeyD', 'ControlLeft', 'Space'];
201+
monitoredKeys.forEach(code => {
202+
initKeyStats(code);
203+
const element = document.getElementById(code);
204+
if (element) {
205+
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="count">0</span>`;
206+
}
207+
});
208+
209+
// Start visual updates
210+
setInterval(() => {
211+
monitoredKeys.forEach(code => {
212+
if (keys[code]) {
213+
updateKeyVisuals(code);
214+
}
215+
});
216+
}, 100);
217+
218+
// Event listeners
219+
document.addEventListener('keydown', handleKeyDown);
220+
document.addEventListener('keyup', handleKeyUp);
221+
222+
// Handle mode changes
223+
document.getElementById('repeatMode').addEventListener('change', () => {
224+
// Release all keys when switching modes
225+
monitoredKeys.forEach(code => {
226+
if (keys[code]) {
227+
releaseKey(code);
228+
}
229+
if (keyTimeouts[code]) {
230+
clearTimeout(keyTimeouts[code]);
231+
delete keyTimeouts[code];
232+
}
233+
});
234+
});
235+
</script>
236+
</body>
237+
</html>

rsbuild.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ const appConfig = defineConfig({
193193
fs.copyFileSync('./assets/playground.html', './dist/playground.html')
194194
fs.copyFileSync('./assets/manifest.json', './dist/manifest.json')
195195
fs.copyFileSync('./assets/config.html', './dist/config.html')
196+
fs.copyFileSync('./assets/debug-inputs.html', './dist/debug-inputs.html')
196197
fs.copyFileSync('./assets/loading-bg.jpg', './dist/loading-bg.jpg')
197198
if (fs.existsSync('./assets/release.json')) {
198199
fs.copyFileSync('./assets/release.json', './dist/release.json')

src/react/Chat.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ div.chat-wrapper {
55
padding-left: calc(env(safe-area-inset-left) / 2);
66
padding-right: calc(env(safe-area-inset-right, 4px) / 2);
77
box-sizing: content-box;
8+
}
9+
10+
/* Only apply overflow hidden when not in mobile mode */
11+
div.chat-wrapper:not(.display-mobile):not(.input-mobile) {
812
overflow: hidden;
913
}
1014

@@ -69,7 +73,7 @@ div.chat-wrapper {
6973
top: 100%;
7074
padding-left: calc(env(safe-area-inset-left) / 2);
7175
margin-top: 14px;
72-
margin-left: 20px;
76+
margin-left: 40px;
7377
/* input height */
7478
}
7579

@@ -116,6 +120,11 @@ div.chat-wrapper {
116120
justify-content: flex-start;
117121
}
118122

123+
.input-mobile .chat-completions-items > div {
124+
padding: 4px 0;
125+
font-size: 10px;
126+
}
127+
119128
.input-mobile {
120129
top: 15px;
121130
position: absolute;

0 commit comments

Comments
 (0)