File size: 4,045 Bytes
9705b6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
const Keyv = require('keyv');
const express = require('express');
const router = express.Router();
const { MeiliSearch } = require('meilisearch');
const { Message } = require('../../models/Message');
const { Conversation, getConvosQueried } = require('../../models/Conversation');
const { reduceHits } = require('../../lib/utils/reduceHits');
const { cleanUpPrimaryKeyValue } = require('../../lib/utils/misc');
const requireJwtAuth = require('../middleware/requireJwtAuth');
const keyvRedis = require('../../cache/keyvRedis');
const { isEnabled } = require('../utils');

const expiration = 60 * 1000;
const cache = isEnabled(process.env.USE_REDIS)
  ? new Keyv({ store: keyvRedis })
  : new Keyv({ namespace: 'search', ttl: expiration });

router.use(requireJwtAuth);

router.get('/sync', async function (req, res) {
  await Message.syncWithMeili();
  await Conversation.syncWithMeili();
  res.send('synced');
});

router.get('/', async function (req, res) {
  try {
    let user = req.user.id ?? '';
    const { q } = req.query;
    const pageNumber = req.query.pageNumber || 1;
    const key = `${user}:search:${q}`;
    const cached = await cache.get(key);
    if (cached) {
      console.log('cache hit', key);
      const { pages, pageSize, messages } = cached;
      res
        .status(200)
        .send({ conversations: cached[pageNumber], pages, pageNumber, pageSize, messages });
      return;
    }

    // const message = await Message.meiliSearch(q);
    const messages = (
      await Message.meiliSearch(
        q,
        {
          attributesToHighlight: ['text'],
          highlightPreTag: '**',
          highlightPostTag: '**',
        },
        true,
      )
    ).hits.map((message) => {
      const { _formatted, ...rest } = message;
      return {
        ...rest,
        searchResult: true,
        text: _formatted.text,
      };
    });
    const titles = (await Conversation.meiliSearch(q)).hits;
    const sortedHits = reduceHits(messages, titles);
    // debugging:
    // console.log('user:', user, 'message hits:', messages.length, 'convo hits:', titles.length);
    // console.log('sorted hits:', sortedHits.length);
    const result = await getConvosQueried(user, sortedHits, pageNumber);

    const activeMessages = [];
    for (let i = 0; i < messages.length; i++) {
      let message = messages[i];
      if (message.conversationId.includes('--')) {
        message.conversationId = cleanUpPrimaryKeyValue(message.conversationId);
      }
      if (result.convoMap[message.conversationId]) {
        const convo = result.convoMap[message.conversationId];
        const { title, chatGptLabel, model } = convo;
        message = { ...message, ...{ title, chatGptLabel, model } };
        activeMessages.push(message);
      }
    }
    result.messages = activeMessages;
    if (result.cache) {
      result.cache.messages = activeMessages;
      cache.set(key, result.cache, expiration);
      delete result.cache;
    }
    delete result.convoMap;
    // for debugging
    // console.log(result, messages.length);
    res.status(200).send(result);
  } catch (error) {
    console.log(error);
    res.status(500).send({ message: 'Error searching' });
  }
});

router.get('/test', async function (req, res) {
  const { q } = req.query;
  const messages = (
    await Message.meiliSearch(q, { attributesToHighlight: ['text'] }, true)
  ).hits.map((message) => {
    const { _formatted, ...rest } = message;
    return { ...rest, searchResult: true, text: _formatted.text };
  });
  res.send(messages);
});

router.get('/enable', async function (req, res) {
  let result = false;
  try {
    const client = new MeiliSearch({
      host: process.env.MEILI_HOST,
      apiKey: process.env.MEILI_MASTER_KEY,
    });

    const { status } = await client.health();
    // console.log(`Meilisearch: ${status}`);
    result = status === 'available' && !!process.env.SEARCH;
    return res.send(result);
  } catch (error) {
    // console.error(error);
    return res.send(false);
  }
});

module.exports = router;