mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-09-01 19:48:52 +02:00
allow overriding reasoning on certain agent actions
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
- "Player Character Toggle: Added a Make Player Character / Unmark as Player action to the World Editor character editor. Promoting a non-player character makes them the player and automatically demotes the previous player (if any) to an AI actor — the previous player stays active in the scene. Unmarking the current player flips them to AI without requiring a replacement, leaving the scene with no explicit player. Promoting an inactive character also activates them."
|
||||
- "Message Revision History: Regenerated AI messages now show a paginator above the message body. Click the arrows to browse previous regenerations; the version you're viewing becomes the canonical one the AI continues from. Lives in the browser session only."
|
||||
- "Scene Perspective Overrides: Expanded the scene outline perspective into a default plus three per-speaker overrides (player, NPCs, narrator). Each override falls back to the default when empty. A new `{player_name}` placeholder is substituted at prompt render time and suppresses the perspective when the scene has no explicit player character. Presets are managed under Settings → Creator → Perspective Presets. Existing scenes are migrated automatically."
|
||||
- "Per-Action Reasoning Override: Force reasoning off for specific agent actions without changing the client's global setting. Toggle via the brain icon in the prompt log; configured overrides are listed via a badge next to the Agents sidebar header."
|
||||
improvements:
|
||||
- "Settings UX: Renamed the Creator sub-tab from 'Content Context' to 'Content Classification' to match the field label used in the scene outline editor. List contents and behavior are unchanged."
|
||||
- "Message Revision History: Continuing a character or narrator message now creates a navigable revision entry tagged 'Continued', alongside the existing regenerate entries. The pre-continuation text is reachable via the paginator arrows. Narrator messages also gain the Continue action on the hover toolbar to match the character-message flow."
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import functools
|
||||
import json
|
||||
from inspect import signature
|
||||
@@ -289,6 +290,19 @@ class store_context_state:
|
||||
return fn
|
||||
|
||||
|
||||
def _agent_action_override_kwargs(agent_type: str, action_name: str) -> dict:
|
||||
"""ClientContext kwargs for any per-action override on ``{agent_type}.{action_name}``; empty when none applies."""
|
||||
config = get_config()
|
||||
override = config.agent_actions.overrides.get(f"{agent_type}.{action_name}")
|
||||
if override is None:
|
||||
return {}
|
||||
|
||||
kwargs: dict = {}
|
||||
if override.disable_reasoning:
|
||||
kwargs["disable_reasoning"] = True
|
||||
return kwargs
|
||||
|
||||
|
||||
def set_processing(fn):
|
||||
"""
|
||||
decorator that emits the agent status as processing while the function
|
||||
@@ -334,7 +348,16 @@ def set_processing(fn):
|
||||
|
||||
self.set_context_states(**all_args)
|
||||
|
||||
return await fn(self, *args, **kwargs)
|
||||
override_kwargs = _agent_action_override_kwargs(
|
||||
self.agent_type, action_name
|
||||
)
|
||||
override_ctx = (
|
||||
ClientContext(**override_kwargs)
|
||||
if override_kwargs
|
||||
else contextlib.nullcontext()
|
||||
)
|
||||
with override_ctx:
|
||||
return await fn(self, *args, **kwargs)
|
||||
finally:
|
||||
try:
|
||||
self._current_action = None
|
||||
|
||||
@@ -201,7 +201,7 @@ class AnthropicClient(ConcurrentInferenceMixin, EndpointOverrideMixin, ClientBas
|
||||
@property
|
||||
def reasoning_display(self) -> ReasoningDisplay | None:
|
||||
"""Returns reasoning display config based on what's actually used at runtime."""
|
||||
if not self.reason_enabled:
|
||||
if not self.reason_enabled_configured:
|
||||
return None
|
||||
|
||||
# Only show effort display if adaptive will ACTUALLY be used.
|
||||
|
||||
@@ -82,6 +82,8 @@ class PromptData(pydantic.BaseModel):
|
||||
client_type: str
|
||||
time: Union[float, int]
|
||||
agent_stack: list[str] = pydantic.Field(default_factory=list)
|
||||
agent_type: str | None = None
|
||||
agent_action: str | None = None
|
||||
generation_parameters: dict = pydantic.Field(default_factory=dict)
|
||||
inference_preset: str = None
|
||||
preset_group: str | None = None
|
||||
@@ -391,9 +393,18 @@ class ClientBase:
|
||||
return self.client_config.preset_group
|
||||
|
||||
@property
|
||||
def reason_enabled(self) -> bool:
|
||||
def reason_enabled_configured(self) -> bool:
|
||||
"""Stable configured value. Subclasses override for client-specific
|
||||
forcing (Gemini 2.5+, OpenAI o-series). `reason_enabled` is the
|
||||
runtime value after per-action context overrides."""
|
||||
return self.client_config.reason_enabled
|
||||
|
||||
@property
|
||||
def reason_enabled(self) -> bool:
|
||||
if client_context_attribute("disable_reasoning") and not self.reason_locked:
|
||||
return False
|
||||
return self.reason_enabled_configured
|
||||
|
||||
@property
|
||||
def reason_tokens(self) -> int:
|
||||
return self.client_config.reason_tokens
|
||||
@@ -567,9 +578,10 @@ class ClientBase:
|
||||
"""Returns reasoning display config based on what's actually used at runtime.
|
||||
|
||||
Override in subclasses for custom behavior (e.g., adaptive thinking).
|
||||
Returns None if reasoning is not enabled.
|
||||
Returns None if reasoning is not enabled. Uses the configured value so
|
||||
per-action overrides don't flicker the persistent UI indicator.
|
||||
"""
|
||||
if not self.reason_enabled:
|
||||
if not self.reason_enabled_configured:
|
||||
return None
|
||||
return ReasoningDisplay(
|
||||
indicator_value=str(self.validated_reason_tokens),
|
||||
@@ -975,7 +987,7 @@ class ClientBase:
|
||||
"embeddings_model_name": self.embeddings_model_name,
|
||||
"can_support_concurrent_inference": self.can_support_concurrent_inference,
|
||||
"supports_concurrent_inference": self.supports_concurrent_inference,
|
||||
"reason_enabled": self.reason_enabled,
|
||||
"reason_enabled": self.reason_enabled_configured,
|
||||
"reason_tokens": self.reason_tokens,
|
||||
"min_reason_tokens": self.min_reason_tokens,
|
||||
"reason_response_pattern": self.client_config.reason_response_pattern,
|
||||
@@ -1639,6 +1651,10 @@ class ClientBase:
|
||||
response_tokens=self._returned_response_tokens
|
||||
or self.count_tokens(response),
|
||||
agent_stack=agent_context.agent_stack if agent_context else [],
|
||||
agent_type=agent_context.agent.agent_type
|
||||
if agent_context
|
||||
else None,
|
||||
agent_action=agent_context.action if agent_context else None,
|
||||
client_name=self.name,
|
||||
client_type=self.client_type,
|
||||
time=time_end - time_start,
|
||||
|
||||
@@ -47,6 +47,8 @@ class ContextModel(BaseModel):
|
||||
inference_preset: str = None
|
||||
data_format: str | None = None
|
||||
requires_active_scene: bool = True
|
||||
# Honored unless the client reports reason_locked.
|
||||
disable_reasoning: bool = False
|
||||
|
||||
|
||||
# Define the context variable as an empty dictionary
|
||||
|
||||
@@ -111,12 +111,11 @@ class GoogleClient(
|
||||
return self.client_config.disable_safety_settings
|
||||
|
||||
@property
|
||||
def reason_enabled(self) -> bool:
|
||||
def reason_enabled_configured(self) -> bool:
|
||||
if self.reason_locked:
|
||||
# Always enable reasoning for Gemini 3 and Gemini 2.5
|
||||
return True
|
||||
|
||||
return self.client_config.reason_enabled
|
||||
return super().reason_enabled_configured
|
||||
|
||||
@property
|
||||
def min_reason_tokens(self) -> int:
|
||||
|
||||
@@ -619,6 +619,14 @@ class PromptsConfig(pydantic.BaseModel):
|
||||
template_sources: Dict[str, str] = pydantic.Field(default_factory=dict)
|
||||
|
||||
|
||||
class AgentActionOverride(pydantic.BaseModel):
|
||||
disable_reasoning: bool = False
|
||||
|
||||
|
||||
class AgentActionsConfig(pydantic.BaseModel):
|
||||
overrides: Dict[str, AgentActionOverride] = pydantic.Field(default_factory=dict)
|
||||
|
||||
|
||||
class Config(pydantic.BaseModel):
|
||||
clients: Dict[str, AnnotatedClient] = {}
|
||||
|
||||
@@ -658,6 +666,8 @@ class Config(pydantic.BaseModel):
|
||||
|
||||
prompts: PromptsConfig = PromptsConfig()
|
||||
|
||||
agent_actions: AgentActionsConfig = AgentActionsConfig()
|
||||
|
||||
dirty: bool = pydantic.Field(default=False, exclude=True)
|
||||
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
@@ -14,7 +14,10 @@ from talemate.client.base import (
|
||||
locked_model_template,
|
||||
)
|
||||
from talemate.config import Config as AppConfigData
|
||||
from talemate.config.schema import GamePlayerCharacter
|
||||
from talemate.config.schema import (
|
||||
AgentActionOverride,
|
||||
GamePlayerCharacter,
|
||||
)
|
||||
from talemate.config import get_config, Config, update_config
|
||||
from talemate.emit import emit
|
||||
from talemate.instance import emit_clients_status, get_client
|
||||
@@ -95,6 +98,15 @@ class SystemCapabilitiesPayload(pydantic.BaseModel):
|
||||
torch_cuda: TorchCudaInfo = TorchCudaInfo()
|
||||
|
||||
|
||||
class SetAgentActionOverridePayload(pydantic.BaseModel):
|
||||
key: str
|
||||
disable_reasoning: bool = False
|
||||
|
||||
|
||||
class ClearAgentActionOverridePayload(pydantic.BaseModel):
|
||||
key: str
|
||||
|
||||
|
||||
class ConfigPlugin(Plugin):
|
||||
router = "config"
|
||||
|
||||
@@ -486,6 +498,29 @@ class ConfigPlugin(Plugin):
|
||||
{"type": "app_config", "data": config.model_dump(), "version": VERSION}
|
||||
)
|
||||
|
||||
async def handle_set_agent_action_override(self, data):
|
||||
"""Set or update an override. Empty overrides are dropped so the config stays sparse."""
|
||||
payload = SetAgentActionOverridePayload(**data["data"])
|
||||
|
||||
config: Config = get_config()
|
||||
override = AgentActionOverride(disable_reasoning=payload.disable_reasoning)
|
||||
|
||||
if override == AgentActionOverride():
|
||||
config.agent_actions.overrides.pop(payload.key, None)
|
||||
else:
|
||||
config.agent_actions.overrides[payload.key] = override
|
||||
|
||||
await config.set_dirty()
|
||||
|
||||
async def handle_clear_agent_action_override(self, data):
|
||||
payload = ClearAgentActionOverridePayload(**data["data"])
|
||||
|
||||
config: Config = get_config()
|
||||
if config.agent_actions.overrides.pop(payload.key, None) is None:
|
||||
return
|
||||
|
||||
await config.set_dirty()
|
||||
|
||||
async def handle_save_unified_api_key(self, data):
|
||||
"""Save a unified API key to app config."""
|
||||
payload = SaveUnifiedAPIKeyPayload(**data["data"])
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
<template>
|
||||
<v-list-subheader class="text-uppercase">
|
||||
<v-icon>mdi-transit-connection-variant</v-icon>
|
||||
Agents
|
||||
<v-tooltip v-if="overrideCount > 0" location="top" :text="`${overrideCount} per-action override${overrideCount === 1 ? '' : 's'} active — manage`">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
@click="openAgentActionOverrides()"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
prepend-icon="mdi-tune"
|
||||
class="ml-2"
|
||||
>{{ overrideCount }}</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-list-subheader>
|
||||
<div v-if="isConnected()">
|
||||
<v-list density="compact">
|
||||
<!-- Ctrl + click toggles agent enable/disable when allowed -->
|
||||
@@ -152,6 +169,9 @@ export default {
|
||||
scene: Object,
|
||||
},
|
||||
computed: {
|
||||
overrideCount() {
|
||||
return Object.keys(this.appConfig?.agent_actions?.overrides || {}).length;
|
||||
},
|
||||
agentStateNotifications() {
|
||||
// if key begins with 'notify__' and value is a string, return the key and value
|
||||
// return the notify__(.+) part as the key, and the value as the value
|
||||
@@ -177,6 +197,7 @@ export default {
|
||||
'registerMessageHandler',
|
||||
'isConnected',
|
||||
'getClients',
|
||||
'openAgentActionOverrides',
|
||||
],
|
||||
provide() {
|
||||
return {
|
||||
|
||||
99
talemate_frontend/src/components/AgentActionOverrides.vue
Normal file
99
talemate_frontend/src/components/AgentActionOverrides.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<v-dialog v-model="dialog" max-width="720" scrollable>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon class="mr-2">mdi-tune</v-icon>
|
||||
Agent action overrides
|
||||
<v-spacer />
|
||||
<v-btn icon="mdi-close" variant="text" size="small" @click="dialog = false" />
|
||||
</v-card-title>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text style="max-height: 60vh">
|
||||
<div v-if="rows.length" class="d-flex align-center px-4 py-1 text-caption text-uppercase text-muted">
|
||||
<span>Action</span>
|
||||
<v-spacer />
|
||||
<span>Disable reasoning</span>
|
||||
</div>
|
||||
<v-list density="compact" v-if="rows.length">
|
||||
<v-list-item v-for="row in rows" :key="row.key">
|
||||
<v-list-item-title class="text-body-2">
|
||||
<span class="text-primary">{{ row.agentLabel }}</span>
|
||||
<span class="text-disabled">.</span>
|
||||
<span>{{ row.action }}</span>
|
||||
</v-list-item-title>
|
||||
<template #append>
|
||||
<v-switch
|
||||
:model-value="!!row.override.disable_reasoning"
|
||||
@update:model-value="onToggleDisableReasoning(row.key, $event)"
|
||||
hide-details
|
||||
density="compact"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-alert v-else type="info" variant="tonal" density="compact">
|
||||
No overrides configured. Open a prompt in the prompt log and click the brain icon next to its action to add one.
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AgentActionOverrides',
|
||||
props: {
|
||||
appConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
agentStatus: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return { dialog: false };
|
||||
},
|
||||
computed: {
|
||||
overridesMap() {
|
||||
return this.appConfig?.agent_actions?.overrides || {};
|
||||
},
|
||||
rows() {
|
||||
return Object.entries(this.overridesMap)
|
||||
.map(([key, override]) => {
|
||||
// Split on the first dot only — action names can theoretically contain dots.
|
||||
const idx = key.indexOf('.');
|
||||
const agentType = idx >= 0 ? key.slice(0, idx) : key;
|
||||
const action = idx >= 0 ? key.slice(idx + 1) : '';
|
||||
return {
|
||||
key,
|
||||
agentType,
|
||||
agentLabel: this.agentStatus?.[agentType]?.label || agentType,
|
||||
action,
|
||||
override
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.key.localeCompare(b.key));
|
||||
}
|
||||
},
|
||||
inject: ['getWebsocket'],
|
||||
methods: {
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
onToggleDisableReasoning(key, value) {
|
||||
const ws = this.getWebsocket();
|
||||
if (!ws) return;
|
||||
ws.send(JSON.stringify({
|
||||
type: 'config',
|
||||
action: 'set_agent_action_override',
|
||||
data: { key, disable_reasoning: !!value }
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -17,7 +17,7 @@
|
||||
@click="openPromptView(prompt)"
|
||||
/>
|
||||
|
||||
<DebugToolPromptView ref="promptView" />
|
||||
<DebugToolPromptView ref="promptView" :app-config="appConfig" />
|
||||
</template>
|
||||
<script>
|
||||
|
||||
@@ -31,6 +31,10 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
appConfig: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
components: {
|
||||
DebugToolPromptView,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<PromptDetailView
|
||||
ref="promptDetailView"
|
||||
:prompt="prompt"
|
||||
:app-config="appConfig"
|
||||
@navigate-to-template="handleNavigateToTemplate"
|
||||
/>
|
||||
</v-card-text>
|
||||
@@ -25,6 +26,12 @@ export default {
|
||||
components: {
|
||||
PromptDetailView,
|
||||
},
|
||||
props: {
|
||||
appConfig: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prompt: null,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="prompts">
|
||||
<DebugToolPromptLog ref="promptLog" :prompts="prompts" @clear-prompts="$emit('clear-prompts')"/>
|
||||
<DebugToolPromptLog ref="promptLog" :prompts="prompts" :app-config="appConfig" @clear-prompts="$emit('clear-prompts')"/>
|
||||
</v-window-item>
|
||||
<v-window-item value="memory_requests">
|
||||
<DebugToolMemoryRequestLog ref="memoryRequestLog"/>
|
||||
@@ -63,6 +63,10 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
appConfig: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ['clear-prompts'],
|
||||
data() {
|
||||
|
||||
@@ -189,7 +189,6 @@
|
||||
<v-list>
|
||||
<AIClient ref="aiClient" @save="saveClients" @error="uxErrorHandler" @clients-updated="saveClients" @client-assigned="saveAgents" @open-app-config="openAppConfig" :immutable-config="appConfig" :app-config="appConfig"></AIClient>
|
||||
<v-divider></v-divider>
|
||||
<v-list-subheader class="text-uppercase"><v-icon>mdi-transit-connection-variant</v-icon> Agents</v-list-subheader>
|
||||
<AIAgent ref="aiAgent" @save="saveAgents" @agents-updated="saveAgents" :agentState="agentState" :templates="worldStateTemplates" :app-config="appConfig" :scene="scene"></AIAgent>
|
||||
<!-- More sections can be added here -->
|
||||
</v-list>
|
||||
@@ -204,7 +203,7 @@
|
||||
<v-navigation-drawer v-model="debugDrawer" app location="right" width="400" disable-resize-watcher>
|
||||
<v-list>
|
||||
<v-list-subheader class="text-uppercase"><v-icon>mdi-bug</v-icon> Debug Tools</v-list-subheader>
|
||||
<DebugTools ref="debugTools" :scene="scene" :prompts="promptsViewPrompts" @clear-prompts="clearPrompts"></DebugTools>
|
||||
<DebugTools ref="debugTools" :scene="scene" :prompts="promptsViewPrompts" :app-config="appConfig" @clear-prompts="clearPrompts"></DebugTools>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
@@ -349,7 +348,7 @@
|
||||
</v-tabs-window-item>
|
||||
<!-- PROMPTS -->
|
||||
<v-tabs-window-item :transition="false" :reverse-transition="false" value="prompts">
|
||||
<PromptsView :visible="tab === 'prompts'" :prompts="prompts" :agent-status="agentStatus" ref="promptsView" v-model:main-tab="promptsMainTab" @clear-prompts="onClearPrompts" />
|
||||
<PromptsView :visible="tab === 'prompts'" :prompts="prompts" :agent-status="agentStatus" :app-config="appConfig" ref="promptsView" v-model:main-tab="promptsMainTab" @clear-prompts="onClearPrompts" />
|
||||
</v-tabs-window-item>
|
||||
|
||||
</v-tabs-window>
|
||||
@@ -358,6 +357,7 @@
|
||||
</v-main>
|
||||
|
||||
<AppConfig ref="appConfig" :agentStatus="agentStatus" :sceneActive="sceneActive" :clientStatus="clientStatus" @appearance-preview="onAppearancePreview" @appearance-preview-clear="onAppearancePreviewClear" />
|
||||
<AgentActionOverrides ref="agentActionOverrides" :app-config="appConfig" :agent-status="agentStatus" />
|
||||
<v-snackbar v-model="errorNotification" color="red-darken-1" :timeout="3000">
|
||||
{{ errorMessage }}
|
||||
</v-snackbar>
|
||||
@@ -379,6 +379,7 @@ 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';
|
||||
import LoadScene from './LoadScene.vue';
|
||||
import SceneTools from './SceneTools.vue';
|
||||
import SceneMessages from './SceneMessages.vue';
|
||||
@@ -419,6 +420,7 @@ export default {
|
||||
AIClient,
|
||||
AIAgent,
|
||||
AgentActivityBar,
|
||||
AgentActionOverrides,
|
||||
LoadScene,
|
||||
SceneTools,
|
||||
SceneMessages,
|
||||
@@ -820,6 +822,7 @@ export default {
|
||||
requestAppConfig: () => this.requestAppConfig(),
|
||||
appConfig: () => this.appConfig,
|
||||
openAppConfig: this.openAppConfig,
|
||||
openAgentActionOverrides: () => this.$refs.agentActionOverrides?.open(),
|
||||
configurationRequired: () => this.configurationRequired(),
|
||||
getTrackedCharacterState: (name, question) => this.$refs.worldState.trackedCharacterState(name, question),
|
||||
getTrackedCharacterStates: (name) => this.$refs.worldState.trackedCharacterStates(name),
|
||||
@@ -1699,16 +1702,15 @@ export default {
|
||||
|
||||
// Handle prompt_sent messages (capture prompts for PromptsMenu)
|
||||
handlePromptSent(data) {
|
||||
// Get active agent (last in agent_stack if not empty)
|
||||
// agent_name here is the verbose display name (from agent_stack); the
|
||||
// canonical type+action come straight from the server now.
|
||||
let agent = null;
|
||||
let agentName = null;
|
||||
let agentAction = null;
|
||||
let agentAction = data.agent_action || null;
|
||||
|
||||
if (data.agent_stack && data.agent_stack.length > 0) {
|
||||
agent = data.agent_stack[data.agent_stack.length - 1];
|
||||
const agentParts = agent.split('.');
|
||||
agentName = agentParts[0];
|
||||
agentAction = agentParts[1];
|
||||
agentName = agent.split('.')[0];
|
||||
}
|
||||
|
||||
// Compute prefix cache ratio against previous prompt with same template_uid + client_name
|
||||
@@ -1731,6 +1733,7 @@ export default {
|
||||
agent_stack: data.agent_stack,
|
||||
agent: agent,
|
||||
agent_name: agentName,
|
||||
agent_type: data.agent_type || null,
|
||||
agent_action: agentAction,
|
||||
client_name: data.client_name,
|
||||
client_type: data.client_type,
|
||||
|
||||
@@ -7,6 +7,25 @@
|
||||
<v-chip size="small" label class="mr-1" color="primary" variant="tonal">
|
||||
<strong class="mr-1">action</strong>{{ prompt.agent_action }}
|
||||
</v-chip>
|
||||
<v-tooltip
|
||||
v-if="overrideKey"
|
||||
location="top"
|
||||
:text="reasoningOverrideActive
|
||||
? `Reasoning is forced off for ${overrideLabel}. Click to remove the override.`
|
||||
: `Force reasoning off whenever ${overrideLabel} runs (clients that always reason will ignore it).`"
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
size="x-small"
|
||||
variant="text"
|
||||
class="mr-1"
|
||||
icon="mdi-brain"
|
||||
:color="reasoningOverrideActive ? 'delete' : 'grey-darken-1'"
|
||||
@click="toggleReasoningOverride"
|
||||
/>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-chip class="mr-1" size="small" color="grey" label variant="tonal">
|
||||
<strong class="mr-1">task</strong> {{ prompt.kind }}
|
||||
</v-chip>
|
||||
@@ -185,6 +204,9 @@
|
||||
>Test Changes</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<span v-if="reasoningOverrideActive" class="text-caption text-muted ml-2">
|
||||
Test Changes runs with the client's default reasoning setting — the per-action override does not apply.
|
||||
</span>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
@@ -209,6 +231,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
appConfig: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ['navigate-to-template', 'test-changes', 'update:modelValue'],
|
||||
data() {
|
||||
@@ -249,6 +275,20 @@ export default {
|
||||
toggleDetailsLabel() {
|
||||
return this.details ? 'Hide Details' : 'Show Details';
|
||||
},
|
||||
overrideKey() {
|
||||
const type = this.prompt?.agent_type;
|
||||
const action = this.prompt?.agent_action;
|
||||
return type && action ? `${type}.${action}` : null;
|
||||
},
|
||||
overrideLabel() {
|
||||
// Verbose form for tooltip display; overrideKey stays the canonical config key.
|
||||
const label = this.prompt?.agent_name || this.prompt?.agent_type;
|
||||
return `${label}.${this.prompt?.agent_action}`;
|
||||
},
|
||||
reasoningOverrideActive() {
|
||||
if (!this.overrideKey) return false;
|
||||
return !!this.appConfig?.agent_actions?.overrides?.[this.overrideKey]?.disable_reasoning;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
prompt: {
|
||||
@@ -311,6 +351,25 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
toggleReasoningOverride() {
|
||||
if (!this.overrideKey) return;
|
||||
const ws = this.getWebsocket();
|
||||
if (!ws) return;
|
||||
if (this.reasoningOverrideActive) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'config',
|
||||
action: 'clear_agent_action_override',
|
||||
data: { key: this.overrideKey }
|
||||
}));
|
||||
} else {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'config',
|
||||
action: 'set_agent_action_override',
|
||||
data: { key: this.overrideKey, disable_reasoning: true }
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
testChanges() {
|
||||
this.busy = true;
|
||||
this.$emit('test-changes', {
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
>
|
||||
<PromptDetailView
|
||||
:prompt="prompt"
|
||||
:app-config="appConfig"
|
||||
@navigate-to-template="handleNavigateToTemplate"
|
||||
/>
|
||||
</v-window-item>
|
||||
@@ -263,6 +264,10 @@ export default {
|
||||
mainTab: {
|
||||
type: String,
|
||||
default: 'prompts'
|
||||
},
|
||||
appConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['clear-prompts', 'update:mainTab'],
|
||||
|
||||
Reference in New Issue
Block a user