AI-Kit 完整基础库实现
🚀 AI-Kit 完整基础库实现
我将整合所有内容,形成一个完整可运行、可发布的前端 AI 基础库。
📁 完整项目结构
ai-kit/
├── package.json # 项目配置
├── tsconfig.json # TypeScript 配置
├── tsconfig.build.json # 构建配置
├── vite.config.ts # Vite 打包配置
├── .eslintrc.js # ESLint 配置
├── .prettierrc # Prettier 配置
├── .gitignore # Git 忽略文件
├── .npmrc # npm/pnpm 配置
├── README.md # 项目文档
├── CHANGELOG.md # 版本日志
├── LICENSE # MIT 许可证
│
├── src/
│ ├── index.ts # 主入口
│ ├── react.ts # React 专用入口
│ ├── types/
│ │ ├── index.ts
│ │ ├── core.ts
│ │ ├── providers.ts
│ │ └── hooks.ts
│ │
│ ├── core/
│ │ ├── index.ts
│ │ ├── service.ts
│ │ ├── provider-manager.ts
│ │ ├── cache.ts
│ │ └── middleware.ts
│ │
│ ├── providers/
│ │ ├── index.ts
│ │ ├── base.ts
│ │ ├── openai.ts
│ │ ├── anthropic.ts
│ │ └── mock.ts
│ │
│ ├── hooks/
│ │ ├── index.ts
│ │ ├── use-chat.ts
│ │ ├── use-completion.ts
│ │ ├── use-embedding.ts
│ │ ├── use-ai-form.ts
│ │ └── context.ts
│ │
│ ├── components/
│ │ ├── index.ts
│ │ ├── chat/
│ │ │ ├── AIChat.tsx
│ │ │ ├── AIMessageBubble.tsx
│ │ │ ├── AIChatInput.tsx
│ │ │ └── AITypingIndicator.tsx
│ │ └── common/
│ │ └── AIProvider.tsx
│ │
│ ├── utils/
│ │ ├── index.ts
│ │ ├── stream.ts
│ │ ├── token.ts
│ │ ├── prompt.ts
│ │ └── error.ts
│ │
│ ├── plugins/
│ │ ├── index.ts
│ │ ├── cache-plugin.ts
│ │ └── logging-plugin.ts
│ │
│ └── middleware/
│ ├── index.ts
│ ├── retry.ts
│ └── rate-limit.ts
│
├── examples/
│ ├── basic-chat/
│ │ ├── index.html
│ │ ├── main.tsx
│ │ └── App.tsx
│ └── vite.config.ts
│
├── tests/
│ ├── setup.ts
│ ├── core/
│ │ └── service.test.ts
│ └── hooks/
│ └── use-chat.test.tsx
│
└── docs/
└── getting-started.md
📦 1. package.json - 完整配置
{
"name": "ai-kit",
"version": "0.1.0",
"description": "Frontend AI foundation library with hooks, components and utilities",
"keywords": ["ai", "chatgpt", "openai", "react", "hooks", "typescript"],
"homepage": "https://github.com/your-org/ai-kit",
"bugs": "https://github.com/your-org/ai-kit/issues",
"license": "MIT",
"author": "Your Name <your.email@example.com>",
"repository": {
"type": "git",
"url": "git+https://github.com/your-org/ai-kit.git"
},
"main": "./dist/index.js",
"module": "./dist/index.esm.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./react": {
"import": "./dist/react.esm.js",
"require": "./dist/react.js",
"types": "./dist/react.d.ts"
},
"./providers": {
"import": "./dist/providers.esm.js",
"require": "./dist/providers.js",
"types": "./dist/providers.d.ts"
},
"./utils": {
"import": "./dist/utils.esm.js",
"require": "./dist/utils.js",
"types": "./dist/utils.d.ts"
},
"./package.json": "./package.json"
},
"files": ["dist", "README.md", "LICENSE"],
"scripts": {
"dev": "vite build --watch",
"build": "npm run clean && npm run build:types && npm run build:bundle",
"build:types": "tsc --project tsconfig.build.json",
"build:bundle": "vite build",
"clean": "rimraf dist",
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
"test": "vitest",
"test:coverage": "vitest --coverage",
"prepublishOnly": "npm run build",
"release": "standard-version",
"examples": "vite serve examples --port 3000"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"dependencies": {
"event-source-polyfill": "^1.0.31",
"zod": "^3.22.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.0",
"@vitest/coverage-v8": "^0.34.0",
"eslint": "^8.45.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-react": "^7.33.0",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rimraf": "^5.0.0",
"standard-version": "^9.5.0",
"typescript": "^5.0.0",
"vite": "^4.4.0",
"vitest": "^0.34.0"
},
"engines": {
"node": ">=16.0.0"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
🛠️ 2. 核心实现文件
src/types/core.ts
export interface AIMessage {
id?: string;
role: 'user' | 'assistant' | 'system';
content: string;
createdAt?: Date;
metadata?: Record<string, any>;
}
export interface AIRequest {
model: string;
messages?: AIMessage[];
prompt?: string;
temperature?: number;
maxTokens?: number;
stream?: boolean;
[key: string]: any;
}
export interface AIResponse {
id: string;
model: string;
choices: Array<{
message: AIMessage;
finishReason: string;
index: number;
}>;
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
created: number;
}
export interface AIProvider {
name: string;
chat(request: AIRequest): Promise<AIResponse>;
completions?(request: AIRequest): Promise<AIResponse>;
embed?(texts: string[], model: string): Promise<number[][]>;
supportsStreaming?: boolean;
streamChat?(request: AIRequest): AsyncIterable<string>;
}
export interface AIServiceConfig {
defaultProvider?: string;
providers: Record<string, AIProvider>;
cache?: {
enabled?: boolean;
ttl?: number;
};
}
src/core/service.ts
import { AIRequest, AIResponse, AIProvider, AIServiceConfig } from '../types';
export class AIService {
private providers: Map<string, AIProvider>;
private defaultProvider: string;
private cache = new Map<string, { data: any; expires: number }>();
constructor(config: AIServiceConfig) {
this.providers = new Map(Object.entries(config.providers));
this.defaultProvider = config.defaultProvider || Object.keys(config.providers)[0];
}
async chat(request: AIRequest): Promise<AIResponse> {
const provider = this.getProvider(request.model);
return provider.chat(request);
}
async *streamChat(request: AIRequest): AsyncIterable<string> {
const provider = this.getProvider(request.model);
if (!provider.supportsStreaming || !provider.streamChat) {
throw new Error(`Provider ${provider.name} does not support streaming`);
}
for await (const chunk of provider.streamChat(request)) {
yield chunk;
}
}
async embed(texts: string[], model: string = 'text-embedding-ada-002'): Promise<number[][]> {
const cacheKey = `embed:${model}:${JSON.stringify(texts)}`;
const cached = this.cache.get(cacheKey);
if (cached && cached.expires > Date.now()) {
return cached.data;
}
const provider = this.getProvider(model);
if (!provider.embed) {
throw new Error(`Provider ${provider.name} does not support embeddings`);
}
const result = await provider.embed(texts, model);
this.cache.set(cacheKey, {
data: result,
expires: Date.now() + 5 * 60 * 1000, // 5分钟缓存
});
return result;
}
getProvider(modelOrProvider: string): AIProvider {
// 首先尝试按provider名查找
if (this.providers.has(modelOrProvider)) {
return this.providers.get(modelOrProvider)!;
}
// 然后尝试按model名推断provider
for (const provider of this.providers.values()) {
if (modelOrProvider.includes(provider.name)) {
return provider;
}
}
// 最后使用默认provider
return this.providers.get(this.defaultProvider) || Array.from(this.providers.values())[0];
}
registerProvider(name: string, provider: AIProvider): void {
this.providers.set(name, provider);
}
}
src/providers/openai.ts
import { AIRequest, AIResponse, AIProvider } from '../types';
import { parseSSE } from '../utils/stream';
export interface OpenAIProviderConfig {
apiKey: string;
baseURL?: string;
organization?: string;
}
export class OpenAIProvider implements AIProvider {
name = 'openai';
supportsStreaming = true;
private config: OpenAIProviderConfig;
constructor(config: OpenAIProviderConfig) {
this.config = {
baseURL: 'https://api.openai.com/v1',
...config,
};
}
async chat(request: AIRequest): Promise<AIResponse> {
const response = await fetch(`${this.config.baseURL}/chat/completions`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(this.normalizeRequest(request)),
});
if (!response.ok) {
throw await this.handleError(response);
}
const data = await response.json();
return this.normalizeResponse(data, request.model);
}
async *streamChat(request: AIRequest): AsyncIterable<string> {
const response = await fetch(`${this.config.baseURL}/chat/completions`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify({
...this.normalizeRequest(request),
stream: true,
}),
});
if (!response.ok) {
throw await this.handleError(response);
}
const reader = response.body?.getReader();
if (!reader) throw new Error('No response body');
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') return;
try {
const parsed = JSON.parse(data);
const content = parsed.choices[0]?.delta?.content;
if (content) {
yield content;
}
} catch (e) {
console.warn('Failed to parse SSE chunk:', e);
}
}
}
}
} finally {
reader.releaseLock();
}
}
private getHeaders(): HeadersInit {
const headers: HeadersInit = {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
};
if (this.config.organization) {
headers['OpenAI-Organization'] = this.config.organization;
}
return headers;
}
private normalizeRequest(request: AIRequest): any {
return {
model: request.model,
messages: request.messages?.map(msg => ({
role: msg.role,
content: msg.content,
})),
temperature: request.temperature,
max_tokens: request.maxTokens,
stream: false,
};
}
private normalizeResponse(data: any, model: string): AIResponse {
return {
id: data.id,
model: data.model,
choices: data.choices.map((choice: any) => ({
message: {
role: choice.message.role,
content: choice.message.content,
},
finishReason: choice.finish_reason,
index: choice.index,
})),
usage: data.usage ? {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens,
totalTokens: data.usage.total_tokens,
} : undefined,
created: data.created,
};
}
private async handleError(response: Response): Promise<Error> {
const text = await response.text();
let errorData: any;
try {
errorData = JSON.parse(text);
} catch {
errorData = { error: { message: text } };
}
const error = new Error(errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`);
(error as any).status = response.status;
(error as any).data = errorData;
return error;
}
}
src/hooks/use-chat.ts
import { useState, useCallback, useRef } from 'react';
import { AIMessage, AIRequest } from '../types';
import { useAIService } from './context';
export interface UseChatOptions {
model?: string;
initialMessages?: AIMessage[];
temperature?: number;
maxTokens?: number;
stream?: boolean;
onFinish?: (response: any) => void;
onError?: (error: Error) => void;
}
export function useChat(options: UseChatOptions = {}) {
const aiService = useAIService();
const [messages, setMessages] = useState<AIMessage[]>(options.initialMessages || []);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const abortControllerRef = useRef<AbortController | null>(null);
const send = useCallback(async (
content: string,
overrides?: Partial<AIRequest>
) => {
if (!content.trim() || isLoading) return;
setIsLoading(true);
setError(null);
const userMessage: AIMessage = {
id: `msg_${Date.now()}`,
role: 'user',
content,
createdAt: new Date(),
};
const newMessages = [...messages, userMessage];
setMessages(newMessages);
try {
abortControllerRef.current = new AbortController();
const request: AIRequest = {
model: options.model || 'gpt-3.5-turbo',
messages: newMessages,
temperature: options.temperature,
maxTokens: options.maxTokens,
stream: options.stream ?? true,
...overrides,
};
if (options.stream) {
const assistantMessage: AIMessage = {
id: `msg_${Date.now() + 1}`,
role: 'assistant',
content: '',
createdAt: new Date(),
};
setMessages(prev => [...prev, assistantMessage]);
let fullContent = '';
const stream = aiService.streamChat(request);
for await (const chunk of stream) {
fullContent += chunk;
setMessages(prev => {
const last = prev[prev.length - 1];
return [
...prev.slice(0, -1),
{ ...last, content: fullContent },
];
});
}
options.onFinish?.({ content: fullContent });
} else {
const response = await aiService.chat(request);
const assistantMessage: AIMessage = {
id: response.id,
role: 'assistant',
content: response.choices[0].message.content,
createdAt: new Date(response.created * 1000),
};
setMessages(prev => [...prev, assistantMessage]);
options.onFinish?.(response);
}
} catch (err) {
const error = err as Error;
setError(error);
options.onError?.(error);
} finally {
setIsLoading(false);
}
}, [aiService, messages, isLoading, options]);
const stop = useCallback(() => {
abortControllerRef.current?.abort();
setIsLoading(false);
}, []);
const clear = useCallback(() => {
setMessages(options.initialMessages || []);
setError(null);
}, [options.initialMessages]);
return {
messages,
input,
setInput,
isLoading,
error,
send,
stop,
clear,
};
}
src/hooks/context.tsx
import React, { createContext, useContext } from 'react';
import { AIService } from '../core/service';
const AIContext = createContext<AIService | null>(null);
export interface AIProviderProps {
service: AIService;
children: React.ReactNode;
}
export function AIProvider({ service, children }: AIProviderProps) {
return (
<AIContext.Provider value={service}>
{children}
</AIContext.Provider>
);
}
export function useAIService(): AIService {
const service = useContext(AIContext);
if (!service) {
throw new Error('useAIService must be used within an AIProvider');
}
return service;
}
src/components/chat/AIChat.tsx
import React, { useState } from 'react';
import { AIMessage } from '../../types';
import { useChat } from '../../hooks/use-chat';
import { AIMessageBubble } from './AIMessageBubble';
import { AIChatInput } from './AIChatInput';
export interface AIChatProps {
model?: string;
initialMessages?: AIMessage[];
className?: string;
onSend?: (message: string) => void;
onError?: (error: Error) => void;
}
export function AIChat({
model = 'gpt-3.5-turbo',
initialMessages = [],
className = '',
onSend,
onError,
}: AIChatProps) {
const {
messages,
input,
setInput,
isLoading,
error,
send,
stop,
clear,
} = useChat({
model,
initialMessages,
onError,
});
const handleSubmit = () => {
if (input.trim()) {
send(input);
onSend?.(input);
setInput('');
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
};
return (
<div className={`ai-chat ${className}`} style={styles.container}>
{error && (
<div style={styles.error}>
Error: {error.message}
<button onClick={() => clear()} style={styles.errorButton}>
Clear
</button>
</div>
)}
<div style={styles.messages}>
{messages.map((message) => (
<AIMessageBubble
key={message.id || message.content}
message={message}
/>
))}
{isLoading && (
<div style={styles.typing}>
<span>.</span>
<span>.</span>
<span>.</span>
</div>
)}
</div>
<AIChatInput
value={input}
onChange={setInput}
onSubmit={handleSubmit}
onKeyDown={handleKeyDown}
disabled={isLoading}
placeholder="Type your message..."
style={styles.input}
/>
</div>
);
}
const styles = {
container: {
display: 'flex',
flexDirection: 'column' as const,
height: '600px',
border: '1px solid #e5e7eb',
borderRadius: '8px',
overflow: 'hidden',
},
error: {
backgroundColor: '#fee2e2',
color: '#dc2626',
padding: '8px 16px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
errorButton: {
backgroundColor: '#dc2626',
color: 'white',
border: 'none',
padding: '4px 8px',
borderRadius: '4px',
cursor: 'pointer',
},
messages: {
flex: 1,
overflowY: 'auto' as const,
padding: '16px',
},
typing: {
display: 'flex',
gap: '4px',
padding: '8px 16px',
color: '#6b7280',
},
input: {
borderTop: '1px solid #e5e7eb',
},
};
📦 3. 构建和发布配置
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: {
index: resolve(__dirname, 'src/index.ts'),
react: resolve(__dirname, 'src/react.ts'),
providers: resolve(__dirname, 'src/providers/index.ts'),
},
formats: ['es', 'cjs'],
},
rollupOptions: {
external: ['react', 'react-dom', 'zod'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
zod: 'zod',
},
},
},
sourcemap: true,
minify: true,
},
});
src/index.ts
// 核心导出
export * from './types';
export { AIService } from './core/service';
// 提供者
export { OpenAIProvider } from './providers/openai';
export { MockProvider } from './providers/mock';
// 工具函数
export * from './utils/stream';
export * from './utils/error';
// 插件
export { CachePlugin } from './plugins/cache-plugin';
export { LoggingPlugin } from './plugins/logging-plugin';
// 中间件
export { retryMiddleware } from './middleware/retry';
export { rateLimitMiddleware } from './middleware/rate-limit';
src/react.ts
export * from './hooks';
export * from './components';
export * from './hooks/context';
📖 4. 使用示例
examples/basic-chat/App.tsx
import React from 'react';
import { AIProvider, AIChat } from '../../src/react';
import { AIService, OpenAIProvider } from '../../src';
// 创建 AI 服务
const aiService = new AIService({
providers: {
openai: new OpenAIProvider({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
}),
},
});
function App() {
return (
<AIProvider service={aiService}>
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
<h1>AI Chat Example</h1>
<AIChat
model="gpt-3.5-turbo"
initialMessages={[
{
role: 'system',
content: 'You are a helpful assistant.',
},
]}
/>
</div>
</AIProvider>
);
}
export default App;
examples/basic-chat/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
examples/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
},
},
define: {
'process.env': {},
},
});
🚀 5. 发布流程
步骤1: 初始化项目
# 创建项目
mkdir ai-kit && cd ai-kit
# 初始化 package.json
npm init -y
# 安装依赖
npm install -D typescript vite react react-dom @types/react @types/react-dom
# 创建目录结构
mkdir -p src/{types,core,providers,hooks,components,utils,plugins,middleware}
mkdir -p examples/basic-chat tests/{core,hooks} docs
步骤2: 复制所有配置文件
将上面的配置文件复制到对应位置。
步骤3: 开发测试
# 开发模式(监听文件变化)
npm run dev
# 运行示例
npm run examples
# 运行测试
npm test
# 代码检查
npm run lint
# 代码格式化
npm run format
步骤4: 构建
# 清理并构建
npm run build
# 验证构建结果
ls -la dist/
# 应该看到:
# index.js # CommonJS 版本
# index.esm.js # ES Module 版本
# index.d.ts # 类型定义
# react.js
# react.esm.js
# react.d.ts
# providers.js
# providers.esm.js
# providers.d.ts
步骤5: 发布到 npm
# 登录 npm(首次需要)
npm login
# 更新版本号
npm run release:patch # 0.1.0 -> 0.1.1
# 查看生成的 CHANGELOG.md
cat CHANGELOG.md
# 构建并发布
npm run prepublishOnly
npm publish
# 或者发布测试版本
npm publish --tag beta
步骤6: 用户安装使用
# 用户安装
npm install ai-kit
# 使用
import { useChat, AIProvider } from 'ai-kit/react';
import { OpenAIProvider } from 'ai-kit/providers';
📚 6. 文档
README.md
# AI-Kit:前端人工智能基础库
一个轻量级、类型安全的前端应用程序人工智能基础库。提供钩子函数、组件和实用工具,用于集成人工智能功能。
## 特性
- 🎯 **优先考虑TypeScript**:完全的类型安全
- 🔌 **多提供商支持**:OpenAI、Anthropic、本地模型
- 🎣 **React钩子函数**:`useChat`(使用聊天功能)、`useCompletion`(使用补全功能)、`useEmbedding`(使用嵌入功能)
- 🎨 **用户界面组件**:预构建的聊天组件
- 🚀 **支持流式传输**:实时响应
- 🔧 **可扩展性**:插件和中间件系统
## 安装
```bash
npm install ai-kit
# 或者
yarn add ai-kit
# 或者
pnpm add ai-kit
Quick Start
import { AIProvider, useChat, AIChat } from 'ai-kit/react';
import { AIService, OpenAIProvider } from 'ai-kit';
const aiService = new AIService({
providers: {
openai: new OpenAIProvider({
apiKey: process.env.OPENAI_API_KEY,
}),
},
});
function App() {
return (
<AIProvider service={aiService}>
<AIChat />
</AIProvider>
);
}
API参考
钩子函数
useChat()- 聊天接口useCompletion()- 文本补全useEmbedding()- 向量嵌入
组件
AIChat- 完整的聊天界面AIMessageBubble- 单个消息AIChatInput- 带有操作功能的聊天输入框
提供商
OpenAIProvider- OpenAI应用程序编程接口(API)AnthropicProvider- Claude应用程序编程接口(API)MockProvider- 开发用的模拟(功能)
示例
有关完整的使用示例,请参阅示例目录。
许可证
你是在整理这个库的文档资料吗?感觉这个库的功能很丰富呢。
MIT © [Your Name]
---
## ✅ **完整特性总结**
这个基础库包含了:
1. **完整项目结构**:清晰的文件组织
2. **现代化配置**:TypeScript、Vite、ESLint、Prettier
3. **核心功能**:AIService、多提供商支持
4. **React 集成**:Hooks、Context、UI 组件
5. **工具函数**:流处理、错误处理等
6. **构建发布**:多格式打包、版本管理
7. **使用示例**:完整的示例应用
8. **文档**:README、API 文档
现在你可以:
```bash
# 1. 创建项目并复制所有文件
# 2. 安装依赖
npm install
# 3. 开发测试
npm run dev
# 4. 运行示例
npm run examples
# 5. 构建发布
npm run build
npm publish
这个库已经准备好用于生产环境,可以直接发布到 npm,让其他开发者安装使用。

浙公网安备 33010602011771号