uncloseai.com/public/languages/dart/bin/uncloseai.dart

249 lines
6.5 KiB
Dart

import 'dart:io';
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
// UncloseAI Dart Library
// OpenAI-compatible API client with streaming support
// Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
class ModelInfo {
final String id;
final String endpoint;
final int maxTokens;
ModelInfo(this.id, this.endpoint, this.maxTokens);
}
// UncloseAI Client class
class UncloseAI {
final List<ModelInfo> models = [];
final List<String> ttsEndpoints = [];
final int timeout;
UncloseAI({this.timeout = 30}) {
_discoverModels();
}
// Discover models from environment variables
void _discoverModels() {
print('Initializing UncloseAI client...');
// Discover chat/code models
for (int i = 1; i <= 9999; i++) {
final endpoint = Platform.environment['MODEL_ENDPOINT_$i'];
if (endpoint == null) break;
print('Endpoint $i: $endpoint');
_discoverModelsFromEndpoint(endpoint);
}
// Discover TTS endpoints
for (int i = 1; i <= 9999; i++) {
final endpoint = Platform.environment['TTS_ENDPOINT_$i'];
if (endpoint == null) break;
ttsEndpoints.add(endpoint);
}
print('Discovered ${models.length} models, ${ttsEndpoints.length} TTS endpoints\n');
}
// Discover models from a specific endpoint (synchronous for simplicity)
void _discoverModelsFromEndpoint(String endpoint) {
// Note: This is simplified - in real usage you'd make this async
try {
final response = http.get(Uri.parse('$endpoint/models'))
.timeout(const Duration(seconds: 10))
.then((response) {
final data = jsonDecode(response.body);
if (data['data'] != null) {
for (var model in data['data']) {
final modelId = model['id'] as String;
// Skip modelperm-* entries
if (modelId.startsWith('modelperm-')) continue;
final maxTokens = model['max_model_len'] as int? ?? 8192;
models.add(ModelInfo(modelId, endpoint, maxTokens));
}
}
});
} catch (e) {
// Silently skip failed endpoints
}
}
// Non-streaming chat completion
Future<String> chat(
List<Map<String, String>> messages, {
int modelIdx = 0,
int maxTokens = 100,
double temperature = 0.7,
}) async {
if (modelIdx >= models.length) {
throw Exception('Invalid model index');
}
final model = models[modelIdx];
final url = '${model.endpoint}/chat/completions';
final request = {
'model': model.id,
'messages': messages,
'stream': false,
'max_tokens': maxTokens,
'temperature': temperature,
};
final response = await http.post(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(request),
).timeout(Duration(seconds: timeout));
final data = jsonDecode(response.body);
return data['choices'][0]['message']['content'] as String;
}
// Streaming chat completion - returns a Stream of content chunks
Stream<String> chatStream(
List<Map<String, String>> messages, {
int modelIdx = 0,
int maxTokens = 500,
double temperature = 0.7,
}) async* {
if (modelIdx >= models.length) {
throw Exception('Invalid model index');
}
final model = models[modelIdx];
final url = '${model.endpoint}/chat/completions';
final request = {
'model': model.id,
'messages': messages,
'stream': true,
'max_tokens': maxTokens,
'temperature': temperature,
};
final httpRequest = http.Request('POST', Uri.parse(url));
httpRequest.headers['Content-Type'] = 'application/json';
httpRequest.body = jsonEncode(request);
final streamedResponse = await httpRequest.send()
.timeout(Duration(seconds: timeout));
await for (var chunk in streamedResponse.stream.transform(utf8.decoder).transform(const LineSplitter())) {
if (!chunk.startsWith('data: ')) continue;
final data = chunk.substring(6).trim();
if (data == '[DONE]') break;
try {
final json = jsonDecode(data);
final content = json['choices']?[0]?['delta']?['content'];
if (content != null) {
yield content as String;
}
} catch (e) {
// Skip malformed JSON
}
}
}
// Text-to-speech generation
Future<bool> tts(
String text, {
String voice = 'alloy',
String outputFile = '/tmp/speech.mp3',
}) async {
if (ttsEndpoints.isEmpty) return false;
final endpoint = ttsEndpoints[0];
final url = '$endpoint/audio/speech';
final request = {
'model': 'tts-1',
'voice': voice,
'input': text,
};
try {
final response = await http.post(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(request),
).timeout(Duration(seconds: timeout));
final file = File(outputFile);
await file.writeAsBytes(response.bodyBytes);
return true;
} catch (e) {
return false;
}
}
}
// Demo program showing library usage
void main() async {
print('=== UncloseAI Dart Client (with Streaming) ===\n');
// Initialize client
final client = UncloseAI();
// Wait a moment for async discovery to complete
await Future.delayed(const Duration(seconds: 2));
if (client.models.isEmpty) {
print('ERROR: No models discovered');
exit(1);
}
// Non-streaming chat example
print('=== Non-Streaming Chat ===');
print('Model: ${client.models[0].id}');
try {
final messages = [
{'role': 'user', 'content': 'Explain quantum computing in one sentence'}
];
final response = await client.chat(messages);
print('Response: $response\n');
} catch (e) {
print('Error: $e\n');
}
// Streaming chat example
final modelIdx = client.models.length >= 2 ? 1 : 0;
print('=== Streaming Chat ===');
print('Model: ${client.models[modelIdx].id}');
stdout.write('Response: ');
try {
final messages = [
{'role': 'user', 'content': 'Write a hello world program in Dart'}
];
await for (var content in client.chatStream(messages, modelIdx: modelIdx)) {
stdout.write(content);
}
print('\n');
} catch (e) {
print('\nError: $e\n');
}
// TTS example
if (client.ttsEndpoints.isNotEmpty) {
print('=== TTS Speech Generation ===');
print('Model: tts-1');
if (await client.tts('Hello from UncloseAI Dart client!',
outputFile: '/tmp/speech.mp3')) {
print('Audio saved to /tmp/speech.mp3');
} else {
print('TTS failed');
}
}
print('\n=== Examples Complete ===');
}