# 검색 연동 프론트엔드 설치 가이드

<figure><img src="/files/JPpK30HLxoHm8PcuJ16U" alt=""><figcaption></figcaption></figure>

프론트엔드에서 genser 검색 엔진을 연동하는 전체 과정을 안내합니다. 복잡한 개발 없이, **스크립트 설치**와 **함수 호출**만으로 AI 검색을 구현할 수 있습니다.

{% stepper %}
{% step %}

## 연동 키 준비하기 (Setup)

#### 서비스 키 및 인스턴스 키 확인

스크립트 연동에 필요한 두 가지 필수 키(Key) 값을 genser 어드민에서 미리 확보해야 합니다.

<table data-full-width="false"><thead><tr><th width="209.5078125">키 이름</th><th width="250.69921875">설명</th><th>용도</th></tr></thead><tbody><tr><td>서비스 키(Service Key)</td><td>서비스 연동을 위한 고유 인증 키</td><td>초기화 스크립트(<code>head</code> 영역) 설정용</td></tr><tr><td>인스턴스 키(Instance Key)</td><td>생성된 검색 인스턴스의 고유 식별자</td><td>검색 API(<code>searchProducts</code>) 및 로그 수집 호출용</td></tr></tbody></table>

{% hint style="info" %}
**서비스 키 확인 경로**

genser 어드민의 설정 메뉴에서 "서비스 키" 확인이 가능합니다.
{% endhint %}

<figure><img src="/files/eh4JZSZlVjnYiKxnpIHk" alt=""><figcaption><p>어드민 설정 화면에서 서비스 키 위치</p></figcaption></figure>

{% hint style="info" %}
**인스턴스 키 확인 경로**

genser 어드민의 인스턴스 설정 메뉴에서 테이블의 인스턴스 관리 도구 "코드 복사"로 인스턴스 키 복사 가능합니다.
{% endhint %}

<figure><img src="/files/jfxTWrorqfgrXudyS5O7" alt=""><figcaption><p>어드민 인스턴스 설정에서 인스턴스 키 복사 위치</p></figcaption></figure>

#### 기능 활성화 확인

API 응답에 특정 데이터를 포함받으려면 어드민의 인스턴스 설정에서 해당 기능을 미리 '활성화(ON)' 상태로 변경해야 합니다.
{% endstep %}

{% step %}

## 스크립트 설치하기 (Install)

genser 서비스를 이용하기 위해서는 모든 페이지의 공통 레이아웃에 기본 스크립트를 삽입해야 합니다.&#x20;

{% hint style="warning" %}
공통 레이아웃이 적용되지 않는 페이지가 있다면 해당 페이지에도 개별적으로 스크립트를 삽입해야 합니다.&#x20;
{% endhint %}

* **위치:** HTML 문서의 `<head>` 태그 종료 직전.
* **주의사항:**
  * 공통 레이아웃이 적용되지 않는 페이지가 있다면 해당 페이지에도 개별적으로 삽입해야 합니다.
  * `발급 받은 서비스 키` 부분에 실제 할당받은 키 값을 입력해야 합니다.

```js
<head>
    <!-- ... 기존 헤더 내용 ... -->
    
    <!-- genser 스크립트 설치 코드 시작 -->
    <script type="text/javascript">
        (function (a, i, u, e, o) {
            a[u] = a[u] || function () { (a[u].q = a[u].q || []).push(arguments) };
        })(window, document, "genser");
        
        // 발급 받은 서비스 키를 입력하세요
        genser("serviceKey", "발급 받은 서비스 키"); 
        genser("siteType", "custom");
    </script>
    <script charset="utf-8" src="//static.groobee.io/dist/g2/genser.init.min.js"></script>
    <!-- genser 스크립트 설치 코드 끝 -->
</head>
```

{% endstep %}

{% step %}

## 검색 결과 호출하기 (Search)

기본 스크립트 모든 페이지에 삽입되어 있다는 가정 하에 해당 페이지 내에서 아래와 같은 자바스크립트 함수를 사용할 수 있습니다.&#x20;

아래의 함수에 타입에 맞는 데이터를 넣어 호출하면 상품 검색 결과 데이터를 받을 수 있습니다.&#x20;

### 검색 호출(searchProducts)

```js
genser.call('searchProducts', {
    instanceKey: 'instanceKey', // 필수: 어드민에서 생성한 인스턴스 키
    queryText: '상품 검색어',     // 필수: 사용자 입력 검색어
  })
  .success((res) => {
    console.log(res);           // 검색 결과 데이터 반환 (아래 응답 구조 참고)
  })
  .error((err) => {
    console.error(err);         // 오류 발생 시
  });
```

