diff --git a/android/app/assets/carinha.html b/android/app/assets/carinha.html index 74e69ac..b54a948 100644 --- a/android/app/assets/carinha.html +++ b/android/app/assets/carinha.html @@ -80,6 +80,11 @@ + + + {#snippet acoes()} + + {/snippet} + + {#if !st || !cfg} +

Carregando…

+ {:else} + {#if st.ocupado} + Procurando ideias na internet e criando as atividades… + {:else if st.erro} +

{st.erro}{st.tentativas ? ` (tentativa ${st.tentativas} de 3; tenta de novo em 30 min)` : ''}

+ {/if} + + {#if st.ideias.length} + + {:else} +

{vazio}

+ {/if} + + {#if sinais.length} +
+ O que ele viu hoje na internet + +
+ {/if} + + + {#if ajustesAbertos} +
+ inspiracao.configurar({ ligado: v })} /> + inspiracao.configurar({ aprendi: v })} + /> +
+ + +
+
+ Cidade do tempo: {cfg.cidade.nome} +
+ + +
+
+
+ De onde tirar inspiração + {#each Object.entries(st.fontes) as [f, nome] (f)} + fonte(f as Fonte, v)} /> + {/each} +
+
+ {/if} + {/if} +
+ + diff --git a/web/src/lib/criacao.svelte.ts b/web/src/lib/criacao.svelte.ts index 05f4cf4..6480392 100644 --- a/web/src/lib/criacao.svelte.ts +++ b/web/src/lib/criacao.svelte.ts @@ -18,6 +18,9 @@ export interface ItemCriado { receita: any; ia: IaCriacao; pedido: string; + origem?: 'pedido' | 'dia'; + motivo?: string; + aprendi?: string; } export interface Provedor { diff --git a/web/src/lib/estado.svelte.ts b/web/src/lib/estado.svelte.ts index 6b94b76..1f7a328 100644 --- a/web/src/lib/estado.svelte.ts +++ b/web/src/lib/estado.svelte.ts @@ -117,6 +117,8 @@ const tratadores: Record void> = { expressao: (d: { ordem: string }) => { // 'humor x' é o modo vida avisando o celular; aparece no cartão Vida, não no feed if (d.ordem === 'calma' || /^(humor|ritmo|zanga|sono) /.test(d.ordem)) return; + // contar o que aprendeu já aparece no diário do cartão Vida + if (d.ordem.startsWith('aprendi ')) return; const criada = /^(expressao|atividade)_criada (.*)$/.exec(d.ordem); if (criada) { let nome = ''; @@ -183,7 +185,7 @@ function abrirEventos() { if (estado.conexao !== 'token') estado.conexao = 'caiu'; }; for (const tipo of ['expressao', 'telemetria', 'app_config', 'aparencia', 'motores', 'estop', 'conversa', 'ia', - 'reacao', 'treinamento', 'historico', 'pir', 'vida', 'criacao']) { + 'reacao', 'treinamento', 'historico', 'pir', 'vida', 'criacao', 'inspiracao']) { fonte.addEventListener(tipo, (e) => { const dados = JSON.parse((e as MessageEvent).data); tratadores[tipo]?.(dados); diff --git a/web/src/lib/guia.ts b/web/src/lib/guia.ts index c787c79..a6e77be 100644 --- a/web/src/lib/guia.ts +++ b/web/src/lib/guia.ts @@ -196,6 +196,21 @@ export const TOPICOS: Record = { { emoji: '⭐', nome: 'Favoritos', texto: 'A estrela põe expressões e atividades criadas na grade de favoritos do Ao vivo (e nas teclas 1 a 9).' }, ], }, + ideias: { + titulo: 'Ideias do dia', + emoji: '💡', + onde: 'Criar com IA › Ideias do dia', + resumo: + 'Todo dia, a partir do horário escolhido, ele procura inspiração na internet e o Gemini cria atividades novas (3 por padrão). A IA descarta tragédias, política e afins: só entram temas leves. As novidades entram na rotina do modo vida, as de hoje com mais chance, e ficam na grade do estúdio para tocar, favoritar ou apagar.', + itens: [ + { emoji: '🌦️', nome: 'Tempo da cidade', texto: 'Chuva, sol, frio e calor de hoje na cidade escolhida (Open-Meteo). Ex.: dia de chuva vira "lendo com chá".' }, + { emoji: '📅', nome: 'Datas comemorativas', texto: 'Feriados (BrasilAPI) e datas como Dia das Crianças, Dia da Pizza, Halloween e Natal, de hoje e dos próximos dias.' }, + { emoji: '📜', nome: 'Neste dia na história', texto: 'Fatos que aconteceram neste dia, da Wikipédia em português.' }, + { emoji: '📈', nome: 'Assuntos em alta', texto: 'O que o pessoal está procurando hoje no Google Brasil. Política e notícias tristes são ignoradas.' }, + { emoji: '🤓', nome: 'Olha o que eu aprendi hoje', texto: 'Cada ideia vem com uma curiosidade. Quando tem alguém perto, ele conta, com legenda e voz de robô (até 2 vezes por dia cada uma). O botão "Contar" faz ele contar na hora.' }, + { emoji: '💳', nome: 'Custo', texto: 'Uma chamada ao Gemini para escolher as ideias e uma por atividade: 4 por dia com o padrão de 3.' }, + ], + }, personalidade: { titulo: 'IA local (Qwen)', emoji: '🧠', @@ -319,7 +334,7 @@ export const TOPICOS: Record = { /** Ordem do tutorial (página Guia). */ export const ORDEM_GUIA = [ - 'inicio', 'vida', 'cuidados', 'humores', 'ritmo', 'sono', 'celular', 'camera', 'gestos', 'atividades', 'criar', 'aparencia', + 'inicio', 'vida', 'cuidados', 'humores', 'ritmo', 'sono', 'celular', 'camera', 'gestos', 'atividades', 'criar', 'ideias', 'aparencia', 'personalidade', 'acontecimentos', 'eventos', 'presenca', 'tela', 'sensores', 'sensibilidade', 'reacoes', 'conversa', 'movimento', 'atalhos', ]; diff --git a/web/src/lib/inspiracao.svelte.ts b/web/src/lib/inspiracao.svelte.ts new file mode 100644 index 0000000..5218d7e --- /dev/null +++ b/web/src/lib/inspiracao.svelte.ts @@ -0,0 +1,78 @@ +/** + * Ideias do dia (/criacao/dia): todo dia o Carinha cria atividades novas inspiradas no tempo, nas + * datas, no "neste dia" da Wikipédia e no que está em alta, e conta o que aprendeu com cada uma. + * O estado chega por SSE ('inspiracao') e é recarregado ao abrir a tela. + */ +import { pedir, postar } from './api'; +import { avisos, tentar } from './avisos.svelte'; +import { ouvir } from './estado.svelte'; + +export type Fonte = 'tempo' | 'datas' | 'historia' | 'alta'; + +export interface ConfigInspiracao { + ligado: boolean; + por_dia: number; + hora: number; + aprendi: boolean; + cidade: { nome: string; lat: number; lon: number }; + fontes: Record; +} + +export interface IdeiaDoDia { + id: number; + nome: string; + emoji: string; + motivo: string; + aprendi: string; + horario: number; +} + +export interface StatusInspiracao { + config: ConfigInspiracao; + fontes: Record; + ocupado: boolean; + pode: boolean; + dia: string; + erro: string; + tentativas: number; + feito_em: number | null; + sinais: Partial>; + contadas: Record; + ideias: IdeiaDoDia[]; +} + +class Inspiracao { + status = $state(null); + + async carregar() { + try { + this.status = await pedir('/criacao/dia'); + } catch (e) { + avisos.erro(e); + } + } + + async configurar(dados: Partial> & { cidade?: string }, aviso?: string) { + const r = await tentar(() => postar('/criacao/dia/config', dados), aviso); + if (r) this.status = r; + return !!r; + } + + agora() { + return tentar(() => postar('/criacao/dia/agora', {}), 'Procurando ideias na internet…'); + } + + contar(id: number) { + return tentar(() => postar('/criacao/dia/contar', { id })); + } +} + +export const inspiracao = new Inspiracao(); +ouvir('inspiracao', (d: StatusInspiracao) => { + const antes = inspiracao.status; + inspiracao.status = d; + if (antes?.ocupado && !d.ocupado) { + if (d.erro) avisos.erro(new Error(d.erro)); + else if (d.ideias.length > (antes.ideias?.length ?? 0)) avisos.ok(`Ideias novas: ${d.ideias.slice(antes.ideias.length).map((i) => i.emoji + ' ' + i.nome).join(', ')}`); + } +}); diff --git a/web/src/telas/Criar.svelte b/web/src/telas/Criar.svelte index 51e1f25..b96eac2 100644 --- a/web/src/telas/Criar.svelte +++ b/web/src/telas/Criar.svelte @@ -3,6 +3,7 @@ import { onMount } from 'svelte'; import { favoritos } from '../lib/comandos.svelte'; import Card from '../lib/componentes/Card.svelte'; + import IdeiasDoDia from '../lib/componentes/IdeiasDoDia.svelte'; import RostoPrevia from '../lib/componentes/RostoPrevia.svelte'; import Selo from '../lib/componentes/Selo.svelte'; import { criacao, TIPOS, type IaCriacao, type TipoCriacao } from '../lib/criacao.svelte'; @@ -153,6 +154,8 @@
+ + {#snippet acoes()}
@@ -174,7 +177,7 @@ {#if podeFavoritar(c.tipo)}