Skip to content

Commit 4dbfb67

Browse files
authored
Merge pull request #64 from beekeeper-studio/feat/context-window-management
Context Window Management
2 parents 7f3005f + cdf042a commit 4dbfb67

26 files changed

+990
-224
lines changed

instructions/compact.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Summarize the above conversation for a new session. The new session will
2+
continue the conversation and will not be able to access it. Include any useful
3+
context needed, such as what has been done, what is currently being done, and
4+
next steps if applicable.

src/assets/styles/_primevue.scss

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,12 @@
136136
// Popover
137137
--p-popover-gutter: 0.5rem;
138138
--p-popover-arrow-offset: 1rem;
139-
--p-popover-background: color-mix(in srgb,
140-
var(--theme-base) 6%,
141-
var(--query-editor-bg));
142-
--p-popover-color: var(--text);
139+
--p-popover-background: var(--p-menu-background);
140+
--p-popover-color: var(--p-menu-item-color);
143141
--p-popover-border-color: transparent;
144-
--p-popover-border-radius: 8px;
142+
--p-popover-border-radius: var(--p-menu-border-radius);
145143
--p-popover-box-shadow: 0 0 0.5rem var(--theme-bg);
144+
--p-popover-shadow: 0 0 0.5rem var(--query-editor-bg);
146145
// -----------------------------------
147146

148147
// Progress

src/assets/styles/components/_message.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,6 @@
198198
font-weight: normal;
199199
}
200200

