Published on

Memahami Ollama dan Deepseek: Integrasi dengan React

Memahami Ollama dan Deepseek: Integrasi dengan React

Ollama

Gambaran Umum

Ollama adalah tool open-source yang memungkinkan pengguna menjalankan large language models (LLMs) secara lokal. Panduan ini fokus pada integrasi Ollama dengan aplikasi React.

Integrasi Dasar React

// OllamaClient.tsx
import React, { useState } from 'react'
import axios from 'axios'

interface OllamaResponse {
  response: string
  context: number[]
}

const OllamaClient: React.FC = () => {
  const [prompt, setPrompt] = useState('')
  const [response, setResponse] = useState('')
  const [loading, setLoading] = useState(false)

  const queryOllama = async () => {
    setLoading(true)
    try {
      const result = await axios.post('http://localhost:11434/api/generate', {
        model: 'llama2',
        prompt: prompt,
      })
      setResponse(result.data.response)
    } catch (error) {
      console.error('Error:', error)
    } finally {
      setLoading(false)
    }
  }

  return (
    <div className="p-4">
      <textarea
        className="w-full rounded border p-2"
        value={prompt}
        onChange={(e) => setPrompt(e.target.value)}
        placeholder="Masukkan prompt Anda..."
      />
      <button
        className="mt-2 rounded bg-blue-500 px-4 py-2 text-white"
        onClick={queryOllama}
        disabled={loading}
      >
        {loading ? 'Memproses...' : 'Generate'}
      </button>
      {response && (
        <div className="mt-4 rounded border p-4">
          <pre>{response}</pre>
        </div>
      )}
    </div>
  )
}

Custom Hook untuk Ollama

// useOllama.ts
import { useState, useCallback } from 'react'
import axios from 'axios'

interface OllamaConfig {
  model?: string
  baseUrl?: string
}

export const useOllama = (config: OllamaConfig = {}) => {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const generate = useCallback(
    async (prompt: string) => {
      setLoading(true)
      setError(null)

      try {
        const response = await axios.post(
          `${config.baseUrl || 'http://localhost:11434'}/api/generate`,
          {
            model: config.model || 'llama2',
            prompt,
          }
        )
        return response.data.response
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Terjadi kesalahan')
        return null
      } finally {
        setLoading(false)
      }
    },
    [config]
  )

  return { generate, loading, error }
}

// Contoh Penggunaan
const AIChat: React.FC = () => {
  const { generate, loading, error } = useOllama({
    model: 'codellama',
    baseUrl: 'http://localhost:11434',
  })

  const handleGenerate = async () => {
    const response = await generate('Tulis komponen React')
    console.log(response)
  }
}

Komponen Manajemen Model

// OllamaModels.tsx
import React, { useEffect, useState } from 'react'
import axios from 'axios'

interface Model {
  name: string
  size: number
  modified: string
}

