Trina-QwQ commited on
Commit
bca2e99
·
verified ·
1 Parent(s): e776a8a

Upload wtc.html

Browse files
Files changed (1) hide show
  1. wtc.html +258 -0
wtc.html ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>WT - Copilot</title>
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ display: flex;
10
+ height: 100vh;
11
+ font-family: "PingFang SC", "Microsoft YaHei", serif;
12
+ background-color: #f5f5f5;
13
+ }
14
+
15
+ /* 侧边栏 */
16
+ #sidebar {
17
+ width: 280px;
18
+ background: #fff;
19
+ border-right: 1px solid #ddd;
20
+ padding: 20px;
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: 15px;
24
+ box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05);
25
+ z-index: 10;
26
+ }
27
+
28
+ .setting-item {
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 5px;
32
+ }
33
+
34
+ .setting-item label {
35
+ font-size: 12px;
36
+ color: #666;
37
+ font-weight: bold;
38
+ }
39
+
40
+ input {
41
+ padding: 8px;
42
+ border: 1px solid #ccc;
43
+ border-radius: 4px;
44
+ }
45
+
46
+ /* 编辑区域 */
47
+ #main-container {
48
+ flex: 1;
49
+ padding: 40px;
50
+ display: flex;
51
+ justify-content: center;
52
+ overflow-y: auto;
53
+ }
54
+
55
+ #editor {
56
+ width: 100%;
57
+ max-width: 800px;
58
+ min-height: 85vh;
59
+ background: white;
60
+ padding: 40px;
61
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
62
+ outline: none;
63
+ font-size: 18px;
64
+ line-height: 1.8;
65
+ color: #333;
66
+ white-space: pre-wrap;
67
+ word-wrap: break-word;
68
+ /* 确保内容垂直对齐 */
69
+ display: block;
70
+ }
71
+
72
+ /* Ghost Text 样式 - 关键:禁止选中,颜色淡化 */
73
+ .ghost-text {
74
+ color: #aaa;
75
+ user-select: none;
76
+ pointer-events: none;
77
+ font-style: italic;
78
+ }
79
+
80
+ .status-box {
81
+ font-size: 12px;
82
+ margin-top: auto;
83
+ color: #888;
84
+ background: #f9f9f9;
85
+ padding: 10px;
86
+ border-radius: 5px;
87
+ }
88
+
89
+ .kbd {
90
+ background: #eee;
91
+ padding: 2px 4px;
92
+ border-radius: 3px;
93
+ border: 1px solid #ccc;
94
+ color: #333;
95
+ }
96
+ </style>
97
+ </head>
98
+ <body>
99
+
100
+ <div id="sidebar">
101
+ <h3>WTC - WebUI</h3>
102
+ <div class="setting-item">
103
+ <label>API Endpoint</label>
104
+ <input type="text" id="api-url" value="http://localhost:8080/v1/chat/completions">
105
+ </div>
106
+ <div class="setting-item">
107
+ <label>Temperature</label>
108
+ <input type="number" id="temp" value="0.7" step="0.1" min="0" max="2">
109
+ </div>
110
+ <div class="setting-item">
111
+ <label>Max Tokens</label>
112
+ <input type="number" id="max-tokens" value="150">
113
+ </div>
114
+
115
+ <div class="status-box">
116
+ <p>✨ <span class="kbd">Tab</span> 触发续写</p>
117
+ <p>🤝 <span class="kbd">Tab</span> 接受建议</p>
118
+ <p>Powered by WT-Copilot™ | Trina AI Lab</p>
119
+ <hr>
120
+ <p id="info-display">状态:就绪</p>
121
+ </div>
122
+ </div>
123
+
124
+ <div id="main-container">
125
+ <div id="editor" contenteditable="true" spellcheck="false">在这里输入故事开头...</div>
126
+ </div>
127
+
128
+ <script>
129
+ const editor = document.getElementById('editor');
130
+ const infoDisplay = document.getElementById('info-display');
131
+ let isFetching = false;
132
+ editor.addEventListener('paste', (e) => {
133
+ e.preventDefault();
134
+ const text = (e.originalEvent || e).clipboardData.getData('text/plain');
135
+ document.execCommand('insertText', false, text);
136
+ });
137
+
138
+ function periodicSanitize() {
139
+ if (isFetching) return;
140
+
141
+ const ghost = editor.querySelector('.ghost-text');
142
+ if (!ghost) {
143
+ const hasComplexHTML = Array.from(editor.childNodes).some(node =>
144
+ node.nodeType === 1 && node.tagName !== 'SPAN' && node.tagName !== 'BR'
145
+ );
146
+ if (hasComplexHTML) {
147
+ const selection = window.getSelection();
148
+ const offset = selection.focusOffset;
149
+ editor.innerText = editor.innerText;
150
+ }
151
+ }
152
+ }
153
+
154
+ setInterval(periodicSanitize, 5000);
155
+
156
+ function getContext() {
157
+ const tempDiv = document.createElement('div');
158
+ tempDiv.innerHTML = editor.innerHTML;
159
+ const ghost = tempDiv.querySelector('.ghost-text');
160
+ if (ghost) ghost.remove();
161
+
162
+ const fullText = tempDiv.innerText;
163
+ return fullText.slice(-1000);
164
+ }
165
+
166
+ function removeGhost() {
167
+ const ghost = editor.querySelector('.ghost-text');
168
+ if (ghost) ghost.remove();
169
+ }
170
+
171
+ function acceptGhost() {
172
+ const ghost = editor.querySelector('.ghost-text');
173
+ if (ghost) {
174
+ const text = ghost.innerText;
175
+ removeGhost();
176
+ document.execCommand('insertText', false, text);
177
+ }
178
+ }
179
+
180
+ async function fetchCompletion() {
181
+ if (isFetching) return;
182
+
183
+ const context = getContext();
184
+ const apiUrl = document.getElementById('api-url').value;
185
+ const temp = parseFloat(document.getElementById('temp').value);
186
+ const maxTokens = parseInt(document.getElementById('max-tokens').value);
187
+
188
+ isFetching = true;
189
+ infoDisplay.innerText = "状态:AI 正在思考...";
190
+
191
+ try {
192
+ const response = await fetch(apiUrl, {
193
+ method: 'POST',
194
+ headers: {'Content-Type': 'application/json'},
195
+ body: JSON.stringify({
196
+ "messages": [{"role": "user", "content": `续写:${context}\n/no_think`}],
197
+ "temperature": temp,
198
+ "max_tokens": maxTokens
199
+ })
200
+ });
201
+ const data = await response.json();
202
+ let rawContent = data.choices[0].message.content;
203
+ const cleanContent = rawContent.replace(/<think>[\s\S]*?<\/think>/g, '').trimStart();
204
+
205
+ if (cleanContent) {
206
+ showGhost(cleanContent);
207
+ infoDisplay.innerText = "状态:已生成 (Tab 接受)";
208
+ } else {
209
+ infoDisplay.innerText = "状态:未生成有效补全";
210
+ }
211
+ } catch (error) {
212
+ infoDisplay.innerText = "状态:API 连接失败";
213
+ } finally {
214
+ isFetching = false;
215
+ }
216
+ }
217
+
218
+ function showGhost(text) {
219
+ removeGhost();
220
+ const selection = window.getSelection();
221
+ if (selection.rangeCount > 0) {
222
+ const range = selection.getRangeAt(0);
223
+ const ghostSpan = document.createElement('span');
224
+ ghostSpan.className = 'ghost-text';
225
+ ghostSpan.innerText = text;
226
+
227
+ range.insertNode(ghostSpan);
228
+ range.setStartBefore(ghostSpan);
229
+ range.collapse(true);
230
+ }
231
+ }
232
+
233
+ editor.addEventListener('keydown', (e) => {
234
+ const hasGhost = !!editor.querySelector('.ghost-text');
235
+
236
+ if (e.key === 'Tab') {
237
+ e.preventDefault();
238
+ if (hasGhost) {
239
+ acceptGhost();
240
+ } else {
241
+ fetchCompletion();
242
+ }
243
+ return;
244
+ }
245
+ if (hasGhost && !['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) {
246
+ removeGhost();
247
+ infoDisplay.innerText = "状态:就绪";
248
+ }
249
+ });
250
+ editor.addEventListener('focus', function () {
251
+ if (this.innerText === '在这里输入故事开头...') {
252
+ this.innerText = '';
253
+ }
254
+ }, {once: true});
255
+ </script>
256
+
257
+ </body>
258
+ </html>