<table data-full-width="false"><thead><tr><th width="131.3828125">항목</th><th width="79.3046875">타입</th><th width="79.578125">필수</th><th>설명</th></tr></thead><tbody><tr><td>instanceKey</td><td>string</td><td>필수</td><td>genser 어드민에서 생성한 인스턴스 키(고유 식별자)</td></tr><tr><td>queryText</td><td>string</td><td>필수</td><td>사용자가 입력한 상품 검색어</td></tr><tr><td>...</td><td></td><td>선택</td><td>기타 확장 필드 (사용 목적에 따라 추가 가능)</td></tr></tbody></table>

### 응답 데이터 구조(Response)

`success` 콜백을 통해 반환되는 데이터는 `type`에 따라 4가지 형태로 구분됩니다.

#### 1. 상품 정보(SEARCH)

검색된 상품 목록입니다.&#x20;

```js
{
    "requestId": "검색어에 대한 ID",
    "type": "SEARCH",
    "products": [
        {
            "code": "상품 코드",
            "name": "상품 이름",
            ...
        },
        {
            "code": "상품 코드",
            "name": "상품 이름",
            ...
        },
    ],
    "facet": {},
    "nextPageToken": ''
}
```

<table data-full-width="false"><thead><tr><th width="151.2421875">항목</th><th width="79.3046875">타입</th><th width="79.578125">필수</th><th>설명</th></tr></thead><tbody><tr><td>requestId</td><td>string</td><td>필수</td><td>검색어에 대한 고유 요청 ID</td></tr><tr><td>type</td><td>string</td><td>필수</td><td>응답 타입(예: " SEARCH")</td></tr><tr><td>products</td><td>array</td><td>필수</td><td>상품 목록 배열</td></tr><tr><td>products[].code</td><td>string</td><td>필수</td><td>상품 코드</td></tr><tr><td>products[].name</td><td>string</td><td>필수</td><td>상품 이름</td></tr><tr><td>facet</td><td>object</td><td>선택</td><td>-</td></tr><tr><td>nextPageToken</td><td>string</td><td>선택</td><td>-</td></tr></tbody></table>

#### 2. 검색 결과 설명(SUMMARY)

`검색 결과 설명 설정`이 어드민에서 활성화된 경우,  검색 결과에 대한 요약 텍스트를 반환합니다.

```js
{
    "requestId": "검색어에 대한 ID",
    "type": "SUMMARY",
    "summary": "요약 내용 텍스트"
}
```

<table data-full-width="false"><thead><tr><th width="151.2421875">항목</th><th width="79.3046875">타입</th><th width="79.578125">필수</th><th>설명</th></tr></thead><tbody><tr><td>requestId</td><td>string</td><td>필수</td><td>검색어에 대한 고유 요청 ID</td></tr><tr><td>type</td><td>string</td><td>필수</td><td>응답 타입(예: " SUMMARY")</td></tr><tr><td>summary</td><td>string</td><td>필수</td><td>검색어 또는 결과에 대한 요약 내용 텍스트</td></tr></tbody></table>

#### 3. 연관 키워드(KEYWORDS)

`연관 키워드 설정`이 어드민에서 활성화된 경우, 연관된 추천 키워드 리스트를 반환됩니다.

```js
{
    "requestId": "검색어에 대한 ID",
    "type": "KEYWORDS",
    "keywords": ["키워드1", "키워드2"]
}
```

<table data-full-width="false"><thead><tr><th width="151.2421875">항목</th><th width="79.3046875">타입</th><th width="79.578125">필수</th><th>설명</th></tr></thead><tbody><tr><td>requestId</td><td>string</td><td>필수</td><td>검색어에 대한 고유 요청 ID</td></tr><tr><td>type</td><td>string</td><td>필수</td><td>응답 타입(예: " KEYWORDS")</td></tr><tr><td>keywords</td><td>string[]</td><td>필수</td><td>추천 또는 연관 키워드 배열 (문자열 리스트)</td></tr></tbody></table>

#### 4. 제안 질문(QUESTIONS)

`제안 질문 설정`이 어드민에서 활성화된 경우, 사용자가 추가로 궁금해할 만한 질문 리스트를 반환합니다.

```js
{
    "requestId": "검색어에 대한 ID",
    "type": "QUESTIONS",
    "questions": [
        "질문 내용 1",
        "질문 내용 2"
    ]
}
```

