lint
This commit is contained in:
parent
37d842c21e
commit
7967e6ab96
|
|
@ -80,7 +80,7 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
|
|
||||||
if (event.request.headers.get('x-pw-serviceworker') === 'forward') {
|
if (event.request.headers.get('x-pw-serviceworker') === 'forward') {
|
||||||
const request = new Request(event.request);
|
const request = new Request(event.request);
|
||||||
request.headers.delete('x-pw-serviceworker')
|
request.headers.delete('x-pw-serviceworker');
|
||||||
return fetch(request);
|
return fetch(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,20 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import Markdown from 'react-markdown'
|
import Markdown from 'react-markdown';
|
||||||
import './aiConversation.css';
|
import './aiConversation.css';
|
||||||
import { clsx } from '@web/uiUtils';
|
import { clsx } from '@web/uiUtils';
|
||||||
import type { Conversation, LLMMessage } from './llm';
|
import type { Conversation, LLMMessage } from './llm';
|
||||||
|
|
@ -15,48 +30,48 @@ export function AIConversation({ history, conversation }: { history: LLMMessage[
|
||||||
}, [conversation]);
|
}, [conversation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chat-container">
|
<div className='chat-container'>
|
||||||
<div className="messages-container">
|
<div className='messages-container'>
|
||||||
{history.filter(({ role }) => role !== 'developer').map((message, index) => (
|
{history.filter(({ role }) => role !== 'developer').map((message, index) => (
|
||||||
<div
|
<div
|
||||||
key={'' + index}
|
key={'' + index}
|
||||||
className={clsx('message', message.role === 'user' && 'user-message')}
|
className={clsx('message', message.role === 'user' && 'user-message')}
|
||||||
>
|
>
|
||||||
{message.role === 'assistant' && (
|
{message.role === 'assistant' && (
|
||||||
<div className="message-icon">
|
<div className='message-icon'>
|
||||||
<img src="playwright-logo.svg" />
|
<img src='playwright-logo.svg' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="message-content">
|
<div className='message-content'>
|
||||||
<Markdown>{message.displayContent ?? message.content}</Markdown>
|
<Markdown>{message.displayContent ?? message.content}</Markdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="input-form">
|
<div className='input-form'>
|
||||||
<textarea
|
<textarea
|
||||||
name='content'
|
name='content'
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={e => setInput(e.target.value)}
|
||||||
onKeyDown={e => {
|
onKeyDown={e => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSubmit();
|
onSubmit();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder="Ask a question..."
|
placeholder='Ask a question...'
|
||||||
className="message-input"
|
className='message-input'
|
||||||
/>
|
/>
|
||||||
{conversation.isSending() ? (
|
{conversation.isSending() ? (
|
||||||
<button type="button" className="send-button" onClick={(evt) => {
|
<button type='button' className='send-button' onClick={evt => {
|
||||||
evt.preventDefault()
|
evt.preventDefault();
|
||||||
conversation.abortSending();
|
conversation.abortSending();
|
||||||
}}>
|
}}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button className="send-button" disabled={!input.trim()} onClick={onSubmit}>
|
<button className='send-button' disabled={!input.trim()} onClick={onSubmit}>
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ function Error({ message, error, errorId, sdkLanguage, pageSnapshot, revealInSou
|
||||||
</div>}
|
</div>}
|
||||||
<span style={{ position: 'absolute', right: '5px' }}>
|
<span style={{ position: 'absolute', right: '5px' }}>
|
||||||
{llmAvailable
|
{llmAvailable
|
||||||
? <ToolbarButton onClick={() => setShowLLM(v => !v)} style={{ width: "96px", justifyContent: 'center' }} title="Fix with AI" className='copy-to-clipboard-text-button'>{showLLM ? 'Hide AI' : 'Fix with AI'}</ToolbarButton>
|
? <ToolbarButton onClick={() => setShowLLM(v => !v)} style={{ width: '96px', justifyContent: 'center' }} title='Fix with AI' className='copy-to-clipboard-text-button'>{showLLM ? 'Hide AI' : 'Fix with AI'}</ToolbarButton>
|
||||||
: <CopyPromptButton error={message} pageSnapshot={pageSnapshot} diff={diff} />}
|
: <CopyPromptButton error={message} pageSnapshot={pageSnapshot} diff={diff} />}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -160,12 +160,12 @@ export const ErrorsTab: React.FunctionComponent<{
|
||||||
|
|
||||||
export function AIErrorConversation({ conversationId, error, pageSnapshot, diff }: { conversationId: string, error: string, pageSnapshot?: string, diff?: string }) {
|
export function AIErrorConversation({ conversationId, error, pageSnapshot, diff }: { conversationId: string, error: string, pageSnapshot?: string, diff?: string }) {
|
||||||
const [history, conversation] = useLLMConversation(
|
const [history, conversation] = useLLMConversation(
|
||||||
conversationId,
|
conversationId,
|
||||||
[
|
[
|
||||||
`My Playwright test failed. What's going wrong?`,
|
`My Playwright test failed. What's going wrong?`,
|
||||||
`Please give me a suggestion how to fix it, and then explain what went wrong. Be very concise and apply Playwright best practices.`,
|
`Please give me a suggestion how to fix it, and then explain what went wrong. Be very concise and apply Playwright best practices.`,
|
||||||
`Don't include many headings in your output. Make sure what you're saying is correct, and take into account whether there might be a bug in the app.`
|
`Don't include many headings in your output. Make sure what you're saying is correct, and take into account whether there might be a bug in the app.`
|
||||||
].join('\n')
|
].join('\n')
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -224,33 +224,32 @@ export function LLMProvider({ children }: React.PropsWithChildren<{}>) {
|
||||||
const cookiePairs = useCookies();
|
const cookiePairs = useCookies();
|
||||||
const chat = React.useMemo(() => {
|
const chat = React.useMemo(() => {
|
||||||
const cookies = Object.fromEntries(cookiePairs);
|
const cookies = Object.fromEntries(cookiePairs);
|
||||||
console.log({ cookies })
|
|
||||||
if (cookies.openai_api_key)
|
if (cookies.openai_api_key)
|
||||||
return new LLMChat(new OpenAI(cookies.openai_api_key, cookies.openai_base_url));
|
return new LLMChat(new OpenAI(cookies.openai_api_key, cookies.openai_base_url));
|
||||||
if (cookies.anthropic_api_key)
|
if (cookies.anthropic_api_key)
|
||||||
return new LLMChat(new Anthropic(cookies.anthropic_api_key, cookies.anthropic_base_url));
|
return new LLMChat(new Anthropic(cookies.anthropic_api_key, cookies.anthropic_base_url));
|
||||||
}, [cookiePairs]);
|
}, [cookiePairs]);
|
||||||
return <llmContext.Provider value={chat}>{children}</llmContext.Provider>;
|
return <llmContext.Provider value={chat}>{children}</llmContext.Provider>;
|
||||||
};
|
}
|
||||||
|
|
||||||
export function useLLMChat() {
|
export function useLLMChat() {
|
||||||
return React.useContext(llmContext);
|
return React.useContext(llmContext);
|
||||||
};
|
}
|
||||||
|
|
||||||
export function useLLMConversation(id: string, systemPrompt: string) {
|
export function useLLMConversation(id: string, systemPrompt: string) {
|
||||||
const chat = useLLMChat();
|
const chat = useLLMChat();
|
||||||
if (!chat)
|
if (!chat)
|
||||||
throw new Error('No LLM chat available, make sure theres a LLMProvider above');
|
throw new Error('No LLM chat available, make sure theres a LLMProvider above');
|
||||||
const conversation = React.useMemo(() => chat.getConversation(id, systemPrompt), [chat, id]);
|
const conversation = React.useMemo(() => chat.getConversation(id, systemPrompt), [chat, id]);
|
||||||
const [history, setHistory] = React.useState(conversation.history);
|
const [history, setHistory] = React.useState(conversation.history);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
function update() {
|
function update() {
|
||||||
setHistory([...conversation.history]);
|
setHistory([...conversation.history]);
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
const subscription = conversation.onChange.event(update);
|
const subscription = conversation.onChange.event(update);
|
||||||
return subscription.dispose;
|
return subscription.dispose;
|
||||||
}, [conversation]);
|
}, [conversation]);
|
||||||
|
|
||||||
return [history, conversation] as const;
|
return [history, conversation] as const;
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -250,8 +250,8 @@ export function useFlash(): [boolean, EffectCallback] {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCookies() {
|
export function useCookies() {
|
||||||
return document.cookie.split("; ").filter(v => v.includes("=")).map(kv => {
|
return document.cookie.split('; ').filter(v => v.includes('=')).map(kv => {
|
||||||
const separator = kv.indexOf("=");
|
const separator = kv.indexOf('=');
|
||||||
return [kv.substring(0, separator), kv.substring(separator + 1)];
|
return [kv.substring(0, separator), kv.substring(separator + 1)];
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue