bugfix
This commit is contained in:
@@ -212,7 +212,7 @@ if [ "$DEPLOY" == "1" ]; then
|
|||||||
esac
|
esac
|
||||||
echo "deployed $PROJECT $REMOTE/ai-$PROJECT:$TAG"
|
echo "deployed $PROJECT $REMOTE/ai-$PROJECT:$TAG"
|
||||||
else
|
else
|
||||||
YAML=$(sed "s#image: dexorder/ai-$PROJECT*#image: $REMOTE/ai-$PROJECT:$TAG#" deploy/k8s/$KUBERNETES.yaml)
|
YAML=$(sed "s#image: dexorder/ai-$PROJECT*#image: $REMOTE/ai-$PROJECT:$TAG#" deploy/k8s/base/$KUBERNETES.yaml)
|
||||||
echo "$YAML" | kubectl apply -f - || { echo "$YAML"; echo "kubectl apply failed"; exit 1; }
|
echo "$YAML" | kubectl apply -f - || { echo "$YAML"; echo "kubectl apply failed"; exit 1; }
|
||||||
echo deployed $KUBERNETES.yaml $REMOTE/ai-$PROJECT:$TAG
|
echo deployed $KUBERNETES.yaml $REMOTE/ai-$PROJECT:$TAG
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -505,8 +505,6 @@ export class AgentHarness {
|
|||||||
|
|
||||||
const messagesCopy = [...messages];
|
const messagesCopy = [...messages];
|
||||||
let iterations = 0;
|
let iterations = 0;
|
||||||
// Track last char of last yielded text chunk to detect missing spaces between tokens
|
|
||||||
let lastChunkTail = '';
|
|
||||||
|
|
||||||
while (iterations < maxIterations) {
|
while (iterations < maxIterations) {
|
||||||
if (signal?.aborted) break;
|
if (signal?.aborted) break;
|
||||||
@@ -525,24 +523,15 @@ export class AgentHarness {
|
|||||||
try {
|
try {
|
||||||
const stream = await model.stream(messagesCopy, { signal });
|
const stream = await model.stream(messagesCopy, { signal });
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
const contents: string[] = [];
|
|
||||||
if (typeof chunk.content === 'string' && chunk.content.length > 0) {
|
if (typeof chunk.content === 'string' && chunk.content.length > 0) {
|
||||||
contents.push(chunk.content);
|
yield { type: 'chunk', content: chunk.content };
|
||||||
} else if (Array.isArray(chunk.content)) {
|
} else if (Array.isArray(chunk.content)) {
|
||||||
for (const block of chunk.content) {
|
for (const block of chunk.content) {
|
||||||
if (block.type === 'text' && block.text) contents.push(block.text);
|
if (block.type === 'text' && block.text) {
|
||||||
|
yield { type: 'chunk', content: block.text };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const content of contents) {
|
|
||||||
// DeepInfra/GLM streams tokens without leading spaces; inject one when
|
|
||||||
// both the tail of the previous chunk and the head of this chunk are
|
|
||||||
// word characters (\w), which would otherwise merge two words.
|
|
||||||
if (lastChunkTail && /\w/.test(lastChunkTail) && /\w/.test(content[0])) {
|
|
||||||
yield { type: 'chunk', content: ' ' };
|
|
||||||
}
|
|
||||||
lastChunkTail = content[content.length - 1];
|
|
||||||
yield { type: 'chunk', content };
|
|
||||||
}
|
|
||||||
response = response ? response.concat(chunk) : chunk;
|
response = response ? response.concat(chunk) : chunk;
|
||||||
}
|
}
|
||||||
} catch (invokeError: any) {
|
} catch (invokeError: any) {
|
||||||
@@ -700,7 +689,6 @@ export class AgentHarness {
|
|||||||
|
|
||||||
// After all tool calls complete, emit a space separator before the next LLM streaming pass
|
// After all tool calls complete, emit a space separator before the next LLM streaming pass
|
||||||
yield { type: 'chunk', content: ' ' };
|
yield { type: 'chunk', content: ' ' };
|
||||||
lastChunkTail = ' ';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max iterations reached - yield done with apology
|
// Max iterations reached - yield done with apology
|
||||||
|
|||||||
157
web/src/App.vue
157
web/src/App.vue
@@ -1,7 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||||
import Splitter from 'primevue/splitter'
|
|
||||||
import SplitterPanel from 'primevue/splitterpanel'
|
|
||||||
import ChartView from './components/ChartView.vue'
|
import ChartView from './components/ChartView.vue'
|
||||||
import ChatPanel from './components/ChatPanel.vue'
|
import ChatPanel from './components/ChatPanel.vue'
|
||||||
import LoginScreen from './components/LoginScreen.vue'
|
import LoginScreen from './components/LoginScreen.vue'
|
||||||
@@ -17,10 +15,35 @@ import { authService } from './composables/useAuth'
|
|||||||
|
|
||||||
const isAuthenticated = authService.isAuthenticated
|
const isAuthenticated = authService.isAuthenticated
|
||||||
const authError = ref<string>()
|
const authError = ref<string>()
|
||||||
const isDragging = ref(false)
|
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false)
|
||||||
let stateSyncCleanup: (() => void) | null = null
|
let stateSyncCleanup: (() => void) | null = null
|
||||||
|
|
||||||
|
// Horizontal split: chart width in pixels (initialized on mount)
|
||||||
|
const CHART_MIN_PX = 300
|
||||||
|
const CHAT_MIN_PX = 240
|
||||||
|
const CHART_DEFAULT_RATIO = 0.62
|
||||||
|
const chartWidth = ref(0)
|
||||||
|
let hDragStartX = 0
|
||||||
|
let hDragStartWidth = 0
|
||||||
|
|
||||||
|
function initChartWidth() {
|
||||||
|
chartWidth.value = Math.round(window.innerWidth * CHART_DEFAULT_RATIO)
|
||||||
|
}
|
||||||
|
|
||||||
|
function startHDrag(e: PointerEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
;(e.target as HTMLElement).setPointerCapture(e.pointerId)
|
||||||
|
hDragStartX = e.clientX
|
||||||
|
hDragStartWidth = chartWidth.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHDragMove(e: PointerEvent) {
|
||||||
|
if (!e.buttons) return
|
||||||
|
const delta = e.clientX - hDragStartX
|
||||||
|
const maxWidth = window.innerWidth - CHAT_MIN_PX - 4
|
||||||
|
chartWidth.value = Math.max(CHART_MIN_PX, Math.min(maxWidth, hDragStartWidth + delta))
|
||||||
|
}
|
||||||
|
|
||||||
// Check screen width for mobile layout
|
// Check screen width for mobile layout
|
||||||
const checkMobile = () => {
|
const checkMobile = () => {
|
||||||
isMobile.value = window.innerWidth < 768
|
isMobile.value = window.innerWidth < 768
|
||||||
@@ -31,7 +54,6 @@ const chartStore = useChartStore()
|
|||||||
// Watch mobile state and update ChartStore
|
// Watch mobile state and update ChartStore
|
||||||
watch(isMobile, (mobile) => {
|
watch(isMobile, (mobile) => {
|
||||||
if (mobile) {
|
if (mobile) {
|
||||||
// Set all chart state to null when chart is hidden
|
|
||||||
chartStore.symbol = null as any
|
chartStore.symbol = null as any
|
||||||
chartStore.start_time = null
|
chartStore.start_time = null
|
||||||
chartStore.end_time = null
|
chartStore.end_time = null
|
||||||
@@ -41,13 +63,15 @@ watch(isMobile, (mobile) => {
|
|||||||
|
|
||||||
// Check if user is already authenticated on page load
|
// Check if user is already authenticated on page load
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// Try to restore session from stored token
|
initChartWidth()
|
||||||
|
checkMobile()
|
||||||
|
window.addEventListener('resize', checkMobile)
|
||||||
|
|
||||||
const token = authService.getToken()
|
const token = authService.getToken()
|
||||||
if (token) {
|
if (token) {
|
||||||
const sessionValid = await authService.checkAuth()
|
const sessionValid = await authService.checkAuth()
|
||||||
if (sessionValid) {
|
if (sessionValid) {
|
||||||
isAuthenticated.value = true
|
isAuthenticated.value = true
|
||||||
// Connect WebSocket with existing token
|
|
||||||
try {
|
try {
|
||||||
await wsManager.connect(token)
|
await wsManager.connect(token)
|
||||||
await initializeApp()
|
await initializeApp()
|
||||||
@@ -56,33 +80,21 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize mobile check
|
|
||||||
checkMobile()
|
|
||||||
window.addEventListener('resize', checkMobile)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleAuthenticate = async (email: string, password: string) => {
|
const handleAuthenticate = async (email: string, password: string) => {
|
||||||
authError.value = undefined
|
authError.value = undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Login via HTTP to get JWT token
|
|
||||||
const result = await authService.login(email, password)
|
const result = await authService.login(email, password)
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
authError.value = result.error || 'Login failed'
|
authError.value = result.error || 'Login failed'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.token) {
|
if (!result.token) {
|
||||||
authError.value = 'No token received from server'
|
authError.value = 'No token received from server'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Connect WebSocket with JWT token for real-time sync
|
|
||||||
await wsManager.connect(result.token)
|
await wsManager.connect(result.token)
|
||||||
|
|
||||||
// Step 3: Initialize application
|
|
||||||
await initializeApp()
|
await initializeApp()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
authError.value = err.message || 'Authentication failed'
|
authError.value = err.message || 'Authentication failed'
|
||||||
@@ -91,7 +103,6 @@ const handleAuthenticate = async (email: string, password: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initializeApp = async () => {
|
const initializeApp = async () => {
|
||||||
// Initialize state sync after successful authentication
|
|
||||||
const chartStore = useChartStore()
|
const chartStore = useChartStore()
|
||||||
const shapeStore = useShapeStore()
|
const shapeStore = useShapeStore()
|
||||||
const indicatorStore = useIndicatorStore()
|
const indicatorStore = useIndicatorStore()
|
||||||
@@ -107,26 +118,9 @@ const initializeApp = async () => {
|
|||||||
stateSyncCleanup = stateSync.cleanup
|
stateSyncCleanup = stateSync.cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// Listen for splitter drag events
|
|
||||||
document.addEventListener('mousedown', (e) => {
|
|
||||||
// Check if the mousedown is on a splitter gutter
|
|
||||||
const target = e.target as HTMLElement
|
|
||||||
if (target.closest('.p-splitter-gutter')) {
|
|
||||||
isDragging.value = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener('mouseup', () => {
|
|
||||||
isDragging.value = false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', checkMobile)
|
window.removeEventListener('resize', checkMobile)
|
||||||
if (stateSyncCleanup) {
|
if (stateSyncCleanup) stateSyncCleanup()
|
||||||
stateSyncCleanup()
|
|
||||||
}
|
|
||||||
wsManager.disconnect()
|
wsManager.disconnect()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -140,35 +134,28 @@ onBeforeUnmount(() => {
|
|||||||
/>
|
/>
|
||||||
<div v-else-if="!isMobile" class="desktop-layout">
|
<div v-else-if="!isMobile" class="desktop-layout">
|
||||||
<div class="top-area">
|
<div class="top-area">
|
||||||
<Splitter class="main-splitter">
|
<div class="chart-panel" :style="{ width: chartWidth + 'px' }">
|
||||||
<SplitterPanel :size="62" :minSize="40" class="chart-panel">
|
<ChartView />
|
||||||
<ChartView />
|
</div>
|
||||||
</SplitterPanel>
|
<div class="h-grabber" @pointerdown="startHDrag" @pointermove="onHDragMove" />
|
||||||
<SplitterPanel :size="38" :minSize="20" class="chat-panel">
|
<div class="chat-panel">
|
||||||
<ChatPanel />
|
<ChatPanel />
|
||||||
</SplitterPanel>
|
</div>
|
||||||
</Splitter>
|
|
||||||
</div>
|
</div>
|
||||||
<BottomTray />
|
<BottomTray />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mobile-layout">
|
<div v-else class="mobile-layout">
|
||||||
<ChatPanel />
|
<ChatPanel />
|
||||||
</div>
|
</div>
|
||||||
<!-- Transparent overlay to prevent iframe from capturing mouse events during drag -->
|
|
||||||
<div v-if="isDragging" class="drag-overlay"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app-container {
|
.app-container {
|
||||||
width: 100vw !important;
|
width: 100vw;
|
||||||
height: 100vh !important;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #0f0f0f !important;
|
background: #0f0f0f;
|
||||||
}
|
|
||||||
|
|
||||||
.app-container.dark {
|
|
||||||
background: #0f0f0f !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-layout {
|
.desktop-layout {
|
||||||
@@ -181,46 +168,38 @@ onBeforeUnmount(() => {
|
|||||||
.top-area {
|
.top-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-splitter {
|
|
||||||
height: 100% !important;
|
|
||||||
background: #0f0f0f !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-splitter :deep(.p-splitter-gutter) {
|
|
||||||
background: #2e2e2e !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-splitter :deep(.p-splitter-gutter-handle) {
|
|
||||||
background: #2e2e2e !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-panel,
|
|
||||||
.chat-panel {
|
|
||||||
height: 100% !important;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-panel :deep(.p-splitter-panel-content),
|
.chart-panel {
|
||||||
.chat-panel :deep(.p-splitter-panel-content) {
|
flex-shrink: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-overlay {
|
.h-grabber {
|
||||||
position: fixed;
|
width: 4px;
|
||||||
top: 0;
|
flex-shrink: 0;
|
||||||
left: 0;
|
height: 100%;
|
||||||
right: 0;
|
background: #2e2e2e;
|
||||||
bottom: 0;
|
cursor: col-resize;
|
||||||
z-index: 9999;
|
}
|
||||||
cursor: auto;
|
|
||||||
background: transparent;
|
.h-grabber:hover {
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-panel {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-layout {
|
.mobile-layout {
|
||||||
@@ -230,10 +209,4 @@ onBeforeUnmount(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.main-splitter {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onBeforeUnmount, type Component } from 'vue'
|
import { ref, computed, type Component } from 'vue'
|
||||||
import Tabs from 'primevue/tabs'
|
import Tabs from 'primevue/tabs'
|
||||||
import TabList from 'primevue/tablist'
|
import TabList from 'primevue/tablist'
|
||||||
import Tab from 'primevue/tab'
|
import Tab from 'primevue/tab'
|
||||||
@@ -53,29 +53,19 @@ function closeTab(tabId: string) {
|
|||||||
let resizeStartY = 0
|
let resizeStartY = 0
|
||||||
let resizeStartHeight = 0
|
let resizeStartHeight = 0
|
||||||
|
|
||||||
function startResize(e: MouseEvent) {
|
function startResize(e: PointerEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
;(e.target as HTMLElement).setPointerCapture(e.pointerId)
|
||||||
resizeStartY = e.clientY
|
resizeStartY = e.clientY
|
||||||
resizeStartHeight = expandedHeight.value
|
resizeStartHeight = expandedHeight.value
|
||||||
document.addEventListener('mousemove', onResizeMove)
|
|
||||||
document.addEventListener('mouseup', stopResize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onResizeMove(e: MouseEvent) {
|
function onResizeMove(e: PointerEvent) {
|
||||||
|
if (!e.buttons) return
|
||||||
const delta = resizeStartY - e.clientY // dragging up increases height
|
const delta = resizeStartY - e.clientY // dragging up increases height
|
||||||
expandedHeight.value = Math.max(MIN_EXPANDED, resizeStartHeight + delta)
|
expandedHeight.value = Math.max(MIN_EXPANDED, resizeStartHeight + delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopResize() {
|
|
||||||
document.removeEventListener('mousemove', onResizeMove)
|
|
||||||
document.removeEventListener('mouseup', stopResize)
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
document.removeEventListener('mousemove', onResizeMove)
|
|
||||||
document.removeEventListener('mouseup', stopResize)
|
|
||||||
})
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openTab(id: string, label: string, component: Component, props?: Record<string, any>) {
|
openTab(id: string, label: string, component: Component, props?: Record<string, any>) {
|
||||||
const existing = tempTabs.value.find(t => t.id === id)
|
const existing = tempTabs.value.find(t => t.id === id)
|
||||||
@@ -90,7 +80,7 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bottom-tray" :style="trayStyle">
|
<div class="bottom-tray" :style="trayStyle">
|
||||||
<div v-if="isExpanded" class="tray-resize-handle" @mousedown="startResize" />
|
<div v-if="isExpanded" class="tray-resize-handle" @pointerdown="startResize" @pointermove="onResizeMove" />
|
||||||
<Tabs :value="activeTab" class="tray-tabs">
|
<Tabs :value="activeTab" class="tray-tabs">
|
||||||
<TabList class="tray-tab-list">
|
<TabList class="tray-tab-list">
|
||||||
<Tab value="orders" @click="onTabClick('orders')">Orders</Tab>
|
<Tab value="orders" @click="onTabClick('orders')">Orders</Tab>
|
||||||
@@ -107,7 +97,9 @@ defineExpose({
|
|||||||
<button class="tab-close-btn" @click.stop="closeTab(tab.id)">×</button>
|
<button class="tab-close-btn" @click.stop="closeTab(tab.id)">×</button>
|
||||||
</Tab>
|
</Tab>
|
||||||
<div class="tray-spacer" />
|
<div class="tray-spacer" />
|
||||||
<button v-if="isExpanded" class="tray-close-btn" @click="isExpanded = false">✕</button>
|
<button v-if="isExpanded" class="tray-close-btn" @click="isExpanded = false" title="Minimize">
|
||||||
|
<i class="pi pi-chevron-down" />
|
||||||
|
</button>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels v-if="isExpanded" class="tray-panels">
|
<TabPanels v-if="isExpanded" class="tray-panels">
|
||||||
<TabPanel value="orders" class="tray-panel"><OrdersTab /></TabPanel>
|
<TabPanel value="orders" class="tray-panel"><OrdersTab /></TabPanel>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
import { ref, onMounted, onUnmounted, computed, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||||
import { register } from 'vue-advanced-chat'
|
import { register } from 'vue-advanced-chat'
|
||||||
import Badge from 'primevue/badge'
|
import Badge from 'primevue/badge'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
@@ -11,6 +11,26 @@ register()
|
|||||||
|
|
||||||
const channelStore = useChannelStore()
|
const channelStore = useChannelStore()
|
||||||
|
|
||||||
|
// Measure container height and feed a concrete pixel value to vue-advanced-chat,
|
||||||
|
// because height: 100% doesn't reliably resolve through flex chains.
|
||||||
|
const chatContainerRef = ref<HTMLElement | null>(null)
|
||||||
|
const chatHeight = ref('400px')
|
||||||
|
let resizeObserver: ResizeObserver | null = null
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (chatContainerRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(entries => {
|
||||||
|
const h = entries[0].contentRect.height
|
||||||
|
if (h > 0) chatHeight.value = h + 'px'
|
||||||
|
})
|
||||||
|
resizeObserver.observe(chatContainerRef.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
resizeObserver?.disconnect()
|
||||||
|
})
|
||||||
|
|
||||||
const SESSION_ID = 'default'
|
const SESSION_ID = 'default'
|
||||||
const CURRENT_USER_ID = 'user-123'
|
const CURRENT_USER_ID = 'user-123'
|
||||||
const AGENT_ID = 'agent'
|
const AGENT_ID = 'agent'
|
||||||
@@ -543,16 +563,8 @@ onMounted(() => {
|
|||||||
wsManager.addHandler(handleMessage)
|
wsManager.addHandler(handleMessage)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Inject styles into shadow DOM to widen message bubbles
|
// Inject styles into shadow DOM to widen message bubbles (fallback if already ready at mount)
|
||||||
const chatEl = document.querySelector('vue-advanced-chat')
|
injectShadowStyles()
|
||||||
if (chatEl?.shadowRoot) {
|
|
||||||
const style = document.createElement('style')
|
|
||||||
style.textContent = `
|
|
||||||
.vac-message-wrapper .vac-message-box { max-width: 80%; }
|
|
||||||
.vac-message-wrapper .vac-offset-current { margin-left: 20%; }
|
|
||||||
`
|
|
||||||
chatEl.shadowRoot.appendChild(style)
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatInput = document.querySelector('.vac-textarea') as HTMLTextAreaElement
|
const chatInput = document.querySelector('.vac-textarea') as HTMLTextAreaElement
|
||||||
if (chatInput) {
|
if (chatInput) {
|
||||||
@@ -561,13 +573,38 @@ onMounted(() => {
|
|||||||
}, 300)
|
}, 300)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const injectShadowStyles = () => {
|
||||||
|
const chatEl = document.querySelector('vue-advanced-chat')
|
||||||
|
if (chatEl?.shadowRoot) {
|
||||||
|
// Remove any previously injected style to avoid duplicates
|
||||||
|
chatEl.shadowRoot.querySelector('#vac-width-override')?.remove()
|
||||||
|
const style = document.createElement('style')
|
||||||
|
style.id = 'vac-width-override'
|
||||||
|
style.textContent = `
|
||||||
|
.vac-message-wrapper .vac-message-box { max-width: 80%; }
|
||||||
|
.vac-message-wrapper .vac-offset-current { margin-left: 20%; }
|
||||||
|
`
|
||||||
|
chatEl.shadowRoot.appendChild(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => channelStore.isReady, async (ready) => {
|
||||||
|
if (!ready) return
|
||||||
|
await nextTick()
|
||||||
|
setTimeout(() => {
|
||||||
|
injectShadowStyles()
|
||||||
|
const chatInput = document.querySelector('.vac-textarea') as HTMLTextAreaElement
|
||||||
|
chatInput?.focus()
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
wsManager.removeHandler(handleMessage)
|
wsManager.removeHandler(handleMessage)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="chat-container">
|
<div class="chat-container" ref="chatContainerRef">
|
||||||
<!--
|
<!--
|
||||||
<div class="chat-header-custom">
|
<div class="chat-header-custom">
|
||||||
<span class="chat-title">AI Agent Chat</span>
|
<span class="chat-title">AI Agent Chat</span>
|
||||||
@@ -586,7 +623,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<vue-advanced-chat
|
<vue-advanced-chat
|
||||||
v-else
|
v-else
|
||||||
height="100vh"
|
:height="chatHeight"
|
||||||
:current-user-id="CURRENT_USER_ID"
|
:current-user-id="CURRENT_USER_ID"
|
||||||
:rooms="JSON.stringify(rooms)"
|
:rooms="JSON.stringify(rooms)"
|
||||||
:messages="JSON.stringify(messages)"
|
:messages="JSON.stringify(messages)"
|
||||||
@@ -640,13 +677,13 @@ onUnmounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
background: #131722;
|
background: #0f0f0f;
|
||||||
color: #787B86;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-loading-spinner {
|
.workspace-loading-spinner {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
color: #787B86;
|
color: #089981;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-loading-message {
|
.workspace-loading-message {
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: linear-gradient(135deg, var(--p-surface-0) 0%, var(--p-surface-100) 100%);
|
background: #0f0f0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-container {
|
.login-container {
|
||||||
@@ -139,7 +139,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.login-card {
|
.login-card {
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-title {
|
.login-title {
|
||||||
|
|||||||
Reference in New Issue
Block a user