<table data-full-width="false"><thead><tr><th width="151.2421875">항목</th><th width="79.3046875">타입</th><th width="79.578125">필수</th><th>설명</th></tr></thead><tbody><tr><td>requestId</td><td>string</td><td>필수</td><td>검색어에 대한 고유 요청 ID</td></tr><tr><td>type</td><td>string</td><td>필수</td><td>응답 타입(예: " QUESTIONS")</td></tr><tr><td>questions</td><td>string[]</td><td>필수</td><td>제안 질문 배열 (문자열 리스트)</td></tr></tbody></table>
{% endstep %}

{% step %}

## 로그 데이터 수집하기 (Analytics)

검색 품질 향상과 통계를 위해 사용자 행동 데이터를 수집합니다.

* **검색(SE):** `searchProducts` 함수 호출 시 자동으로 수집됩니다.
* **노출(DI) & 클릭(CL):** 아래 함수를 사용하여 직접 호출해야 합니다.

### 노출 (DI)

검색 결과 상품이 화면에 노출되었을 때 호출합니다.

```js
genser.call('DI', {
    instance: { key: 'instanceKey' }, // 인스턴스 키
    goods: [
      {
        code: '상품 코드', // searchProducts 결과의 code
        name: '상품 이름', // searchProducts 결과의 name
      },
      ...
    ], // 상품 정보
    requestId: '검색어에 대한 ID', // searchProducts 결과에서 응답 받은 requestId
  })
  .success((res) => {
    console.log(res);   // 정상 callback
  })
  .error((err) => {
    console.error(err); // 오류 callback
  });

```

<table data-full-width="false"><thead><tr><th width="151.2421875">항목</th><th width="79.3046875">타입</th><th width="79.578125">필수</th><th>설명</th></tr></thead><tbody><tr><td>instance</td><td>object</td><td>필수</td><td>genser 어드민에서 생성한 인스턴스 정보</td></tr><tr><td>instance.key</td><td>string</td><td>필수</td><td>genser 어드민에서 생성한 인스턴스 키 (고유 식별자)</td></tr><tr><td>goods</td><td>array</td><td>필수</td><td>사용자에게 추천/선택된 상품 목록</td></tr><tr><td>goods[].code</td><td>string</td><td>필수</td><td>상품 코드 (<code>searchProducts</code> 응답의 상품 코드 사용)</td></tr><tr><td>goods[].name</td><td>string</td><td>필수</td><td>상품 이름 (<code>searchProducts</code> 응답의 상품 이름 사용)</td></tr><tr><td>requestId</td><td>string</td><td>필수</td><td>상품 검색 결과에서 받은 요청 ID (<code>searchProducts</code> 응답의 requestId 사용)</td></tr></tbody></table>

### 클릭 (CL)

사용자가 상품을 클릭했을 때 호출합니다.

```js
genser.call('CL', {
    instance: { key: 'instanceKey' }, // 인스턴스 키
    goods: [
      {
        code: '상품 코드', // searchProducts 결과의 code
        name: '상품 이름', // searchProducts 결과의 name
      },
      ...
    ], // 상품 정보
    requestId: '검색어에 대한 ID', // searchProducts 결과에서 응답 받은 requestId
  })
  .success((res) => {
    console.log(res);   // 정상 callback
  })
  .error((err) => {
    console.error(err); // 오류 callback
  });

```

<table data-full-width="false"><thead><tr><th width="151.2421875">항목</th><th width="79.3046875">타입</th><th width="79.578125">필수</th><th>설명</th></tr></thead><tbody><tr><td>instance</td><td>object</td><td>필수</td><td>genser 어드민에서 생성한 인스턴스 정보</td></tr><tr><td>instance.key</td><td>string</td><td>필수</td><td>genser 어드민에서 생성한 인스턴스 키 (고유 식별자)</td></tr><tr><td>goods</td><td>array</td><td>필수</td><td>사용자에게 추천/선택된 상품 목록</td></tr><tr><td>goods[].code</td><td>string</td><td>필수</td><td>상품 코드 (<code>searchProducts</code> 응답의 상품 코드 사용)</td></tr><tr><td>goods[].name</td><td>string</td><td>필수</td><td>상품 이름 (<code>searchProducts</code> 응답의 상품 이름 사용)</td></tr><tr><td>requestId</td><td>string</td><td>필수</td><td>상품 검색 결과에서 받은 요청 ID (<code>searchProducts</code> 응답의 requestId 사용)</td></tr></tbody></table>
{% endstep %}
{% endstepper %}

