# Elice CDK Usage Guide

This guide explains how to use the Elice Contents Development Kit (`@eliceio/cdk`) to build external content that integrates with the Elice platform.

## Table of Contents

- [Installation](#installation)
- [Initialization](#initialization)
- [Account & Metadata](#account--metadata)
- [Score Reporting](#score-reporting)
- [Key-Value Store](#key-value-store)
- [Material Key-Value Store](#material-key-value-store)
- [File Store](#file-store)
- [AI Chat](#ai-chat)
- [Tutoring](#tutoring)
- [Navigation](#navigation)
- [Translation](#translation)

---

## Installation

```bash
# npm
npm install @eliceio/cdk

# yarn
yarn add @eliceio/cdk
```

---

## Initialization

Before using any SDK features, you must initialize the SDK instance. The `init()` method reads the required parameters from the URL query string.

### Required Query Parameters

When embedded in Elice, your content URL receives these query parameters:

| Parameter    | Required | Description                          |
| ------------ | -------- | ------------------------------------ |
| `extToken`   | Yes      | JWT token for authentication         |
| `courseId`   | Yes      | Current course ID                    |
| `materialId` | Yes      | Current material (lecture page) ID   |
| `locale`     | No       | User's locale (default: `ko`)        |
| `parentUrl`  | No       | Parent frame URL                     |

### Basic Initialization

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();

// Initialize the SDK (reads query params from window.location.search)
await sdk.init();

console.log('Initialized:', sdk.initialized); // true
```

### Custom Initialization

If your application uses custom routing or stores query parameters elsewhere, pass the `search` string explicitly:

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();

// Pass query string manually
await sdk.init({
  search: '?extToken=...&courseId=123&materialId=456',
});
```

### Configuration Options

You can customize API endpoints when creating the SDK instance:

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK({
  // Custom API base URL (default: 'https://api-external-contents.elice.io')
  baseUrl: 'https://custom-api.example.com',
  // Custom ESP proxy URL (default: 'https://api-esp-proxy.elice.io')
  espProxyBaseUrl: 'https://custom-proxy.example.com',
});

await sdk.init();
```

---

## Account & Metadata

After initialization, you can access user account information and content metadata.

### Account Information

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const account = sdk.account;
// {
//   uid: 12345,
//   fullname: 'John Doe',
//   accountId: '...',        // deprecated, use uid instead
//   __isTutorAccount: false  // true if accessing via tutor mode
// }
```

### Content Metadata

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const metadata = sdk.metadata;
// { courseId: 123, materialId: 456 }
```

### Locale

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const locale = sdk.locale;
// 'ko' | 'en' | 'ja' | 'th' (default: 'ko')
```

---

## Score Reporting

Report the user's completion score (0–100) to the Elice platform.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Report completion with full score
await sdk.sendScore({ score: 100 });

// Report partial completion
await sdk.sendScore({ score: 75 });
```

**Constraints:**
- Score must be a number between 0 and 100 (inclusive).
- Throws `EliceCDKError` if SDK is not initialized.

---

## Key-Value Store

Store and retrieve user-specific data. Keys are automatically scoped to the current material or course.

### Material-Scoped Storage

Data is scoped to the current user + material combination.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Save data
await sdk.kvstore.post({
  key: 'userProgress',
  value: { step: 3, completed: false },
});

// Retrieve data
const progress = await sdk.kvstore.get({ key: 'userProgress' });
// { step: 3, completed: false }

// Access nested values using dot notation
const step = await sdk.kvstore.get({ key: 'userProgress.step' });
// 3

// Delete data
await sdk.kvstore.delete('userProgress');
```

### Course-Scoped Storage (Global)

Data is scoped to the current user + course combination, shared across all materials in the course.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Save course-wide data
await sdk.kvstore.postGlobal({
  key: 'courseSettings',
  value: { theme: 'dark', fontSize: 16 },
});

// Retrieve course-wide data
const settings = await sdk.kvstore.getGlobal({ key: 'courseSettings' });

// Delete course-wide data
await sdk.kvstore.deleteGlobal('courseSettings');
```

**Key Naming Rules:**
- Use camelCase format.
- Only alphanumeric characters allowed (`[a-zA-Z0-9]+`).

**Supported Value Types:**
- Primitives: `string`, `number`, `boolean`
- Objects and arrays (keys must be camelCase)

---

## Material Key-Value Store

Store data at the material level (not user-scoped). Useful for shared content state.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Save material-level data
await sdk.materialKvstore.post({
  key: 'sharedConfig',
  value: { maxAttempts: 3 },
});

// Retrieve material-level data
const config = await sdk.materialKvstore.get({ key: 'sharedConfig' });

// Delete material-level data
await sdk.materialKvstore.delete('sharedConfig');
```

---

## File Store

Upload and manage user files. Files are stored in Azure Blob Storage and tracked via KV store.

### Upload a File

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Get file from input element
const fileInput = document.querySelector<HTMLInputElement>('#file-input');
const file = fileInput?.files?.[0];

if (file) {
  await sdk.filestore.post('myDocument', file);
}
```

### Retrieve a File

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const fileData = await sdk.filestore.get('myDocument');
if (fileData) {
  console.log(fileData);
  // {
  //   name: 'document.pdf',
  //   size: 102400,
  //   mime: 'application/pdf',
  //   url: 'https://...'  // Download URL
  // }
}
```

### Delete a File

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

await sdk.filestore.delete('myDocument');
```

**Constraints:**
- File must be a `File` instance.
- Maximum file size: **50 MB**.

---

## AI Chat

Interact with Elice AI for chat-based features.

### Send a Prompt

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Send a prompt (creates a new session automatically)
const { sessionId, responseMessageContent } = await sdk.ai.chat.prompt(
  'What is the capital of South Korea?'
);

console.log(responseMessageContent); // 'Seoul is the capital...'
console.log(sessionId); // UUID of the chat session
```

### Use System Instructions

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const response = await sdk.ai.chat.prompt('Explain variables in programming', {
  systemInstruction: 'Explain concepts as if teaching a 10-year-old.',
});
```

### Load an Existing Session

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Load a previously saved session
const { sessionId, messages } = await sdk.ai.chat.load(
  '550e8400-e29b-41d4-a716-446655440000'
);

// Continue the conversation
await sdk.ai.chat.prompt('Tell me more about that.');
```

### Subscribe to Chat Events

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const { unsubscribe } = sdk.ai.chat.subscribe((event) => {
  switch (event.type) {
    case 'comment':
      console.log('New message:', event.payload);
      break;
    case 'load':
      console.log('Session loaded:', event.payload.sessionId);
      break;
    case 'reset':
      console.log('Session reset');
      break;
    case 'clear':
      console.log('Session cleared:', event.payload.sessionId);
      break;
  }
});

// Unsubscribe when done
unsubscribe();
```

### Reset & Clear Sessions

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Reset local state (session ID and messages in memory)
sdk.ai.chat.reset();

// Clear session from server storage
await sdk.ai.chat.clear('550e8400-e29b-41d4-a716-446655440000');
```

### Access Current Session Data

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Current session ID (null if no active session)
const sessionId = sdk.ai.chat.sessionId;

// Current messages array
const messages = sdk.ai.chat.messages;
// [
//   { role: 'system', content: '...', ts: 1234567890 },
//   { role: 'user', content: 'Hello', ts: 1234567891 },
//   { role: 'assistant', content: 'Hi there!', ts: 1234567892 },
// ]
```

**Constraints:**
- Maximum 100 messages per session.

---

## Tutoring

Access tutoring-specific features when viewing content as a tutor.

### Check Tutoring Mode

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

if (sdk.tutoring.isEnabled) {
  console.log('Tutor mode is active');
}
```

### Get Student List

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const students = await sdk.tutoring.getAccountList({
  offset: 0,
  count: 10,
});
// [{ uid: 123, fullname: 'Student A' }, ...]
```

### Access Student's KV Store

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const studentProgress = await sdk.tutoring.getAccountKvstore({
  uid: 12345,
  key: 'userProgress',
});
```

### Access Student's File Store

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const studentFile = await sdk.tutoring.getAccountFilestore({
  uid: 12345,
  key: 'submission',
});

if (studentFile) {
  console.log('File URL:', studentFile.url);
}
```

### Batch Retrieve KV Store Values

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

const results = await sdk.tutoring.getAccountKvstoreList({
  key: 'userProgress',
  filterUids: [123, 456, 789], // Max 10 UIDs
});
// [{ uid: 123, value: {...} }, { uid: 456, value: {...} }, ...]
```

---

## Navigation

Navigate between lecture pages within the Elice platform. Requires the content to be embedded in an iframe.

### Navigate to Previous/Next Page

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Go to previous page
sdk.navigation.previous();

// Go to next page
sdk.navigation.next();
```

### Check Navigation Availability

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

try {
  const hasPrevious = await sdk.navigation.getHasPrevious();
  const hasNext = await sdk.navigation.getHasNext();

  console.log('Can go back:', hasPrevious);
  console.log('Can go forward:', hasNext);
} catch (error) {
  // Timeout after 1 second if not in iframe
  console.log('Navigation state unavailable');
}
```

**Note:** Navigation methods communicate with the parent frame via `postMessage`. They will timeout (1 second) if the content is not embedded in the Elice platform.

---

## Translation

Enable automatic translation of content using Google Translate.

```ts
import { EliceCDK } from '@eliceio/cdk';

const sdk = new EliceCDK();
await sdk.init();

// Display the translation language selector
await sdk.translation.displayAutoLanguageOptions();
```

This adds a language selector to the page that allows users to translate content to English, Japanese, or Thai. Korean is excluded since most content is originally in Korean.

**Note:** This feature only works in browser environments.
