d9ab341223
- Created a new document for using components in Milkdown. - Added a guide for using plugins in Milkdown, including toggling plugins programmatically and listing official plugins. - Introduced a recipe for integrating Milkdown with Angular, including installation steps and component creation. - Added a recipe for using Milkdown with Next.js, detailing installation and component setup. - Created a guide for integrating Milkdown with NuxtJS, including installation and component creation. - Added a comprehensive guide for using Milkdown with React, covering both Crepe and core Milkdown usage. - Introduced a recipe for SolidJS integration with Milkdown, including installation and component creation. - Added a guide for using Milkdown with Svelte, detailing installation and component setup. - Created a comprehensive guide for integrating Milkdown with Vue, covering both Crepe and core Milkdown usage. - Added a recipe for using Milkdown with Vue2, including installation and component creation.
115 lines
5.2 KiB
TypeScript
115 lines
5.2 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
import { createServiceIdentifier } from '../../../../../../util/common/services';
|
|
import { APIChoice } from '../openai/openai';
|
|
import { ResultType } from './ghostText';
|
|
|
|
export const ICompletionsCurrentGhostText = createServiceIdentifier<ICompletionsCurrentGhostText>('ICompletionsCurrentGhostText');
|
|
export interface ICompletionsCurrentGhostText {
|
|
readonly _serviceBrand: undefined;
|
|
|
|
readonly clientCompletionId: string | undefined;
|
|
|
|
currentRequestId: string | undefined;
|
|
|
|
setGhostText(prefix: string, suffix: string, choices: APIChoice[], resultType: ResultType): void;
|
|
getCompletionsForUserTyping(prefix: string, suffix: string): APIChoice[] | undefined;
|
|
hasAcceptedCurrentCompletion(prefix: string, suffix: string): boolean;
|
|
}
|
|
|
|
/**
|
|
* Stores the internal concept of the currently shown completion, as inferred by
|
|
* the output of getGhostText. Used to check if a subsequent call to
|
|
* getGhostText is typing-as-suggested.
|
|
*/
|
|
export class CurrentGhostText implements ICompletionsCurrentGhostText {
|
|
declare _serviceBrand: undefined;
|
|
/** The document prefix at the start of the typing-as-suggested flow. This
|
|
* does not use the prompt prefix since the ellision means that prefix is a
|
|
* sliding window over long documents. */
|
|
private prefix?: string;
|
|
|
|
/** The prompt suffix at the start of the typing-as-suggested flow. */
|
|
private suffix?: string;
|
|
|
|
/** The original APIChoice array created at the start of the
|
|
* typing-as-suggested flow. The first element in the array should be the
|
|
* completion shown to the user. */
|
|
private choices: APIChoice[] = [];
|
|
|
|
/** The currently shown completion id. */
|
|
get clientCompletionId(): string | undefined {
|
|
return this.choices[0]?.clientCompletionId;
|
|
}
|
|
|
|
/** The most recent inline completion request id, excluding speculative requests. */
|
|
currentRequestId: string | undefined;
|
|
|
|
/** Updates the current ghost text if it was not produced via
|
|
* TypingAsSuggested. Should only be called from the end of getGhostText. */
|
|
setGhostText(prefix: string, suffix: string, choices: APIChoice[], resultType: ResultType) {
|
|
if (resultType === ResultType.TypingAsSuggested) { return; }
|
|
this.prefix = prefix;
|
|
this.suffix = suffix;
|
|
this.choices = choices;
|
|
}
|
|
|
|
/** Returns the current choices if the request context matches. */
|
|
getCompletionsForUserTyping(prefix: string, suffix: string): APIChoice[] | undefined {
|
|
const remainingPrefix = this.getRemainingPrefix(prefix, suffix);
|
|
if (remainingPrefix === undefined) { return; }
|
|
// If the first choice text does not match return empty to fall through
|
|
// to either the cache or network.
|
|
if (!startsWithAndExceeds(this.choices[0].completionText, remainingPrefix)) { return; }
|
|
return adjustChoicesStart(this.choices, remainingPrefix);
|
|
}
|
|
|
|
/** Returns whether the current completion is fully completed, and covers a full line. */
|
|
hasAcceptedCurrentCompletion(prefix: string, suffix: string): boolean {
|
|
const remainingPrefix = this.getRemainingPrefix(prefix, suffix);
|
|
if (remainingPrefix === undefined) { return false; }
|
|
|
|
// Check if the completion text matches exactly
|
|
const exactMatch = remainingPrefix === this.choices?.[0].completionText;
|
|
|
|
// Check finishReason - return false if it indicates that the server cut off a part of it (thus it might not complete a full line), due to RAI or snippy
|
|
const finishReason = this.choices?.[0].finishReason;
|
|
return exactMatch && finishReason === 'stop';
|
|
}
|
|
|
|
/** If the given document prefix and prompt suffix match the current
|
|
* completion returns the remaining prefix of the document after the stored
|
|
* prefix. Returns undefined if the completion does not match. */
|
|
private getRemainingPrefix(prefix: string, suffix: string): string | undefined {
|
|
// Check that there is a current completion.
|
|
if (this.prefix === undefined || this.suffix === undefined || this.choices.length === 0) { return; }
|
|
// Check that the prompt suffixes are an exact match.
|
|
if (this.suffix !== suffix) { return; }
|
|
// Check that the document prefix is a prefix of the new prefix.
|
|
// This doesn't use the prompt prefix since the ellision means that
|
|
// subsequent prefixes will not be a prefix of earlier ones.
|
|
if (!prefix.startsWith(this.prefix)) { return; }
|
|
// Return the remaining new document prefix after the prefix stored for
|
|
// the current completion.
|
|
return prefix.substring(this.prefix.length);
|
|
}
|
|
}
|
|
|
|
/** Returns choices adjusted to remove the remainingPrefix from the start of the
|
|
* completionText if it matches. */
|
|
function adjustChoicesStart(choices: APIChoice[], remainingPrefix: string): APIChoice[] {
|
|
return choices
|
|
.filter(choice => startsWithAndExceeds(choice.completionText, remainingPrefix))
|
|
.map(choice => ({
|
|
...choice,
|
|
completionText: choice.completionText.substring(remainingPrefix.length),
|
|
}));
|
|
}
|
|
|
|
/** Returns true if `prefix` is a prefix of `text` and `text` is longer. */
|
|
function startsWithAndExceeds(text: string, prefix: string) {
|
|
return text.startsWith(prefix) && text.length > prefix.length;
|
|
}
|