[[{“value”:”
็ๆ AI ใฏ็ ็ฉถๅฎคใฎๅคใธ้ฃใณๅบใใไปใใใธใใน็พๅ ดใฎๅธธ่ญใๅกใๆฟใใฆใใพใใSAPใฏๅ จ้ๅใงใใฎๆณขใซไนใฃใฆใใพใใใใฎใใญใฐใทใชใผใบใงใฏใSAPย AIย Core ใฎๆขๅฎใขใใซใๆ้ใงๅผใณๅบใใๅฎๅใงไฝฟใใ AI ใจใผใธใงใณใใธๆกๅผตใใโ็ง้ใใณใบใชใณโใใๅฑใใใพใใ
ใ็ฅใใ
You can find the English version here.
ย
๐ย ๆฌใทใชใผใบใงๅญฆในใใใจ
- SAPย AIย Core ไธใงใซในใฟใ AI Agent ใ โ็ง้โ ใงๅใใๆนๆณ
- LangChainใปGoogle ๆค็ดขใใผใซใปRAGใไฝฟใฃใๅฎ่ฃ
- AI Agent ใ RESTย API ๅใใSAPUI5/Fiori ใฎ UI ใซ่ผใใCloud Foundryใซใใใญใคใใๆ้
ๅญฆ็ฟๆ้
ๅPart ใฏย 10โ15 ๅย ใง่ชญใใ๏ผๆใๅใใใใใชใฅใผใ ใไบๅฎใใฆใใพใใ
ย
๐บ๏ธ ้ฃ่ผใญใผใใใใ
- Part 0 ใใญใญใผใฐ
- Part 1 ็ฐๅขๆง็ฏ: SAP AI CoreใจAI Launchpad
- Part 2 LangChain ใงใใฃใใใขใใซใๆง็ฏ
- Part 3 Agentใใผใซ: Googleย ๆค็ดขใ็ตฑๅ
- Part 4 RAG ๅบ็ค โ : HANA Cloud VectorEngineใจๅใ่พผใฟๅฆ็
- Part 5 RAG ๅบ็ค โก: Retriever Toolใฎๆง็ฏ
- Part 6 AI Agent ใ RESTย API ๅ
- Part 7 SAPUI5 ใงใใฃใใUI ใๆง็ฏย [็พๅจใฎใใญใฐ]
- Part 8 CloudFoundry ใซใใใญใค
ใใฎ่จไบใใๅฝนใซ็ซใฃใใใใใฒโฏKudosโฏใๆผใใฆใใใ ใใใจๅฑใฟใซใชใใพใใ ใใใใใใฃใจ่ฉณใใ็ฅใใใใใๅฅใฎใใผใใๅใไธใใฆใปใใใใชใฉใใ่ฆๆใใใใฐใๆฐ่ปฝใซใณใกใณใใใ ใใ๏ผ
SAPUI5 ใงใใฃใใUI ใๆง็ฏ
1 | ใฏใใใซ
ไปๅใฎPart ใงใฏใๅๅใพใงใซๆง็ฏใใ AI Agent ใใSAP UI5/Fiori ใใผในใฎใใฃใใ UI ใใๅผใณๅบใใใใใซใใพใใ
SAP UI5 ใฏ SAP ใๆไพใใ UI ใใฌใผใ ใฏใผใฏใงใใใไผๆฅญๅใใฎใขใใณใช Web ใขใใชใฑใผใทใงใณใ็ญๆ้ใง้็บใงใใ็นใ็นๅพดใงใใ็นใซ SAP Business Application Studio๏ผBAS๏ผ ใๅฉ็จใใใจใFiori ็จใใณใใฌใผใใใใใญใธใงใฏใๆงๆใ่ชๅ็ๆใงใใใใใใใฃใฌใฏใใชใ่จญๅฎใใกใคใซใฎๆๅไฝๆใไธ่ฆใซใชใใพใใ
ย
2 | ไบๅๆบๅ
- BTP ใตใใขใซใฆใณใ
- SAP AI Core ใคใณในใฟใณใน
- SAP AI Launchpadใฎใตใในใฏใชใใทใงใณ
- Python 3.13็ฐๅข & pip
- VSCodeใBASใชใฉใฎIDE
Trial ็ฐๅขใฎๆณจๆ
Trial ใฎ HANAโฏCloud ใคใณในใฟใณในใฏย ๆฏๆฉ่ชๅๅๆญข ใใใพใใๆฅใใพใใไฝๆฅญใฎๅ ดๅใฏ็ฟๆฅใคใณในใฟใณในใๅ่ตทๅใใฆใใ ใใ
ย
3 | Fiori ใขใใชใฑใผใทใงใณใฎๆบๅ
SAP ็ณป UI ้็บใงใฏใBAS ใฎใใณใใฌใผใๆฉ่ฝใฎๅฉ็จใๅผทใๆจๅฅจใใพใใใใณใใฌใผใใใ็ๆใใใใใฉใซใๆงๆใจ่จญๅฎใใกใคใซใใใผในใซใใใใจใงใFiori ใขใใชๅบๆใฎใใใใงในใ๏ผmta.yaml๏ผใใขใธใฅใผใซๆงๆใ็ด ๆฉใๆดๅใงใใพใใ
ย
BAS ใง Create Dev Space ใใฏใชใใฏใใใใณใใฌใผใใซ SAP Fiori ใ้ธๆใใพใใไฝตใใฆๅๅใซไฝๆใใ Python API ใใญใผใซใซใง่ตทๅใใใใฎใงใAdditional SAP Extensions ใใ Python Tools ใ้ธๆใใพใใ
ๅทฆไธใฎใใณใใผใฌใผใกใใฅใผ โ File > New Project From Template ใ้ธๆใใพใใ
Template Selection ใฏ Basic ใ้ธๆใใพใใ
Data Source and Service Selection ใงใฏใUI ใซ่กจ็คบใใใใผใฟใฏ Python API ใใๅๅพใใใใ None ใ้ธๆใใพใใ
Entity Selection ใง Entity name ใซ ChatEntity ใจๅ ฅๅใใพใใ
Project Attributes ใไปฅไธใฎใใใซ่จญๅฎใใAdd deployment configuration ใ Yes ใซใใพใใ
| ้ ็ฎ | ๅค |
| Module Name | my-ai-agent-ui |
| Application Title | My AI Chat |
| Description | Chat UI for AI Agent |
Deployment Configuration ใงใฏๆฌกใฎใใใซ่จญๅฎใใพใใ
| ้ ็ฎ | ๅค |
| Target | Cloud Foundry |
| Destination | None |
| Add Router Module | Add Application to Managed Application Router |
ใพใใซ Target ใ CF ใซใใฆใ Router Module ใฎใชใใทใงใณใ่กจ็คบใใใชใใใจใใใใพใใใใฎๅ ดๅใฏใไธๅบฆ Target ใ ABAP ใซๅใๆฟใใฆใใ Cloud Foundry ใซๆปใใจๆญฃใใ่กจ็คบใใใพใใ
ย
ๆๅพใซใPython API ใฎ้ ็ฝฎใจๅไฝ็ขบ่ชใๅฎๆฝใใพใใๆงๆใฏๆฌกใฎใใใซใชใใพใใ
# ใใฉใซใๆงๆ
PROJECTS
โโโ my-ai-agent-ui/
โโโ my-ai-agent-api/
โโโ main.py
โโโ requirements.txt
โโโ .env
my-ai-agent-apiย ้ ไธใงไปฎๆณ็ฐๅขใไฝๆใปๆๅนๅใใไปฅไธใฎใณใใณใใงใญใผใซใซใตใผใใ่ตทๅใใใ็ขบ่ชใใพใใ
gunicorn -w 1 -k uvicorn.workers.UvicornWorker main:app –bind 0.0.0.0:${PORT:-8000}
ใใใงใFioriใขใใชใฑใผใทใงใณใฎๆง็ฏๆบๅใๆดใใพใใ๏ผ
ย
4 | Fiori ใขใใชใฑใผใทใงใณใฎๆน่ฏ
็ๆใใใ Fiori ใใญใธใงใฏใใใใผในใซใใใฃใใ UI ใ AI Agent ใจ้ไฟกใงใใใใใซใณใผใใ่ชฟๆดใใฆใใใพใใใใ
ใพใใณใณใใญใผใฉใผ๏ผmy-ai-agent-ui/webapp/controller/ChatEntity.controller.js๏ผใไปฅไธใฎใใใซๆธใใพใใ
sap.ui.define([
“sap/ui/core/mvc/Controller”,
“sap/ui/model/json/JSONModel”,
“sap/m/MessageToast”,
“sap/ui/core/BusyIndicator”
], function (Controller, JSONModel, MessageToast, BusyIndicator) {
“use strict”;
// ็ฐๅขใซใใฃใฆใจใณใใใคใณใใๅใๆฟใ
const ENDPOINT = (
[“localhost”, “applicationstudio”].some(h => window.location.hostname.includes(h)) ||
window.location.port === “8080”
) ? “” : “https://my-ai-agent-api-relaxed-raven-ie.cfapps.us10-001.hana.ondemand.com”;
return Controller.extend(“myaiagentui.controller.ChatEntity”, {
/** ๅๆๅ */
onInit() {
this.getView().setModel(new JSONModel({
busy: false,
txtInput: “”,
uploadedFiles: [],
messages: [{
role: “assistant”,
content: “ใใใซใกใฏ๏ผ็งใฏใใฃใใใใใใงใใไฝใใๆไผใใงใใใใจใฏใใใพใใ๏ผ”,
hasThinkingProcess: false
}]
}), “ui”);
this.byId(“fileUploader”)?.setUploadUrl(`${ENDPOINT}/agent/upload`);
},
/** ใใฃใใๅฑฅๆญดใๅๆๅ */
onClearChatPress() {
this.getView().getModel(“ui”).setProperty(“/messages”, [{
role: “assistant”,
content: “ใใใซใกใฏ๏ผ็งใฏใใฃใใใใใใงใใไฝใใๆไผใใงใใใใจใฏใใใพใใ๏ผ”,
hasThinkingProcess: false
}]);
MessageToast.show(“ใใฃใใใใฏใชใขใใพใใ”);
},
/** ใกใใปใผใธ้ไฟก */
async onBtnChatbotSendPress(){
const ui=this.getView().getModel(“ui”);
const input=(ui.getProperty(“/txtInput”)||””).trim();
if(!input) return;
const msgs=ui.getProperty(“/messages”);
msgs.push({role:”user”, content:input});
ui.setProperty(“/messages”, msgs);
ui.setProperty(“/txtInput”, “”);
ui.setProperty(“/busy”, true);
try{
const {output, intermediate_steps=[]}=await this._apiChatCompletion(input);
msgs.push({role:”assistant”, content:output, hasThinkingProcess:Boolean(intermediate_steps.length), thinkingProcess:intermediate_steps.map((s,i)=>({…s, stepIndex:i+1, observationTruncated:s.observation?.slice(0,100)+(s.observation?.length>100?”…”:””), observationFull:s.observation, isObservationExpanded:false, hasLongObservation:(s.observation?.length||0)>100})), isExpanded:false});
ui.setProperty(“/messages”, msgs);
this._scrollToBottom();
}catch(err){
console.error(err);
MessageToast.show(`ใจใฉใผ: ${err.message}`);
}finally{
ui.setProperty(“/busy”, false);
}
},
/** ๆ่ใใญใปใน่กจ็คบๅๆฟ */
onToggleThinkingProcess(oEvent) {
const ctx = oEvent.getSource()?.getBindingContext(“ui”);
if (ctx) this._toggleFlag(ctx, “isExpanded”);
},
/** Observation ่กจ็คบๅๆฟ */
onToggleObservation(oEvent) {
const ctx = oEvent.getSource()?.getBindingContext(“ui”);
if (ctx) this._toggleFlag(ctx, “isObservationExpanded”);
},
/** ไปปๆใใฉใฐใๅ่ปข */
_toggleFlag(ctx, flag) {
const ui = this.getView().getModel(“ui”);
const path = ctx.getPath();
ui.setProperty(`${path}/${flag}`, !ui.getProperty(`${path}/${flag}`));
},
/** ใใฃใใใๆไธ้จใธในใฏใญใผใซ */
_scrollToBottom() {
setTimeout(() => {
const sc = this.byId(“chatScrollContainer”);
const items = sc?.getContent()[0].getItems();
if (items?.length) sc.scrollToElement(items[items.length – 1]);
}, 100);
},
/** AI ใใฃใใ API ๅผใณๅบใ */
async _apiChatCompletion(query) {
const res = await fetch(`${ENDPOINT}/agent/chat`, {
method: “POST”,
headers: { “Content-Type”: “application/json” },
body: JSON.stringify({ query })
});
if (!res.ok) throw new Error(`ใตใผใใผใจใฉใผ (${res.status}): ${await res.text()}`);
return res.json();
}
});
});
ย
ๆฌกใซ View๏ผmy-ai-agent-ui/webapp/view/ChatEntity.view.xml๏ผใไปฅไธใฎใใใซๅคๆดใใพใใ
<mvc:View controllerName=”myaiagentui.controller.ChatEntity”
xmlns:core=”sap.ui.core”
xmlns:mvc=”sap.ui.core.mvc”
xmlns:f=”sap.f”
xmlns:u=”sap.ui.unified”
displayBlock=”true”
xmlns=”sap.m”>
<f:DynamicPage id=”mainDynamicPage” stickySubheaderProvider=”iconTabBar” class=”sapUiNoContentPadding”>
<f:title>
<f:DynamicPageTitle id=”mainDynamicTitle”>
<f:heading>
<Title id=”pageTitle” text=”AI ใใฃใใ with Fiori”/>
</f:heading>
<f:actions>
<Button id=”clearChatButton”
text=”ใใฃใใใใฏใชใข”
icon=”sap-icon://refresh”
type=”Emphasized”
press=”onClearChatPress”/>
</f:actions>
</f:DynamicPageTitle>
</f:title>
<f:content>
<!– ใใฃใใ UI ใกใคใณใจใชใข –>
<VBox id=”chatMainAreaBox” class=”sapUiNoContentPadding chatMainArea”>
<IconTabBar id=”iconTabBar”
class=”sapUiNoContentPadding”
stretchContentHeight=”true”
expanded=”true”
expandable=”false”>
<items>
<IconTabFilter id=”chatbotTab” text=”ใใฃใใใใใ” class=”sapUiNoContentPadding”>
<VBox id=”chatContainerBox” class=”chatContainer sapUiNoContentPadding”>
<!– ใใฃใใใกใใปใผใธใจใชใข –>
<ScrollContainer id=”chatScrollContainer”
height=”100%”
width=”100%”
vertical=”true”
focusable=”true”
class=”chatScrollContainer”>
<VBox id=”chatMessagesBox” items=”{ui>/messages}” class=”chatMessagesContainer”>
<VBox id=”messageItemBox”>
<!– assistant ใณใกใณใ –>
<VBox id=”assistantMessageVBox” visible=”{= ${ui>role} === ‘assistant’}”
class=”chatMessageWrapper”>
<HBox id=”assistantMessageHBox” direction=”Row” class=”chatMessageRow”>
<HBox id=”assistantMessageHBox1″ backgroundDesign=”Solid”
class=”chatBubbleAssistant”>
<core:Icon id=”assistantMessageIcon” decorative=”true”
src=”sap-icon://ai”
class=”chatIcon”/>
<FormattedText id=”assistantMessageFormattedText” htmlText=”{ui>content}”/>
</HBox>
</HBox>
<!– ๆ่ใใญใปในใขใณใผใใฃใชใณ –>
<HBox id=”thinkingToggleBox” visible=”{ui>hasThinkingProcess}”
class=”thinkingProcessContainer”>
<Button id=”thinkingToggleButton” icon=”{= ${ui>isExpanded} ? ‘sap-icon://collapse’ : ‘sap-icon://expand’ }”
text=”ๆ่ใใญใปในใ{= ${ui>isExpanded} ? ‘้ ใ’ : ‘่กจ็คบ’ }”
type=”Transparent”
press=”onToggleThinkingProcess”
class=”thinkingProcessToggle”/>
</HBox>
<VBox id=”thinkingVBox” visible=”{= ${ui>isExpanded} && ${ui>hasThinkingProcess} }”
class=”thinkingProcessContent”>
<VBox id=”thinkingVBox1″ items=”{ui>thinkingProcess}”>
<VBox id=”thinkingStepBox” class=”thinkingStepBox”>
<Title id=”thinkingStepTitle”
text=”{= ‘Step ‘ + ${ui>stepIndex} }”
level=”H5″
class=”thinkingStepTitle”/>
<VBox id=”thinkingStepContent” class=”thinkingStepContent”>
<HBox id=”thinkingStepThought” class=”thinkingStepItem”>
<Label id=”ThoughtLabel” text=”Thought:” design=”Bold” class=”thinkingLabel”/>
<Text id=”ThoughtText” text=”{ui>thought}” wrapping=”true” class=”thinkingText”/>
</HBox>
<HBox id=”thinkingStepAction” class=”thinkingStepItem”>
<Label id=”ActionLabel” text=”Action:” design=”Bold” class=”thinkingLabel”/>
<Text id=”ActionText” text=”{ui>action}” class=”thinkingText”/>
</HBox>
<HBox id=”thinkingStepInput” class=”thinkingStepItem”>
<Label id=”InputLabel” text=”Input:” design=”Bold” class=”thinkingLabel”/>
<Text id=”InputText” text=”{ui>action_input}” class=”thinkingText”/>
</HBox>
<HBox id=”thinkingStepObservation” class=”thinkingStepItem”>
<Label id=”ObservationLabel” text=”Observation:” design=”Bold” class=”thinkingLabel”/>
<VBox id=”ObservationVBox” class=”thinkingText”>
<Text id=”ObservationText” text=”{= ${ui>isObservationExpanded} ? ${ui>observationFull} : ${ui>observationTruncated} }”
wrapping=”true”/>
<Link id=”ObservationLink”
text=”{= ${ui>isObservationExpanded} ? ‘ๆใใใใ’ : ‘็ถใใ่ชญใ’ }”
visible=”{ui>hasLongObservation}”
press=”onToggleObservation”
class=”observationToggleLink”/>
</VBox>
</HBox>
</VBox>
</VBox>
</VBox>
</VBox>
</VBox>
<!– user ใณใกใณใ –>
<HBox id=”UserComment”
visible=”{= ${ui>role} === ‘user’}”
direction=”RowReverse”
class=”chatMessageRow”>
<HBox id=”UserCommentBox”
direction=”RowReverse”
backgroundDesign=”Solid”
class=”chatBubbleUser sapThemeBrand-asBackgroundColor”>
<core:Icon id=”UserCommentIcon”
decorative=”true”
src=”sap-icon://customer”
class=”chatIcon sapThemeTextInverted”/>
<Text id=”UserCommentText” class=”sapThemeTextInverted” text=”{ui>content}”/>
</HBox>
</HBox>
</VBox>
</VBox>
</ScrollContainer>
<!– ๅ
ฅๅๆฌ๏ผ้ไฟกใใฟใณ –>
<HBox id=”chatInputAreaBox” class=”chatInputArea”>
<TextArea id=”chatInput” value=”{ui>/txtInput}”
width=”100%”
growing=”true”
placeholder=”ใกใใปใผใธใๅ
ฅๅใใฆใใ ใใ…”
editable=”{= !${ui>/busy}}”
busyIndicatorDelay=”0″
class=”chatTextArea”>
<layoutData>
<FlexItemData id=”InputData” growFactor=”1″/>
</layoutData>
</TextArea>
<Button id=”sendButton” class=”chatSendButton”
type=”Emphasized”
icon=”sap-icon://paper-plane”
text=”้ไฟก”
press=”onBtnChatbotSendPress”
busy=”{ui>/busy}”
busyIndicatorDelay=”0″/>
</HBox>
</VBox>
</IconTabFilter>
</items>
</IconTabBar>
</VBox>
</f:content>
</f:DynamicPage>
</mvc:View>
ย
ใใใซใUI ใฎ่ฉณ็ดฐใชใใถใคใณ่จญๅฎใจใใฆใในใฟใคใซ๏ผmy-ai-agent-ui/webapp/css/style.css๏ผใ่จญๅฎใใพใใ
/* ใใฃใใใกใคใณใจใชใข */
.chatMainArea {
height: 100vh;
min-height: 300px;
}
/* ใใฃใใใณใณใใ */
.chatContainer {
display: flex;
flex-direction: column;
height: calc(90vh – 100px);
padding: 0;
}
/* ใใฃใใในใฏใญใผใซใณใณใใ */
.chatScrollContainer {
flex: 1;
min-height: 300px;
background-color: #f8f9fa;
border: none;
border-radius: 0;
padding: 0;
margin: 0;
}
/* ใใฃใใใกใใปใผใธใณใณใใ */
.chatMessagesContainer {
padding: 1rem;
}
/* ใใฃใใใกใใปใผใธ่ก */
.chatMessageRow {
width: 100%;
margin-bottom: 0.5rem;
}
/* ใใฃใใใกใใปใผใธใฉใใใผ */
.chatMessageWrapper {
margin-bottom: 1rem;
}
/* ใใฃใใใณใกใณใ – ใขใทในใฟใณใ */
.chatBubbleAssistant {
max-width: 70%;
min-width: 100px;
background-color: #ffffff;
border: 1px solid #d0d7de;
border-radius: 18px;
padding: 12px 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
align-items: flex-start;
}
.chatBubbleAssistant .sapMFormattedText {
word-wrap: break-word;
line-height: 1.4;
}
/* ใใฃใใใณใกใณใ – ใฆใผใถใผ */
.chatBubbleUser {
max-width: 70%;
min-width: 100px;
border-radius: 18px;
padding: 12px 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
align-items: flex-start;
}
.chatBubbleUser .sapMText {
word-wrap: break-word;
line-height: 1.4;
}
/* ๆ่ใใญใปในใณใณใใ */
.thinkingProcessContainer {
margin-top: 0.5rem;
margin-left: 2.5rem;
}
/* ๆ่ใใญใปในใใฐใซใใฟใณ */
.thinkingProcessToggle {
font-size: 0.875rem;
color: #0073e6;
}
.thinkingProcessToggle:hover {
background-color: rgba(0, 115, 230, 0.08) !important;
}
/* ๆ่ใใญใปในใณใณใใณใ */
.thinkingProcessContent {
margin-left: 2.5rem;
margin-top: 0.5rem;
max-width: 70%;
}
/* ๆ่ในใใใใใใฏใน */
.thinkingStepBox {
margin-bottom: 1.5rem;
padding: 1rem;
background-color: #f5f7fa;
border: 1px solid #e4e7ea;
border-radius: 8px;
}
.thinkingStepBox:last-child {
margin-bottom: 0;
}
/* ๆ่ในใใใใฟใคใใซ */
.thinkingStepTitle {
color: #0a6ed1;
margin-bottom: 0.75rem;
font-size: 1rem;
}
/* ๆ่ในใใใใณใณใใณใ */
.thinkingStepContent {
padding-left: 0.5rem;
}
/* ๆ่ในใใใ้
็ฎ */
.thinkingStepItem {
margin-bottom: 0.5rem;
align-items: flex-start;
}
.thinkingStepItem:last-child {
margin-bottom: 0;
}
/* ๆ่ใฉใใซ */
.thinkingLabel {
min-width: 100px;
margin-right: 1rem;
color: #32363a;
font-size: 0.875rem;
}
/* ๆ่ใใญในใ */
.thinkingText {
flex: 1;
font-size: 0.875rem;
color: #515456;
line-height: 1.5;
}
/* Observation็ถใใ่ชญใใชใณใฏ */
.observationToggleLink {
margin-top: 0.25rem;
font-size: 0.875rem;
}
/* ๅ
ฅๅใจใชใข */
.chatInputArea {
background-color: #ffffff;
padding: 1rem;
border-top: 1px solid #e4e7ea;
align-items: flex-end;
flex-shrink: 0;
}
/* ใใญในใใจใชใขใฎในใฟใคใซ่ชฟๆด */
.chatTextArea .sapMTextAreaInner {
border: 2px solid #e4e7ea;
background-color: #ffffff;
}
.chatTextArea .sapMTextAreaInner:focus-within {
border-color: #0073e6;
}
/* ้ไฟกใใฟใณ */
.chatSendButton {
min-height: 40px;
border-radius: 20px;
margin-left: 0.5rem;
}
/* ใขใคใณใณ่ชฟๆด */
.chatIcon {
font-size: 1.2rem;
margin-top: 2px;
}
.chatBubbleAssistant .chatIcon {
margin-right: 0.5rem;
}
.chatBubbleUser .chatIcon {
margin-left: 0.5rem;
}
/* IconTabBar ใฎใใใฃใณใฐๅ้ค */
.sapMITB {
padding: 0;
}
.sapMITBContent {
padding: 0;
background-color: transparent;
}
/* ใฌในใใณใทใๅฏพๅฟ */
@media (max-width: 768px) {
.chatBubbleAssistant,
.chatBubbleUser {
max-width: 85%;
}
.chatContainer {
height: calc(100vh – 80px);
}
.chatScrollContainer {
min-height: 300px;
}
.chatSendButton .sapMBtnInner .sapMBtnContent {
font-size: 0;
}
.chatSendButton .sapMBtnIcon {
margin-right: 0;
}
.thinkingProcessContent {
max-width: 85%;
}
.thinkingLabel {
min-width: 80px;
margin-right: 0.5rem;
}
}
ย
ใใฃใใ UI ใใ Python API ใธใชใฏใจในใใ่ปข้ใใใใใใใญใญใทใใใซใฆใงใขใ่ฟฝๅ ใใพใใใพใไพๅญ้ขไฟใใคใณในใใผใซใใพใใใใ
npm install ui5-middleware-simpleproxy –save-dev
ย
ใใใฆ my-ai-agent-ui/package.json ใซๆฌกใฎ่จญๅฎใ่ฟฝ่จใใฆใใ ใใใ
…
“main”: “webapp/index.html”,
“devDependencies”: {
(็็ฅ)
},
/* ใใใใ
“ui5”: {
“dependencies”: [
“ui5-middleware-simpleproxy”
ใใ]
},
ใใใใพใง */
“scripts”: {
….
ใใใงใใฃใใ UI ใใใฎ /api ใชใฏใจในใใใญใผใซใซใฎ Python API ใธ่ปข้ใใใใใใซใชใใพใใ
ย
5 | Fiori ใขใใชใฑใผใทใงใณใฎใในใ
ใญใผใซใซ็ฐๅขใงใขใใชใจ API ใๅๆใซ่ตทๅใใๅฎ้ใซใใฃใใๆฉ่ฝใๅไฝใใใ็ขบ่ชใใพใใใใ
ใพใใmy-ai-agent-api ใใฃใฌใฏใใชใงไปฅไธใฎใณใใณใใๅฎ่กใใฆ Python API ใ่ตทๅใใพใใ
gunicorn -w 1 -k uvicorn.workers.UvicornWorker main:app –bind 0.0.0.0:${PORT:-8000}
ใพใใๅฅใฎใฟใผใใใซใ้ใใmy-ai-agent-ui ใฎใซใผใใงๆฌกใฎใณใใณใใๅฎ่กใใพใใ
npm start
ย
ใญใฐใซ่กจ็คบใใใ URL๏ผ้ๅธธใฏ http://localhost:8080๏ผใใใฉใฆใถใง้ใใใใฃใใๅ ฅๅๆฌใซใกใใปใผใธใ้ใฃใฆใฟใฆใใ ใใใAI ใจใผใธใงใณใใใ่ฟ็ญใ่กจ็คบใใใใฐๆๅใงใ๏ผ
ย
ใใฃใใ็ป้ขใฎ ใใฃใใใใฏใชใข ใใฟใณใใฏใชใใฏใใใจๅฑฅๆญดใใชใปใใใใใใใใฃใใใใฏใชใขใใพใใใใจ็ขบ่ชใงใใพใใๆฐใใไผ่ฉฑใๅ้กใชใๅงใใใใใ็ขบ่ชใใพใใใ๏ผ
ย
6 | ใใฃใฌใณใธ โ ใใญในใใใกใคใซใขใใใญใผใใใฟใณใ่ฟฝๅ ใใใ
ใใญในใใ่ฟฝๅ ใงใขใใใญใผใใใฆใ่ฟฝๅ ใใใใญใฅใกใณใใ่ธใพใใ AI Agent ใจใฎใใใจใใๅฎ็พใใฆ่ฆใพใใใใใชใใใใฎใใฃใฌใณใธใฏๅๅใฎใใฃใฌใณใธใๅฎๆฝใใฆใใชใใจๅฎ่ฃ ใงใใชใใฎใงๆณจๆใใพใใใใ
ใใฃใใ UI ใฎใใใใผใซ .txt ใใกใคใซๅฐ็จใฎใขใใใญใผใใใฟใณ ใ่ฟฝๅ ใใ้ธๆใใใใกใคใซใ Python API ใธ้ไฟกใใใตใผใๅดใงใใกใคใซใใใฃใณใฏๅใใฆไฟๅญใใใใใซใใพใใ้ไธญ็ต้ใใใใใใใใใใใใใขใใใญใผใไธญใฏ BusyIndicator ใง็ป้ขใใใญใใฏใใใใใซใใพใใใใ
ย
ChatEntity.controller.js ใงๆฌกใฎ 3 ็นใ่ฟฝๅ ใใพใ๏ผใในใๅๅ็ฉบ้ใฏ็ฐๅขใซๅใใใฆใใ ใใ๏ผ:
- ๅ้ ญใฎ sap/ui/core/BusyIndicator ใคใณใใผใ
- onFileChange ใใณใใฉ โ ใใกใคใซ็จฎๅฅใใงใใฏ & ใขใใใญใผใๅผใณๅบใ
- _uploadFile ใกใฝใใ โ fetch + FormData ใง API /agent/upload ใธ POST
/** ใใกใคใซ้ธๆๆ */
async onFileChange(oEvent) {
const oFileUploader = oEvent.getSource();
const file = (oEvent.getParameter(“files”) || [])[0];
if (!file) return;
if (!file.name.endsWith(“.txt”)) {
MessageToast.show(“txtๅฝขๅผใฎใใกใคใซใฎใฟใขใใใญใผใๅฏ่ฝใงใใ”);
oFileUploader.clear();
return;
}
await this._uploadFile(file);
oFileUploader.clear();
},
/** ใใกใคใซใขใใใญใผใ */
async _uploadFile(file) {
const ui = this.getView().getModel(“ui”);
ui.setProperty(“/busy”, true);
BusyIndicator.show(0);
try {
const fd = new FormData();
fd.append(“file”, file);
const res = await fetch(`${ENDPOINT}/agent/upload`, {
method: “POST”,
body: fd
});
if (!res.ok) throw new Error(await res.text());
const { filename, chunks_created } = await res.json();
const files = ui.getProperty(“/uploadedFiles”) || [];
ui.setProperty(“/uploadedFiles”, […files, {
name: filename,
chunks: chunks_created,
uploadedAt: new Date()
}]);
MessageToast.show(`”${filename}” ใใขใใใญใผใใใพใใ๏ผ${chunks_created} ใใฃใณใฏไฝๆ๏ผ`);
} catch (err) {
console.error(err);
MessageToast.show(`ใขใใใญใผใใจใฉใผ: ${err.message}`);
} finally {
ui.setProperty(“/busy”, false);
BusyIndicator.hide();
}
}
ย
ๆฌกใซใใใใใผใธ FileUploader ใจ Clear ใใฟใณใไธฆในใพใใFileUploader ใฏ buttonOnly=”true” ใซใใฆใใขใคใณใณใใฟใณใซใใใฎใใณใใงใใ
<f:actions>
<u:FileUploader id=”fileUploader”
name=”file”
placeholder=”ใใกใคใซใ้ธๆ…”
fileType=”txt”
change=”onFileChange”
buttonText=””
buttonOnly=”true”
icon=”sap-icon://upload”
class=”fileUploaderButton” />
<Button id=”clearChatButton”
text=”ใใฃใใใใฏใชใข”
icon=”sap-icon://refresh”
type=”Emphasized”
press=”onClearChatPress” />
</f:actions>
ย
ใใใซใใใใผไธ้จใซย “ใขใใใญใผใๆธใใกใคใซไธ่ฆง” ใๅบใใใจใงใใฉใฎใใกใคใซใไฝใใฃใณใฏใซๅๅฒใใใใใ่ฆ่ฆๅใงใใพใใ
<HBox id=”fileUploadAreaBox”
visible=”{= ${ui>/uploadedFiles}.length > 0}”>
<Text text=”่ฟฝๅ ใงใขใใใญใผใใใใใกใคใซ:” />
<HBox items=”{ui>/uploadedFiles}”>
<ObjectStatus text=”{ui>name}” icon=”sap-icon://document-text” />
</HBox>
</HBox>
ย
ไธ็ดใใฃใฌใณใธ โ ใชใปใใใใฟใณ
ใใกใคใซใใขใใใญใผใใใฆใใใจใใฏใใซ DB ใใฉใใฉใ่จใใฟใพใใใในใ็จใซ ใใผใฟใใผในใไธๆฌใชใปใใ ใใใใฟใณใ่ฟฝๅ ใใฆใฟใพใใใใ
- API ๅด: /agent/delete ใๅฎ่ฃ ๏ผใใณใ: Part4 ใซใใใ DB ใๅๆๅใใใณใผใ๏ผใ
- Controller: onDeletePress ใๅฎ่ฃ ใใ/agent/delete ใๅผใณๅบใใฆ UI ใฎใใกใคใซไธ่ฆงใ็ฉบใซใใใ
- View: ใใใใผใฎใขใใใญใผใใใฟใณใจใฏใชใขใใฟใณใฎ้ใซ Delete ใใฟใณใ้ ็ฝฎใ
ๅฎ่ฃ ใใณใ
UI Model ใฎ /uploadedFiles ใ็ฉบ้ ๅ [] ใซใใใจใใใกใคใซไธ่ฆงใจใชใขใ่ชๅ็ใซ้่กจ็คบใซใชใใพใใโ
ย
7 | ๆฌกๅไบๅ
Part 8 CloudFoundry ใซใใใญใค
Partโฏ8 ใงใฏใๅฎๆใใใขใใชใฑใผใทใงใณใ Cloud Foundry ใธใใใญใคใใพใใUI ใฏ mta build โ cf deployใPython ใใใฏใจใณใใฏ manifest.yaml ใไฝฟใฃใฆ cf push โ 2 ๆฌ็ซใฆใง โ็ง้ใใใญใคโ ใซๆๆฆใใพใใใๆฅฝใใฟใซ๏ผ
ย
ๅ ่ฒฌไบ้
ๆฌใใญใฐใซ่จ่ผใใใ่ฆ่งฃใใใณๆ่ฆใฏใในใฆ็งๅไบบใฎใใฎใงใใใ็งใฎๅไบบ็ใช็ซๅ ดใง็บไฟกใใฆใใพใใSAP ใฏๆฌใใญใฐใฎๅ ๅฎนใซใคใใฆไธๅใฎ่ฒฌไปปใ่ฒ ใใพใใใ
“}]]ย
ย [[{“value”:”็ๆ AI ใฏ็ ็ฉถๅฎคใฎๅคใธ้ฃใณๅบใใไปใใใธใใน็พๅ ดใฎๅธธ่ญใๅกใๆฟใใฆใใพใใSAPใฏๅ
จ้ๅใงใใฎๆณขใซไนใฃใฆใใพใใใใฎใใญใฐใทใชใผใบใงใฏใSAPย AIย Core ใฎๆขๅฎใขใใซใๆ้ใงๅผใณๅบใใๅฎๅใงไฝฟใใ AI ใจใผใธใงใณใใธๆกๅผตใใโ็ง้ใใณใบใชใณโใใๅฑใใใพใใใ็ฅใใYou can find the English version here.ย ๐ย ๆฌใทใชใผใบใงๅญฆในใใใจSAPย AIย Core ไธใงใซในใฟใ AI Agent ใ โ็ง้โ ใงๅใใๆนๆณLangChainใปGoogle ๆค็ดขใใผใซใปRAGใไฝฟใฃใๅฎ่ฃ
AI Agent ใ RESTย API ๅใใSAPUI5/Fiori ใฎ UI ใซ่ผใใCloud Foundryใซใใใญใคใใๆ้ ๅญฆ็ฟๆ้ๅPart ใฏย 10โ15 ๅย ใง่ชญใใ๏ผๆใๅใใใใใชใฅใผใ ใไบๅฎใใฆใใพใใย ๐บ๏ธ ้ฃ่ผใญใผใใใใPart 0 ใใญใญใผใฐPart 1 ็ฐๅขๆง็ฏ: SAP AI CoreใจAI LaunchpadPart 2 LangChain ใงใใฃใใใขใใซใๆง็ฏPart 3 Agentใใผใซ: Googleย ๆค็ดขใ็ตฑๅPart 4 RAG ๅบ็ค โ : HANA Cloud VectorEngineใจๅใ่พผใฟๅฆ็Part 5 RAG ๅบ็ค โก: Retriever Toolใฎๆง็ฏPart 6 AI Agent ใ RESTย API ๅPart 7 SAPUI5 ใงใใฃใใUI ใๆง็ฏย [็พๅจใฎใใญใฐ]Part 8 CloudFoundry ใซใใใญใคใใฎ่จไบใใๅฝนใซ็ซใฃใใใใใฒโฏKudosโฏใๆผใใฆใใใ ใใใจๅฑใฟใซใชใใพใใ ใใใใใใฃใจ่ฉณใใ็ฅใใใใใๅฅใฎใใผใใๅใไธใใฆใปใใใใชใฉใใ่ฆๆใใใใฐใๆฐ่ปฝใซใณใกใณใใใ ใใ๏ผSAPUI5 ใงใใฃใใUI ใๆง็ฏ1 | ใฏใใใซไปๅใฎPart ใงใฏใๅๅใพใงใซๆง็ฏใใ AI Agent ใใSAP UI5/Fiori ใใผในใฎใใฃใใ UI ใใๅผใณๅบใใใใใซใใพใใSAP UI5 ใฏ SAP ใๆไพใใ UI ใใฌใผใ ใฏใผใฏใงใใใไผๆฅญๅใใฎใขใใณใช Web ใขใใชใฑใผใทใงใณใ็ญๆ้ใง้็บใงใใ็นใ็นๅพดใงใใ็นใซ SAP Business Application Studio๏ผBAS๏ผ ใๅฉ็จใใใจใFiori ็จใใณใใฌใผใใใใใญใธใงใฏใๆงๆใ่ชๅ็ๆใงใใใใใใใฃใฌใฏใใชใ่จญๅฎใใกใคใซใฎๆๅไฝๆใไธ่ฆใซใชใใพใใย 2 | ไบๅๆบๅBTP ใตใใขใซใฆใณใSAP AI Core ใคใณในใฟใณในSAP AI LaunchpadใฎใตใในใฏใชใใทใงใณPython 3.13็ฐๅข & pipVSCodeใBASใชใฉใฎIDETrial ็ฐๅขใฎๆณจๆTrial ใฎ HANAโฏCloud ใคใณในใฟใณในใฏย ๆฏๆฉ่ชๅๅๆญข ใใใพใใๆฅใใพใใไฝๆฅญใฎๅ ดๅใฏ็ฟๆฅใคใณในใฟใณในใๅ่ตทๅใใฆใใ ใใย 3 | Fiori ใขใใชใฑใผใทใงใณใฎๆบๅSAP ็ณป UI ้็บใงใฏใBAS ใฎใใณใใฌใผใๆฉ่ฝใฎๅฉ็จใๅผทใๆจๅฅจใใพใใใใณใใฌใผใใใ็ๆใใใใใฉใซใๆงๆใจ่จญๅฎใใกใคใซใใใผในใซใใใใจใงใFiori ใขใใชๅบๆใฎใใใใงในใ๏ผmta.yaml๏ผใใขใธใฅใผใซๆงๆใ็ด ๆฉใๆดๅใงใใพใใย BAS ใง Create Dev Space ใใฏใชใใฏใใใใณใใฌใผใใซ SAP Fiori ใ้ธๆใใพใใไฝตใใฆๅๅใซไฝๆใใ Python API ใใญใผใซใซใง่ตทๅใใใใฎใงใAdditional SAP Extensions ใใ Python Tools ใ้ธๆใใพใใๅทฆไธใฎใใณใใผใฌใผใกใใฅใผ โ File > New Project From Template ใ้ธๆใใพใใTemplate Selection ใฏ Basic ใ้ธๆใใพใใData Source and Service Selection ใงใฏใUI ใซ่กจ็คบใใใใผใฟใฏ Python API ใใๅๅพใใใใ None ใ้ธๆใใพใใEntity Selection ใง Entity name ใซ ChatEntity ใจๅ
ฅๅใใพใใProject Attributes ใไปฅไธใฎใใใซ่จญๅฎใใAdd deployment configuration ใ Yes ใซใใพใใ้
็ฎๅคModule Namemy-ai-agent-uiApplication TitleMy AI ChatDescriptionChat UI for AI AgentDeployment Configuration ใงใฏๆฌกใฎใใใซ่จญๅฎใใพใใ้
็ฎๅคTargetCloud FoundryDestinationNoneAdd Router ModuleAdd Application to Managed Application Routerใพใใซ Target ใ CF ใซใใฆใ Router Module ใฎใชใใทใงใณใ่กจ็คบใใใชใใใจใใใใพใใใใฎๅ ดๅใฏใไธๅบฆ Target ใ ABAP ใซๅใๆฟใใฆใใ Cloud Foundry ใซๆปใใจๆญฃใใ่กจ็คบใใใพใใย ๆๅพใซใPython API ใฎ้
็ฝฎใจๅไฝ็ขบ่ชใๅฎๆฝใใพใใๆงๆใฏๆฌกใฎใใใซใชใใพใใ# ใใฉใซใๆงๆ
PROJECTS
โโโ my-ai-agent-ui/
โโโ my-ai-agent-api/
โโโ main.py
โโโ requirements.txt
โโโ .envmy-ai-agent-apiย ้
ไธใงไปฎๆณ็ฐๅขใไฝๆใปๆๅนๅใใไปฅไธใฎใณใใณใใงใญใผใซใซใตใผใใ่ตทๅใใใ็ขบ่ชใใพใใgunicorn -w 1 -k uvicorn.workers.UvicornWorker main:app –bind 0.0.0.0:${PORT:-8000}ใใใงใFioriใขใใชใฑใผใทใงใณใฎๆง็ฏๆบๅใๆดใใพใใ๏ผย 4 | Fiori ใขใใชใฑใผใทใงใณใฎๆน่ฏ็ๆใใใ Fiori ใใญใธใงใฏใใใใผในใซใใใฃใใ UI ใ AI Agent ใจ้ไฟกใงใใใใใซใณใผใใ่ชฟๆดใใฆใใใพใใใใใพใใณใณใใญใผใฉใผ๏ผmy-ai-agent-ui/webapp/controller/ChatEntity.controller.js๏ผใไปฅไธใฎใใใซๆธใใพใใsap.ui.define([
“sap/ui/core/mvc/Controller”,
“sap/ui/model/json/JSONModel”,
“sap/m/MessageToast”,
“sap/ui/core/BusyIndicator”
], function (Controller, JSONModel, MessageToast, BusyIndicator) {
“use strict”;
// ็ฐๅขใซใใฃใฆใจใณใใใคใณใใๅใๆฟใ
const ENDPOINT = (
[“localhost”, “applicationstudio”].some(h => window.location.hostname.includes(h)) ||
window.location.port === “8080”
) ? “” : “https://my-ai-agent-api-relaxed-raven-ie.cfapps.us10-001.hana.ondemand.com”;
return Controller.extend(“myaiagentui.controller.ChatEntity”, {
/** ๅๆๅ */
onInit() {
this.getView().setModel(new JSONModel({
busy: false,
txtInput: “”,
uploadedFiles: [],
messages: [{
role: “assistant”,
content: “ใใใซใกใฏ๏ผ็งใฏใใฃใใใใใใงใใไฝใใๆไผใใงใใใใจใฏใใใพใใ๏ผ”,
hasThinkingProcess: false
}]
}), “ui”);
this.byId(“fileUploader”)?.setUploadUrl(`${ENDPOINT}/agent/upload`);
},
/** ใใฃใใๅฑฅๆญดใๅๆๅ */
onClearChatPress() {
this.getView().getModel(“ui”).setProperty(“/messages”, [{
role: “assistant”,
content: “ใใใซใกใฏ๏ผ็งใฏใใฃใใใใใใงใใไฝใใๆไผใใงใใใใจใฏใใใพใใ๏ผ”,
hasThinkingProcess: false
}]);
MessageToast.show(“ใใฃใใใใฏใชใขใใพใใ”);
},
/** ใกใใปใผใธ้ไฟก */
async onBtnChatbotSendPress(){
const ui=this.getView().getModel(“ui”);
const input=(ui.getProperty(“/txtInput”)||””).trim();
if(!input) return;
const msgs=ui.getProperty(“/messages”);
msgs.push({role:”user”, content:input});
ui.setProperty(“/messages”, msgs);
ui.setProperty(“/txtInput”, “”);
ui.setProperty(“/busy”, true);
try{
const {output, intermediate_steps=[]}=await this._apiChatCompletion(input);
msgs.push({role:”assistant”, content:output, hasThinkingProcess:Boolean(intermediate_steps.length), thinkingProcess:intermediate_steps.map((s,i)=>({…s, stepIndex:i+1, observationTruncated:s.observation?.slice(0,100)+(s.observation?.length>100?”…”:””), observationFull:s.observation, isObservationExpanded:false, hasLongObservation:(s.observation?.length||0)>100})), isExpanded:false});
ui.setProperty(“/messages”, msgs);
this._scrollToBottom();
}catch(err){
console.error(err);
MessageToast.show(`ใจใฉใผ: ${err.message}`);
}finally{
ui.setProperty(“/busy”, false);
}
},
/** ๆ่ใใญใปใน่กจ็คบๅๆฟ */
onToggleThinkingProcess(oEvent) {
const ctx = oEvent.getSource()?.getBindingContext(“ui”);
if (ctx) this._toggleFlag(ctx, “isExpanded”);
},
/** Observation ่กจ็คบๅๆฟ */
onToggleObservation(oEvent) {
const ctx = oEvent.getSource()?.getBindingContext(“ui”);
if (ctx) this._toggleFlag(ctx, “isObservationExpanded”);
},
/** ไปปๆใใฉใฐใๅ่ปข */
_toggleFlag(ctx, flag) {
const ui = this.getView().getModel(“ui”);
const path = ctx.getPath();
ui.setProperty(`${path}/${flag}`, !ui.getProperty(`${path}/${flag}`));
},
/** ใใฃใใใๆไธ้จใธในใฏใญใผใซ */
_scrollToBottom() {
setTimeout(() => {
const sc = this.byId(“chatScrollContainer”);
const items = sc?.getContent()[0].getItems();
if (items?.length) sc.scrollToElement(items[items.length – 1]);
}, 100);
},
/** AI ใใฃใใ API ๅผใณๅบใ */
async _apiChatCompletion(query) {
const res = await fetch(`${ENDPOINT}/agent/chat`, {
method: “POST”,
headers: { “Content-Type”: “application/json” },
body: JSON.stringify({ query })
});
if (!res.ok) throw new Error(`ใตใผใใผใจใฉใผ (${res.status}): ${await res.text()}`);
return res.json();
}
});
});ย ๆฌกใซ View๏ผmy-ai-agent-ui/webapp/view/ChatEntity.view.xml๏ผใไปฅไธใฎใใใซๅคๆดใใพใใ<mvc:View controllerName=”myaiagentui.controller.ChatEntity”
xmlns:core=”sap.ui.core”
xmlns:mvc=”sap.ui.core.mvc”
xmlns:f=”sap.f”
xmlns:u=”sap.ui.unified”
displayBlock=”true”
xmlns=”sap.m”>
<f:DynamicPage id=”mainDynamicPage” stickySubheaderProvider=”iconTabBar” class=”sapUiNoContentPadding”>
<f:title>
<f:DynamicPageTitle id=”mainDynamicTitle”>
<f:heading>
<Title id=”pageTitle” text=”AI ใใฃใใ with Fiori”/>
</f:heading>
<f:actions>
<Button id=”clearChatButton”
text=”ใใฃใใใใฏใชใข”
icon=”sap-icon://refresh”
type=”Emphasized”
press=”onClearChatPress”/>
</f:actions>
</f:DynamicPageTitle>
</f:title>
<f:content>
<!– ใใฃใใ UI ใกใคใณใจใชใข –>
<VBox id=”chatMainAreaBox” class=”sapUiNoContentPadding chatMainArea”>
<IconTabBar id=”iconTabBar”
class=”sapUiNoContentPadding”
stretchContentHeight=”true”
expanded=”true”
expandable=”false”>
<items>
<IconTabFilter id=”chatbotTab” text=”ใใฃใใใใใ” class=”sapUiNoContentPadding”>
<VBox id=”chatContainerBox” class=”chatContainer sapUiNoContentPadding”>
<!– ใใฃใใใกใใปใผใธใจใชใข –>
<ScrollContainer id=”chatScrollContainer”
height=”100%”
width=”100%”
vertical=”true”
focusable=”true”
class=”chatScrollContainer”>
<VBox id=”chatMessagesBox” items=”{ui>/messages}” class=”chatMessagesContainer”>
<VBox id=”messageItemBox”>
<!– assistant ใณใกใณใ –>
<VBox id=”assistantMessageVBox” visible=”{= ${ui>role} === ‘assistant’}”
class=”chatMessageWrapper”>
<HBox id=”assistantMessageHBox” direction=”Row” class=”chatMessageRow”>
<HBox id=”assistantMessageHBox1″ backgroundDesign=”Solid”
class=”chatBubbleAssistant”>
<core:Icon id=”assistantMessageIcon” decorative=”true”
src=”sap-icon://ai”
class=”chatIcon”/>
<FormattedText id=”assistantMessageFormattedText” htmlText=”{ui>content}”/>
</HBox>
</HBox>
<!– ๆ่ใใญใปในใขใณใผใใฃใชใณ –>
<HBox id=”thinkingToggleBox” visible=”{ui>hasThinkingProcess}”
class=”thinkingProcessContainer”>
<Button id=”thinkingToggleButton” icon=”{= ${ui>isExpanded} ? ‘sap-icon://collapse’ : ‘sap-icon://expand’ }”
text=”ๆ่ใใญใปในใ{= ${ui>isExpanded} ? ‘้ ใ’ : ‘่กจ็คบ’ }”
type=”Transparent”
press=”onToggleThinkingProcess”
class=”thinkingProcessToggle”/>
</HBox>
<VBox id=”thinkingVBox” visible=”{= ${ui>isExpanded} && ${ui>hasThinkingProcess} }”
class=”thinkingProcessContent”>
<VBox id=”thinkingVBox1″ items=”{ui>thinkingProcess}”>
<VBox id=”thinkingStepBox” class=”thinkingStepBox”>
<Title id=”thinkingStepTitle”
text=”{= ‘Step ‘ + ${ui>stepIndex} }”
level=”H5″
class=”thinkingStepTitle”/>
<VBox id=”thinkingStepContent” class=”thinkingStepContent”>
<HBox id=”thinkingStepThought” class=”thinkingStepItem”>
<Label id=”ThoughtLabel” text=”Thought:” design=”Bold” class=”thinkingLabel”/>
<Text id=”ThoughtText” text=”{ui>thought}” wrapping=”true” class=”thinkingText”/>
</HBox>
<HBox id=”thinkingStepAction” class=”thinkingStepItem”>
<Label id=”ActionLabel” text=”Action:” design=”Bold” class=”thinkingLabel”/>
<Text id=”ActionText” text=”{ui>action}” class=”thinkingText”/>
</HBox>
<HBox id=”thinkingStepInput” class=”thinkingStepItem”>
<Label id=”InputLabel” text=”Input:” design=”Bold” class=”thinkingLabel”/>
<Text id=”InputText” text=”{ui>action_input}” class=”thinkingText”/>
</HBox>
<HBox id=”thinkingStepObservation” class=”thinkingStepItem”>
<Label id=”ObservationLabel” text=”Observation:” design=”Bold” class=”thinkingLabel”/>
<VBox id=”ObservationVBox” class=”thinkingText”>
<Text id=”ObservationText” text=”{= ${ui>isObservationExpanded} ? ${ui>observationFull} : ${ui>observationTruncated} }”
wrapping=”true”/>
<Link id=”ObservationLink”
text=”{= ${ui>isObservationExpanded} ? ‘ๆใใใใ’ : ‘็ถใใ่ชญใ’ }”
visible=”{ui>hasLongObservation}”
press=”onToggleObservation”
class=”observationToggleLink”/>
</VBox>
</HBox>
</VBox>
</VBox>
</VBox>
</VBox>
</VBox>
<!– user ใณใกใณใ –>
<HBox id=”UserComment”
visible=”{= ${ui>role} === ‘user’}”
direction=”RowReverse”
class=”chatMessageRow”>
<HBox id=”UserCommentBox”
direction=”RowReverse”
backgroundDesign=”Solid”
class=”chatBubbleUser sapThemeBrand-asBackgroundColor”>
<core:Icon id=”UserCommentIcon”
decorative=”true”
src=”sap-icon://customer”
class=”chatIcon sapThemeTextInverted”/>
<Text id=”UserCommentText” class=”sapThemeTextInverted” text=”{ui>content}”/>
</HBox>
</HBox>
</VBox>
</VBox>
</ScrollContainer>
<!– ๅ
ฅๅๆฌ๏ผ้ไฟกใใฟใณ –>
<HBox id=”chatInputAreaBox” class=”chatInputArea”>
<TextArea id=”chatInput” value=”{ui>/txtInput}”
width=”100%”
growing=”true”
placeholder=”ใกใใปใผใธใๅ
ฅๅใใฆใใ ใใ…”
editable=”{= !${ui>/busy}}”
busyIndicatorDelay=”0″
class=”chatTextArea”>
<layoutData>
<FlexItemData id=”InputData” growFactor=”1″/>
</layoutData>
</TextArea>
<Button id=”sendButton” class=”chatSendButton”
type=”Emphasized”
icon=”sap-icon://paper-plane”
text=”้ไฟก”
press=”onBtnChatbotSendPress”
busy=”{ui>/busy}”
busyIndicatorDelay=”0″/>
</HBox>
</VBox>
</IconTabFilter>
</items>
</IconTabBar>
</VBox>
</f:content>
</f:DynamicPage>
</mvc:View>ย ใใใซใUI ใฎ่ฉณ็ดฐใชใใถใคใณ่จญๅฎใจใใฆใในใฟใคใซ๏ผmy-ai-agent-ui/webapp/css/style.css๏ผใ่จญๅฎใใพใใ/* ใใฃใใใกใคใณใจใชใข */
.chatMainArea {
height: 100vh;
min-height: 300px;
}
/* ใใฃใใใณใณใใ */
.chatContainer {
display: flex;
flex-direction: column;
height: calc(90vh – 100px);
padding: 0;
}
/* ใใฃใใในใฏใญใผใซใณใณใใ */
.chatScrollContainer {
flex: 1;
min-height: 300px;
background-color: #f8f9fa;
border: none;
border-radius: 0;
padding: 0;
margin: 0;
}
/* ใใฃใใใกใใปใผใธใณใณใใ */
.chatMessagesContainer {
padding: 1rem;
}
/* ใใฃใใใกใใปใผใธ่ก */
.chatMessageRow {
width: 100%;
margin-bottom: 0.5rem;
}
/* ใใฃใใใกใใปใผใธใฉใใใผ */
.chatMessageWrapper {
margin-bottom: 1rem;
}
/* ใใฃใใใณใกใณใ – ใขใทในใฟใณใ */
.chatBubbleAssistant {
max-width: 70%;
min-width: 100px;
background-color: #ffffff;
border: 1px solid #d0d7de;
border-radius: 18px;
padding: 12px 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
align-items: flex-start;
}
.chatBubbleAssistant .sapMFormattedText {
word-wrap: break-word;
line-height: 1.4;
}
/* ใใฃใใใณใกใณใ – ใฆใผใถใผ */
.chatBubbleUser {
max-width: 70%;
min-width: 100px;
border-radius: 18px;
padding: 12px 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
align-items: flex-start;
}
.chatBubbleUser .sapMText {
word-wrap: break-word;
line-height: 1.4;
}
/* ๆ่ใใญใปในใณใณใใ */
.thinkingProcessContainer {
margin-top: 0.5rem;
margin-left: 2.5rem;
}
/* ๆ่ใใญใปในใใฐใซใใฟใณ */
.thinkingProcessToggle {
font-size: 0.875rem;
color: #0073e6;
}
.thinkingProcessToggle:hover {
background-color: rgba(0, 115, 230, 0.08) !important;
}
/* ๆ่ใใญใปในใณใณใใณใ */
.thinkingProcessContent {
margin-left: 2.5rem;
margin-top: 0.5rem;
max-width: 70%;
}
/* ๆ่ในใใใใใใฏใน */
.thinkingStepBox {
margin-bottom: 1.5rem;
padding: 1rem;
background-color: #f5f7fa;
border: 1px solid #e4e7ea;
border-radius: 8px;
}
.thinkingStepBox:last-child {
margin-bottom: 0;
}
/* ๆ่ในใใใใฟใคใใซ */
.thinkingStepTitle {
color: #0a6ed1;
margin-bottom: 0.75rem;
font-size: 1rem;
}
/* ๆ่ในใใใใณใณใใณใ */
.thinkingStepContent {
padding-left: 0.5rem;
}
/* ๆ่ในใใใ้
็ฎ */
.thinkingStepItem {
margin-bottom: 0.5rem;
align-items: flex-start;
}
.thinkingStepItem:last-child {
margin-bottom: 0;
}
/* ๆ่ใฉใใซ */
.thinkingLabel {
min-width: 100px;
margin-right: 1rem;
color: #32363a;
font-size: 0.875rem;
}
/* ๆ่ใใญในใ */
.thinkingText {
flex: 1;
font-size: 0.875rem;
color: #515456;
line-height: 1.5;
}
/* Observation็ถใใ่ชญใใชใณใฏ */
.observationToggleLink {
margin-top: 0.25rem;
font-size: 0.875rem;
}
/* ๅ
ฅๅใจใชใข */
.chatInputArea {
background-color: #ffffff;
padding: 1rem;
border-top: 1px solid #e4e7ea;
align-items: flex-end;
flex-shrink: 0;
}
/* ใใญในใใจใชใขใฎในใฟใคใซ่ชฟๆด */
.chatTextArea .sapMTextAreaInner {
border: 2px solid #e4e7ea;
background-color: #ffffff;
}
.chatTextArea .sapMTextAreaInner:focus-within {
border-color: #0073e6;
}
/* ้ไฟกใใฟใณ */
.chatSendButton {
min-height: 40px;
border-radius: 20px;
margin-left: 0.5rem;
}
/* ใขใคใณใณ่ชฟๆด */
.chatIcon {
font-size: 1.2rem;
margin-top: 2px;
}
.chatBubbleAssistant .chatIcon {
margin-right: 0.5rem;
}
.chatBubbleUser .chatIcon {
margin-left: 0.5rem;
}
/* IconTabBar ใฎใใใฃใณใฐๅ้ค */
.sapMITB {
padding: 0;
}
.sapMITBContent {
padding: 0;
background-color: transparent;
}
/* ใฌในใใณใทใๅฏพๅฟ */
@media (max-width: 768px) {
.chatBubbleAssistant,
.chatBubbleUser {
max-width: 85%;
}
.chatContainer {
height: calc(100vh – 80px);
}
.chatScrollContainer {
min-height: 300px;
}
.chatSendButton .sapMBtnInner .sapMBtnContent {
font-size: 0;
}
.chatSendButton .sapMBtnIcon {
margin-right: 0;
}
.thinkingProcessContent {
max-width: 85%;
}
.thinkingLabel {
min-width: 80px;
margin-right: 0.5rem;
}
}ย ใใฃใใ UI ใใ Python API ใธใชใฏใจในใใ่ปข้ใใใใใใใญใญใทใใใซใฆใงใขใ่ฟฝๅ ใใพใใใพใไพๅญ้ขไฟใใคใณในใใผใซใใพใใใใnpm install ui5-middleware-simpleproxy –save-devย ใใใฆ my-ai-agent-ui/package.json ใซๆฌกใฎ่จญๅฎใ่ฟฝ่จใใฆใใ ใใใ…
“main”: “webapp/index.html”,
“devDependencies”: {
(็็ฅ)
},
/* ใใใใ
“ui5”: {
“dependencies”: [
“ui5-middleware-simpleproxy”
ใใ]
},
ใใใใพใง */
“scripts”: {
….ใใใงใใฃใใ UI ใใใฎ /api ใชใฏใจในใใใญใผใซใซใฎ Python API ใธ่ปข้ใใใใใใซใชใใพใใย 5 | Fiori ใขใใชใฑใผใทใงใณใฎใในใใญใผใซใซ็ฐๅขใงใขใใชใจ API ใๅๆใซ่ตทๅใใๅฎ้ใซใใฃใใๆฉ่ฝใๅไฝใใใ็ขบ่ชใใพใใใใใพใใmy-ai-agent-api ใใฃใฌใฏใใชใงไปฅไธใฎใณใใณใใๅฎ่กใใฆ Python API ใ่ตทๅใใพใใgunicorn -w 1 -k uvicorn.workers.UvicornWorker main:app –bind 0.0.0.0:${PORT:-8000}ใพใใๅฅใฎใฟใผใใใซใ้ใใmy-ai-agent-ui ใฎใซใผใใงๆฌกใฎใณใใณใใๅฎ่กใใพใใnpm startย ใญใฐใซ่กจ็คบใใใ URL๏ผ้ๅธธใฏ http://localhost:8080๏ผใใใฉใฆใถใง้ใใใใฃใใๅ
ฅๅๆฌใซใกใใปใผใธใ้ใฃใฆใฟใฆใใ ใใใAI ใจใผใธใงใณใใใ่ฟ็ญใ่กจ็คบใใใใฐๆๅใงใ๏ผย ใใฃใใ็ป้ขใฎ ใใฃใใใใฏใชใข ใใฟใณใใฏใชใใฏใใใจๅฑฅๆญดใใชใปใใใใใใใใฃใใใใฏใชใขใใพใใใใจ็ขบ่ชใงใใพใใๆฐใใไผ่ฉฑใๅ้กใชใๅงใใใใใ็ขบ่ชใใพใใใ๏ผย 6 | ใใฃใฌใณใธ โ ใใญในใใใกใคใซใขใใใญใผใใใฟใณใ่ฟฝๅ ใใใใใญในใใ่ฟฝๅ ใงใขใใใญใผใใใฆใ่ฟฝๅ ใใใใญใฅใกใณใใ่ธใพใใ AI Agent ใจใฎใใใจใใๅฎ็พใใฆ่ฆใพใใใใใชใใใใฎใใฃใฌใณใธใฏๅๅใฎใใฃใฌใณใธใๅฎๆฝใใฆใใชใใจๅฎ่ฃ
ใงใใชใใฎใงๆณจๆใใพใใใใใใฃใใ UI ใฎใใใใผใซ .txt ใใกใคใซๅฐ็จใฎใขใใใญใผใใใฟใณ ใ่ฟฝๅ ใใ้ธๆใใใใกใคใซใ Python API ใธ้ไฟกใใใตใผใๅดใงใใกใคใซใใใฃใณใฏๅใใฆไฟๅญใใใใใซใใพใใ้ไธญ็ต้ใใใใใใใใใใใใใขใใใญใผใไธญใฏ BusyIndicator ใง็ป้ขใใใญใใฏใใใใใซใใพใใใใย ChatEntity.controller.js ใงๆฌกใฎ 3 ็นใ่ฟฝๅ ใใพใ๏ผใในใๅๅ็ฉบ้ใฏ็ฐๅขใซๅใใใฆใใ ใใ๏ผ:ๅ้ ญใฎ sap/ui/core/BusyIndicator ใคใณใใผใonFileChange ใใณใใฉ โ ใใกใคใซ็จฎๅฅใใงใใฏ & ใขใใใญใผใๅผใณๅบใ_uploadFile ใกใฝใใ โ fetch + FormData ใง API /agent/upload ใธ POST/** ใใกใคใซ้ธๆๆ */
async onFileChange(oEvent) {
const oFileUploader = oEvent.getSource();
const file = (oEvent.getParameter(“files”) || [])[0];
if (!file) return;
if (!file.name.endsWith(“.txt”)) {
MessageToast.show(“txtๅฝขๅผใฎใใกใคใซใฎใฟใขใใใญใผใๅฏ่ฝใงใใ”);
oFileUploader.clear();
return;
}
await this._uploadFile(file);
oFileUploader.clear();
},
/** ใใกใคใซใขใใใญใผใ */
async _uploadFile(file) {
const ui = this.getView().getModel(“ui”);
ui.setProperty(“/busy”, true);
BusyIndicator.show(0);
try {
const fd = new FormData();
fd.append(“file”, file);
const res = await fetch(`${ENDPOINT}/agent/upload`, {
method: “POST”,
body: fd
});
if (!res.ok) throw new Error(await res.text());
const { filename, chunks_created } = await res.json();
const files = ui.getProperty(“/uploadedFiles”) || [];
ui.setProperty(“/uploadedFiles”, […files, {
name: filename,
chunks: chunks_created,
uploadedAt: new Date()
}]);
MessageToast.show(`”${filename}” ใใขใใใญใผใใใพใใ๏ผ${chunks_created} ใใฃใณใฏไฝๆ๏ผ`);
} catch (err) {
console.error(err);
MessageToast.show(`ใขใใใญใผใใจใฉใผ: ${err.message}`);
} finally {
ui.setProperty(“/busy”, false);
BusyIndicator.hide();
}
}ย ๆฌกใซใใใใใผใธ FileUploader ใจ Clear ใใฟใณใไธฆในใพใใFileUploader ใฏ buttonOnly=”true” ใซใใฆใใขใคใณใณใใฟใณใซใใใฎใใณใใงใใ<f:actions>
<u:FileUploader id=”fileUploader”
name=”file”
placeholder=”ใใกใคใซใ้ธๆ…”
fileType=”txt”
change=”onFileChange”
buttonText=””
buttonOnly=”true”
icon=”sap-icon://upload”
class=”fileUploaderButton” />
<Button id=”clearChatButton”
text=”ใใฃใใใใฏใชใข”
icon=”sap-icon://refresh”
type=”Emphasized”
press=”onClearChatPress” />
</f:actions>ย ใใใซใใใใผไธ้จใซย “ใขใใใญใผใๆธใใกใคใซไธ่ฆง” ใๅบใใใจใงใใฉใฎใใกใคใซใไฝใใฃใณใฏใซๅๅฒใใใใใ่ฆ่ฆๅใงใใพใใ<HBox id=”fileUploadAreaBox”
visible=”{= ${ui>/uploadedFiles}.length > 0}”>
<Text text=”่ฟฝๅ ใงใขใใใญใผใใใใใกใคใซ:” />
<HBox items=”{ui>/uploadedFiles}”>
<ObjectStatus text=”{ui>name}” icon=”sap-icon://document-text” />
</HBox>
</HBox>ย ไธ็ดใใฃใฌใณใธ โ ใชใปใใใใฟใณใใกใคใซใใขใใใญใผใใใฆใใใจใใฏใใซ DB ใใฉใใฉใ่จใใฟใพใใใในใ็จใซ ใใผใฟใใผในใไธๆฌใชใปใใ ใใใใฟใณใ่ฟฝๅ ใใฆใฟใพใใใใAPI ๅด: /agent/delete ใๅฎ่ฃ
๏ผใใณใ: Part4 ใซใใใ DB ใๅๆๅใใใณใผใ๏ผใController: onDeletePress ใๅฎ่ฃ
ใใ/agent/delete ใๅผใณๅบใใฆ UI ใฎใใกใคใซไธ่ฆงใ็ฉบใซใใใView: ใใใใผใฎใขใใใญใผใใใฟใณใจใฏใชใขใใฟใณใฎ้ใซ Delete ใใฟใณใ้
็ฝฎใๅฎ่ฃ
ใใณใUI Model ใฎ /uploadedFiles ใ็ฉบ้
ๅ [] ใซใใใจใใใกใคใซไธ่ฆงใจใชใขใ่ชๅ็ใซ้่กจ็คบใซใชใใพใใโย 7 | ๆฌกๅไบๅPart 8 CloudFoundry ใซใใใญใคPartโฏ8 ใงใฏใๅฎๆใใใขใใชใฑใผใทใงใณใ Cloud Foundry ใธใใใญใคใใพใใUI ใฏ mta build โ cf deployใPython ใใใฏใจใณใใฏ manifest.yaml ใไฝฟใฃใฆ cf push โ 2 ๆฌ็ซใฆใง โ็ง้ใใใญใคโ ใซๆๆฆใใพใใใๆฅฝใใฟใซ๏ผย ๅ
่ฒฌไบ้
ๆฌใใญใฐใซ่จ่ผใใใ่ฆ่งฃใใใณๆ่ฆใฏใในใฆ็งๅไบบใฎใใฎใงใใใ็งใฎๅไบบ็ใช็ซๅ ดใง็บไฟกใใฆใใพใใSAP ใฏๆฌใใญใฐใฎๅ
ๅฎนใซใคใใฆไธๅใฎ่ฒฌไปปใ่ฒ ใใพใใใ”}]]ย Read Moreย Technology Blog Posts by SAP articlesย
#SAPCHANNEL