separate autocomplete into mixin

This commit is contained in:
vegu-ai-tools
2026-05-19 22:46:32 +03:00
parent f3d41d23df
commit 64b136b2f3
2 changed files with 98 additions and 71 deletions

View File

@@ -0,0 +1,95 @@
/**
* Mixin that owns the SceneMessageInput autocompletion flow.
*
* Host component requirements:
* - `messageInput` data property (v-model on SceneMessageInput)
* - `inputDisabled` data property (disabled while a suggestion is in flight)
* - `websocket` data property (used to send the autocomplete request)
* - `getAgents()` method (used to read the creator agent's hints toggle)
*
* The host must also forward `autocomplete_suggestion` websocket messages to
* `handleAutocompleteMessage(data)`, which returns `true` when consumed.
*/
import { primaryModifierLabel } from '@/utils/keyboardModifiers';
import { applyCompletion as applyAutocompleteCompletion } from '@/utils/autocompleteHint';
export default {
data() {
return {
autocompleting: false,
autocompleteCallback: null,
autocompleteFocusElement: null,
autocompleteHintsEnabledAtSend: true,
}
},
methods: {
handleAutocompleteMessage(data) {
if (data.type !== 'autocomplete_suggestion') {
return false;
}
if (!this.autocompleteCallback) {
return true;
}
const completion = data.message;
this.autocompleteCallback(completion);
if (this.autocompleteFocusElement) {
const focus_element = this.autocompleteFocusElement;
setTimeout(() => {
focus_element.focus();
}, 1000);
this.autocompleteFocusElement = null;
}
this.autocompleteCallback = null;
return true;
},
onAutocompleteStart() {
this.autocompleting = true;
this.inputDisabled = true;
},
onAutocompleteEnd(completion) {
this.inputDisabled = false;
this.autocompleting = false;
// Use the toggle value captured at send time, so toggling mid-flight
// doesn't desync from what the backend already decided.
this.messageInput = applyAutocompleteCompletion(
this.messageInput, completion, this.autocompleteHintsEnabledAtSend
);
},
autocompleteHintsEnabled() {
// `!== false` mirrors the Python `value=True` default during the
// brief pre-sync window before agent state has been received.
const creator = this.getAgents().find(a => a.name === 'creator');
const value = creator?.actions?.autocomplete?.config?.hints_enabled?.value;
return value !== false;
},
autocompleteRequest(param, callback, focus_element, delay = 500) {
const hintsEnabled = this.autocompleteHintsEnabled();
this.autocompleteCallback = (completion) => {
setTimeout(() => {
callback(completion, { hintsEnabled });
}, delay);
};
this.autocompleteFocusElement = focus_element;
this.autocompleteHintsEnabledAtSend = hintsEnabled;
const param_copy = JSON.parse(JSON.stringify(param));
param_copy.type = "assistant";
param_copy.action = "autocomplete";
this.websocket.send(JSON.stringify(param_copy));
},
autocompleteInfoMessage(active) {
return active ? 'Generating ...' : `${primaryModifierLabel}+Enter to autocomplete`;
},
},
}

View File

@@ -376,7 +376,6 @@
<script>
import AIClient from './AIClient.vue';
import { primaryModifierLabel } from '@/utils/keyboardModifiers';
import AIAgent from './AIAgent.vue';
import AgentActivityBar from './AgentActivityBar.vue';
import AgentActionOverrides from './AgentActionOverrides.vue';
@@ -413,7 +412,7 @@ import PromptsMenu from './prompts/PromptsMenu.vue';
import { debounce } from 'lodash';
import { isVisualAgentReady, isImageEditAvailable, isImageCreateAvailable } from '@/constants/visual';
import { createSceneAssetsRequester } from './VisualAssetsMixin.js';
import { applyCompletion as applyAutocompleteCompletion } from '@/utils/autocompleteHint';
import AutocompleteMixin from './AutocompleteMixin.js';
export default {
components: {
@@ -451,6 +450,7 @@ export default {
PromptsMenu,
},
name: 'TalemateApp',
mixins: [AutocompleteMixin],
data() {
return {
appearancePreview: null, // Preview config while editing settings (null = use saved config)
@@ -553,11 +553,6 @@ export default {
scene: {},
actAs: null,
appConfig: {},
autocompleting: false,
autocompletePartialInput: "",
autocompleteCallback: null,
autocompleteFocusElement: null,
autocompleteHintsEnabledAtSend: true,
worldStateTemplates: {},
// busy status of agents
agentStatus: {},
@@ -1117,24 +1112,7 @@ export default {
return;
}
if (data.type === 'autocomplete_suggestion') {
if(!this.autocompleteCallback)
return;
const completion = data.message;
this.autocompleteCallback(completion);
if (this.autocompleteFocusElement) {
let focus_element = this.autocompleteFocusElement;
setTimeout(() => {
focus_element.focus();
}, 1000);
this.autocompleteFocusElement = null;
}
this.autocompleteCallback = null;
this.autocompletePartialInput = "";
if (this.handleAutocompleteMessage(data)) {
return;
}
@@ -1285,29 +1263,6 @@ export default {
this.waitingForInput = false;
},
onAutocompleteStart() {
this.autocompleting = true;
this.inputDisabled = true;
},
onAutocompleteEnd(completion) {
this.inputDisabled = false;
this.autocompleting = false;
// Use the toggle value captured at send time, so toggling mid-flight
// doesn't desync from what the backend already decided.
this.messageInput = applyAutocompleteCompletion(
this.messageInput, completion, this.autocompleteHintsEnabledAtSend
);
},
autocompleteHintsEnabled() {
// `!== false` mirrors the Python `value=True` default during the
// brief pre-sync window before agent state has been received.
const creator = this.getAgents().find(a => a.name === 'creator');
const value = creator?.actions?.autocomplete?.config?.hints_enabled?.value;
return value !== false;
},
scrollInputIntoView() {
const scroll = () => this.$refs.sceneMessageInput?.scrollIntoView(false);
this.$nextTick(scroll);
@@ -1344,25 +1299,6 @@ export default {
}));
},
autocompleteRequest(param, callback, focus_element, delay=500) {
const hintsEnabled = this.autocompleteHintsEnabled();
this.autocompleteCallback = (completion) => {
setTimeout(() => {
callback(completion, { hintsEnabled });
}, delay);
};
this.autocompleteFocusElement = focus_element;
this.autocompletePartialInput = param.partial;
this.autocompleteHintsEnabledAtSend = hintsEnabled;
const param_copy = JSON.parse(JSON.stringify(param));
param_copy.type = "assistant";
param_copy.action = "autocomplete";
this.websocket.send(JSON.stringify(param_copy));
},
syncActAs() {
// sets the appropriate actAs
@@ -1385,10 +1321,6 @@ export default {
this.$refs.sceneMessageInput?.cycleActAs();
},
autocompleteInfoMessage(active) {
return active ? 'Generating ...' : `${primaryModifierLabel}+Enter to autocomplete`;
},
requestAppConfig() {
this.websocket.send(JSON.stringify({ type: 'request_app_config' }));
},