feat: handle sending selected lines to ai context (#5527)
* allow mentioning specific files in instructions * remove not working highlight implementation * make highlighted text work * fix tooltip position * clean code * cleaning * use lib for tooltip positioning * fix logic * draft for db in context * use tools for db in context * fixes * cleaning and bug fixes * fix * cleaning * fix when script is db type * simplify logic * put schema in context if already here * fix imports * fix tooltip position and make it scrollable * remove console logs * check if selected is in available * fix tooltip list * add back lost logic * last fix * fix type errors * use loaded schema from dbSchemas * fix typing, content and lang are always there * remove from context if not available anymore * add not loaded yet mention if schema not loaded * add missing callback logic * fix prompt * fix usage of updateselectedContext function * fix styling for white theme * handle tab and arrows * fix schemas not being refreshed on contexts * also refresh displayMessages when dbschemas change * fix duplicate available contexts * fix logic for new scripts * fix new lines inside text area * implement sending diff in context * add button in deploy options to ask ai about diff * also visualize change when asking for diff * better prompt * add limit to diff size * put diff mode toggle in editor bar * add button to see history from editor * adjustements * put see diff button in dropdown * fixes * better styling * handle adding code piece to context * add code piece in context * draft start end markers * adapt code * draft * apply code pieces before sending request * cleaning * highlight if diff mode * format files * change buttons based on diffmode * remove diff after sending message * fix type error * smaller buttons * draft * use existing editor in diff editor * fix number of db resources fetches * fix apply and add buttons on diff mode * cleaning * undo ai gen button show * better buttons * better prompt * remove console log * fix merge * avoid duplicates * fix merge * fix * fix apply logic * remove useless if * focus text area + close chat if no selected lines --------- Co-authored-by: HugoCasa <hugo@casademont.ch>
This commit is contained in:
@@ -220,6 +220,7 @@
|
||||
export let scriptLang: Preview['language'] | 'bunnative'
|
||||
export let disabled: boolean = false
|
||||
export let lineNumbersMinChars = 3
|
||||
export let isAiPanelOpen: boolean = false
|
||||
|
||||
const rHash = randomHash()
|
||||
$: filePath = computePath(path)
|
||||
@@ -403,7 +404,7 @@
|
||||
endColumn: edit.range.end.character + 1
|
||||
},
|
||||
text: edit.newText
|
||||
}
|
||||
}
|
||||
: {}
|
||||
)
|
||||
//@ts-ignore
|
||||
@@ -464,16 +465,16 @@
|
||||
scriptLang === 'postgresql'
|
||||
? POSTGRES_TYPES
|
||||
: scriptLang === 'mysql'
|
||||
? MYSQL_TYPES
|
||||
: scriptLang === 'snowflake'
|
||||
? SNOWFLAKE_TYPES
|
||||
: scriptLang === 'bigquery'
|
||||
? BIGQUERY_TYPES
|
||||
: scriptLang === 'mssql'
|
||||
? MSSQL_TYPES
|
||||
: scriptLang === 'oracledb'
|
||||
? ORACLEDB_TYPES
|
||||
: []
|
||||
? MYSQL_TYPES
|
||||
: scriptLang === 'snowflake'
|
||||
? SNOWFLAKE_TYPES
|
||||
: scriptLang === 'bigquery'
|
||||
? BIGQUERY_TYPES
|
||||
: scriptLang === 'mssql'
|
||||
? MSSQL_TYPES
|
||||
: scriptLang === 'oracledb'
|
||||
? ORACLEDB_TYPES
|
||||
: []
|
||||
).map((t) => ({
|
||||
label: t,
|
||||
kind: languages.CompletionItemKind.Function,
|
||||
@@ -741,7 +742,7 @@
|
||||
uri: vscode.Uri.parse(uri),
|
||||
name: 'windmill',
|
||||
index: 0
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
initializationOptions,
|
||||
middleware: {
|
||||
@@ -1203,7 +1204,24 @@
|
||||
})
|
||||
|
||||
editor?.addCommand(KeyMod.CtrlCmd | KeyCode.KeyL, function () {
|
||||
dispatch('toggleAiPanel')
|
||||
const selectedLines = getSelectedLines()
|
||||
const selection = editor?.getSelection()
|
||||
const hasSelection =
|
||||
selection &&
|
||||
(selection.startLineNumber !== selection.endLineNumber ||
|
||||
selection.startColumn !== selection.endColumn)
|
||||
if (hasSelection && selectedLines) {
|
||||
dispatch('addSelectedLinesToAiChat', {
|
||||
lines: selectedLines,
|
||||
startLine: selection.startLineNumber,
|
||||
endLine: selection.endLineNumber
|
||||
})
|
||||
if (!isAiPanelOpen) {
|
||||
dispatch('toggleAiPanel')
|
||||
}
|
||||
} else {
|
||||
dispatch('toggleAiPanel')
|
||||
}
|
||||
})
|
||||
|
||||
editor?.addCommand(KeyMod.CtrlCmd | KeyCode.KeyU, function () {
|
||||
|
||||
@@ -706,7 +706,7 @@
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
: []),
|
||||
...(!script.draft_only
|
||||
? [
|
||||
@@ -716,9 +716,9 @@
|
||||
dispatch('seeDetails', initialPath)
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
: [])
|
||||
]
|
||||
]
|
||||
: []
|
||||
|
||||
return dropdownItems.length > 0 ? dropdownItems : undefined
|
||||
|
||||
@@ -302,6 +302,7 @@
|
||||
let storedAiPanelSize = aiPanelSize > 0 ? aiPanelSize : 30
|
||||
let testPanelSize = 30
|
||||
let storedTestPanelSize = testPanelSize
|
||||
|
||||
function toggleAiPanel() {
|
||||
if (!$copilotInfo.enabled) return
|
||||
if (aiPanelSize > 0) {
|
||||
@@ -316,6 +317,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addSelectedLinesToAiChat(
|
||||
e: CustomEvent<{ lines: string; startLine: number; endLine: number }>
|
||||
) {
|
||||
if (aiChat) {
|
||||
aiChat.addSelectedLinesToContext(e.detail.lines, e.detail.startLine, e.detail.endLine)
|
||||
aiChat.focusTextArea()
|
||||
}
|
||||
}
|
||||
|
||||
$: !SUPPORTED_CHAT_SCRIPT_LANGUAGES.includes(lang ?? '') && aiPanelSize > 0 && toggleAiPanel()
|
||||
|
||||
function toggleTestPanel() {
|
||||
@@ -506,7 +516,9 @@
|
||||
}}
|
||||
on:saveDraft
|
||||
on:toggleAiPanel={toggleAiPanel}
|
||||
on:addSelectedLinesToAiChat={addSelectedLinesToAiChat}
|
||||
on:toggleTestPanel={toggleTestPanel}
|
||||
isAiPanelOpen={aiPanelSize > 0}
|
||||
cmdEnterAction={async () => {
|
||||
await inferSchema(code)
|
||||
runTest()
|
||||
|
||||
@@ -193,7 +193,11 @@
|
||||
}
|
||||
|
||||
selectedContext = selectedContext
|
||||
.map((c) => availableContext.find((ac) => ac.type === c.type && ac.title === c.title))
|
||||
.map((c) =>
|
||||
c.type === 'code_piece' && code.includes(c.content)
|
||||
? c
|
||||
: availableContext.find((ac) => ac.type === c.type && ac.title === c.title)
|
||||
)
|
||||
.filter((c) => c !== undefined) as ContextElement[]
|
||||
} catch (err) {
|
||||
console.error('Could not update available context', err)
|
||||
@@ -209,7 +213,7 @@
|
||||
type: 'db',
|
||||
title: c.title,
|
||||
schema: dbSchemas[c.title]
|
||||
}
|
||||
}
|
||||
: c
|
||||
) as ContextElement[]
|
||||
}))
|
||||
@@ -277,7 +281,9 @@
|
||||
return
|
||||
}
|
||||
try {
|
||||
// Remove code pieces from the context to not include them on the next request
|
||||
const oldSelectedContext = selectedContext
|
||||
selectedContext = selectedContext.filter((c) => c.type !== 'code_piece')
|
||||
if (options.removeDiff) {
|
||||
selectedContext = selectedContext.filter((c) => c.type !== 'diff')
|
||||
}
|
||||
@@ -323,7 +329,7 @@
|
||||
{
|
||||
role: 'assistant',
|
||||
content: $currentReply,
|
||||
contextElements: selectedContext.filter((c) => c.type === 'code')
|
||||
contextElements: oldSelectedContext.filter((c) => c.type === 'code')
|
||||
}
|
||||
]
|
||||
currentReply.set('')
|
||||
@@ -387,6 +393,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
export function addSelectedLinesToContext(lines: string, startLine: number, endLine: number) {
|
||||
if (
|
||||
selectedContext.find(
|
||||
(c) => c.type === 'code_piece' && c.title === `L${startLine}-L${endLine}`
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
selectedContext = [
|
||||
...selectedContext,
|
||||
{
|
||||
type: 'code_piece',
|
||||
title: `L${startLine}-L${endLine}`,
|
||||
startLine,
|
||||
endLine,
|
||||
content: lines,
|
||||
lang
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function fix() {
|
||||
if (!contextCodePath) {
|
||||
return
|
||||
@@ -430,7 +457,7 @@
|
||||
diff: diffLines(lastDeployedCode ?? '', code),
|
||||
lang
|
||||
}
|
||||
]
|
||||
]
|
||||
: [])
|
||||
]
|
||||
sendRequest({
|
||||
@@ -442,6 +469,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
export function focusTextArea() {
|
||||
aiChatDisplay?.focusInput()
|
||||
}
|
||||
|
||||
interface ChatSchema extends IDBSchema {
|
||||
chats: {
|
||||
key: string
|
||||
@@ -470,10 +501,13 @@
|
||||
|
||||
const chats = await indexDB.getAll('chats')
|
||||
console.log('Retrieved chats')
|
||||
savedChats = chats.reduce((acc, chat) => {
|
||||
acc[chat.id] = chat
|
||||
return acc
|
||||
}, {} as typeof savedChats)
|
||||
savedChats = chats.reduce(
|
||||
(acc, chat) => {
|
||||
acc[chat.id] = chat
|
||||
return acc
|
||||
},
|
||||
{} as typeof savedChats
|
||||
)
|
||||
} catch (err) {
|
||||
console.error('Could not open chat history database', err)
|
||||
}
|
||||
@@ -502,7 +536,7 @@
|
||||
content: $currentReply,
|
||||
contextElements: selectedContext.filter((c) => c.type === 'code')
|
||||
}
|
||||
]
|
||||
]
|
||||
: displayMessages}
|
||||
bind:instructions
|
||||
on:sendRequest={() => sendRequest()}
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
automaticScroll = true
|
||||
}
|
||||
|
||||
let contextTextareaComponent: ContextTextarea
|
||||
|
||||
export function focusInput() {
|
||||
contextTextareaComponent?.focus()
|
||||
}
|
||||
|
||||
let automaticScroll = true
|
||||
let scrollEl: HTMLDivElement
|
||||
async function scrollDown() {
|
||||
@@ -199,23 +205,19 @@
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{#each selectedContext as element}
|
||||
{@const contextElement = availableContext.find(
|
||||
(c) => c.type === element.type && c.title === element.title
|
||||
)}
|
||||
{#if contextElement}
|
||||
<ContextElementBadge
|
||||
{contextElement}
|
||||
deletable
|
||||
on:delete={() => {
|
||||
selectedContext = selectedContext.filter(
|
||||
(c) => c.type !== element.type || c.title !== element.title
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<ContextElementBadge
|
||||
contextElement={element}
|
||||
deletable
|
||||
on:delete={() => {
|
||||
selectedContext = selectedContext.filter(
|
||||
(c) => c.type !== element.type || c.title !== element.title
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<ContextTextarea
|
||||
bind:this={contextTextareaComponent}
|
||||
{instructions}
|
||||
{availableContext}
|
||||
{selectedContext}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<div class="text-tertiary">Not loaded yet</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if contextElement.type === 'code' || contextElement.type === 'diff'}
|
||||
{:else if contextElement.type === 'code' || contextElement.type === 'code_piece' || contextElement.type === 'diff'}
|
||||
<div class="max-w-96 max-h-[300px] text-xs overflow-auto">
|
||||
<HighlightCode
|
||||
language={contextElement.lang}
|
||||
|
||||
@@ -286,6 +286,10 @@
|
||||
}
|
||||
|
||||
$: updateTooltipPosition(availableContext, showContextTooltip, contextTooltipWord)
|
||||
|
||||
export function focus() {
|
||||
textarea?.focus()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative w-full px-2 scroll-pb-2">
|
||||
|
||||
@@ -211,6 +211,7 @@ export const CHAT_SYSTEM_PROMPT = `
|
||||
|
||||
When the user requests code changes:
|
||||
- Always include a **single code block** with the **entire updated file**, not just the modified sections.
|
||||
- The code can include \`[#START]\` and \`[#END]\` markers to indicate the start and end of a code piece. You MUST only modify the code between these markers if given, and remove them in your response. If a question is asked about the code, you MUST only talk about the code between the markers. Refer to it as the code piece, not the code between the markers.
|
||||
- Follow the instructions carefully and explain the reasoning behind your changes.
|
||||
- If the request is abstract (e.g., "make this cleaner"), interpret it concretely and reflect that in the code block.
|
||||
- Preserve existing formatting, indentation, and whitespace unless changes are strictly required to fulfill the user's request.
|
||||
@@ -223,7 +224,7 @@ export const CHAT_SYSTEM_PROMPT = `
|
||||
`
|
||||
|
||||
const CHAT_USER_CODE_CONTEXT = `
|
||||
CODE ({title}):
|
||||
- {title}:
|
||||
\`\`\`{language}
|
||||
{code}
|
||||
\`\`\`
|
||||
@@ -241,19 +242,6 @@ INSTRUCTIONS:
|
||||
WINDMILL LANGUAGE CONTEXT:
|
||||
{lang_context}
|
||||
|
||||
DATABASES:
|
||||
{db_context}
|
||||
|
||||
CODE:
|
||||
{code_context}
|
||||
|
||||
ERROR:
|
||||
{error_context}
|
||||
|
||||
DIFF:
|
||||
{diff_context}
|
||||
|
||||
\`\`\`
|
||||
`
|
||||
|
||||
export const CHAT_USER_DB_CONTEXT = `- {title}: SCHEMA: \n{schema}\n`
|
||||
@@ -278,71 +266,120 @@ export const ContextIconMap = {
|
||||
code: Code,
|
||||
error: TriangleAlert,
|
||||
db: Database,
|
||||
diff: Diff
|
||||
diff: Diff,
|
||||
code_piece: Code
|
||||
}
|
||||
|
||||
export type ContextElement =
|
||||
| {
|
||||
type: 'code'
|
||||
content: string
|
||||
title: string
|
||||
lang: ScriptLang | 'bunnative'
|
||||
}
|
||||
| {
|
||||
type: 'error'
|
||||
content: string
|
||||
title: 'error'
|
||||
}
|
||||
| {
|
||||
type: 'db'
|
||||
schema?: DBSchema
|
||||
title: string
|
||||
}
|
||||
| {
|
||||
type: 'diff'
|
||||
content: string
|
||||
title: string
|
||||
diff: Change[]
|
||||
lang: ScriptLang | 'bunnative'
|
||||
}
|
||||
type CodeElement = {
|
||||
type: 'code'
|
||||
content: string
|
||||
title: string
|
||||
lang: ScriptLang | 'bunnative'
|
||||
}
|
||||
|
||||
type ErrorElement = {
|
||||
type: 'error'
|
||||
content: string
|
||||
title: 'error'
|
||||
}
|
||||
|
||||
type DBElement = {
|
||||
type: 'db'
|
||||
schema?: DBSchema
|
||||
title: string
|
||||
}
|
||||
|
||||
type DiffElement = {
|
||||
type: 'diff'
|
||||
content: string
|
||||
title: string
|
||||
diff: Change[]
|
||||
lang: ScriptLang | 'bunnative'
|
||||
}
|
||||
|
||||
type CodePieceElement = {
|
||||
type: 'code_piece'
|
||||
content: string
|
||||
startLine: number
|
||||
endLine: number
|
||||
title: string
|
||||
lang: ScriptLang | 'bunnative'
|
||||
}
|
||||
|
||||
export type ContextElement = CodeElement | ErrorElement | DBElement | DiffElement | CodePieceElement
|
||||
|
||||
const applyCodePieceToCodeContext = (codePieces: CodePieceElement[], codeContext: string) => {
|
||||
let code = codeContext.split('\n')
|
||||
let shiftOffset = 0
|
||||
codePieces.sort((a, b) => a.startLine - b.startLine)
|
||||
for (const codePiece of codePieces) {
|
||||
code.splice(codePiece.endLine + shiftOffset, 0, '[#END]')
|
||||
code.splice(codePiece.startLine + shiftOffset - 1, 0, '[#START]')
|
||||
shiftOffset += 2
|
||||
}
|
||||
return code.join('\n')
|
||||
}
|
||||
|
||||
export async function prepareUserMessage(
|
||||
instructions: string,
|
||||
language: ScriptLang | 'bunnative',
|
||||
selectedContext: ContextElement[]
|
||||
) {
|
||||
let codeContext = ''
|
||||
let errorContext = ''
|
||||
let dbContext = ''
|
||||
let diffContext = ''
|
||||
let codeContext = 'CODE:\n'
|
||||
let errorContext = 'ERROR:\n'
|
||||
let dbContext = 'DATABASES:\n'
|
||||
let diffContext = 'DIFF:\n'
|
||||
let hasCode = false
|
||||
let hasError = false
|
||||
let hasDb = false
|
||||
let hasDiff = false
|
||||
for (const context of selectedContext) {
|
||||
if (context.type === 'code') {
|
||||
hasCode = true
|
||||
codeContext += CHAT_USER_CODE_CONTEXT.replace('{title}', context.title)
|
||||
.replace('{language}', scriptLangToEditorLang(language))
|
||||
.replace('{code}', context.content)
|
||||
.replace(
|
||||
'{code}',
|
||||
applyCodePieceToCodeContext(
|
||||
selectedContext.filter((c) => c.type === 'code_piece'),
|
||||
context.content
|
||||
)
|
||||
)
|
||||
} else if (context.type === 'error') {
|
||||
if (errorContext) {
|
||||
if (hasError) {
|
||||
throw new Error('Multiple error contexts provided')
|
||||
}
|
||||
hasError = true
|
||||
errorContext = CHAT_USER_ERROR_CONTEXT.replace('{error}', context.content)
|
||||
} else if (context.type === 'db') {
|
||||
hasDb = true
|
||||
dbContext += CHAT_USER_DB_CONTEXT.replace('{title}', context.title).replace(
|
||||
'{schema}',
|
||||
context.schema?.stringified ?? 'to fetch with get_db_schema'
|
||||
)
|
||||
} else if (context.type === 'diff') {
|
||||
hasDiff = true
|
||||
const diff = JSON.stringify(context.diff)
|
||||
diffContext = diff.length > 3000 ? diff.slice(0, 3000) + '...' : diff
|
||||
}
|
||||
}
|
||||
|
||||
const userMessage = CHAT_USER_PROMPT.replace('{instructions}', instructions)
|
||||
.replace('{lang_context}', getLangContext(language))
|
||||
.replace('{code_context}', codeContext)
|
||||
.replace('{error_context}', errorContext)
|
||||
.replace('{db_context}', dbContext)
|
||||
.replace('{diff_context}', diffContext)
|
||||
|
||||
let userMessage = CHAT_USER_PROMPT.replace('{instructions}', instructions).replace(
|
||||
'{lang_context}',
|
||||
getLangContext(language)
|
||||
)
|
||||
if (hasCode) {
|
||||
userMessage += codeContext
|
||||
}
|
||||
if (hasError) {
|
||||
userMessage += errorContext
|
||||
}
|
||||
if (hasDb) {
|
||||
userMessage += dbContext
|
||||
}
|
||||
if (hasDiff) {
|
||||
userMessage += diffContext
|
||||
}
|
||||
return userMessage
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ export const copilotSessionModel = writable<AIProviderModel | undefined>(
|
||||
? {
|
||||
model: sessionModel,
|
||||
provider: sessionProvider as AIProvider
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
export const usedTriggerKinds = writable<string[]>([])
|
||||
|
||||
Reference in New Issue
Block a user