Skip to content

Commit 3a3b422

Browse files
committed
feat: Support AI search
1 parent 72fc069 commit 3a3b422

3 files changed

Lines changed: 90 additions & 24 deletions

File tree

packages/webapp/cf/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,17 @@ export default {
135135
});
136136
}
137137
const q = params.get('q') || '';
138+
const l = params.get('l') || req.headers.get('Accept-Language') || 'en';
138139
const [values] = await embedding(env.AI, [q]);
139140
const res = await env.GAMES_SEARCH.query(values, { returnMetadata: true });
140141
const messages = [
141142
{
142143
role: 'system',
143144
content: `You are an application assistant.
144-
Response user input based on the context and your existing knowledge.
145145
146146
Requirements:
147-
1. Language: respond in (${req.headers.get('Accept-Language') ?? 'en'}).
148-
2. Maximum 1000 words.
147+
1. Respond briefly to user input based on context.
148+
2. Language: respond in (${l}).
149149
150150
Context:
151151
${res.matches.map((e) => (e.metadata as any).text).join('\n\n---\n\n')}

packages/webapp/src/modules/search.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { DuoyunOptionsElement, Option } from 'duoyun-ui/elements/options';
1818
import { createPath } from 'duoyun-ui/elements/route';
1919
import { getDisplayKey, hotkeys, isMac } from 'duoyun-ui/lib/hotkeys';
2020
import { locale } from 'duoyun-ui/lib/locale';
21+
import { debounce } from 'duoyun-ui/lib/timer';
2122
import { isNotNullish } from 'duoyun-ui/lib/types';
2223
import { isIncludesString } from 'duoyun-ui/lib/utils';
2324
import { configure, getShortcut, SearchCommand, setSearchCommand, toggleSearchState } from 'src/configure';
@@ -36,8 +37,7 @@ import 'duoyun-ui/elements/list';
3637
import 'duoyun-ui/elements/options';
3738
import 'duoyun-ui/elements/paragraph';
3839
import 'duoyun-ui/elements/space';
39-
40-
import { debounce } from 'duoyun-ui/lib/timer';
40+
import 'src/modules/sse';
4141

4242
const style = css`
4343
:scope {
@@ -224,7 +224,7 @@ export class MSearchElement extends GemElement {
224224
};
225225

226226
#genHelpOptions = (): Option[] => {
227-
return this.#helpMessages
227+
const result = this.#helpMessages
228228
.map((value) => {
229229
if (!this.#matchSearch(value)) return;
230230
const [title, desc] = value.split('\n');
@@ -244,6 +244,13 @@ export class MSearchElement extends GemElement {
244244
};
245245
})
246246
.filter(isNotNullish);
247+
return result.length
248+
? result
249+
: [
250+
{
251+
label: html`<m-sse prompt=${this.#state.search}></m-sse>`,
252+
},
253+
];
247254
};
248255

249256
#genFriendOptions = (): Option[] => {
@@ -395,6 +402,25 @@ export class MSearchElement extends GemElement {
395402
});
396403
}
397404

405+
const placeholder = getTempText(
406+
i18n.get(
407+
configure.searchCommand === SearchCommand.HELP
408+
? 'tooltip.docs.help'
409+
: configure.searchCommand === SearchCommand.SELECT_GAME
410+
? 'tooltip.game.change'
411+
: this.#isRooms
412+
? 'placeholder.roomSearch'
413+
: configure.user?.playing
414+
? 'placeholder.searchPlaying'
415+
: 'placeholder.search',
416+
configure.searchCommand === SearchCommand.HELP
417+
? getShortcut('OPEN_SEARCH', true)
418+
: configure.searchCommand === SearchCommand.SELECT_GAME
419+
? ''
420+
: getShortcut('OPEN_SEARCH', true),
421+
),
422+
);
423+
398424
return html`
399425
<div class="input-wrap">
400426
<dy-input
@@ -405,24 +431,7 @@ export class MSearchElement extends GemElement {
405431
.icon=${this.#getSearchCommandIcon(configure.searchCommand)}
406432
@change=${this.#onChange}
407433
@keydown=${this.#onKeydownInput}
408-
placeholder=${getTempText(
409-
i18n.get(
410-
configure.searchCommand === SearchCommand.HELP
411-
? 'tooltip.docs.help'
412-
: configure.searchCommand === SearchCommand.SELECT_GAME
413-
? 'tooltip.game.change'
414-
: this.#isRooms
415-
? 'placeholder.roomSearch'
416-
: configure.user?.playing
417-
? 'placeholder.searchPlaying'
418-
: 'placeholder.search',
419-
configure.searchCommand === SearchCommand.HELP
420-
? getShortcut('OPEN_SEARCH', true)
421-
: configure.searchCommand === SearchCommand.SELECT_GAME
422-
? ''
423-
: getShortcut('OPEN_SEARCH', true),
424-
),
425-
)}
434+
placeholder=${placeholder}
426435
></dy-input>
427436
</div>
428437
<div class="result">

packages/webapp/src/modules/sse.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { adoptedStyle, attribute, createState, css, customElement, effect, GemElement, html } from '@mantou/gem';
2+
import { marked } from 'marked';
3+
import { eventStream } from 'src/services';
4+
5+
import 'duoyun-ui/elements/unsafe';
6+
7+
import { i18n } from 'src/i18n/basic';
8+
9+
const style = css`
10+
:scope {
11+
display: block;
12+
white-space: normal;
13+
}
14+
`;
15+
16+
const contentStyle = css`
17+
:host > :first-child {
18+
margin-top: 0;
19+
}
20+
:host > :last-child {
21+
margin-bottom: 0;
22+
}
23+
`;
24+
25+
@customElement('m-sse')
26+
@adoptedStyle(style)
27+
export class MSeeElement extends GemElement {
28+
@attribute prompt: string;
29+
30+
#state = createState({ md: '' });
31+
32+
#control = new AbortController();
33+
34+
@effect((i) => [i.prompt])
35+
#req = () => {
36+
const initMd = 'Thinking...';
37+
this.#state({ md: initMd });
38+
const timer = setTimeout(async () => {
39+
const url = `https://nesbox.709922234.workers.dev/completions?${new URLSearchParams({ q: this.prompt, l: i18n.currentLanguage })}`;
40+
const iter = eventStream(url, { signal: this.#control.signal });
41+
for await (const chunk of iter) {
42+
const append = chunk.choices?.at(0)?.delta?.content;
43+
if (!append) continue;
44+
this.#state({ md: (this.#state.md === initMd ? '' : this.#state.md) + append });
45+
}
46+
}, 2000);
47+
return () => {
48+
clearTimeout(timer);
49+
this.#control.abort();
50+
this.#control = new AbortController();
51+
};
52+
};
53+
54+
render = () => {
55+
return html`<dy-unsafe html=${marked.parse(this.#state.md)} .styles=${contentStyle}></dy-unsafe>`;
56+
};
57+
}

0 commit comments

Comments
 (0)