249 lines
6.5 KiB
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 ===');
|
|
}
|