const OllamaModels: React.FC = () => {
  const [models, setModels] = useState<Model[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const fetchModels = async () => {
      try {
        const response = await axios.get('http://localhost:11434/api/tags')
        setModels(response.data.models)
      } catch (error) {
        console.error('Error mengambil model:', error)
      } finally {
        setLoading(false)
      }
    }

    fetchModels()
  }, [])

  return (
    <div className="p-4">
      <h2 className="mb-4 text-xl font-bold">Model yang Terinstal</h2>
      {loading ? (
        <div>Memuat model...</div>
      ) : (
        <div className="grid gap-4">
          {models.map((model) => (
            <div key={model.name} className="rounded border p-4">
              <h3 className="font-bold">{model.name}</h3>
              <p>Ukuran: {(model.size / 1024 / 1024 / 1024).toFixed(2)} GB</p>
              <p>Dimodifikasi: {new Date(model.modified).toLocaleDateString()}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  )
}

Deepseek

Integrasi React

// DeepseekClient.tsx
import React, { useState } from 'react'
import axios from 'axios'

interface DeepseekProps {
  apiKey: string
  model?: string
}

const DeepseekClient: React.FC<DeepseekProps> = ({ apiKey, model = 'deepseek-coder' }) => {
  const [prompt, setPrompt] = useState('')
  const [response, setResponse] = useState('')
  const [loading, setLoading] = useState(false)

  const generateResponse = async () => {
    setLoading(true)
    try {
      const result = await axios.post(
        'https://api.deepseek.com/v1/completions',
        {
          model,
          prompt,
          max_tokens: 500,
        },
        {
          headers: {
            Authorization: `Bearer ${apiKey}`,
            'Content-Type': 'application/json',
          },
        }
      )
      setResponse(result.data.choices[0].text)
    } catch (error) {
      console.error('Error:', error)
    } finally {
      setLoading(false)
    }
  }

  return (
    <div className="p-4">
      <textarea
        className="w-full rounded border p-2"
        value={prompt}
        onChange={(e) => setPrompt(e.target.value)}
        placeholder="Masukkan prompt Anda..."
      />
      <button
        className="mt-2 rounded bg-blue-500 px-4 py-2 text-white"
        onClick={generateResponse}
        disabled={loading}
      >
        {loading ? 'Menghasilkan...' : 'Generate'}
      </button>
      {response && (
        <div className="mt-4 rounded border bg-gray-50 p-4">
          <pre className="whitespace-pre-wrap">{response}</pre>
        </div>
      )}
    </div>
  )
}

Custom Hook untuk Deepseek

// useDeepseek.ts
import { useState, useCallback } from 'react'
import axios from 'axios'

interface DeepseekConfig {
  apiKey: string
  model?: string
  maxTokens?: number
}

export const useDeepseek = (config: DeepseekConfig) => {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const generate = useCallback(
    async (prompt: string) => {
      setLoading(true)
      setError(null)

      try {
        const response = await axios.post(
          'https://api.deepseek.com/v1/completions',
          {
            model: config.model || 'deepseek-coder',
            prompt,
            max_tokens: config.maxTokens || 500,
          },
          {
            headers: {
              Authorization: `Bearer ${config.apiKey}`,
              'Content-Type': 'application/json',
            },
          }
        )
        return response.data.choices[0].text
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Terjadi kesalahan')
        return null
      } finally {
        setLoading(false)
      }
    },
    [config]
  )

  return { generate, loading, error }
}

Komponen Code Generator

// CodeGenerator.tsx
import React, { useState } from 'react'
import { useDeepseek } from './useDeepseek'
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs'

interface CodeGeneratorProps {
  apiKey: string
}

const CodeGenerator: React.FC<CodeGeneratorProps> = ({ apiKey }) => {
  const [prompt, setPrompt] = useState('')
  const [code, setCode] = useState('')

  const { generate, loading, error } = useDeepseek({
    apiKey,
    model: 'deepseek-coder',
    maxTokens: 1000,
  })

  const handleGenerate = async () => {
    const response = await generate(prompt)
    if (response) {
      setCode(response)
    }
  }

  return (
    <div className="p-4">
      <div className="mb-4">
        <label className="mb-2 block text-sm font-medium">
          Deskripsikan kode yang ingin Anda hasilkan:
        </label>
        <textarea
          className="w-full rounded border p-2"
          value={prompt}
          onChange={(e) => setPrompt(e.target.value)}
          rows={4}
        />
      </div>
      <button
        className="rounded bg-blue-500 px-4 py-2 text-white"
        onClick={handleGenerate}
        disabled={loading}
      >
        {loading ? 'Menghasilkan...' : 'Generate Kode'}
      </button>
      {error && (
        <div className="mt-4 rounded border border-red-500 bg-red-50 p-4 text-red-700">{error}</div>
      )}
      {code && (
        <div className="mt-4">
          <SyntaxHighlighter language="typescript" style={docco}>
            {code}
          </SyntaxHighlighter>
        </div>
      )}
    </div>
  )
}

Perbandingan dan Best Practice

Context Provider Pattern

// AIContext.tsx
import React, { createContext, useContext, ReactNode } from 'react'
import { useOllama } from './useOllama'
import { useDeepseek } from './useDeepseek'

interface AIContextType {
  ollama: ReturnType<typeof useOllama>
  deepseek: ReturnType<typeof useDeepseek>
}

const AIContext = createContext<AIContextType | null>(null)

export const AIProvider: React.FC<{
  children: ReactNode
  deepseekApiKey: string
}> = ({ children, deepseekApiKey }) => {
  const ollama = useOllama()
  const deepseek = useDeepseek({ apiKey: deepseekApiKey })

  return <AIContext.Provider value={{ ollama, deepseek }}>{children}</AIContext.Provider>
}

export const useAI = () => {
  const context = useContext(AIContext)
  if (!context) {
    throw new Error('useAI harus digunakan dalam AIProvider')
  }
  return context
}

Sumber Daya

Dokumentasi Resmi

Sumber Integrasi React

Tools Pengembangan

Best Practice Keamanan

  • Simpan API key dalam environment variable
  • Implementasikan rate limiting
  • Sanitasi input dan output
  • Gunakan HTTPS untuk panggilan API
  • Implementasikan error handling yang tepat