<details>

<summary>React 코드 예시</summary>

```js
import React, { useState, useEffect } from 'react';

/**
 * Genser Search React Component
 * 전제조건: public/index.html의 <head>에 Genser SDK 스크립트가 로드되어 있어야 합니다.
 */
const GenserSearch = () => {
  const [query, setQuery] = useState('');
  const [products, setProducts] = useState([]);
  const [requestId, setRequestId] = useState(null);
  
  // 인스턴스 키 (환경변수나 상수로 관리 권장)
  const INSTANCE_KEY = 'YOUR_INSTANCE_KEY';

  // 1. 검색 핸들러
  const handleSearch = (e) => {
    e.preventDefault();
    if (!query.trim()) return;

    if (!window.genser) {
      console.error("Genser SDK가 로드되지 않았습니다.");
      return;
    }

    // searchProducts 호출
    window.genser.call('searchProducts', {
      instanceKey: INSTANCE_KEY,
      queryText: query
    })
    .success((res) => {
      if (res.type === 'SEARCH') {
        // 상태 업데이트 -> 리렌더링 유발
        setProducts(res.products);
        setRequestId(res.requestId);
      }
    })
    .error((err) => {
      console.error('검색 에러:', err);
    });
  };

  // 2. 노출(DI) 로그 전송
  // products나 requestId가 변경되면(즉, 검색 결과가 렌더링되면) 실행
  useEffect(() => {
    if (products.length > 0 && requestId) {
      window.genser.call('DI', {
        instance: { key: INSTANCE_KEY },
        goods: products.map(p => ({ code: p.code, name: p.name })),
        requestId: requestId
      });
      // console.log('DI(노출) 로그 전송 완료');
    }
  }, [products, requestId]);

  // 3. 클릭(CL) 로그 전송 핸들러
  const handleProductClick = (product) => {
    if (!requestId) return;

    window.genser.call('CL', {
      instance: { key: INSTANCE_KEY },
      goods: [{ code: product.code, name: product.name }],
      requestId: requestId
    });
    // console.log('CL(클릭) 로그 전송 완료:', product.name);

    // 실제 상품 페이지로 이동 로직
    // window.location.href = `/product/${product.code}`;
  };

  return (
    <div className="genser-search-container">
      {/* 검색 폼 */}
      <form onSubmit={handleSearch}>
        <input 
          type="text" 
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="검색어를 입력하세요"
        />
        <button type="submit">검색</button>
      </form>

      {/* 검색 결과 리스트 */}
      <div className="product-list">
        {products.map((product) => (
          <div 
            key={product.code} 
            className="product-item"
            onClick={() => handleProductClick(product)}
            style={{ cursor: 'pointer', margin: '10px 0', border: '1px solid #ddd', padding: '10px' }}
          >
            {/* 이미지 처리 (API 응답에 따라 필드명 조정 필요) */}
            {product.imageUrl && <img src={product.imageUrl} alt={product.name} width="100" />}
            <h3>{product.name}</h3>
            <p>{product.code}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export default GenserSearch;
```

</details>

<details>

<summary>Vue 코드 예시</summary>