201-
.message-content.literally-empty {
202-
font-style: italic;
203-
--theme-text-message-system: var(--text-muted);
204-
}
205-
206201
[data-tool-state="input-streaming"] {
207202
&>.markdown code .hljs-comment::after {
208203
content: "";

src/assets/styles/pages/configuration/_main.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
width: 100%;
4949
box-shadow: none;
5050
text-align: left;
51-
height: 2rem;
51+
height: 1.75rem;
5252
justify-content: flex-start;
5353
background-color: transparent;
5454

src/components/ChatInterface.vue

Lines changed: 115 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
<template>
2-
<div class="chat-container" :class="{ 'empty-chat': messages.length === 0 }" :data-status="status">
2+
<div
3+
class="chat-container"
4+
:class="{ 'empty-chat': messages.length === 0 }"
5+
:data-status="status"
6+
>
37
<div class="scroll-container" ref="scrollContainerRef">
48
<div class="header">
5-
<button class="btn btn-flat-2 settings-btn" @click="$emit('open-configuration')">
9+
<button
10+
class="btn btn-flat-2 settings-btn"
11+
@click="$emit('open-configuration')"
12+
>
613
<span class="material-symbols-outlined">settings</span>
714
<span class="title-popup">Settings</span>
815
</button>
@@ -12,30 +19,51 @@
1219
<p>
1320
The AI Shell can see your table schemas, and (with your permission)
1421
run {{ sqlOrCode }} to answer your questions.
15-
<ExternalLink href="https://docs.beekeeperstudio.io/user_guide/sql-ai-shell/">Learn more</ExternalLink>.
22+
<ExternalLink
23+
href="https://docs.beekeeperstudio.io/user_guide/sql-ai-shell/"
24+
>Learn more</ExternalLink
25+
>.
1626
</p>
1727
</div>
1828
<div class="chat-messages">
1929
<template v-for="(message, index) in messages" :key="message.id">
2030
<message
2131
v-if="
22-
!(message.role === 'assistant' && message.parts.find((p) => p.type === 'data-userEditedToolCall'))
23-
&& !(message.role === 'user' && message.parts.find((p) => p.type === 'data-editedQuery'))
24-
"
32+
!(
33+
message.role === 'assistant' &&
34+
message.parts.find((p) => p.type === 'data-userEditedToolCall')
35+
) &&
36+
!(
37+
message.role === 'user' &&
38+
message.parts.find((p) => p.type === 'data-editedQuery')
39+
)
40+
"
2541
:message="message"
26-
:status="index === messages.length - 1 ? (status === 'ready' || status === 'error' ? 'ready' : 'processing') : 'ready'"
27-
@accept-permission="acceptPermission" @reject-permission="handleRejectPermission" />
42+
:status="
43+
index === messages.length - 1
44+
? status === 'ready' || status === 'error'
45+
? 'ready'
46+
: 'processing'
47+
: 'ready'
48+
"
49+
@accept-permission="acceptPermission"
50+
@reject-permission="handleRejectPermission"
51+
/>
2852
</template>
2953
<div class="message error" v-if="isUnexpectedError">
3054
<div class="message-content">
3155
Something went wrong.
3256
<div v-if="isOllamaToolError" class="error-hint">
33-
💡 <strong>Hint:</strong> This might be because your Ollama model doesn't support tools. Try using a
34-
different model, or switch to a different provider.
57+
💡 <strong>Hint:</strong> This might be because your Ollama model
58+
doesn't support tools. Try using a different model.
3559
</div>
3660
<pre v-if="!isErrorTruncated || showFullError" v-text="error" />
3761
<pre v-else v-text="truncatedError" />
38-
<button v-if="isErrorTruncated" @click="showFullError = !showFullError" class="btn show-more-btn">
62+
<button
63+
v-if="isErrorTruncated"
64+
@click="showFullError = !showFullError"
65+
class="btn show-more-btn"
66+
>
3967
{{ showFullError ? "Show less" : "Show more" }}
4068
</button>
4169
<button class="btn" @click="() => reload()">
@@ -47,18 +75,46 @@
4775
<div class="message error" v-if="noModelError">
4876
<div class="message-content">No model selected</div>
4977
</div>
50-
<div class="spinner-container" :style="{ visibility: showSpinner ? 'visible' : 'hidden' }">
78+
<div
79+
class="spinner-container"
80+
:style="{ visibility: showSpinner ? 'visible' : 'hidden' }"
81+
>
5182
<span class="spinner" />
83+
<span class="label" v-if="compacting">Compacting</span>
5284
</div>
5385
</div>
54-
<button v-if="!isAtBottom" @click="scrollToBottom({ smooth: true })" class="btn scroll-down-btn"
55-
title="Scroll to bottom">
86+
<button
87+
v-if="!isAtBottom"
88+
@click="scrollToBottom({ smooth: true })"
89+
class="btn scroll-down-btn"
90+
title="Scroll to bottom"
91+
>
5692
<span class="material-symbols-outlined">keyboard_arrow_down</span>
5793
</button>
5894
</div>
5995
<div class="chat-input-container-container">
60-
<PromptInput ref="promptInput" storage-key="inputHistory" :processing="processing" :selected-model="model"
61-
@select-model="selectModel" @manage-models="$emit('manage-models')" @submit="submit" @stop="stop" />
96+
<PromptInput
97+
ref="promptInput"
98+
storage-key="inputHistory"
99+
:processing="processing"
100+
:selected-model="model"
101+
@select-model="selectModel"
102+
@manage-models="$emit('manage-models')"
103+
@submit="submit"
104+
@stop="stop"
105+
/>
106+
<div
107+
v-if="enableAutoCompact && contextLeftUntilAutoCompact <= 15"
108+
class="auto-compact-notice"
109+
>
110+
<template v-if="contextOverflow">
111+
Auto-compacting on next message
112+
</template>
113+
<template v-else>
114+
Context left until auto-compact:
115+
{{ contextLeftUntilAutoCompact.toFixed(1) }}%
116+
</template>
117+
</div>
62118
</div>
63119
</div>
64120
</template>
@@ -79,6 +135,7 @@ import PromptInput from "@/components/common/PromptInput.vue";
79135
import { getConnectionInfo } from "@beekeeperstudio/plugin";
80136
import ExternalLink from "@/components/common/ExternalLink.vue";
81137
import { log } from "@beekeeperstudio/plugin";
138+
import { useConfigurationStore } from "@/stores/configuration";
82139
83140
export default {
84141
name: "ChatInterface",
@@ -101,20 +158,7 @@ export default {
101158
},
102159
103160
setup(props) {
104-
const ai = useAI({ initialMessages: props.initialMessages });
105-
106-
return {
107-
send: ai.send,
108-
abort: ai.abort,
109-
messages: ai.messages,
110-
error: ai.error,
111-
status: ai.status,
112-
acceptPermission: ai.acceptPermission,
113-
rejectPermission: ai.rejectPermission,
114-
rejectAllPendingApprovals: ai.rejectAllPendingApprovals,
115-
hasPendingApprovals: ai.hasPendingApprovals,
116-
retry: ai.retry,
117-
};
161+
return useAI({ initialMessages: props.initialMessages });
118162
},
119163
120164
data() {
@@ -127,13 +171,22 @@ export default {
127171
},
128172
129173
computed: {
130-
...mapGetters(useChatStore, ["systemPrompt"]),
174+
...mapGetters(useConfigurationStore, ["enableAutoCompact"]),
175+
...mapGetters(useChatStore, [
176+
"systemPrompt",
177+
"contextOverflow",
178+
"contextLeftUntilAutoCompact",
179+
]),
131180
...mapWritableState(useChatStore, ["model"]),
132181
processing() {
133182
if (this.hasPendingApprovals) return false;
134183
return this.status !== "ready" && this.status !== "error";
135184
},
136185
showSpinner() {
186+
if (this.compacting) {
187+
return true;
188+
}
189+
137190
return (
138191
!this.hasPendingApprovals &&
139192
(this.status === "submitted" || this.status === "streaming")
@@ -148,12 +201,12 @@ export default {
148201
return true;
149202
}
150203
151-
if (this.error.message.includes('User rejected tool call')) {
204+
if (this.error.message.includes("User rejected tool call")) {
152205
return false;
153206
}
154207
155208
// User aborted request before AI got a chance to respond
156-
if (this.error.message.includes('aborted without reason')) {
209+
if (this.error.message.includes("aborted without reason")) {
157210
return false;
158211
}
159212
@@ -168,8 +221,8 @@ export default {
168221
isOllamaToolError() {
169222
if (!this.error || !this.model) return false;
170223
const errorStr = this.error.toString().toLowerCase();
171-
const isOllama = this.model.provider === 'ollama';
172-
const hasToolError = errorStr.includes('bad request');
224+
const isOllama = this.model.provider === "ollama";
225+
const hasToolError = errorStr.includes("bad request");
173226
return isOllama && hasToolError;
174227
},
175228
@@ -184,7 +237,7 @@ export default {
184237
}
185238
},
186239
},
187-
]
240+
];
188241
},
189242
},
190243
@@ -207,9 +260,11 @@ export default {
207260
208261
async mounted() {
209262
getConnectionInfo().then((connection) => {
210-
if (connection.databaseType === "mongodb"
211-
|| connection.connectionType === "surrealdb"
212-
|| connection.connectionType === "redis") {
263+
if (
264+
connection.databaseType === "mongodb" ||
265+
connection.connectionType === "surrealdb" ||
266+
connection.connectionType === "redis"
267+
) {
213268
this.sqlOrCode = "Code";
214269
}
215270
});
@@ -219,8 +274,8 @@ export default {
219274
// Calculate if we're near bottom (within 50px of bottom)
220275
const isNearBottom =
221276
scrollContainer.scrollHeight -
222-
scrollContainer.scrollTop -
223-
scrollContainer.clientHeight <
277+
scrollContainer.scrollTop -
278+
scrollContainer.clientHeight <
224279
50;
225280
226281
this.isAtBottom = isNearBottom;
@@ -233,7 +288,7 @@ export default {
233288
methods: {
234289
...mapActions(useInternalDataStore, ["setInternal"]),
235290
236-
submit(input: string) {
291+
async submit(input: string) {
237292
if (!this.model) {
238293
// FIXME we should catch this and show it on screen
239294
this.noModelError = true;
@@ -246,7 +301,11 @@ export default {
246301
this.rejectAllPendingApprovals();
247302
}
248303
249-
this.send(input);
304+
if (this.contextOverflow) {
305+
await this.compact(input);
306+
} else {
307+
await this.send(input);
308+
}
250309
},
251310
252311
async reload() {
@@ -268,7 +327,7 @@ export default {
268327
if (options?.smooth) {
269328
this.$refs.scrollContainerRef.scrollTo({
270329
top: this.$refs.scrollContainerRef.scrollHeight,
271-
behavior: 'smooth'
330+
behavior: "smooth",
272331
});
273332
} else {
274333
this.$refs.scrollContainerRef.scrollTop =
@@ -299,3 +358,15 @@ export default {
299358
},
300359
};
301360
</script>
361+
362+
<style scoped>
363+
.spinner-container .label {
364+
padding-left: 1ch;
365+
}
366+
.auto-compact-notice {
367+
width: 100%;
368+
margin-top: 0.5rem;
369+
text-align: right;
370+
color: var(--text);
371+
}
372+
</style>

src/components/OnboardingScreen.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export default {
6262
methods: {
6363
handleChangeProvider(provider: AvailableProviders) {
6464
this.providerChanged = true;
65-
if (provider === "ollama") {
65+
if (provider === "ollama" || provider === "mock") {
6666
this.changed = true;
6767
}
6868
},

src/components/common/BaseInput.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ export default {
9292
9393
emits: ["update:modelValue", "input", "change", "click"],
9494
95-
expose: ["focus"],
95+
// FIXME: Strip this out for now cause vue-tsc isn't happy
96+
// See https://github.com/vuejs/language-tools/issues/5069
97+
// expose: ["focus"],
9698
9799
methods: {
98100
emitInput(event: Event) {

src/components/common/PromptInput.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ export default defineComponent({
4747
4848
emits: ["submit", "stop", "manage-models", "select-model"],
4949
50-
expose: ["focus"],
50+
// FIXME: Strip this out for now cause vue-tsc isn't happy
51+
// See https://github.com/vuejs/language-tools/issues/5069
52+
// expose: ["focus"],
5153
5254
props: {
5355
processing: Boolean,

0 commit comments

Comments
 (0)