mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-19 15:26:59 +08:00
feat: use footnote for perplexity search results (#9851)
Support issue [BS-2475](https://linear.app/affine-design/issue/BS-2475). 
This commit is contained in:
@@ -1096,10 +1096,14 @@ test('CitationParser should replace citation placeholders with URLs', t => {
|
||||
const citations = ['https://example1.com', 'https://example2.com'];
|
||||
|
||||
const parser = new CitationParser();
|
||||
const result = parser.parse(content, citations);
|
||||
const result = parser.parse(content, citations) + parser.end();
|
||||
|
||||
const expected = [
|
||||
'This is [a] test sentence with [citations [^1]] and [^2] and [3].',
|
||||
`[^1]: {"type":"url","url":"${encodeURIComponent(citations[0])}"}`,
|
||||
`[^2]: {"type":"url","url":"${encodeURIComponent(citations[1])}"}`,
|
||||
].join('\n\n');
|
||||
|
||||
const expected =
|
||||
'This is [a] test sentence with [citations [[1](https://example1.com)]] and [[2](https://example2.com)] and [3].';
|
||||
t.is(result, expected);
|
||||
});
|
||||
|
||||
@@ -1130,10 +1134,18 @@ test('CitationParser should replace chunks of citation placeholders with URLs',
|
||||
let result = contents.reduce((acc, current) => {
|
||||
return acc + parser.parse(current, citations);
|
||||
}, '');
|
||||
result += parser.flush();
|
||||
result += parser.end();
|
||||
|
||||
const expected =
|
||||
'[[]]This is [a] test sentence with citations [[1](https://example1.com)] and [[2](https://example2.com)] and [[3](https://example3.com)] and [[4](https://example4.com)] and [[5](https://example5.com)] and [[6](https://example6.com)] and [7';
|
||||
const expected = [
|
||||
'[[]]This is [a] test sentence with citations [^1] and [^2] and [^3] and [^4] and [^5] and [^6] and [7',
|
||||
`[^1]: {"type":"url","url":"${encodeURIComponent(citations[0])}"}`,
|
||||
`[^2]: {"type":"url","url":"${encodeURIComponent(citations[1])}"}`,
|
||||
`[^3]: {"type":"url","url":"${encodeURIComponent(citations[2])}"}`,
|
||||
`[^4]: {"type":"url","url":"${encodeURIComponent(citations[3])}"}`,
|
||||
`[^5]: {"type":"url","url":"${encodeURIComponent(citations[4])}"}`,
|
||||
`[^6]: {"type":"url","url":"${encodeURIComponent(citations[5])}"}`,
|
||||
`[^7]: {"type":"url","url":"${encodeURIComponent(citations[6])}"}`,
|
||||
].join('\n\n');
|
||||
t.is(result, expected);
|
||||
});
|
||||
|
||||
@@ -1147,9 +1159,14 @@ test('CitationParser should not replace citation already with URLs', t => {
|
||||
];
|
||||
|
||||
const parser = new CitationParser();
|
||||
const result = parser.parse(content, citations);
|
||||
const result = parser.parse(content, citations) + parser.end();
|
||||
|
||||
const expected = content;
|
||||
const expected = [
|
||||
content,
|
||||
`[^1]: {"type":"url","url":"${encodeURIComponent(citations[0])}"}`,
|
||||
`[^2]: {"type":"url","url":"${encodeURIComponent(citations[1])}"}`,
|
||||
`[^3]: {"type":"url","url":"${encodeURIComponent(citations[2])}"}`,
|
||||
].join('\n\n');
|
||||
t.is(result, expected);
|
||||
});
|
||||
|
||||
@@ -1169,8 +1186,13 @@ test('CitationParser should not replace chunks of citation already with URLs', t
|
||||
let result = contents.reduce((acc, current) => {
|
||||
return acc + parser.parse(current, citations);
|
||||
}, '');
|
||||
result += parser.flush();
|
||||
result += parser.end();
|
||||
|
||||
const expected = contents.join('');
|
||||
const expected = [
|
||||
contents.join(''),
|
||||
`[^1]: {"type":"url","url":"${encodeURIComponent(citations[0])}"}`,
|
||||
`[^2]: {"type":"url","url":"${encodeURIComponent(citations[1])}"}`,
|
||||
`[^3]: {"type":"url","url":"${encodeURIComponent(citations[2])}"}`,
|
||||
].join('\n\n');
|
||||
t.is(result, expected);
|
||||
});
|
||||
|
||||
@@ -63,7 +63,10 @@ export class CitationParser {
|
||||
|
||||
private numberToken: string[] = [];
|
||||
|
||||
private citations: string[] = [];
|
||||
|
||||
public parse(content: string, citations: string[]) {
|
||||
this.citations = citations;
|
||||
let result = '';
|
||||
const contentArray = content.split('');
|
||||
for (const [index, char] of contentArray.entries()) {
|
||||
@@ -85,7 +88,7 @@ export class CitationParser {
|
||||
cIndex <= citations.length &&
|
||||
contentArray[index + 1] !== this.PARENTHESES_OPEN
|
||||
) {
|
||||
const content = `[[${cIndex}](${citations[cIndex - 1]})]`;
|
||||
const content = `[^${cIndex}]`;
|
||||
result += content;
|
||||
this.resetToken();
|
||||
} else {
|
||||
@@ -116,13 +119,26 @@ export class CitationParser {
|
||||
return result;
|
||||
}
|
||||
|
||||
public flush() {
|
||||
const content = this.getFullContent();
|
||||
public end() {
|
||||
return this.flush() + this.getFootnotes();
|
||||
}
|
||||
|
||||
private flush() {
|
||||
const content = this.getTokenContent();
|
||||
this.resetToken();
|
||||
return content;
|
||||
}
|
||||
|
||||
private getFullContent() {
|
||||
private getFootnotes() {
|
||||
const footnotes = this.citations.map((citation, index) => {
|
||||
return `[^${index + 1}]: {"type":"url","url":"${encodeURIComponent(
|
||||
citation
|
||||
)}"}`;
|
||||
});
|
||||
return '\n\n' + footnotes.join('\n\n');
|
||||
}
|
||||
|
||||
private getTokenContent() {
|
||||
return this.startToken.concat(this.numberToken, this.endToken).join('');
|
||||
}
|
||||
|
||||
@@ -208,7 +224,7 @@ export class PerplexityProvider implements CopilotTextToTextProvider {
|
||||
const { content } = data.choices[0].message;
|
||||
const { citations } = data;
|
||||
let result = parser.parse(content, citations);
|
||||
result += parser.flush();
|
||||
result += parser.end();
|
||||
return result;
|
||||
}
|
||||
} catch (e: any) {
|
||||
@@ -277,7 +293,7 @@ export class PerplexityProvider implements CopilotTextToTextProvider {
|
||||
}
|
||||
},
|
||||
flush(controller) {
|
||||
controller.enqueue(parser.flush());
|
||||
controller.enqueue(parser.end());
|
||||
controller.enqueue(null);
|
||||
},
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ import { css, html, nothing } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { debounce, throttle } from 'lodash-es';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import {
|
||||
EdgelessEditorActions,
|
||||
@@ -132,9 +132,6 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
||||
@query('.chat-panel-messages')
|
||||
accessor messagesContainer: HTMLDivElement | null = null;
|
||||
|
||||
@query('.message:nth-last-child(2)')
|
||||
accessor lastMessage: HTMLDivElement | null = null;
|
||||
|
||||
private _renderAIOnboarding() {
|
||||
return this.isLoading ||
|
||||
!this.host?.doc.get(FeatureFlagService).getFlag('enable_ai_onboarding')
|
||||
@@ -192,16 +189,6 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
||||
100
|
||||
);
|
||||
|
||||
private readonly _scrollIntoView = () => {
|
||||
if (!this.lastMessage) return;
|
||||
this.lastMessage.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
private readonly _throttledScrollIntoView = throttle(
|
||||
this._scrollIntoView,
|
||||
500
|
||||
);
|
||||
|
||||
protected override render() {
|
||||
const { items } = this.chatContextValue;
|
||||
const { isLoading } = this;
|
||||
@@ -300,12 +287,6 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
||||
);
|
||||
}
|
||||
|
||||
protected override updated() {
|
||||
if (this.chatContextValue.status === 'transmitting') {
|
||||
this._throttledScrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
renderItem(item: ChatItem, isLast: boolean) {
|
||||
const { status, error } = this.chatContextValue;
|
||||
const { host } = this;
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { Store } from '@blocksuite/affine/store';
|
||||
import { css, html, type PropertyValues } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
import { createRef, type Ref, ref } from 'lit/directives/ref.js';
|
||||
import { throttle } from 'lodash-es';
|
||||
|
||||
import { AIHelpIcon, SmallHintIcon } from '../_common/icons';
|
||||
import { AIProvider } from '../provider';
|
||||
@@ -191,6 +192,8 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) {
|
||||
this._chatMessages.value?.scrollToEnd();
|
||||
};
|
||||
|
||||
private readonly _throttledScrollToEnd = throttle(this._scrollToEnd, 1000);
|
||||
|
||||
private readonly _cleanupHistories = async () => {
|
||||
const notification = this.host.std.getOptional(NotificationProvider);
|
||||
if (!notification) return;
|
||||
@@ -229,7 +232,6 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
|
||||
if (
|
||||
!this.isLoading &&
|
||||
_changedProperties.has('chatContextValue') &&
|
||||
(this.chatContextValue.status === 'loading' ||
|
||||
this.chatContextValue.status === 'error' ||
|
||||
@@ -237,6 +239,13 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) {
|
||||
) {
|
||||
setTimeout(this._scrollToEnd, 500);
|
||||
}
|
||||
|
||||
if (
|
||||
_changedProperties.has('chatContextValue') &&
|
||||
this.chatContextValue.status === 'transmitting'
|
||||
) {
|
||||
this._throttledScrollToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
|
||||
Reference in New Issue
Block a user