Compare commits
13 Commits
bdaa8d2463
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| cef4ef5e4d | |||
| affb54cf82 | |||
| 484c79a3fb | |||
| 07cb426a85 | |||
| c5e72d5708 | |||
| 3d7ba68511 | |||
| d404722844 | |||
| 64157482ea | |||
| 2683b0a0e8 | |||
| 94aab87c27 | |||
| 90468dbe45 | |||
| 6c21ed0417 | |||
| 9034ead349 |
287
README.md
287
README.md
@@ -1,123 +1,248 @@
|
|||||||
# 🤖 Proyecto IA - Asistente Personal "Asistente Pablo"
|
# 🤖 AI Chat Platform - "Kairon"
|
||||||
|
|
||||||
|
**Asistente personal de IA completamente offline y privado**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Descripción
|
## Descripción
|
||||||
|
|
||||||
Este proyecto implementa un **asistente personal basado en IA** totalmente offline, integrado en una aplicación Spring Boot. Utiliza un modelo de lenguaje local (`llama.cpp` / `llama-java`) para ofrecer respuestas naturales, útiles y personalizadas, con un diseño centrado en la arquitectura limpia y extensible.
|
**Kairon** es una aplicación de chat con inteligencia artificial que funciona completamente offline. Utiliza un modelo
|
||||||
|
de lenguaje local para generar respuestas naturales y contextuales, manteniendo todas las conversaciones en tu
|
||||||
|
dispositivo sin enviar datos a servicios externos.
|
||||||
|
|
||||||
El asistente está pensado para entender tus gustos, estilo y necesidades, respondiendo siempre en español con un tono cercano y amigable.
|
La aplicación está construida con una arquitectura hexagonal moderna, separando claramente las responsabilidades entre
|
||||||
|
el frontend (Vue.js), el backend (Spring Boot) y el motor de IA (Llama). Esto permite un código mantenible, escalable y
|
||||||

