@fcg-labs/cx-agent-hook 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/astro.d.ts +1 -0
- package/astro.js +4 -1
- package/client.d.ts +4 -1
- package/client.js +26 -2
- package/element.d.ts +2 -0
- package/element.js +10 -1
- package/index.d.ts +4 -1
- package/index.js +4 -3
- package/package.json +1 -1
- package/react.d.ts +2 -0
- package/react.js +11 -3
- package/vue.d.ts +2 -0
- package/vue.js +5 -1
package/astro.d.ts
CHANGED
package/astro.js
CHANGED
|
@@ -43,11 +43,14 @@ export { aiSuggestView } from "./view.js";
|
|
|
43
43
|
*
|
|
44
44
|
* @returns {() => void} 구독 해제
|
|
45
45
|
*/
|
|
46
|
-
export function setupCxAiSuggest(
|
|
46
|
+
export function setupCxAiSuggest(
|
|
47
|
+
element, { hook, inquiry = "", context = null, onAdopt } = {},
|
|
48
|
+
) {
|
|
47
49
|
defineCxAiSuggest();
|
|
48
50
|
if (!element) return () => {};
|
|
49
51
|
element.hook = hook;
|
|
50
52
|
element.inquiry = inquiry;
|
|
53
|
+
element.context = context;
|
|
51
54
|
if (!onAdopt) return () => {};
|
|
52
55
|
const handler = (event) => onAdopt(event.detail.text, event.detail.answerId);
|
|
53
56
|
element.addEventListener("cx-adopt", handler);
|
package/client.d.ts
CHANGED
|
@@ -56,7 +56,10 @@ export interface FeedbackInput {
|
|
|
56
56
|
|
|
57
57
|
export declare class CxAgentClient {
|
|
58
58
|
constructor(config: CxAgentConfig);
|
|
59
|
-
getAnswer(
|
|
59
|
+
getAnswer(
|
|
60
|
+
inquiry: string,
|
|
61
|
+
context?: Record<string, string>,
|
|
62
|
+
): Promise<AnswerResult>;
|
|
60
63
|
sendFeedback(fb: FeedbackInput): Promise<FeedbackResult>;
|
|
61
64
|
sent(answerId: number | string, finalText: string, agent?: string): Promise<FeedbackResult>;
|
|
62
65
|
scored(answerId: number | string, score: number, agent?: string): Promise<FeedbackResult>;
|
package/client.js
CHANGED
|
@@ -41,6 +41,24 @@ function sleep(ms) {
|
|
|
41
41
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* 고객 상황 맵 정규화 — 값 문자열 강제, 빈 값 제거. 실을 것이 없으면 null.
|
|
46
|
+
*
|
|
47
|
+
* 어휘 판단(어떤 키가 프롬프트에 실리나)은 SDK 의 몫이 아니다 — 서버의
|
|
48
|
+
* serving_context 가 정본이고, 여기는 "문자열 맵" 이라는 모양만 보장한다.
|
|
49
|
+
*/
|
|
50
|
+
function normalizeContext(context) {
|
|
51
|
+
if (!context || typeof context !== "object") return null;
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const [key, value] of Object.entries(context)) {
|
|
54
|
+
if (value === null || value === undefined) continue;
|
|
55
|
+
const v = String(value).trim();
|
|
56
|
+
if (!v) continue;
|
|
57
|
+
out[String(key)] = v;
|
|
58
|
+
}
|
|
59
|
+
return Object.keys(out).length ? out : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
export class CxAgentClient {
|
|
45
63
|
/**
|
|
46
64
|
* @param {object} config
|
|
@@ -141,10 +159,13 @@ export class CxAgentClient {
|
|
|
141
159
|
/**
|
|
142
160
|
* 서빙 답변 요청. throw 하지 않는다 — ok 로 판별.
|
|
143
161
|
* @param {string} inquiry 고객 문의 원문
|
|
162
|
+
* @param {Record<string,string>} [context] 문의에 붙는 고객 상황 —
|
|
163
|
+
* 기기·앱 버전·문의 유형 같은 접지 신호와 융합 키(user_id 등).
|
|
164
|
+
* 값은 문자열로 강제되고, 비면 키 자체를 싣지 않는다(역호환).
|
|
144
165
|
* @returns {Promise<{ok:boolean, answered:boolean, answer:string,
|
|
145
166
|
* answerId:number|null, evidence:Array, declinedReason:string, raw:object}>}
|
|
146
167
|
*/
|
|
147
|
-
async getAnswer(inquiry) {
|
|
168
|
+
async getAnswer(inquiry, context) {
|
|
148
169
|
const target = this._target("answer");
|
|
149
170
|
if (!target.path) {
|
|
150
171
|
// throw 하지 않는다 — 이 SDK 의 원칙은 "CS팀 업무를 절대 막지 않는다".
|
|
@@ -159,8 +180,11 @@ export class CxAgentClient {
|
|
|
159
180
|
};
|
|
160
181
|
}
|
|
161
182
|
try {
|
|
183
|
+
const body = { inquiry };
|
|
184
|
+
const ctx = normalizeContext(context);
|
|
185
|
+
if (ctx) body.context = ctx;
|
|
162
186
|
const { status, data } = await this._post(
|
|
163
|
-
target.path(this.domain),
|
|
187
|
+
target.path(this.domain), body, target,
|
|
164
188
|
);
|
|
165
189
|
if (status !== 200) {
|
|
166
190
|
// 서버가 준 사유를 그대로 옮긴다. `http_503` 으로 뭉개면 "아직 안 켬"
|
package/element.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export declare const TAG: "cx-ai-suggest";
|
|
|
9
9
|
export interface CxAiSuggestElement extends HTMLElement {
|
|
10
10
|
hook: CxHook | null;
|
|
11
11
|
inquiry: string;
|
|
12
|
+
/** 고객 상황 — 문의 리셋과 무관하게 최신값만 쓴다 */
|
|
13
|
+
context: Record<string, string> | null;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/** 커스텀 엘리먼트 등록. 두 번 불러도 안전하고, 브라우저가 아니면 무동작(false). */
|
package/element.js
CHANGED
|
@@ -45,6 +45,7 @@ function createClass() {
|
|
|
45
45
|
return class CxAiSuggestElement extends HTMLElement {
|
|
46
46
|
#hook = null;
|
|
47
47
|
#inquiry = "";
|
|
48
|
+
#context = null;
|
|
48
49
|
#state = "idle";
|
|
49
50
|
#result = null;
|
|
50
51
|
|
|
@@ -52,6 +53,12 @@ function createClass() {
|
|
|
52
53
|
get hook() { return this.#hook; }
|
|
53
54
|
set hook(value) { this.#hook = value; this.#render(); }
|
|
54
55
|
|
|
56
|
+
/** 고객 상황 — 기기·버전·융합 키. 문의 리셋과 무관하게 최신값만 쓴다. */
|
|
57
|
+
get context() { return this.#context; }
|
|
58
|
+
set context(value) {
|
|
59
|
+
this.#context = value && typeof value === "object" ? value : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
/** 대상 문의 원문 */
|
|
56
63
|
get inquiry() { return this.#inquiry; }
|
|
57
64
|
set inquiry(value) {
|
|
@@ -73,7 +80,9 @@ function createClass() {
|
|
|
73
80
|
if (!this.#inquiry || this.#state === "loading") return;
|
|
74
81
|
this.#state = "loading";
|
|
75
82
|
this.#render();
|
|
76
|
-
this.#result = await this.#hook.requestAnswer(
|
|
83
|
+
this.#result = await this.#hook.requestAnswer(
|
|
84
|
+
this.#inquiry, this.#context || undefined,
|
|
85
|
+
);
|
|
77
86
|
this.#state = "done";
|
|
78
87
|
this.#render();
|
|
79
88
|
}
|
package/index.d.ts
CHANGED
|
@@ -37,7 +37,10 @@ export interface CxHook {
|
|
|
37
37
|
/** 사유 코드 → 이 훅의 언어로 된 평문 */
|
|
38
38
|
declineText(reason: string): string;
|
|
39
39
|
|
|
40
|
-
requestAnswer(
|
|
40
|
+
requestAnswer(
|
|
41
|
+
inquiry: string,
|
|
42
|
+
context?: Record<string, string>,
|
|
43
|
+
): Promise<AnswerResult>;
|
|
41
44
|
|
|
42
45
|
/** 제안을 에디터에 넣었다 (패널이 부른다) */
|
|
43
46
|
noteAdopted(answerId: number | string | null): void;
|
package/index.js
CHANGED
|
@@ -98,10 +98,11 @@ export function createCxHook(config = {}) {
|
|
|
98
98
|
return textOf(messages, reason);
|
|
99
99
|
},
|
|
100
100
|
|
|
101
|
-
/** 답변 제안 요청 — throw 하지
|
|
102
|
-
|
|
101
|
+
/** 답변 제안 요청 — throw 하지 않음.
|
|
102
|
+
* context: 문의에 붙는 고객 상황(기기·버전·융합 키) — 선택. */
|
|
103
|
+
requestAnswer(inquiry, context) {
|
|
103
104
|
if (!client || !inquiry) return Promise.resolve(NOT_CONFIGURED);
|
|
104
|
-
return client.getAnswer(inquiry);
|
|
105
|
+
return client.getAnswer(inquiry, context);
|
|
105
106
|
},
|
|
106
107
|
|
|
107
108
|
/** 제안을 에디터에 넣었다 (패널이 부른다) */
|
package/package.json
CHANGED
package/react.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type { AiSuggestView } from "./view.js";
|
|
|
7
7
|
export interface AiSuggestPanelProps {
|
|
8
8
|
hook: CxHook;
|
|
9
9
|
inquiry: string | undefined | null;
|
|
10
|
+
/** 고객 상황 — 요청 시점 최신값 사용 (리셋 deps 아님) */
|
|
11
|
+
context?: Record<string, string> | null;
|
|
10
12
|
onAdopt?: (text: string) => void;
|
|
11
13
|
/** 바깥 배치용 (레이아웃만 — 색·간격은 styles.css 변수로) */
|
|
12
14
|
className?: string;
|
package/react.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* 단계가 없고(의존성 0 이 설계 성질), JSX 를 그대로 배포하면 소비처 번들러가
|
|
18
18
|
* node_modules 를 변환해 주기를 기대해야 한다. createElement 로 직접 쓴다.
|
|
19
19
|
*/
|
|
20
|
-
import { createElement as h, useCallback, useEffect, useState } from "react";
|
|
20
|
+
import { createElement as h, useCallback, useEffect, useRef, useState } from "react";
|
|
21
21
|
|
|
22
22
|
import { aiSuggestView, CLS } from "./view.js";
|
|
23
23
|
|
|
@@ -28,10 +28,13 @@ export { aiSuggestView };
|
|
|
28
28
|
* @param {object} props
|
|
29
29
|
* @param {object} props.hook createCxHook() 결과
|
|
30
30
|
* @param {string} props.inquiry 대상 문의 원문
|
|
31
|
+
* @param {Record<string,string>} [props.context] 고객 상황 — 기기·버전·융합 키.
|
|
32
|
+
* 요청 시점의 최신 값을 쓴다. **리셋 deps 에 넣지 않는다** — 인라인 객체를
|
|
33
|
+
* 넘기면 리렌더마다 제안·채택 상태가 초기화된다.
|
|
31
34
|
* @param {(text: string) => void} props.onAdopt "에디터에 넣기"
|
|
32
35
|
* @param {string} [props.className] 바깥 배치용 (레이아웃만)
|
|
33
36
|
*/
|
|
34
|
-
export function AiSuggestPanel({ hook, inquiry, onAdopt, className }) {
|
|
37
|
+
export function AiSuggestPanel({ hook, inquiry, context, onAdopt, className }) {
|
|
35
38
|
const [state, setState] = useState("idle");
|
|
36
39
|
const [result, setResult] = useState(null);
|
|
37
40
|
|
|
@@ -43,10 +46,15 @@ export function AiSuggestPanel({ hook, inquiry, onAdopt, className }) {
|
|
|
43
46
|
hook.clearAdopted();
|
|
44
47
|
}, [inquiry, hook]);
|
|
45
48
|
|
|
49
|
+
// context 는 ref 로 최신값만 읽는다 — deps 에 넣으면 인라인 객체가
|
|
50
|
+
// 리렌더마다 새 참조가 되어 요청 콜백이 계속 재생성된다.
|
|
51
|
+
const contextRef = useRef(context);
|
|
52
|
+
contextRef.current = context;
|
|
53
|
+
|
|
46
54
|
const request = useCallback(async () => {
|
|
47
55
|
if (!inquiry || state === "loading") return;
|
|
48
56
|
setState("loading");
|
|
49
|
-
setResult(await hook.requestAnswer(inquiry));
|
|
57
|
+
setResult(await hook.requestAnswer(inquiry, contextRef.current));
|
|
50
58
|
setState("done");
|
|
51
59
|
}, [hook, inquiry, state]);
|
|
52
60
|
|
package/vue.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type { AiSuggestView } from "./view.js";
|
|
|
7
7
|
export interface AiSuggestPanelProps {
|
|
8
8
|
hook: CxHook;
|
|
9
9
|
inquiry?: string;
|
|
10
|
+
/** 고객 상황 — 기기·버전·융합 키 */
|
|
11
|
+
context?: Record<string, string> | null;
|
|
10
12
|
/** 바깥 배치용 (레이아웃만 — 색·간격은 styles.css 변수로) */
|
|
11
13
|
className?: string;
|
|
12
14
|
}
|
package/vue.js
CHANGED
|
@@ -30,6 +30,8 @@ export const AiSuggestPanel = defineComponent({
|
|
|
30
30
|
props: {
|
|
31
31
|
hook: { type: Object, required: true },
|
|
32
32
|
inquiry: { type: String, default: "" },
|
|
33
|
+
/** 고객 상황 — 기기·버전·융합 키 (요청 시점 최신값 사용) */
|
|
34
|
+
context: { type: Object, default: null },
|
|
33
35
|
/** 바깥 배치용 (레이아웃만 — 색·간격은 styles.css 변수로) */
|
|
34
36
|
className: { type: String, default: "" },
|
|
35
37
|
},
|
|
@@ -53,7 +55,9 @@ export const AiSuggestPanel = defineComponent({
|
|
|
53
55
|
const request = async () => {
|
|
54
56
|
if (!props.inquiry || state.value === "loading") return;
|
|
55
57
|
state.value = "loading";
|
|
56
|
-
result.value = await props.hook.requestAnswer(
|
|
58
|
+
result.value = await props.hook.requestAnswer(
|
|
59
|
+
props.inquiry, props.context || undefined,
|
|
60
|
+
);
|
|
57
61
|
state.value = "done";
|
|
58
62
|
};
|
|
59
63
|
|