git mv preserves history. fusion_accounting/ retains only __manifest__.py, __init__.py, CLAUDE.md, and docs/ — the meta-module shell. All Python, data, views, security, services, static, tests, wizards, report move to fusion_accounting_ai/. Manifest data list updated; security.xml move to _core deferred to Task 12. Made-with: Cursor
298 lines
19 KiB
XML
298 lines
19 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<templates xml:space="preserve">
|
|
<t t-name="fusion_accounting.ChatPanel">
|
|
<div class="fusion_chat_panel card h-100 d-flex flex-column">
|
|
<div class="card-header d-flex justify-content-between align-items-center py-2">
|
|
<div class="d-flex align-items-center">
|
|
<h5 class="mb-0 d-inline"><i class="fa fa-comments-o me-2"/>Fusion AI</h5>
|
|
<small class="text-muted ms-2" t-if="state.sessionName" t-esc="state.sessionName"/>
|
|
</div>
|
|
<div class="d-flex gap-1 align-items-center">
|
|
<!-- Session history button -->
|
|
<button class="btn btn-outline-secondary btn-sm"
|
|
t-on-click="toggleSessionPicker"
|
|
title="Load previous session">
|
|
<i class="fa fa-history"/>
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm" t-on-click="onNewChat"
|
|
title="Start a new conversation">
|
|
<i class="fa fa-plus me-1"/>New Chat
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Session Picker Dropdown -->
|
|
<t t-if="state.showSessionPicker">
|
|
<div class="fusion_session_picker border-bottom">
|
|
<div class="p-2 bg-body-tertiary">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<small class="fw-semibold text-muted">Recent Sessions</small>
|
|
<button class="btn-close btn-close-sm" t-on-click="toggleSessionPicker"/>
|
|
</div>
|
|
<t t-if="state.sessionList.length === 0">
|
|
<p class="text-muted small mb-0">No previous sessions found.</p>
|
|
</t>
|
|
<div class="fusion_session_list overflow-auto" style="max-height: 200px;">
|
|
<t t-foreach="state.sessionList" t-as="sess" t-key="sess.id">
|
|
<div class="fusion_session_item d-flex justify-content-between align-items-center p-2 rounded cursor-pointer"
|
|
t-att-class="sess.id === state.internalSessionId ? 'bg-primary-subtle' : ''"
|
|
t-on-click="() => this.loadSession(sess.id)">
|
|
<div>
|
|
<div class="small fw-semibold" t-esc="sess.name"/>
|
|
<div class="text-muted" style="font-size: 0.72rem;">
|
|
<t t-esc="formatSessionDate(sess.date)"/>
|
|
<span class="ms-2" t-if="sess.message_count">
|
|
<t t-esc="sess.message_count"/> msgs
|
|
</span>
|
|
<span class="ms-1 badge"
|
|
t-att-class="sess.state === 'active' ? 'bg-success-subtle text-success' : 'bg-secondary-subtle text-secondary'"
|
|
t-esc="sess.state"/>
|
|
</div>
|
|
</div>
|
|
<i class="fa fa-chevron-right text-muted" style="font-size: 0.7rem;"/>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
|
|
<!-- Messages -->
|
|
<div class="fusion_chat_messages flex-grow-1 overflow-auto p-3" t-ref="messages">
|
|
<t t-if="state.loading">
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fa fa-spinner fa-spin fa-2x"/>
|
|
<p class="mt-2">Loading conversation...</p>
|
|
</div>
|
|
</t>
|
|
<t t-elif="state.messages.length === 0">
|
|
<div class="text-center text-muted py-3">
|
|
<i class="fa fa-robot fa-3x mb-2 d-block"/>
|
|
<p class="mb-3">What would you like to work on?</p>
|
|
</div>
|
|
<div class="d-flex flex-wrap gap-2 justify-content-center px-3 pb-3">
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('Reconcile the latest ADP payment on Scotia Current')">
|
|
<i class="fa fa-exchange me-1"/>Match ADP Payment
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('Show me unreconciled bank lines on all journals')">
|
|
<i class="fa fa-bank me-1"/>Unreconciled Lines
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('How much do we owe to Pride Mobility?')">
|
|
<i class="fa fa-credit-card me-1"/>Vendor Balance
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('Show me invoicing by month for this year')">
|
|
<i class="fa fa-bar-chart me-1"/>Invoicing by Month
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('How much are we collecting this month?')">
|
|
<i class="fa fa-money me-1"/>Collections
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('What is our current HST balance?')">
|
|
<i class="fa fa-percent me-1"/>HST Balance
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('Show me overdue invoices')">
|
|
<i class="fa fa-exclamation-circle me-1"/>Overdue Invoices
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('Run month-end close checklist')">
|
|
<i class="fa fa-check-square-o me-1"/>Month-End Close
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('Show me the P&L for this quarter')">
|
|
<i class="fa fa-line-chart me-1"/>Profit & Loss
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm fusion_starter"
|
|
t-on-click="() => this.sendStarter('Find duplicate bills')">
|
|
<i class="fa fa-copy me-1"/>Duplicate Bills
|
|
</button>
|
|
</div>
|
|
</t>
|
|
<t t-foreach="state.messages" t-as="msg" t-key="msg_index">
|
|
<!-- User message -->
|
|
<t t-if="msg.role === 'user'">
|
|
<div class="fusion_chat_msg mb-2 p-2 rounded bg-primary-subtle ms-4">
|
|
<small class="text-muted d-block mb-1">
|
|
<i class="fa fa-user me-1"/>You
|
|
<t t-if="msg.hasImage">
|
|
<i class="fa fa-image ms-1 text-info" title="Image attached"/>
|
|
</t>
|
|
</small>
|
|
<t t-if="msg.imageUrl">
|
|
<img t-att-src="msg.imageUrl" class="rounded mb-1 d-block" style="max-height: 120px; max-width: 200px; cursor: pointer; object-fit: cover;"
|
|
t-on-click="() => window.open(msg.imageUrl, '_blank')"/>
|
|
</t>
|
|
<span style="white-space: pre-wrap;" t-esc="msg.content"/>
|
|
</div>
|
|
</t>
|
|
<!-- AI message — rich HTML rendered via onPatched -->
|
|
<t t-else="">
|
|
<div class="fusion_chat_msg fusion_ai_msg mb-3 p-3 rounded me-4">
|
|
<small class="text-muted d-block mb-2">
|
|
<i class="fa fa-robot me-1"/>Fusion AI
|
|
</small>
|
|
<!-- Collapsible tool calls log (like Claude Code) -->
|
|
<t t-if="msg.toolCalls and msg.toolCalls.length">
|
|
<details class="fusion_tool_calls mb-2">
|
|
<summary class="small text-muted cursor-pointer d-inline-flex align-items-center gap-1 user-select-none">
|
|
<i class="fa fa-wrench" style="font-size: 0.7rem;"/>
|
|
<span><t t-esc="msg.toolCalls.length"/> tool call<t t-if="msg.toolCalls.length > 1">s</t></span>
|
|
</summary>
|
|
<div class="mt-1 ms-2 border-start ps-2" style="border-color: var(--o-border-color) !important;">
|
|
<t t-foreach="msg.toolCalls" t-as="tc" t-key="tc_index">
|
|
<div class="d-flex align-items-start gap-1 py-1 small"
|
|
style="line-height: 1.3;">
|
|
<i t-att-class="'fa fa-fw ' + (tc.status === 'error' ? 'fa-times-circle text-danger' : tc.status === 'pending_approval' ? 'fa-clock-o text-warning' : 'fa-check-circle text-success')"
|
|
style="font-size: 0.7rem; margin-top: 3px;"/>
|
|
<span>
|
|
<code class="small" style="font-size: 0.78rem;" t-esc="tc.name"/>
|
|
<t t-if="tc.summary">
|
|
<span class="text-muted ms-1">— <t t-esc="tc.summary"/></span>
|
|
</t>
|
|
<t t-if="tc.duration_ms">
|
|
<span class="text-muted ms-1" style="font-size: 0.7rem;">(<t t-esc="tc.duration_ms"/>ms)</span>
|
|
</t>
|
|
</span>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</details>
|
|
</t>
|
|
<div class="fusion_rich_content fusion_rich_slot"
|
|
t-att-data-idx="msg_index"/>
|
|
</div>
|
|
</t>
|
|
</t>
|
|
<t t-if="state.sending">
|
|
<div class="fusion_ai_msg rounded p-3 me-4 mb-2 fusion_live_status">
|
|
<small class="text-muted d-block mb-1">
|
|
<i class="fa fa-robot me-1"/>Fusion AI
|
|
</small>
|
|
<!-- Live thinking text -->
|
|
<t t-if="state.liveThinking">
|
|
<div class="fusion_thinking_block mb-2 p-2 rounded small fst-italic"
|
|
style="background: rgba(var(--bs-body-color-rgb), 0.03); border-left: 3px solid var(--bs-purple, #6f42c1); max-height: 120px; overflow-y: auto;">
|
|
<i class="fa fa-brain me-1 text-purple" style="color: var(--bs-purple, #6f42c1);"/>
|
|
<span t-esc="state.liveThinking"/>
|
|
</div>
|
|
</t>
|
|
<!-- Live tool calls -->
|
|
<t t-if="state.liveToolCalls.length > 0">
|
|
<div class="mb-1">
|
|
<t t-foreach="state.liveToolCalls" t-as="tc" t-key="tc_index">
|
|
<div class="d-flex align-items-center gap-1 small py-1" style="line-height: 1.3;">
|
|
<t t-if="tc.status === 'running'">
|
|
<i class="fa fa-spinner fa-spin text-primary" style="font-size: 0.7rem;"/>
|
|
</t>
|
|
<t t-elif="tc.status === 'ok'">
|
|
<i class="fa fa-check-circle text-success" style="font-size: 0.7rem;"/>
|
|
</t>
|
|
<t t-elif="tc.status === 'error'">
|
|
<i class="fa fa-times-circle text-danger" style="font-size: 0.7rem;"/>
|
|
</t>
|
|
<t t-else="">
|
|
<i class="fa fa-clock-o text-warning" style="font-size: 0.7rem;"/>
|
|
</t>
|
|
<code class="small" style="font-size: 0.75rem;" t-esc="tc.name"/>
|
|
<t t-if="tc.summary">
|
|
<span class="text-muted">— <t t-esc="tc.summary"/></span>
|
|
</t>
|
|
<t t-if="tc.duration_ms">
|
|
<span class="text-muted" style="font-size: 0.68rem;">(<t t-esc="tc.duration_ms"/>ms)</span>
|
|
</t>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</t>
|
|
<!-- Default thinking indicator if no live data yet -->
|
|
<t t-if="!state.liveThinking and state.liveToolCalls.length === 0">
|
|
<i class="fa fa-spinner fa-spin me-1"/> Thinking...
|
|
</t>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
|
|
<!-- Pending Approvals — compact table -->
|
|
<t t-if="state.pendingApprovals.length > 0">
|
|
<div class="border-top">
|
|
<div class="d-flex justify-content-between align-items-center px-2 py-1 bg-warning-subtle">
|
|
<small class="fw-semibold">
|
|
<i class="fa fa-exclamation-triangle me-1 text-warning"/>
|
|
<t t-esc="state.pendingApprovals.length"/> Pending Approval<t t-if="state.pendingApprovals.length > 1">s</t>
|
|
</small>
|
|
<div class="d-flex gap-1" t-if="state.pendingApprovals.length > 1">
|
|
<button class="btn btn-success px-2 py-0" style="font-size: 0.75rem;"
|
|
t-on-click="onApproveAll" title="Approve all">
|
|
<i class="fa fa-check me-1"/>All
|
|
</button>
|
|
<button class="btn btn-outline-danger px-2 py-0" style="font-size: 0.75rem;"
|
|
t-on-click="onRejectAll" title="Reject all">
|
|
<i class="fa fa-times me-1"/>All
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="overflow-auto" style="max-height: 280px;">
|
|
<table class="table table-sm table-hover align-middle mb-0">
|
|
<thead>
|
|
<tr class="small text-muted">
|
|
<th class="px-2 py-1 fw-semibold">Type</th>
|
|
<th class="px-2 py-1 fw-semibold">Details</th>
|
|
<th class="px-2 py-1 fw-semibold text-end">Amount</th>
|
|
<th class="px-1 py-1 fw-semibold text-end" style="width: 80px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<t t-foreach="state.pendingApprovals" t-as="approval" t-key="approval.id">
|
|
<FusionApprovalCard
|
|
approval="approval"
|
|
onApprove.bind="onApprove"
|
|
onReject.bind="onReject"/>
|
|
</t>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
|
|
<!-- Input -->
|
|
<div class="fusion_chat_input border-top p-2">
|
|
<!-- Image preview -->
|
|
<t t-if="state.pendingImage">
|
|
<div class="fusion_image_preview d-flex align-items-center gap-2 mb-1 p-1 rounded bg-body-tertiary">
|
|
<img t-att-src="state.pendingImage.dataUrl" class="rounded" style="max-height: 48px; max-width: 80px; object-fit: cover;"/>
|
|
<small class="text-muted flex-grow-1 text-truncate" t-esc="state.pendingImage.name"/>
|
|
<button class="btn btn-sm p-0 text-danger" t-on-click="clearImage" title="Remove">
|
|
<i class="fa fa-times"/>
|
|
</button>
|
|
</div>
|
|
</t>
|
|
<div class="input-group">
|
|
<button class="btn btn-outline-secondary btn-sm" t-on-click="triggerFileUpload"
|
|
title="Attach image (screenshot, remittance advice, etc.)">
|
|
<i class="fa fa-paperclip"/>
|
|
</button>
|
|
<textarea
|
|
t-ref="chatInput"
|
|
class="form-control form-control-sm"
|
|
placeholder="Ask Fusion AI... (paste screenshot with Ctrl+V)"
|
|
rows="1"
|
|
t-model="state.inputText"
|
|
t-on-keydown="onKeyDown"
|
|
t-on-paste="onPaste"/>
|
|
<button class="btn btn-primary btn-sm" t-on-click="sendMessage"
|
|
t-att-disabled="state.sending">
|
|
<i class="fa fa-paper-plane"/>
|
|
</button>
|
|
</div>
|
|
<input type="file" t-ref="fileInput" class="d-none" accept="image/*"
|
|
t-on-change="onFileSelected"/>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</templates>
|