Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions frontend/src/lib/components/copilot/chat/AIChat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,7 @@
() => aiChatManager.contextManager.getSelectedContext(),
(sc) => aiChatManager.contextManager.setSelectedContext(sc)
}
availableContext={aiChatManager.mode === AIMode.APP
? aiChatManager.getAppAvailableContext()
: aiChatManager.contextManager.getAvailableContext()}
availableContext={aiChatManager.contextManager.getAvailableContext()}
messages={aiChatManager.currentReply
? [
...aiChatManager.displayMessages,
Expand Down
41 changes: 4 additions & 37 deletions frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import { type Snippet } from 'svelte'
import {
CheckIcon,
Code2,
FileCode,
HistoryIcon,
Loader2,
MousePointer2,
Expand All @@ -26,7 +24,7 @@
import { aiChatManager, AIMode } from './AIChatManager.svelte'
import AIChatInput from './AIChatInput.svelte'
import { getModifierKey } from '$lib/utils'
import type { SelectedContext } from './app/core'
import type { AppTransientContext } from './app/core'

let {
messages,
Expand Down Expand Up @@ -103,11 +101,11 @@
)

// Get app context for display when in APP mode
const appContext = $derived.by((): SelectedContext | undefined => {
const appContext = $derived.by((): AppTransientContext | undefined => {
if (aiChatManager.mode !== AIMode.APP || !aiChatManager.appAiChatHelpers) {
return undefined
}
return aiChatManager.appAiChatHelpers.getSelectedContext()
return aiChatManager.appAiChatHelpers.getTransientContext()
})
</script>

Expand Down Expand Up @@ -293,38 +291,7 @@
{/if}
<ProviderModelSelector />

{#if aiChatManager.mode === AIMode.APP && appContext && (appContext.type !== 'none' || appContext.inspectorElement || appContext.codeSelection)}
{#if appContext.type === 'frontend' && appContext.frontendPath && !appContext.selectionExcluded}
<div
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-2xs"
title={appContext.frontendPath}
>
<FileCode class="w-3 h-3" />
<span class="truncate max-w-[80px]">{appContext.frontendPath}</span>
<button
class="hover:bg-blue-200 dark:hover:bg-blue-800/50 rounded p-0.5 -mr-0.5"
onclick={() => appContext.toggleSelectionExcluded?.()}
title="Exclude from prompt"
>
<X class="w-2.5 h-2.5" />
</button>
</div>
{:else if appContext.type === 'backend' && appContext.backendKey && !appContext.selectionExcluded}
<div
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 text-2xs"
title={appContext.backendKey}
>
<Code2 class="w-3 h-3" />
<span class="truncate max-w-[80px]">{appContext.backendKey}</span>
<button
class="hover:bg-green-200 dark:hover:bg-green-800/50 rounded p-0.5 -mr-0.5"
onclick={() => appContext.toggleSelectionExcluded?.()}
title="Exclude from prompt"
>
<X class="w-2.5 h-2.5" />
</button>
</div>
{/if}
{#if aiChatManager.mode === AIMode.APP && appContext && (appContext.inspectorElement || appContext.codeSelection)}
{#if appContext.inspectorElement}
<div
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 text-2xs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
getAppTools,
prepareAppSystemMessage,
prepareAppUserMessage,
type AppAIChatHelpers
type AppAIChatHelpers,
type AppSelection
} from './app/core'
import ContextManager from './ContextManager.svelte'
import HistoryManager from './HistoryManager.svelte'
Expand Down Expand Up @@ -445,10 +446,7 @@ class AIChatManager {
if (!pendingPrompt) return undefined
this.pendingPrompt = ''
if (this.mode === AIMode.SCRIPT) {
return prepareScriptUserMessage(
pendingPrompt,
this.contextManager.getSelectedContext()
)
return prepareScriptUserMessage(pendingPrompt, this.contextManager.getSelectedContext())
} else if (this.mode === AIMode.FLOW) {
return prepareFlowUserMessage(
pendingPrompt,
Expand Down Expand Up @@ -627,7 +625,7 @@ class AIChatManager {
role: 'user',
content: this.instructions,
contextElements:
this.mode === AIMode.SCRIPT || this.mode === AIMode.FLOW
this.mode === AIMode.SCRIPT || this.mode === AIMode.FLOW || this.mode === AIMode.APP
? oldSelectedContext
: undefined,
snapshot,
Expand Down Expand Up @@ -668,7 +666,7 @@ class AIChatManager {
case AIMode.APP:
userMessage = prepareAppUserMessage(
oldInstructions,
this.appAiChatHelpers?.getSelectedContext(),
this.appAiChatHelpers?.getTransientContext(),
oldSelectedContext
)
break
Expand Down Expand Up @@ -903,13 +901,27 @@ class AIChatManager {
!copilotSessionModel?.model.endsWith('/thinking'),
untrack(() => this.contextManager.getSelectedContext())
)
} else if (this.mode === AIMode.APP) {
this.contextManager.updateAvailableContextForApp(
this.getAppAvailableContext(),
untrack(() => this.contextManager.getSelectedContext())
)
}

if (this.scriptEditorOptions) {
this.contextManager.setScriptOptions(this.scriptEditorOptions)
}
}

syncAppSelection = (selectedContext: AppSelection | undefined) => {
const availableContext = this.getAppAvailableContext()
this.contextManager.updateAvailableContextForApp(
availableContext,
untrack(() => this.contextManager.getSelectedContext())
)
this.contextManager.setSelectedAppContext(selectedContext, availableContext)
}

listenForDbSchemasChanges = (dbSchemas: DBSchemas) => {
this.displayMessages = ContextManager.updateDisplayMessages(
untrack(() => this.displayMessages),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { FlowModule } from '$lib/gen'
import type { DisplayMessage } from './shared'
import { langToExt } from '$lib/editorLangUtils'
import type { ExtendedOpenFlow } from '$lib/components/flows/types'
import type { AppSelection } from './app/core'

export interface ScriptOptions {
lang: ScriptLang | 'bunnative'
Expand Down Expand Up @@ -285,6 +286,40 @@ export default class ContextManager {
return this.availableContext
}

updateAvailableContextForApp(
availableContext: ContextElement[],
currentlySelectedContext: ContextElement[]
) {
const newSelectedContext: ContextElement[] = []

for (const context of currentlySelectedContext) {
if (context.type === 'app_code_selection') {
newSelectedContext.push(context)
continue
}

if (
context.type === 'app_frontend_file' ||
context.type === 'app_backend_runnable' ||
context.type === 'app_datatable'
) {
const refreshedContext = availableContext.find(
(available) => available.type === context.type && available.title === context.title
)

if (refreshedContext) {
newSelectedContext.push({
...refreshedContext,
activeSelection: context.activeSelection
})
}
}
}

this.availableContext = availableContext
this.selectedContext = newSelectedContext
}

setScriptOptions(scriptOptions: ScriptOptions) {
this.scriptOptions = scriptOptions
}
Expand Down Expand Up @@ -422,6 +457,47 @@ export default class ContextManager {
}
}

setSelectedAppContext(
selectedContext: AppSelection | undefined,
availableContext: ContextElement[] | undefined
) {
this.selectedContext = this.selectedContext.filter(
(context) =>
!(
context.activeSelection &&
(context.type === 'app_frontend_file' || context.type === 'app_backend_runnable')
)
)

if (!availableContext) {
return
}

const selectedAppContext =
selectedContext?.type === 'frontend' && selectedContext.frontendPath
? availableContext.find(
(context) =>
context.type === 'app_frontend_file' && context.title === selectedContext.frontendPath
)
: selectedContext?.type === 'backend' && selectedContext.backendKey
? availableContext.find(
(context) =>
context.type === 'app_backend_runnable' &&
context.title === selectedContext.backendKey
)
: undefined

if (selectedAppContext) {
this.selectedContext = [
{ ...selectedAppContext, activeSelection: true },
...this.selectedContext.filter(
(context) =>
context.type !== selectedAppContext.type || context.title !== selectedAppContext.title
)
]
}
}

clearContext() {
this.selectedContext = []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
AppFiles,
BackendRunnable,
LintResult,
SelectedContext
AppTransientContext
} from '../../app/core'

/**
Expand All @@ -30,7 +30,10 @@ export function createAppEvalHelpers(
let frontend: Record<string, string> = { ...initialFrontend }
let backend: Record<string, BackendRunnable> = { ...initialBackend }
let snapshotId = 0
const snapshots: Map<number, { frontend: Record<string, string>; backend: Record<string, BackendRunnable> }> = new Map()
const snapshots: Map<
number,
{ frontend: Record<string, string>; backend: Record<string, BackendRunnable> }
> = new Map()

const helpers: AppAIChatHelpers = {
// Frontend file operations
Expand Down Expand Up @@ -78,9 +81,7 @@ export function createAppEvalHelpers(
backend: { ...backend }
}),

getSelectedContext: (): SelectedContext => ({
type: 'none'
}),
getTransientContext: (): AppTransientContext => ({}),

// Snapshot management
snapshot: () => {
Expand Down Expand Up @@ -126,11 +127,7 @@ export function createAppEvalHelpers(
return { success: true, result: [] }
},

addTableToWhitelist: (
_datatableName: string,
_schemaName: string,
_tableName: string
) => {
addTableToWhitelist: (_datatableName: string, _schemaName: string, _tableName: string) => {
// No-op for eval testing - tables are not tracked in test context
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function runAppEval(
const model = resolveModel(options?.variant, options?.model)

// Build user message
const userMessage = prepareAppUserMessage(userPrompt, helpers.getSelectedContext())
const userMessage = prepareAppUserMessage(userPrompt, helpers.getTransientContext(), [])

// Run the base evaluation
const rawResult = await runEval({
Expand Down
Loading
Loading