Todo dia, a partir das 9h, o Carinha cria 3 atividades novas (inspiracao.py): - sinais de fontes abertas: tempo da cidade (Open-Meteo), datas comemorativas da semana (lista fixa + feriados da BrasilAPI), "neste dia" da Wikipédia e assuntos em alta no Google Trends Brasil; - o Gemini escolhe ideias leves (descarta tragédia, política e afins) e cada uma vira atividade pelo estúdio (Criacao.gerar), com motivo e uma curiosidade; - até 3 tentativas por dia, 30 min entre elas; botão "Criar mais agora"; - as atividades criadas entram na rotina do modo vida (as de hoje com 40% de chance) e, com alguém perto, ele conta a curiosidade: legenda e voz de robô (ordem "aprendi", até 2x por dia cada); - painel: cartão "Ideias do dia" no estúdio (Mostrar, Contar, o que ele viu na internet, ajustes: quantas, horário, cidade, fontes) e tópico no guia. Correções achadas no caminho: - WebView de fábrica do S10 (Chrome 102) não tem AbortSignal.timeout: o celular nunca mandava o status e a placa o via offline; - arquivos estáticos com Cache-Control no-cache e o app com LOAD_NO_CACHE: o WebView seguia com uma carinha.html velha depois de deploy; - status do celular recusado vai para o journal (1 linha por minuto); - legenda mais estreita para não cobrir a bateria. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
3.2 KiB
TypeScript
104 lines
3.2 KiB
TypeScript
/**
|
|
* Estúdio de criação (/criacao/*): expressões, atividades e figurinhas feitas por IA.
|
|
* O estado chega por SSE ('criacao') e é recarregado ao abrir a tela.
|
|
*/
|
|
import { pedir, postar } from './api';
|
|
import { avisos, tentar } from './avisos.svelte';
|
|
import { ouvir } from './estado.svelte';
|
|
|
|
export type TipoCriacao = 'expressao' | 'atividade' | 'figurinha';
|
|
export type IaCriacao = 'gemini' | 'local' | 's10';
|
|
|
|
export interface ItemCriado {
|
|
id: number;
|
|
horario: number;
|
|
tipo: TipoCriacao;
|
|
nome: string;
|
|
emoji: string;
|
|
receita: any;
|
|
ia: IaCriacao;
|
|
pedido: string;
|
|
origem?: 'pedido' | 'dia';
|
|
motivo?: string;
|
|
aprendi?: string;
|
|
}
|
|
|
|
export interface Provedor {
|
|
nome: string;
|
|
pronto: boolean;
|
|
detalhe: string;
|
|
motivo: string;
|
|
url?: string;
|
|
modelo?: string;
|
|
chave?: boolean;
|
|
}
|
|
|
|
export interface StatusCriacao {
|
|
ocupado: { tipo: TipoCriacao; ia: IaCriacao; desde: number; pedido: string } | null;
|
|
erro: string;
|
|
provedores: Record<IaCriacao, Provedor>;
|
|
criacoes: ItemCriado[];
|
|
bases: Record<'expressao' | 'atividade', { id: string; nome: string; emoji: string }[]>;
|
|
}
|
|
|
|
export const TIPOS: { id: TipoCriacao; nome: string; emoji: string; exemplo: string }[] = [
|
|
{ id: 'expressao', nome: 'Expressão', emoji: '😊', exemplo: 'Ex.: desconfiado, olhando de lado com um olho meio fechado' },
|
|
{ id: 'atividade', nome: 'Atividade', emoji: '🤹', exemplo: 'Ex.: fazendo malabarismo com 3 bolinhas, concentrado' },
|
|
{ id: 'figurinha', nome: 'Figurinha', emoji: '🎨', exemplo: 'Ex.: um sol de óculos escuros sorrindo' },
|
|
];
|
|
|
|
class Criacao {
|
|
status = $state<StatusCriacao | null>(null);
|
|
|
|
async carregar() {
|
|
try {
|
|
this.status = await pedir<StatusCriacao>('/criacao/status');
|
|
} catch (e) {
|
|
avisos.erro(e);
|
|
}
|
|
}
|
|
|
|
item(id: number) {
|
|
return this.status?.criacoes.find((c) => c.id === id) ?? null;
|
|
}
|
|
|
|
async criar(tipo: TipoCriacao, pedido: string, ia: IaCriacao, base: string) {
|
|
const r = await tentar(() => postar<StatusCriacao>('/criacao/criar', { tipo, pedido, ia, base: base || null }));
|
|
if (r) this.status = r;
|
|
return !!r;
|
|
}
|
|
|
|
tocar(id: number) {
|
|
return tentar(() => postar('/criacao/tocar', { id }));
|
|
}
|
|
|
|
async apagar(id: number) {
|
|
await tentar(() => postar('/criacao/apagar', { id }), 'Criação apagada.');
|
|
}
|
|
|
|
async configurar(local: { url?: string; modelo?: string; chave?: string }) {
|
|
const r = await tentar(() => postar<StatusCriacao>('/criacao/config', { local }), 'IA local salva.');
|
|
if (r) this.status = r;
|
|
}
|
|
|
|
async testar(ia: IaCriacao) {
|
|
const r = await tentar(() => postar<{ mensagem: string }>('/criacao/testar', { ia }));
|
|
if (r) avisos.ok(r.mensagem);
|
|
}
|
|
}
|
|
|
|
export const criacao = new Criacao();
|
|
ouvir('criacao', (d: StatusCriacao) => {
|
|
const antes = criacao.status;
|
|
criacao.status = d;
|
|
// terminou: avisa o resultado
|
|
if (antes?.ocupado && !d.ocupado) {
|
|
if (d.erro) avisos.erro(new Error(d.erro));
|
|
else if (d.criacoes[0]) avisos.ok(`Criado: ${d.criacoes[0].emoji} ${d.criacoes[0].nome}`);
|
|
}
|
|
});
|
|
|
|
/** Favoritos podem guardar criações ("criado:12"): tocar no celular. */
|
|
export const ehCriado = (g: string) => g.startsWith('criado:');
|
|
export const idCriado = (g: string) => Number(g.slice(7));
|