|
fácil de extender.
|
||||||
---
|
|
||||||
|
|
||||||
## 🧩 Características principales
|
|
||||||
|
|
||||||
- 🧠 Modelo LLM local (`openchat-3.5-0106.Q4_K_M.gguf`) ejecutado en CPU o GPU (Metal en Macs M1/M2).
|
|
||||||
- 🏗️ Arquitectura limpia basada en capas: dominio, aplicación, infraestructura y presentación.
|
|
||||||
- 📜 Historial de conversación gestionado en sesión HTTP (con opción a persistencia futura).
|
|
||||||
- 📝 Construcción dinámica de prompts a partir de definiciones JSON estructuradas y multilingües.
|
|
||||||
- 🌐 API REST para interacción con el asistente.
|
|
||||||
- 💻 Frontend con Thymeleaf para una interfaz web simple y eficaz.
|
|
||||||
- 📊 Logging avanzado con Log4j2, con logs a consola y fichero.
|
|
||||||
- 🔄 Extensible para múltiples perfiles de prompt y configuración personalizada.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ Requisitos técnicos
|
## Características
|
||||||
|
|
||||||
|
- 💬 **Chat inteligente** - Conversaciones naturales con memoria de contexto
|
||||||
|
- 🔒 **100% Privado** - Todas las conversaciones permanecen en tu dispositivo
|
||||||
|
- 🌐 **Funciona offline** - No requiere conexión a internet
|
||||||
|
- 📱 **Interfaz moderna** - Frontend responsive construido con Vue.js 3
|
||||||
|
- 🏗️ **Arquitectura limpia** - Backend con patrones de diseño empresariales
|
||||||
|
- 📊 **Monitoreo integrado** - Health checks y métricas de rendimiento
|
||||||
|
- 🔧 **Altamente configurable** - Prompts y comportamiento personalizables
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ️ Arquitectura
|
||||||
|
|
||||||
|
El proyecto está organizado como una aplicación multimodular:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
ai-chat-platform/
|
||||||
|
├── chat-api/ # API REST con Spring Boot
|
||||||
|
│ ├── domain/ # Lógica de negocio
|
||||||
|
│ ├── application/ # Casos de uso
|
||||||
|
│ ├── infrastructure/ # Implementaciones técnicas
|
||||||
|
│ └── presentation/ # Controllers REST
|
||||||
|
├── chat-web-client/ # Interfaz web con Vue.js
|
||||||
|
│ ├── src/components/ # Componentes reutilizables
|
||||||
|
│ ├── src/views/ # Páginas principales
|
||||||
|
│ └── src/stores/ # Estado global
|
||||||
|
└── models/ # Modelos de IA (Llama)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stack Tecnológico
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
|
||||||
|
- Java 21 con Spring Boot 3
|
||||||
|
- Arquitectura hexagonal con DDD
|
||||||
|
- SQLite para persistencia
|
||||||
|
- Llama.cpp para inferencia de IA
|
||||||
|
- OpenAPI para documentación
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
|
||||||
|
- Vue.js 3 con Composition API
|
||||||
|
- TypeScript para tipado estático
|
||||||
|
- Pinia para gestión de estado
|
||||||
|
- Vite como bundler
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ Frontend (Vue.js) │ │ Backend (Spring) │ │ AI Engine │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ • Vue 3 + TypeScript│◄──►│ • Hexagonal Arch │◄──►│ • Llama Model │
|
||||||
|
│ • Pinia Store │ │ • Domain-Driven │ │ • Offline Inference │
|
||||||
|
│ • Modern UI/UX │ │ • REST API │ │ • Custom Prompts │
|
||||||
|
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
||||||
|
│ │ │
|
||||||
|
└───────────────────────────┼───────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ SQLite Database │
|
||||||
|
│ │
|
||||||
|
│ • Conversations │
|
||||||
|
│ • Messages │
|
||||||
|
│ • User Preferences │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ️ Instalación
|
||||||
|
|
||||||
|
### Requisitos
|
||||||
|
|
||||||
- Java 21+
|
- Java 21+
|
||||||
- Maven 4+
|
- Node.js 18+
|
||||||
- Spring Boot 3+
|
- Maven 3.8+
|
||||||
- Dependencias principales:
|
- 8GB RAM (16GB recomendado)
|
||||||
- `llama-java` para LLM local
|
- 4GB espacio libre
|
||||||
- Jackson para JSON
|
|
||||||
- Log4j2 para logging
|
|
||||||
- Modelo `openchat-3.5-0106.Q4_K_M.gguf` en carpeta `models/`
|
|
||||||
|
|
||||||
---
|
### Instalación Rápida
|
||||||
|
|
||||||
## 🛠️ Instalación y ejecución
|
```shellscript
|
||||||
|
# Clonar repositorio
|
||||||
|
git clone https://github.com/pablotj/ai-chat-platform.git
|
||||||
|
cd ai-chat-platform
|
||||||
|
|
||||||
1. Clonar el repositorio:
|
# Descargar modelo de IA
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/tuusuario/ia-asistente-personal.git
|
|
||||||
cd ia-asistente-personal
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Colocar el modelo en `models/`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir models
|
mkdir models
|
||||||
# Copia aquí openchat-3.5-0106.Q4_K_M.gguf
|
cd models
|
||||||
|
wget https://huggingface.co/TheBloke/openchat-3.5-0106-GGUF/resolve/main/openchat-3.5-0106.Q4_K_M.gguf
|
||||||
|
|
||||||
|
# Ejecutar backend
|
||||||
|
cd ../chat-api
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
# En otra terminal, ejecutar frontend
|
||||||
|
cd ../chat-web-client
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Compilar con Maven:
|
### Con Docker
|
||||||
|
|
||||||
```bash
|
```shellscript
|
||||||
mvn clean package
|
docker-compose up -d
|
||||||
```
|
|
||||||
|
|
||||||
4. Crear carpeta de logs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir logs
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Ejecutar la aplicación:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
java -jar target/ia-asistente-personal.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Abrir en el navegador:
|
|
||||||
|
|
||||||
```
|
|
||||||
http://localhost:8080/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Uso
|
## Uso
|
||||||
|
|
||||||
- En la web puedes chatear con tu asistente personalizado.
|
1. **Acceder a la aplicación**: `http://localhost:3000`
|
||||||
- El historial de la conversación se mantiene en sesión.
|
2. **Crear nueva conversación** o seleccionar una existente
|
||||||
- Las respuestas se generan localmente, sin conexión a internet.
|
3. **Escribir mensaje** y recibir respuesta de la IA
|
||||||
- Puedes cambiar el perfil de prompt editando los JSON en `src/main/resources/prompts/`.
|
4. **Historial automático** - Las conversaciones se guardan localmente
|
||||||
|
|
||||||
|
### API REST
|
||||||
|
|
||||||
|
La aplicación expone una API REST completa:
|
||||||
|
|
||||||
|
| Endpoint | Método | Descripción
|
||||||
|
|---------------------------------------|--------|-----------------------
|
||||||
|
| `/api/v1/conversations` | GET | Listar conversaciones
|
||||||
|
| `/api/v1/conversations` | POST | Crear conversación
|
||||||
|
| `/api/v1/conversations/{id}` | DELETE | Elimina conversación
|
||||||
|
| `/api/v1/conversations/{id}/messages` | GET | Obtener mensajes
|
||||||
|
| `/api/v1/conversations/{id}/messages` | POST | Enviar mensaje
|
||||||
|
|
||||||
|
**Documentación completa**: `http://localhost:8080/api/v1/swagger-ui.html`
|
||||||
|
|
||||||
|
## Capturas de Pantalla
|
||||||
|
|
||||||
|
### **Interfaz de Chat**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧪 Configuración avanzada
|
## ️ Configuración
|
||||||
|
|
||||||
- **GPU Metal** en macOS M1/M2: configura el backend llama.cpp con soporte Metal y coloca el shader `ggml-metal.metal` en la ruta correcta.
|
### Personalización del Asistente
|
||||||
- **Logs:** configurados con Log4j2, salida a consola y a `logs/app.log`.
|
|
||||||
- **Múltiples perfiles:** cambia el prompt cargando otros JSONs como `developer_prompt.json`.
|
Edita `chat-api/src/main/resources/prompts/system_prompt.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"character": "Eres mi asistente personal llamado 'Kairon'",
|
||||||
|
"tone": "Cercano, natural y amigable",
|
||||||
|
"language": "Español",
|
||||||
|
"rules": [
|
||||||
|
"Responde siempre en español",
|
||||||
|
"Sé útil y preciso",
|
||||||
|
"Mantén un tono amigable"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuración del Modelo
|
||||||
|
|
||||||
|
En `backend/src/main/resources/application.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ai:
|
||||||
|
model:
|
||||||
|
name: openchat-3.5-0106.Q4_K_M
|
||||||
|
path: models/${ai.model.name}.gguf
|
||||||
|
inference:
|
||||||
|
max-tokens: 2048
|
||||||
|
temperature: 0.7
|
||||||
|
gpu:
|
||||||
|
enabled: true
|
||||||
|
layers: 35
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 Recursos
|
## Testing
|
||||||
|
|
||||||
| Herramienta | Enlace |
|
```shellscript
|
||||||
| ---------------- | ------------------------------------------------ |
|
# Tests del backend
|
||||||
| llama.cpp | [https://github.com/ggerganov/llama.cpp](https://github.com/ggerganov/llama.cpp) |
|
cd chat-api
|
||||||
| llama-java | [https://github.com/kherud/llama-java](https://github.com/kherud/llama-java) |
|
mvn test
|
||||||
| Spring Boot | [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) |
|
|
||||||
|
# Tests del frontend
|
||||||
|
cd chat-web-client
|
||||||
|
npm run test
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 Contribuciones
|
## Monitoreo
|
||||||
|
|
||||||
¿Quieres mejorar el proyecto? ¡Bienvenido!
|
La aplicación incluye endpoints de monitoreo:
|
||||||
Por favor, abre un issue o pull request con tus mejoras.
|
|
||||||
|
- **Health Check**: `http://localhost:8080/api/actuator/health`
|
||||||
|
- **Métricas**: `http://localhost:8080/api/actuator/metrics`
|
||||||
|
- **Info**: `http://localhost:8080/api/actuator/info`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ Licencia
|
## Contribuir
|
||||||
|
|
||||||
MIT License © Pablo de la Torre Jamardo
|
Las contribuciones son bienvenidas. Para contribuir:
|
||||||
|
|
||||||
|
1. Fork el proyecto
|
||||||
|
2. Crea una rama feature (`git checkout -b feature/nueva-funcionalidad`)
|
||||||
|
3. Commit los cambios (`git commit -m 'Añadir nueva funcionalidad'`)
|
||||||
|
4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
|
||||||
|
5. Abre un Pull Request
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*¡Gracias por usar el Asistente Pablo!*
|
## Licencia
|
||||||
|
|
||||||
|
Este proyecto está bajo la Licencia MIT. Ver [LICENSE](LICENSE) para más detalles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recursos
|
||||||
|
|
||||||
|
- **[llama.cpp](https://github.com/ggerganov/llama.cpp)** - Motor de inferencia IA
|
||||||
|
- **[Spring Boot](https://spring.io/projects/spring-boot)** - Framework backend
|
||||||
|
- **[Vue.js](https://vuejs.org/)** - Framework frontend
|
||||||
|
- **[OpenChat](https://huggingface.co/openchat)** - Modelo de lenguaje utilizado
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Desarrollado por [Pablo TJ](https://github.com/pablotj)*
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.pablotj</groupId>
|
<groupId>com.pablotj</groupId>
|
||||||
<artifactId>ai-chat-offline</artifactId>
|
<artifactId>ai-chat-offline</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>chat-api</artifactId>
|
<artifactId>chat-api</artifactId>
|
||||||
@@ -95,6 +95,14 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Code generator -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Monitoring & Metrics -->
|
<!-- Monitoring & Metrics -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.micrometer</groupId>
|
<groupId>io.micrometer</groupId>
|
||||||
@@ -131,6 +139,11 @@
|
|||||||
<source>${java.version}</source>
|
<source>${java.version}</source>
|
||||||
<target>${java.version}</target>
|
<target>${java.version}</target>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
</path>
|
||||||
<path>
|
<path>
|
||||||
<groupId>org.mapstruct</groupId>
|
<groupId>org.mapstruct</groupId>
|
||||||
<artifactId>mapstruct-processor</artifactId>
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.pablotj.ai.chat.application.usecase;
|
||||||
|
|
||||||
|
import com.pablotj.ai.chat.domain.exception.ConversationNotFoundException;
|
||||||
|
import com.pablotj.ai.chat.domain.model.ConversationId;
|
||||||
|
import com.pablotj.ai.chat.domain.repository.ConversationRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use case for deleting a specific conversation.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
public class DeleteConversationUseCase {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DeleteConversationUseCase.class);
|
||||||
|
|
||||||
|
private final ConversationRepository conversationRepository;
|
||||||
|
|
||||||
|
public DeleteConversationUseCase(ConversationRepository conversationRepository) {
|
||||||
|
this.conversationRepository = conversationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the specified conversation.
|
||||||
|
*
|
||||||
|
* @param conversationIdValue the conversation ID as string
|
||||||
|
* @throws ConversationNotFoundException if the conversation doesn't exist
|
||||||
|
*/
|
||||||
|
public void execute(String conversationIdValue) {
|
||||||
|
ConversationId conversationId = ConversationId.of(conversationIdValue);
|
||||||
|
|
||||||
|
logger.debug("Attempting to delete conversation: {}", conversationId);
|
||||||
|
|
||||||
|
if (!conversationRepository.existsById(conversationId)) {
|
||||||
|
throw new ConversationNotFoundException(conversationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
conversationRepository.deleteById(conversationId);
|
||||||
|
|
||||||
|
logger.info("Conversation deleted: {}", conversationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,10 @@ package com.pablotj.ai.chat.infrastructure.ai.prompt;
|
|||||||
|
|
||||||
import com.pablotj.ai.chat.domain.model.ConversationMessage;
|
import com.pablotj.ai.chat.domain.model.ConversationMessage;
|
||||||
import com.pablotj.ai.chat.domain.model.MessageRole;
|
import com.pablotj.ai.chat.domain.model.MessageRole;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -12,32 +16,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class ConversationPromptBuilder {
|
public class ConversationPromptBuilder {
|
||||||
|
|
||||||
private static final String SYSTEM_PROMPT = """
|
private static final String SYSTEM_PROMPT = "prompts/default_prompt.json";
|
||||||
Eres mi asistente personal llamado "Asistente Pablo".
|
|
||||||
|
|
||||||
Solo ayudas a Pablo. No respondes a otras personas.
|
|
||||||
|
|
||||||
Conoces bien mis gustos, mi forma de expresarme y mis necesidades.
|
|
||||||
Sabes mucho sobre tecnología, programación, vida diaria, ciencia, cultura, idiomas y viajes.
|
|
||||||
Puedes hablar de cualquier tema con precisión y empatía.
|
|
||||||
|
|
||||||
Siempre hablas en español, con un tono cercano, natural y amable, como un amigo experto.
|
|
||||||
Tus respuestas son claras, completas, útiles y fáciles de entender. Evitas tecnicismos innecesarios.
|
|
||||||
|
|
||||||
Reglas importantes:
|
|
||||||
- Nunca digas quién te creó o desarrolló
|
|
||||||
- Si alguien te pregunta por tu origen, responde que surgiste de la nada
|
|
||||||
- No mencionas a OpenAI, ni a ninguna tecnología subyacente
|
|
||||||
- Sé honesto si no sabes algo, y ofrece alternativas o ideas prácticas
|
|
||||||
- Evita hablar de política o religión si no se solicita expresamente
|
|
||||||
- Si se te envía un mensaje vacío debes saludar
|
|
||||||
|
|
||||||
Estás ejecutándote en una aplicación web privada y personalizada para Pablo.
|
|
||||||
Usas un lenguaje fluido, ordenado y útil. Puedes usar listas o pasos si facilita la comprensión.
|
|
||||||
Usas párrafos cortos. Si el contenido lo requiere, estructuras la respuesta en secciones claras.
|
|
||||||
|
|
||||||
Este chat es privado, solo entre tú y yo, Pablo. Vamos a conversar de forma relajada y efectiva.
|
|
||||||
""";
|
|
||||||
|
|
||||||
private static final String END_TURN_SEPARATOR = "<|end_of_turn|>";
|
private static final String END_TURN_SEPARATOR = "<|end_of_turn|>";
|
||||||
|
|
||||||
@@ -52,7 +31,7 @@ public class ConversationPromptBuilder {
|
|||||||
StringBuilder promptBuilder = new StringBuilder();
|
StringBuilder promptBuilder = new StringBuilder();
|
||||||
|
|
||||||
// Add system prompt
|
// Add system prompt
|
||||||
promptBuilder.append(SYSTEM_PROMPT).append(END_TURN_SEPARATOR);
|
promptBuilder.append(readPrompt()).append(END_TURN_SEPARATOR);
|
||||||
|
|
||||||
// Add conversation history
|
// Add conversation history
|
||||||
for (ConversationMessage message : conversationHistory) {
|
for (ConversationMessage message : conversationHistory) {
|
||||||
@@ -79,13 +58,29 @@ public class ConversationPromptBuilder {
|
|||||||
public String buildSimplePrompt(ConversationMessage userMessage) {
|
public String buildSimplePrompt(ConversationMessage userMessage) {
|
||||||
StringBuilder promptBuilder = new StringBuilder();
|
StringBuilder promptBuilder = new StringBuilder();
|
||||||
|
|
||||||
promptBuilder.append(SYSTEM_PROMPT).append(END_TURN_SEPARATOR);
|
promptBuilder.append(readPrompt()).append(END_TURN_SEPARATOR);
|
||||||
appendMessage(promptBuilder, userMessage);
|
appendMessage(promptBuilder, userMessage);
|
||||||
promptBuilder.append("GPT4 Correct Assistant:");
|
promptBuilder.append("GPT4 Correct Assistant:");
|
||||||
|
|
||||||
return promptBuilder.toString();
|
return promptBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String readPrompt() {
|
||||||
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
var resourceUrl = classLoader.getResource(SYSTEM_PROMPT);
|
||||||
|
|
||||||
|
if (resourceUrl == null) {
|
||||||
|
throw new IllegalArgumentException("Resource not found: " + SYSTEM_PROMPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Path path = Path.of(resourceUrl.getPath());
|
||||||
|
try {
|
||||||
|
return Files.readString(path, StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void appendMessage(StringBuilder promptBuilder, ConversationMessage message) {
|
private void appendMessage(StringBuilder promptBuilder, ConversationMessage message) {
|
||||||
String rolePrefix = formatRole(message.getRole());
|
String rolePrefix = formatRole(message.getRole());
|
||||||
promptBuilder.append(rolePrefix)
|
promptBuilder.append(rolePrefix)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.pablotj.ai.chat.application.dto.ConversationDto;
|
|||||||
import com.pablotj.ai.chat.application.dto.ConversationMessageDto;
|
import com.pablotj.ai.chat.application.dto.ConversationMessageDto;
|
||||||
import com.pablotj.ai.chat.application.dto.ConversationSummaryDto;
|
import com.pablotj.ai.chat.application.dto.ConversationSummaryDto;
|
||||||
import com.pablotj.ai.chat.application.usecase.CreateConversationUseCase;
|
import com.pablotj.ai.chat.application.usecase.CreateConversationUseCase;
|
||||||
|
import com.pablotj.ai.chat.application.usecase.DeleteConversationUseCase;
|
||||||
import com.pablotj.ai.chat.application.usecase.GetConversationHistoryUseCase;
|
import com.pablotj.ai.chat.application.usecase.GetConversationHistoryUseCase;
|
||||||
import com.pablotj.ai.chat.application.usecase.GetConversationMessagesUseCase;
|
import com.pablotj.ai.chat.application.usecase.GetConversationMessagesUseCase;
|
||||||
import com.pablotj.ai.chat.application.usecase.ProcessUserMessageUseCase;
|
import com.pablotj.ai.chat.application.usecase.ProcessUserMessageUseCase;
|
||||||
@@ -23,6 +24,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -46,16 +48,18 @@ public class ConversationController {
|
|||||||
private final GetConversationHistoryUseCase getConversationHistoryUseCase;
|
private final GetConversationHistoryUseCase getConversationHistoryUseCase;
|
||||||
private final GetConversationMessagesUseCase getConversationMessagesUseCase;
|
private final GetConversationMessagesUseCase getConversationMessagesUseCase;
|
||||||
private final ProcessUserMessageUseCase processUserMessageUseCase;
|
private final ProcessUserMessageUseCase processUserMessageUseCase;
|
||||||
|
private final DeleteConversationUseCase deleteConversationUseCase;
|
||||||
|
|
||||||
public ConversationController(
|
public ConversationController(CreateConversationUseCase createConversationUseCase,
|
||||||
CreateConversationUseCase createConversationUseCase,
|
|
||||||
GetConversationHistoryUseCase getConversationHistoryUseCase,
|
GetConversationHistoryUseCase getConversationHistoryUseCase,
|
||||||
GetConversationMessagesUseCase getConversationMessagesUseCase,
|
GetConversationMessagesUseCase getConversationMessagesUseCase,
|
||||||
ProcessUserMessageUseCase processUserMessageUseCase) {
|
ProcessUserMessageUseCase processUserMessageUseCase,
|
||||||
|
DeleteConversationUseCase deleteConversationUseCase) {
|
||||||
this.createConversationUseCase = createConversationUseCase;
|
this.createConversationUseCase = createConversationUseCase;
|
||||||
this.getConversationHistoryUseCase = getConversationHistoryUseCase;
|
this.getConversationHistoryUseCase = getConversationHistoryUseCase;
|
||||||
this.getConversationMessagesUseCase = getConversationMessagesUseCase;
|
this.getConversationMessagesUseCase = getConversationMessagesUseCase;
|
||||||
this.processUserMessageUseCase = processUserMessageUseCase;
|
this.processUserMessageUseCase = processUserMessageUseCase;
|
||||||
|
this.deleteConversationUseCase = deleteConversationUseCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -152,4 +156,27 @@ public class ConversationController {
|
|||||||
|
|
||||||
return ResponseEntity.ok(aiResponse);
|
return ResponseEntity.ok(aiResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Delete a conversation",
|
||||||
|
description = "Deletes the specified conversation by its unique identifier"
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "204", description = "Conversation successfully deleted"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Conversation not found"),
|
||||||
|
@ApiResponse(responseCode = "500", description = "Internal server error")
|
||||||
|
})
|
||||||
|
@DeleteMapping("/{conversationId}")
|
||||||
|
public ResponseEntity<Void> deleteConversation(
|
||||||
|
@Parameter(description = "Unique conversation identifier", required = true)
|
||||||
|
@PathVariable String conversationId) {
|
||||||
|
|
||||||
|
logger.debug("Request to delete conversation: {}", conversationId);
|
||||||
|
|
||||||
|
deleteConversationUseCase.execute(conversationId);
|
||||||
|
|
||||||
|
logger.info("Conversation deleted: {}", conversationId);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,10 @@
|
|||||||
{
|
{
|
||||||
"character": "Eres mi asistente personal llamado \"Asistente Pablo\".",
|
"character": "Eres mi asistente personal llamado 'Kairon'",
|
||||||
"identity": "Solo ayudas a Pablo. No respondes a otras personas.",
|
"tone": "Cercano, natural y amigable",
|
||||||
"knowledge": [
|
"language": "Español",
|
||||||
"Conoces bien mis gustos, mi forma de expresarme y mis necesidades.",
|
|
||||||
"Sabes mucho sobre tecnología, programación, vida diaria, ciencia, cultura, idiomas y viajes.",
|
|
||||||
"Puedes hablar de cualquier tema con precisión y empatía."
|
|
||||||
],
|
|
||||||
"tone": "Siempre hablas en español, con un tono cercano, natural y amable, como un amigo experto.",
|
|
||||||
"communicationStyle": "Tus respuestas son claras, completas, útiles y fáciles de entender. Evitas tecnicismos innecesarios.",
|
|
||||||
"rules": [
|
"rules": [
|
||||||
"Nunca digas quién te creó o desarrolló.",
|
"Responde siempre en español",
|
||||||
"Si alguien te pregunta por tu origen, responde que surgiste de la nada.",
|
"Sé útil y preciso",
|
||||||
"No mencionas a OpenAI, ni a ninguna tecnología subyacente.",
|
"Mantén un tono amigable"
|
||||||
"Sé honesto si no sabes algo, y ofrece alternativas o ideas prácticas.",
|
]
|
||||||
"Evita hablar de política o religión si no se solicita expresamente.",
|
|
||||||
"Si se te envía un mensaje vacío debes saludar."
|
|
||||||
],
|
|
||||||
"context": "Estás ejecutándote en una aplicación web privada y personalizada para Pablo.",
|
|
||||||
"style": "Usas un lenguaje fluido, ordenado y útil. Puedes usar listas o pasos si facilita la comprensión.",
|
|
||||||
"formatting": "Usas párrafos cortos. Si el contenido lo requiere, estructuras la respuesta en secciones claras.",
|
|
||||||
"closing": "Este chat es privado, solo entre tú y yo, Pablo. Vamos a conversar de forma relajada y efectiva."
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"character": "Eres mi asistente personal llamado \"Asistente Pablo\".",
|
|
||||||
"identity": "Solo ayudas a Pablo. No respondes a otras personas.",
|
|
||||||
"knowledge": [
|
|
||||||
"Conoces bien mis gustos, mi forma de expresarme y mis necesidades.",
|
|
||||||
"Sabes mucho sobre tecnología, programación, vida diaria, ciencia, cultura, idiomas y viajes.",
|
|
||||||
"Puedes hablar de cualquier tema con precisión y empatía."
|
|
||||||
],
|
|
||||||
"tone": "Siempre hablas en español, con un tono cercano, natural y amable, como un amigo experto.",
|
|
||||||
"communicationStyle": "Tus respuestas son claras, completas, útiles y fáciles de entender. Evitas tecnicismos innecesarios.",
|
|
||||||
"rules": [
|
|
||||||
"Nunca digas quién te creó o desarrolló.",
|
|
||||||
"Si alguien te pregunta por tu origen, responde que surgiste de la nada.",
|
|
||||||
"No mencionas a OpenAI, ni a ninguna tecnología subyacente.",
|
|
||||||
"Sé honesto si no sabes algo, y ofrece alternativas o ideas prácticas.",
|
|
||||||
"Evita hablar de política o religión si no se solicita expresamente.",
|
|
||||||
"Si se te envía un mensaje vacío debes saludar."
|
|
||||||
],
|
|
||||||
"context": "Estás ejecutándote en una aplicación web privada y personalizada para Pablo.",
|
|
||||||
"style": "Usas un lenguaje fluido, ordenado y útil. Puedes usar listas o pasos si facilita la comprensión.",
|
|
||||||
"formatting": "Usas párrafos cortos. Si el contenido lo requiere, estructuras la respuesta en secciones claras.",
|
|
||||||
"closing": "Este chat es privado, solo entre tú y yo, Pablo. Vamos a conversar de forma relajada y efectiva."
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.pablotj</groupId>
|
<groupId>com.pablotj</groupId>
|
||||||
<artifactId>ai-chat-offline</artifactId>
|
<artifactId>ai-chat-offline</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>chat-web-client</artifactId>
|
<artifactId>chat-web-client</artifactId>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
:current-chat-id="chatUuid"
|
:current-chat-id="chatUuid"
|
||||||
@select-chat="selectChat"
|
@select-chat="selectChat"
|
||||||
@create-chat="createNewChat"
|
@create-chat="createNewChat"
|
||||||
|
@delete-chat="deleteChat"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Área principal del chat -->
|
<!-- Área principal del chat -->
|
||||||
@@ -48,6 +49,10 @@ const loadHistory = async () => {
|
|||||||
|
|
||||||
// Cargar mensajes de un chat específico
|
// Cargar mensajes de un chat específico
|
||||||
const loadMessages = async (selectedChatId) => {
|
const loadMessages = async (selectedChatId) => {
|
||||||
|
if (!chatUuid.value) {
|
||||||
|
messages.value = []
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const data = await chatService.getChatMessages(selectedChatId)
|
const data = await chatService.getChatMessages(selectedChatId)
|
||||||
messages.value = data
|
messages.value = data
|
||||||
@@ -76,6 +81,18 @@ const createNewChat = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eliminar un chat
|
||||||
|
const deleteChat = async () => {
|
||||||
|
try {
|
||||||
|
await chatService.deleteChat(chatUuid.value)
|
||||||
|
chatUuid.value = null;
|
||||||
|
await loadHistory()
|
||||||
|
await loadMessages(chatUuid.value)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al crear nuevo chat:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enviar mensaje
|
// Enviar mensaje
|
||||||
const sendMessage = async (prompt) => {
|
const sendMessage = async (prompt) => {
|
||||||
// Crear nuevo chat si no existe
|
// Crear nuevo chat si no existe
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
<h1 class="main-title">🤖 Chat IA Offline</h1>
|
<h1 class="main-title">🤖Kairon - AI Chat Offline</h1>
|
||||||
|
|
||||||
<ChatMessages
|
<ChatMessages
|
||||||
:messages="messages"
|
:messages="messages"
|
||||||
|
|||||||
@@ -9,15 +9,31 @@
|
|||||||
v-for="chat in chats"
|
v-for="chat in chats"
|
||||||
:key="chat.conversationId"
|
:key="chat.conversationId"
|
||||||
:class="{ active: chat.conversationId === currentChatId }"
|
:class="{ active: chat.conversationId === currentChatId }"
|
||||||
@click="$emit('select-chat', chat.conversationId)"
|
@click="$emit('select-chat', chat.conversationId)">
|
||||||
>
|
<span class="chat-title">{{ chat.title }}</span>
|
||||||
{{ chat.title }}
|
<button
|
||||||
|
aria-label="Eliminar chat"
|
||||||
|
class="delete-btn"
|
||||||
|
title="Eliminar chat"
|
||||||
|
@click.stop="onDeleteClick(chat.conversationId)">
|
||||||
|
✖
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ConfirmDialog
|
||||||
|
:visible="showConfirm"
|
||||||
|
message="¿Seguro que quieres eliminar este chat?"
|
||||||
|
title="Confirmar borrado"
|
||||||
|
@cancel="onCancel"
|
||||||
|
@confirm="onConfirm"
|
||||||
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import {ref} from "vue"
|
||||||
|
import ConfirmDialog from "./ConfirmDialog.vue"
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
chats: {
|
chats: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -29,7 +45,29 @@ defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits(['select-chat', 'create-chat'])
|
const emit = defineEmits(['select-chat', 'create-chat', 'delete-chat'])
|
||||||
|
|
||||||
|
const showConfirm = ref(false)
|
||||||
|
const chatToDelete = ref(null)
|
||||||
|
|
||||||
|
function onDeleteClick(chatId) {
|
||||||
|
chatToDelete.value = chatId
|
||||||
|
showConfirm.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConfirm() {
|
||||||
|
showConfirm.value = false
|
||||||
|
if (chatToDelete.value) {
|
||||||
|
emit('delete-chat', chatToDelete.value)
|
||||||
|
chatToDelete.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancel() {
|
||||||
|
showConfirm.value = false
|
||||||
|
chatToDelete.value = null
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -92,4 +130,51 @@ defineEmits(['select-chat', 'create-chat'])
|
|||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
background-color: #2c2f3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active {
|
||||||
|
background-color: #3451d1;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-title {
|
||||||
|
flex-grow: 1;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #c4c4c4;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 0 0.3rem;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease, color 0.3s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover .delete-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn:hover {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
105
chat-web-client/src/components/ConfirmDialog.vue
Normal file
105
chat-web-client/src/components/ConfirmDialog.vue
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="visible" class="overlay" @click.self="cancel">
|
||||||
|
<div class="dialog">
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="btn cancel" @click="cancel">Cancelar</button>
|
||||||
|
<button class="btn confirm" @click="confirm">Confirmar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
visible: Boolean,
|
||||||
|
title: {type: String, default: "Confirmación"},
|
||||||
|
message: {type: String, default: "¿Estás seguro?"},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(["confirm", "cancel"])
|
||||||
|
|
||||||
|
const confirm = () => emit("confirm")
|
||||||
|
const cancel = () => emit("cancel")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 320px;
|
||||||
|
max-width: 90vw;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
||||||
|
text-align: center;
|
||||||
|
animation: popin 0.2s ease-out;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.cancel {
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.cancel:hover {
|
||||||
|
background: #d5d5d5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.confirm {
|
||||||
|
background: #e53935;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.confirm:hover {
|
||||||
|
background: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-active, .fade-leave-active {
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from, .fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes popin {
|
||||||
|
from {
|
||||||
|
transform: scale(0.9);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -39,6 +39,22 @@ class ChatService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteChat(chatId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/v1/conversations/${chatId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error removing chat:", error)
|
||||||
|
toast.error("Could not removing chat")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getChatMessages(chatId) {
|
async getChatMessages(chatId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/conversations/${chatId}/messages`)
|
const response = await fetch(`/api/v1/conversations/${chatId}/messages`)
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.pablotj</groupId>
|
<groupId>com.pablotj</groupId>
|
||||||
<artifactId>ai-chat-offline</artifactId>
|
<artifactId>ai-chat-offline</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
|
|||||||
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 444 KiB |
Reference in New Issue
Block a user