Browse Source

Oasis release 0.3.8

psy 1 week ago
parent
commit
c1fc03f54f

+ 15 - 1
README.md

@@ -39,7 +39,6 @@ But it has others features that are also really interesting, for example:
     
  +  Support for multiple themes.
  
- 
    ![SNH](https://solarnethub.com/git/snh-clear-theme.png "SolarNET.HuB")
    ![SNH](https://solarnethub.com/git/snh-purple-theme.png "SolarNET.HuB")
    ![SNH](https://solarnethub.com/git/snh-matrix-theme.png "SolarNET.HuB")
@@ -53,6 +52,7 @@ And much more, that we invite you to discover by yourself ;-)
 Oasis is TRULY MODULAR. Here's a list of what comes deployed with the "core".
 
  + Agenda: Module to manage all your assigned items.
+ + AI: Module to talk with a LLM called '42'.
  + Audios: Module to discover and manage audios.
  + Bookmarks: Module to discover and manage bookmarks.	
  + Cipher: Module to encrypt and decrypt your text symmetrically (using a shared password).	
@@ -85,6 +85,20 @@ Both the codebase and the inhabitants can generate new modules.
 
 ----------
 
+## C-AI (collective artificial intelligence)
+
+Oasis contains its own AI model called "42". 
+
+The main idea behind this implementation is to enable distributed learning generated through the collective action of many individuals, with the goal of redistributing the necessary processing load, as well as the ecological footprint and corporate bias.
+
+  ![SNH](https://solarnethub.com/git/oasis-ai-example.png "SolarNET.HuB")
+
+Our AI is trained with content from the OASIS network and its purpose is to take action and obtain answers to individual, but also global, problems.
+
+ + https://wiki.solarnethub.com/socialnet/ai
+
+----------
+
 ## ECOin (crypto-economy)
 
 Oasis contains its own cryptocurrency. With it, you can exchange items and services in the marketplace. 

+ 17 - 0
docs/AI/info.md

@@ -0,0 +1,17 @@
+# Oasis AI General Info
+
+Collective Artificial Intelligence (CAI) model called "42" is based into: llama-2-7b-chat.Q4_K_M.
+
+https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF
+
+This is a static model trained in English on an offline dataset with a size of: 3,8 GiB (4.081.004.224 bytes). 
+
+---
+
+The main idea behind this implementation of an GGUF/LLM in the OASIS network is to enable distributed learning generated through the collective action of many individuals, with the goal of redistributing the necessary processing load, as well as the ecological footprint and corporate bias.
+
+Our AI will be trained with content from the OASIS network and its purpose is to take action and obtain answers to individual, but also global, problems.
+
+Future versions of the tuned model will be released as we improve model safety with community feedback.
+
+---

+ 6 - 0
docs/CHANGELOG.md

@@ -13,6 +13,12 @@ All notable changes to this project will be documented in this file.
 ### Security
 -->
 
+## v0.3.8 - 2025-07-21
+
+### Added
+
+- AI model called "42".
+
 ## v0.3.5 - 2025-06-21 (summer solstic)
 
 ### Changed

+ 22 - 2
install.sh

@@ -1,14 +1,34 @@
 #!/bin/bash
 
 cd src/server
+
 printf "==========================\n"
-printf "|| OASIS Installer v0.1 ||\n"
+printf "|| OASIS Installer v0.2 ||\n"
 printf "==========================\n"
-sudo apt-get install git curl
+
+sudo apt-get install -y git curl tar
+
 curl -sL http://deb.nodesource.com/setup_22.x | sudo bash -
 sudo apt-get install -y nodejs
 npm install .
 npm audit fix
+
+MODEL_DIR="../AI"
+MODEL_FILE="oasis-42-1-chat.Q4_K_M.gguf"
+MODEL_TAR="$MODEL_FILE.tar.gz"
+MODEL_URL="https://solarnethub.com/code/models/$MODEL_TAR"
+
+if [ ! -f "$MODEL_DIR/$MODEL_FILE" ]; then
+    echo ""
+    echo "downloading AI model [size: 3,8 GiB (4.081.004.224 bytes)] ..."
+    curl -L -o "$MODEL_DIR/$MODEL_TAR" "$MODEL_URL"
+    echo ""
+    echo "extracting package: $MODEL_TAR..."
+    echo ""
+    tar -xzf "$MODEL_DIR/$MODEL_TAR" -C "$MODEL_DIR"
+    rm "$MODEL_DIR/$MODEL_TAR"
+fi
+
 printf "==========================\n"
 printf "\nOASIS has been correctly deployed! ;)\n\n"
 printf "Run: 'sh oasis.sh' to start ...\n\n"

+ 13 - 2
oasis.sh

@@ -2,11 +2,22 @@
 
 CURRENT_DIR=$(pwd)
 MODE=$1
+MODEL_PATH="$CURRENT_DIR/src/AI/oasis-42-1-chat.Q4_K_M.gguf"
+CONFIG_FILE="$CURRENT_DIR/src/configs/oasis-config.json"
+
+if [ -f "$CONFIG_FILE" ]; then
+  if [ -f "$MODEL_PATH" ]; then
+    sed -i.bak 's/"aiMod": *"off"/"aiMod": "on"/' "$CONFIG_FILE"
+  else
+    sed -i.bak 's/"aiMod": *"on"/"aiMod": "off"/' "$CONFIG_FILE"
+  fi
+  rm -f "$CONFIG_FILE.bak"
+fi
 
 if [ "$MODE" = "server" ]; then
-  cd "$CURRENT_DIR/src/server" || { echo "Directory not found: $CURRENT_DIR/src/server"; exit 1; }
+  cd "$CURRENT_DIR/src/server" || exit 1
   exec node SSB_server.js start
 else
-  cd "$CURRENT_DIR/src/backend" || { echo "Directory not found: $CURRENT_DIR/src/backend"; exit 1; }
+  cd "$CURRENT_DIR/src/backend" || exit 1
   exec node backend.js
 fi

+ 48 - 0
src/AI/ai_service.mjs

@@ -0,0 +1,48 @@
+import path from 'path';
+import { fileURLToPath } from 'url';
+import express from '../server/node_modules/express/index.js'; 
+import cors from '../server/node_modules/cors/lib/index.js';   
+import { getLlama, LlamaChatSession } from '../server/node_modules/node-llama-cpp/dist/index.js';
+
+const app = express();
+app.use(cors());
+app.use(express.json());
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+let llamaInstance, model, context, session;
+
+async function initModel() {
+  if (!model) {
+    llamaInstance = await getLlama({ gpu: false });
+    model = await llamaInstance.loadModel({
+      modelPath: path.join(__dirname, '..', 'AI', 'oasis-42-1-chat.Q4_K_M.gguf')
+    });
+    context = await model.createContext();
+    session = new LlamaChatSession({ contextSequence: context.getSequence() });
+  }
+}
+
+app.post('/ai', async (req, res) => {
+    const userInput = req.body.input;
+
+    await initModel();  
+    const prompt = `
+      Context: You are an AI assistant in Oasis, a distributed, encrypted and federated social network created by old-school hackers.
+
+      Query: "${userInput}"
+
+      Provide an informative and precise response.
+    `;
+
+    const response = await session.prompt(prompt);
+    if (!response) {
+      res.status(500).json({ error: 'Failed to get response from model' });
+      return;
+    }
+    res.json({ answer: response.trim() });
+});
+
+app.listen(4001, () => {
+});

+ 52 - 12
src/backend/backend.js

@@ -11,6 +11,7 @@ const promisesFs = require("fs").promises;
 const SSBconfig = require('../server/SSB_server.js');
 const moment = require('../server/node_modules/moment');
 const FileType = require('../server/node_modules/file-type');
+const ssbRef = require("../server/node_modules/ssb-ref");
 
 const defaultConfig = {};
 const defaultConfigFile = path.join(
@@ -40,6 +41,17 @@ if (config.debug) {
   process.env.DEBUG = "oasis,oasis:*";
 }
 
+//AI
+const { spawn } = require('child_process');
+function startAI() {
+  const aiPath = path.resolve(__dirname, '../AI/ai_service.mjs');
+  const aiProcess = spawn('node', [aiPath], {
+    detached: false,
+    stdio: 'ignore', //inherit for debug
+  });
+  aiProcess.unref();
+}
+
 const customStyleFile = path.join(
   envPaths("oasis", { suffix: "" }).config,
   "/custom-style.css"
@@ -394,8 +406,7 @@ const { imageView, singleImageView } = require("../views/image_view");
 const { settingsView } = require("../views/settings_view");
 const { trendingView } = require("../views/trending_view");
 const { marketView, singleMarketView } = require("../views/market_view");
-
-const ssbRef = require("../server/node_modules/ssb-ref");
+const { aiView } = require("../views/AI_view");
 
 let sharp;
 
@@ -506,25 +517,40 @@ router
     ctx.body = await publicPopular({ period });
    }) 
    
-   // pixelArt
-  .get('/pixelia', async (ctx) => {
-    const pixelArt = await pixeliaModel.listPixels();
-    ctx.body = pixeliaView(pixelArt);
-  })
-  // modules
+   // modules
   .get("/modules", async (ctx) => {
     const configMods = getConfig().modules;
     const modules = [
     'popular', 'topics', 'summaries', 'latest', 'threads', 'multiverse', 'invites', 'wallet', 
     'legacy', 'cipher', 'bookmarks', 'videos', 'docs', 'audios', 'tags', 'images', 'trending', 
     'events', 'tasks', 'market', 'tribes', 'governance', 'reports', 'opinions', 'transfers', 
-    'feed', 'pixelia', 'agenda'
+    'feed', 'pixelia', 'agenda', 'ai'
     ];
     const moduleStates = modules.reduce((acc, mod) => {
       acc[`${mod}Mod`] = configMods[`${mod}Mod`];
       return acc;
     }, {});
     ctx.body = modulesView(moduleStates);
+  })
+   // AI
+  .get('/ai', async (ctx) => {
+    const aiMod = ctx.cookies.get("aiMod") || 'on';
+    if (aiMod !== 'on') {
+      ctx.redirect('/modules');
+      return;
+    }
+    startAI();
+    ctx.body = aiView();
+  })
+   // pixelArt
+  .get('/pixelia', async (ctx) => {
+    const pixeliaMod = ctx.cookies.get("pixeliaMod") || 'on';
+    if (pixeliaMod !== 'on') {
+      ctx.redirect('/modules');
+      return;
+    }
+    const pixelArt = await pixeliaModel.listPixels();
+    ctx.body = pixeliaView(pixelArt);
   })
   .get("/public/latest", async (ctx) => {
     const latestMod = ctx.cookies.get("latestMod") || 'on';
@@ -1260,6 +1286,16 @@ router
   })
 
   //POST backend routes   
+  .post('/ai', koaBody(), async (ctx) => {
+    const axios = require('../server/node_modules/axios').default;
+    const { input } = ctx.request.body;
+    if (!input) {
+      return ctx.status = 400, ctx.body = { error: 'No input provided' };
+    }
+    const response = await axios.post('http://localhost:4001/ai', { input });
+    const aiResponse = response.data.answer;
+    ctx.body = aiView(aiResponse, input);
+  })
   .post('/pixelia/paint', koaBody(), async (ctx) => {
     const { x, y, color } = ctx.request.body;
     if (x < 1 || x > 50 || y < 1 || y > 200) {
@@ -2085,7 +2121,7 @@ router
   })
 
   // UPDATE OASIS
- .post("/update", koaBody(), async (ctx) => {
+  .post("/update", koaBody(), async (ctx) => {
     const util = require("node:util");
     const exec = util.promisify(require("node:child_process").exec);
     async function updateTool() {
@@ -2093,11 +2129,15 @@ router
       console.log("oasis@version: updating Oasis...");
       console.log(stdout);
       console.log(stderr);
+      const { stdout: shOut, stderr: shErr } = await exec("sh install.sh");
+      console.log("oasis@version: running install.sh...");
+      console.log(shOut);
+      console.error(shErr);
     }
     await updateTool();
     const referer = new URL(ctx.request.header.referer);
     ctx.redirect(referer.href);
-  })
+  }) 
   .post("/settings/theme", koaBody(), async (ctx) => {
     const theme = String(ctx.request.body.theme);
     const currentConfig = getConfig();
@@ -2153,7 +2193,7 @@ router
     'popular', 'topics', 'summaries', 'latest', 'threads', 'multiverse', 'invites', 'wallet',
     'legacy', 'cipher', 'bookmarks', 'videos', 'docs', 'audios', 'tags', 'images', 'trending',
     'events', 'tasks', 'market', 'tribes', 'governance', 'reports', 'opinions', 'transfers',
-    'feed', 'pixelia', 'agenda'
+    'feed', 'pixelia', 'agenda', 'ai'
     ];
     const currentConfig = getConfig();
     modules.forEach(mod => {

+ 2 - 2
src/backend/updater.js

@@ -95,7 +95,7 @@ exports.getRemoteVersion = async () => {
       diffVersion(data, (status) => {
         if (status === "required" && !printed) {
           printed = true; 
-          console.log("\noasis@version: new code updates are available:\n\n1) Run Oasis and go to 'Settings' tab\n2) Click at 'Get updates' button to download latest code\n3) Restart Oasis when finished\n");
+          console.log("\noasis@version: new code updates are available!\n\n1) Run Oasis and go to 'Settings' tab\n2) Click at 'Get updates' button to download latest code\n3) Restart Oasis when finished\n");
         } else if (status === "") {
           console.log("\noasis@version: no updates requested.\n");
         }
@@ -108,7 +108,7 @@ exports.getRemoteVersion = async () => {
           diffVersion(data, (status) => {
             if (status === "required" && !printed) {
               printed = true; 
-              console.log("\noasis@version: new code updates are available:\n\n1) Run Oasis and go to 'Settings' tab\n2) Click at 'Get updates' button to download latest code\n3) Restart Oasis when finished\n");
+              console.log("\noasis@version: new code updates are available!\n\n1) Run Oasis and go to 'Settings' tab\n2) Click at 'Get updates' button to download latest code\n3) Restart Oasis when finished\n");
             } else {
               console.log("oasis@version: no updates requested.\n");
             }

+ 11 - 1
src/client/assets/translations/oasis_en.js

@@ -1296,6 +1296,14 @@ module.exports = {
     statsSize: "Total (size)",
     statsBlockchainSize: "Blockchain (size)",
     statsBlobsSize: "Blobs (size)",
+    //AI
+    ai: "AI",
+    aiTitle: "AI",
+    aiDescription: "A Collective Artificial Intelligence (CAI) called '42' that learns from your network.",
+    aiInputPlaceholder: "What's up?",
+    aiUserQuestion: "Your Question",
+    aiResponseTitle: "AI Reply",
+    aiSubmitButton: "Send!",
     //market
     marketMineSectionTitle: "Your Items",
     marketCreateSectionTitle: "Create a New Item",
@@ -1406,7 +1414,9 @@ module.exports = {
     modulesPixeliaLabel: "Pixelia",
     modulesPixeliaDescription: "Module to draw on a collaborative grid.",
     modulesAgendaLabel: "Agenda",
-    modulesAgendaDescription: "Module to manage all your assigned items."
+    modulesAgendaDescription: "Module to manage all your assigned items.",
+    modulesAILabel: "AI",
+    modulesAIDescription: "Module to talk with a LLM called '42'."   
      
      //END
     }

+ 11 - 1
src/client/assets/translations/oasis_es.js

@@ -1295,6 +1295,14 @@ module.exports = {
     statsSize: "Total (tamaño)",
     statsBlockchainSize: "Blockchain (tamaño)",
     statsBlobsSize: "Blobs (tamaño)",
+    //AI
+    ai: "IA",
+    aiTitle: "IA",
+    aiDescription: "Una Inteligencia Artificial Colectiva (IAC) llamada '42' que aprende de tu red.",
+    aiInputPlaceholder: "Qué quieres saber?",
+    aiUserQuestion: "Tu pregunta",
+    aiResponseTitle: "Respuesta",
+    aiSubmitButton: "Enviar!",
     //market
     marketMineSectionTitle: "Tus Artículos",
     marketCreateSectionTitle: "Crear un Nuevo Artículo",
@@ -1405,7 +1413,9 @@ module.exports = {
     modulesPixeliaLabel: "Pixelia",
     modulesPixeliaDescription: "Módulo para dibujar en una cuadrícula colaborativa.",
     modulesAgendaLabel: "Agenda",
-    modulesAgendaDescription: "Módulo para gestionar todos tus elementos asignados."
+    modulesAgendaDescription: "Módulo para gestionar todos tus elementos asignados.",
+    modulesAILabel: "AI",
+    modulesAIDescription: "Módulo para hablar con un LLM llamado '42'."   
      
      //END
     }

+ 11 - 1
src/client/assets/translations/oasis_eu.js

@@ -1296,6 +1296,14 @@ module.exports = {
     statsSize: "Guztira (taimaina)",
     statsBlockchainSize: "Blockchain (tamaina)",
     statsBlobsSize: "Blob-ak (tamaina)",
+    //IA
+    ai: "IA",
+    aiTitle: "IA",
+    aiDescription: "Zure saretik ikasten duen '42' izeneko Adimen Artifizial Kolektibo (AIA) bat.",
+    aiInputPlaceholder: "Zer jakin nahi duzu?",
+    aiUserQuestion: "Zure galdera",
+    aiResponseTitle: "Erantzuna",
+    aiSubmitButton: "Bidali!",
     //market
     marketMineSectionTitle: "Zure Elementuak",
     marketCreateSectionTitle: "Sortu Elementu Berria",
@@ -1343,6 +1351,7 @@ module.exports = {
     marketItemSeller: "Saltzailea",
     marketNoItems: "Elementurik ez, oraindik.",
     marketYourBid: "Zeure eskaintza",
+    marketCreateFormImageLabel: "Igo Irudia (jpeg, jpg, png, gif) (gehienez: 500px x 400px)",
     //modules
     modulesModuleName: "Izena",
     modulesModuleDescription: "Deskribapena",
@@ -1406,7 +1415,8 @@ module.exports = {
     modulesPixeliaDescription: "Lauki kolaboratibo batean marrazteko modulua.",
     modulesAgendaLabel: "Agenda",
     modulesAgendaDescription: "Esleitu zaizkizun elementu guztiak kudeatzeko modulua.",
-    marketCreateFormImageLabel: "Igo Irudia (jpeg, jpg, png, gif) (gehienez: 500px x 400px)",
+    modulesAILabel: "AI",
+    modulesAIDescription: "'42' izeneko LLM batekin hitz egiteko modulua."
 
      //END
   }

+ 2 - 1
src/configs/config-manager.js

@@ -36,7 +36,8 @@ if (!fs.existsSync(configFilePath)) {
       "transfersMod": "on",
       "feedMod": "on",
       "pixeliaMod": "on",
-      "agendaMod": "on"
+      "agendaMod": "on",
+      "aiMod": "on"
     },
     "wallet": {
       "url": "http://localhost:7474",

+ 2 - 1
src/configs/oasis-config.json

@@ -30,7 +30,8 @@
     "transfersMod": "on",
     "feedMod": "on",
     "pixeliaMod": "on",
-    "agendaMod": "on"
+    "agendaMod": "on",
+    "aiMod": "on"
   },
   "wallet": {
     "url": "http://localhost:7474",

File diff suppressed because it is too large
+ 2645 - 333
src/server/package-lock.json


+ 5 - 2
src/server/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@krakenslab/oasis",
-  "version": "0.3.7",
+  "version": "0.3.8",
   "description": "Oasis Social Networking Project Utopia",
   "repository": {
     "type": "git",
@@ -29,14 +29,16 @@
     "abstract-level": "^2.0.1",
     "archiver": "^7.0.1",
     "await-exec": "^0.1.2",
-    "axios": "^1.7.9",
+    "axios": "^1.10.0",
     "base64-url": "^2.3.3",
     "broadcast-stream": "^0.2.1",
     "caller-path": "^4.0.0",
+    "cors": "^2.8.5",
     "crypto": "^1.0.1",
     "debug": "^4.3.1",
     "env-paths": "^2.2.1",
     "epidemic-broadcast-trees": "^9.0.4",
+    "express": "^5.1.0",
     "file-type": "^16.5.4",
     "gpt-3-encoder": "^1.1.4",
     "has-network": "0.0.1",
@@ -63,6 +65,7 @@
     "muxrpc-validation": "^3.0.2",
     "muxrpcli": "^3.1.2",
     "node-iframe": "^1.8.5",
+    "node-llama-cpp": "^3.10.0",
     "non-private-ip": "^2.2.0",
     "open": "^8.4.2",
     "packet-stream": "^2.0.6",

+ 29 - 0
src/views/AI_view.js

@@ -0,0 +1,29 @@
+const { div, h2, p, section, button, form, textarea, br, span } = require("../server/node_modules/hyperaxe");
+const { template, i18n } = require('./main_views');
+
+exports.aiView = (response = '', userQuestion = '') => {
+  return template(
+    i18n.aiTitle,
+    section(
+      div({ class: "tags-header" },
+        h2(i18n.aiTitle),
+        p(i18n.aiDescription),
+        form({ method: 'POST', action: '/ai' },
+          textarea({ name: 'input', placeholder: i18n.aiInputPlaceholder, required: true }),
+          br(),
+          button({ type: 'submit' }, i18n.aiSubmitButton)
+        ),
+        br(),
+        userQuestion ? div({ class: 'user-question' },
+          h2(`${i18n.aiUserQuestion}:`),
+          userQuestion
+        ) : null,
+
+        response ? div({ class: 'ai-response' },
+          h2(`${i18n.aiResponseTitle}:`),
+          response
+        ) : null
+      )
+    )
+  );
+};

+ 1 - 2
src/views/agenda_view.js

@@ -68,9 +68,8 @@ const renderAgendaItem = (item, userId, filter) => {
 
   if (item.type === 'tribe') {
     details = [
-      renderCardField(i18n.agendaDescriptionLabel + ":",item.description || i18n.noDescription),
       renderCardField(i18n.agendaAnonymousLabel + ":", item.isAnonymous ? i18n.agendaYes : i18n.agendaNo),
-      renderCardField(i18n.agendaInviteModeLabel + ":", item.inviteMode || i18n.noInviteMode),
+      renderCardField(i18n.agendaInviteModeLabel + ":", item.inviteMode.toUpperCase() || i18n.noInviteMode),
       renderCardField(i18n.agendaLARPLabel + ":", item.isLARP ? i18n.agendaYes : i18n.agendaNo),
       renderCardField(i18n.agendaLocationLabel + ":", item.location || i18n.noLocation),
       renderCardField(i18n.agendaMembersCount + ":", item.members.length || 0),

+ 10 - 1
src/views/main_views.js

@@ -325,6 +325,15 @@ const renderAgendaLink = () => {
     : '';
 };
 
+const renderAILink = () => {
+  const aiMod = getConfig().modules.aiMod === 'on';
+  return aiMod 
+    ? [
+      navLink({ href: "/ai", emoji: "ꘜ", text: i18n.ai, class: "ai-link enabled" }),
+      ]
+    : '';
+};
+
 const renderEventsLink = () => {
   const eventsMod = getConfig().modules.eventsMod === 'on';
   return eventsMod 
@@ -389,8 +398,8 @@ const template = (titlePrefix, ...elements) => {
              renderCipherLink(),
              navLink({ href: "/pm", emoji: "ꕕ", text: i18n.privateMessage }),
              navLink({ href: "/publish", emoji: "❂", text: i18n.publish }),
-             //navLink({ href: "/ai", emoji: "ꘜ", text: i18n.ai }),
              renderTagsLink(),
+             renderAILink(),
              navLink({ href: "/search", emoji: "ꔅ", text: i18n.search })
              )
           ),

+ 1 - 0
src/views/modules_view.js

@@ -6,6 +6,7 @@ const modulesView = () => {
   const config = getConfig().modules;
   const modules = [
     { name: 'agenda', label: i18n.modulesAgendaLabel, description: i18n.modulesAgendaDescription },
+    { name: 'ai', label: i18n.modulesAILabel, description: i18n.modulesAIDescription },
     { name: 'audios', label: i18n.modulesAudiosLabel, description: i18n.modulesAudiosDescription },
     { name: 'bookmarks', label: i18n.modulesBookmarksLabel, description: i18n.modulesBookmarksDescription },
     { name: 'cipher', label: i18n.modulesCipherLabel, description: i18n.modulesCipherDescription },