- carinha.html assina /eventos (expressao, app_config, conversa, reacao, recarregar) e volta ao polling de /fila sozinho se o SSE cair; foto dos ajustes a cada (re)conexao; token (?t=) vai em todo fetch ao servidor - APK 0.8-token: token nas preferencias e no menu de toque longo, enviado na pagina, no HTTP do Java e ao llama-server (--api-key); trocar servidor por Intent exige o token salvo; camera/microfone so para o servidor configurado ou a copia local - cerebro/conversa mandam a chave ao llama-server do S10 - servidor: evento SSE 'recarregar'; remove /camera/frame e /app/connect; enderecos antigos redirecionam para a secao do painel novo - remove dashboard, painel, conversa, emojis, treinamento, braco, servos, api, scripts e CSS antigos, audio-robot.js e servir-local.bat - logica pura do painel em web/src/lib/nucleo.ts com testes node --test (substitui test_movimentos.js e test_rastreamento.js) - testes de navegador portados para o painel novo (rtc, rosto, rastreamento) - README, docs/PAINEL.md e docs/SEGURANCA.md reescritos; versao 0.8.0; carinha-pc.service le /etc/carinha.env Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
3.9 KiB
Java
62 lines
3.9 KiB
Java
package br.carinha.rosto;
|
|
|
|
import android.content.Context;
|
|
import android.util.Log;
|
|
import java.io.*;
|
|
import java.net.*;
|
|
import java.security.MessageDigest;
|
|
|
|
/** Executor nativo no telefone. Preparação fora da thread da interface. */
|
|
public final class CerebroLocal {
|
|
private volatile boolean fechado;
|
|
private Process processo;
|
|
/** Com token, o llama-server (aberto na rede para a placa) exige a mesma chave. */
|
|
public volatile String chave = "";
|
|
public void iniciar(final Context contexto, final String servidor) {
|
|
new Thread(() -> {
|
|
try {
|
|
File libs = new File(contexto.getApplicationInfo().nativeLibraryDir);
|
|
File executavel = new File(libs, "libcarinha_ai.so");
|
|
if (!executavel.exists()) { Log.w("CarinhaIA", "Runtime ARM64 indisponivel"); return; }
|
|
File modelo = new File(contexto.getFilesDir(), "qwen3-q4.gguf");
|
|
if (!modelo.exists()) {
|
|
Log.i("CarinhaIA", "Baixando modelo do servidor local");
|
|
HttpURLConnection con = (HttpURLConnection)new URL(servidor.replaceAll("/+$", "") + "/ia/modelo").openConnection();
|
|
con.setConnectTimeout(10000); con.setReadTimeout(60000);
|
|
File temporario = new File(contexto.getFilesDir(), "qwen3-q4.partial");
|
|
MessageDigest hash = MessageDigest.getInstance("SHA-256");
|
|
try (InputStream input = con.getInputStream(); FileOutputStream output = new FileOutputStream(temporario)) {
|
|
byte[] buffer = new byte[65536]; int n;
|
|
while ((n = input.read(buffer)) != -1) {
|
|
if (fechado) return;
|
|
output.write(buffer, 0, n); hash.update(buffer, 0, n);
|
|
}
|
|
} finally { con.disconnect(); }
|
|
StringBuilder digest = new StringBuilder();
|
|
for (byte b : hash.digest()) digest.append(String.format("%02x", b & 255));
|
|
if (!digest.toString().equals("ac2d97712095a558e31573f62f466a3f9d93990898b0ec79d7c974c1780d524a")) throw new IOException("Checksum do modelo invalido");
|
|
if (!temporario.renameTo(modelo)) throw new IOException("Falha ao guardar modelo");
|
|
}
|
|
if (fechado) return;
|
|
// Um executor já ativo (teste ADB) pode ser reutilizado durante a transição.
|
|
try {
|
|
HttpURLConnection health = (HttpURLConnection)new URL("http://127.0.0.1:8101/health").openConnection();
|
|
health.setConnectTimeout(500); health.setReadTimeout(500);
|
|
try { if (health.getResponseCode() == 200) { Log.i("CarinhaIA", "Executor existente pronto"); return; } }
|
|
finally { health.disconnect(); }
|
|
} catch (IOException ignored) { }
|
|
java.util.List<String> comando = new java.util.ArrayList<>(java.util.Arrays.asList(executavel.getAbsolutePath(), "-m", modelo.getAbsolutePath(), "--host", "0.0.0.0", "--port", "8101", "-c", "1024", "-t", "2", "-np", "1", "--jinja"));
|
|
if (chave != null && !chave.isEmpty()) { comando.add("--api-key"); comando.add(chave); }
|
|
ProcessBuilder builder = new ProcessBuilder(comando);
|
|
builder.environment().put("LD_LIBRARY_PATH", libs.getAbsolutePath());
|
|
builder.environment().put("GGML_BACKEND_DL_PATH", libs.getAbsolutePath());
|
|
builder.redirectErrorStream(true);
|
|
builder.redirectOutput(new File(contexto.getFilesDir(), "ia-runtime.log"));
|
|
synchronized (this) { if (!fechado) processo = builder.start(); }
|
|
Log.i("CarinhaIA", "Executor nativo iniciado");
|
|
} catch (Exception e) { Log.e("CarinhaIA", "IA local: " + e); }
|
|
}, "CarinhaIA").start();
|
|
}
|
|
public synchronized void fechar() { fechado = true; if (processo != null) processo.destroy(); }
|
|
}
|