```js
<template>
  <div class="genser-search">
    <div class="search-bar">
      <input 
        v-model="query" 
        @keyup.enter="handleSearch" 
        placeholder="상품을 검색하세요" 
      />
      <button @click="handleSearch">검색</button>
    </div>

    <ul v-if="products.length > 0">
      <li 
        v-for="product in products" 
        :key="product.code" 
        @click="handleProductClick(product)"
      >
        {{ product.name }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue';

// 전제조건: public/index.html의 <head>에 Genser SDK 스크립트가 로드되어 있어야 합니다. [cite: 64, 239]

// 어드민에서 발급받은 인스턴스 키 [cite: 11]
const INSTANCE_KEY = 'YOUR_INSTANCE_KEY';

const query = ref('');
const products = ref([]);
const requestId = ref(null);

// 1. 검색 핸들러 [cite: 95]
const handleSearch = () => {
  if (!query.value.trim()) return;

  if (!window.genser) {
    console.error("Genser SDK가 로드되지 않았습니다.");
    return;
  }

  // genser 검색 API 호출
  window.genser.call('searchProducts', {
    instanceKey: INSTANCE_KEY, // [cite: 98]
    queryText: query.value     // [cite: 99]
  })
  .success((res) => {
    // 응답 타입이 SEARCH인 경우 데이터 업데이트 [cite: 118]
    if (res.type === 'SEARCH') {
      products.value = res.products;   // [cite: 119]
      requestId.value = res.requestId; // [cite: 117]
    }
  })
  .error((err) => {
    console.error('검색 에러:', err);
  });
};

// 2. 노출(DI) 로그 전송 [cite: 177]
// products 상태가 변경되어 화면에 렌더링된 후 호출
watch([products, requestId], ([newProducts, newRequestId]) => {
  if (newProducts.length > 0 && newRequestId) {
    window.genser.call('DI', {
      instance: { key: INSTANCE_KEY }, // [cite: 180]
      goods: newProducts.map(p => ({ 
        code: p.code, 
        name: p.name 
      })), // [cite: 181]
      requestId: newRequestId // [cite: 186]
    })
    .success((res) => {
       // console.log('DI 로그 전송 성공');
    });
  }
});

// 3. 클릭(CL) 로그 전송 핸들러 [cite: 200]
const handleProductClick = (product) => {
  if (!requestId.value) return;

  window.genser.call('CL', {
    instance: { key: INSTANCE_KEY }, // [cite: 206]
    goods: [{ 
      code: product.code, 
      name: product.name 
    }], // [cite: 207]
    requestId: requestId.value // [cite: 212]
  })
  .success((res) => {
    console.log('CL 로그 전송 성공, 상품 페이지로 이동 로직 수행');
  });
};
</script>
```

</details>

<details>

<summary>HTML/JS 코드 예시</summary>

```js
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Genser Search Example</title>
    </head>
<body>

    <div id="search-container">
        <input type="text" id="searchInput" placeholder="검색어를 입력하세요">
        <button id="searchBtn">검색</button>
    </div>

    <ul id="resultList"></ul>

    <script type="text/javascript">
        // 설정 값
        const INSTANCE_KEY = 'YOUR_INSTANCE_KEY'; // [cite: 11]
        
        // 상태 변수
        let currentRequestId = null;

        // DOM 요소 참조
        const searchInput = document.getElementById('searchInput');
        const searchBtn = document.getElementById('searchBtn');
        const resultList = document.getElementById('resultList');

        // 1. 검색 버튼 클릭 이벤트
        searchBtn.addEventListener('click', function() {
            const query = searchInput.value;
            if (!query.trim()) return;
            
            doSearch(query);
        });

        // 검색 실행 함수 [cite: 95]
        function doSearch(queryText) {
            if (!window.genser) return console.error("SDK 미로드");

            window.genser.call('searchProducts', {
                instanceKey: INSTANCE_KEY, // [cite: 98]
                queryText: queryText       // [cite: 99]
            })
            .success((res) => {
                if (res.type === 'SEARCH') {
                    currentRequestId = res.requestId; // [cite: 117]
                    renderProducts(res.products);
                    
                    // 렌더링 직후 노출(DI) 로그 전송
                    sendImpressionLog(res.products, res.requestId);
                }
            })
            .error((err) => {
                console.error("검색 실패:", err);
            });
        }

        // 화면 렌더링 함수
        function renderProducts(products) {
            resultList.innerHTML = ''; // 기존 목록 초기화

            products.forEach(product => {
                const li = document.createElement('li');
                li.textContent = product.name;
                li.style.cursor = 'pointer';
                
                // 클릭 이벤트 바인딩
                li.addEventListener('click', () => {
                    handleProductClick(product);
                });

                resultList.appendChild(li);
            });
        }

        // 2. 노출(DI) 로그 전송 함수 [cite: 177]
        function sendImpressionLog(products, reqId) {
            window.genser.call('DI', {
                instance: { key: INSTANCE_KEY }, // [cite: 180]
                goods: products.map(p => ({
                    code: p.code,
                    name: p.name
                })), // [cite: 181]
                requestId: reqId // [cite: 186]
            });
        }

        // 3. 클릭(CL) 로그 전송 함수 [cite: 200]
        function handleProductClick(product) {
            if (!currentRequestId) return;

            window.genser.call('CL', {
                instance: { key: INSTANCE_KEY }, // [cite: 206]
                goods: [{
                    code: product.code,
                    name: product.name
                }], // [cite: 207]
                requestId: currentRequestId // [cite: 212]
            })
            .success(() => {
                console.log('클릭 로그 전송 완료: ' + product.name);
                // 이후 상품 상세 페이지 이동 등의 로직 수행
            });
        }
    </script>
</body>
</html>
```

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.genser.ai/integration/frontend-installation/installation-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
