update
This commit is contained in:
158
fusion_clock_ai/static/src/js/ai_chat_backend.js
Normal file
158
fusion_clock_ai/static/src/js/ai_chat_backend.js
Normal file
@@ -0,0 +1,158 @@
|
||||
/** @odoo-module **/
|
||||
// Copyright 2026 Nexa Systems Inc.
|
||||
// License OPL-1 (Odoo Proprietary License v1.0)
|
||||
|
||||
import { Component, useState, useRef, onMounted, onWillUnmount } from "@odoo/owl";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { useDropdownState } from "@web/core/dropdown/dropdown_hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
const SUGGESTIONS = [
|
||||
"Who had the most overtime this week?",
|
||||
"Show me late arrivals today",
|
||||
"Absence summary this month",
|
||||
"Which team is understaffed?",
|
||||
];
|
||||
|
||||
const TOOLS = [
|
||||
{ key: "anomaly", icon: "fa-exclamation-triangle", label: "Anomaly Scan" },
|
||||
{ key: "understaffing", icon: "fa-users", label: "Staffing Forecast" },
|
||||
{ key: "shift", icon: "fa-calendar", label: "Shift Optimizer" },
|
||||
{ key: "compliance", icon: "fa-gavel", label: "Compliance Check" },
|
||||
{ key: "geofence", icon: "fa-map-marker", label: "Geofence Tuning" },
|
||||
];
|
||||
|
||||
export class FusionClockAIChat extends Component {
|
||||
static props = {};
|
||||
static template = "fusion_clock_ai.AISystray";
|
||||
static components = { Dropdown, DropdownItem };
|
||||
|
||||
setup() {
|
||||
this.notification = useService("notification");
|
||||
this.chatBodyRef = useRef("chatBody");
|
||||
this.dropdown = useDropdownState();
|
||||
|
||||
this.state = useState({
|
||||
activeTab: "chat",
|
||||
messages: [],
|
||||
inputText: "",
|
||||
loading: false,
|
||||
conversationId: null,
|
||||
toolLoading: null,
|
||||
toolResult: null,
|
||||
});
|
||||
}
|
||||
|
||||
get suggestions() {
|
||||
return SUGGESTIONS;
|
||||
}
|
||||
|
||||
get tools() {
|
||||
return TOOLS;
|
||||
}
|
||||
|
||||
get hasMessages() {
|
||||
return this.state.messages.length > 0;
|
||||
}
|
||||
|
||||
get canSend() {
|
||||
return this.state.inputText.trim().length > 0 && !this.state.loading;
|
||||
}
|
||||
|
||||
switchTab(tab) {
|
||||
this.state.activeTab = tab;
|
||||
this.state.toolResult = null;
|
||||
}
|
||||
|
||||
newConversation() {
|
||||
this.state.messages = [];
|
||||
this.state.conversationId = null;
|
||||
this.state.inputText = "";
|
||||
}
|
||||
|
||||
onInput(ev) {
|
||||
this.state.inputText = ev.target.value;
|
||||
}
|
||||
|
||||
onKeydown(ev) {
|
||||
if (ev.key === "Enter" && !ev.shiftKey) {
|
||||
ev.preventDefault();
|
||||
this.sendMessage();
|
||||
}
|
||||
}
|
||||
|
||||
onSuggestionClick(text) {
|
||||
this.state.inputText = text;
|
||||
this.sendMessage();
|
||||
}
|
||||
|
||||
_scrollToBottom() {
|
||||
const el = this.chatBodyRef.el;
|
||||
if (el) {
|
||||
requestAnimationFrame(() => {
|
||||
el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
const text = this.state.inputText.trim();
|
||||
if (!text || this.state.loading) return;
|
||||
|
||||
this.state.messages.push({ role: "user", content: text });
|
||||
this.state.inputText = "";
|
||||
this.state.loading = true;
|
||||
this._scrollToBottom();
|
||||
|
||||
try {
|
||||
const result = await rpc("/fusion_clock_ai/manager_chat", {
|
||||
message: text,
|
||||
conversation_id: this.state.conversationId || undefined,
|
||||
});
|
||||
if (result.error) {
|
||||
this.state.messages.push({ role: "assistant", content: result.error });
|
||||
this.notification.add(result.error, { type: "warning" });
|
||||
} else {
|
||||
this.state.messages.push({ role: "assistant", content: result.response });
|
||||
this.state.conversationId = result.conversation_id;
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = "Failed to get AI response. Please try again.";
|
||||
this.state.messages.push({ role: "assistant", content: errorMsg });
|
||||
this.notification.add(errorMsg, { type: "danger" });
|
||||
}
|
||||
|
||||
this.state.loading = false;
|
||||
this._scrollToBottom();
|
||||
}
|
||||
|
||||
async runTool(toolKey) {
|
||||
if (this.state.toolLoading) return;
|
||||
this.state.toolLoading = toolKey;
|
||||
this.state.toolResult = null;
|
||||
|
||||
try {
|
||||
const result = await rpc("/fusion_clock_ai/run_analysis", {
|
||||
analysis_type: toolKey,
|
||||
});
|
||||
if (result.error) {
|
||||
this.state.toolResult = { error: true, text: result.error };
|
||||
this.notification.add(result.error, { type: "warning" });
|
||||
} else {
|
||||
this.state.toolResult = { error: false, text: result.response };
|
||||
}
|
||||
} catch (e) {
|
||||
this.state.toolResult = { error: true, text: "Analysis failed. Please try again." };
|
||||
this.notification.add("Analysis failed.", { type: "danger" });
|
||||
}
|
||||
|
||||
this.state.toolLoading = null;
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("systray").add("fusion_clock_ai.AISystray", {
|
||||
Component: FusionClockAIChat,
|
||||
}, { sequence: 100 });
|
||||
Reference in New Issue
Block a user