diff --git a/websites/G/Grok/Grok.json b/websites/G/Grok/Grok.json index 82835b68e820..3b9240f2d691 100644 --- a/websites/G/Grok/Grok.json +++ b/websites/G/Grok/Grok.json @@ -1,22 +1,34 @@ { "grok.talkingWithAI": { "description": "Displayed when the user is engaging in a conversation with the AI.", - "message": "Talking with AI about something" + "message": "Talking with Grok" }, - "grok.aiResponding": { - "description": "Displayed when the AI is generating a response.", - "message": "AI is responding..." - }, - "grok.conversationStats": { - "description": "Displayed to show the number of times the user asked a question and total words used, e.g., 'asked 3 times | 150 words'.", - "message": "asked {0} times | {1} words" + "grok.replyingTo": { + "description": "Displayed as the state line under the chat title. {0} is the user's latest prompt.", + "message": "Replying to: \"{0}\"" }, "grok.startNewConversation": { - "description": "Button text to start a new conversation.", + "description": "Displayed when the user is on the new chat screen.", "message": "Start new conversation" }, "grok.thinkingOfPrompt": { - "description": "Displayed when the AI is thinking of a new prompt.", + "description": "Displayed when the user is thinking of a new prompt.", "message": "Thinking of a new prompt..." + }, + "grok.creatingImages": { + "description": "Displayed when the user is on Grok Imagine.", + "message": "Creating with Imagine" + }, + "grok.viewingFiles": { + "description": "Displayed when the user is browsing files or the library.", + "message": "Browsing files" + }, + "grok.browsingProjects": { + "description": "Displayed when the user is in projects or workspaces.", + "message": "Browsing projects" + }, + "grok.readingSharedChat": { + "description": "Displayed when the user is viewing a shared conversation.", + "message": "Reading a shared chat" } } diff --git a/websites/G/Grok/assets.ts b/websites/G/Grok/assets.ts new file mode 100644 index 000000000000..47c72cc4f3cc --- /dev/null +++ b/websites/G/Grok/assets.ts @@ -0,0 +1,9 @@ +export enum ActivityAssets { + Logo = 'https://i.imgur.com/KygGP1w.png', + Fast = 'https://i.imgur.com/uJSu7il.png', + Auto = 'https://i.imgur.com/S5ACb1x.png', + Expert = 'https://i.imgur.com/FiP0gX4.png', + Heavy = 'https://i.imgur.com/5miQ3Fc.png', + Build = 'https://i.imgur.com/IxrTU1f.png', + Voice = 'https://i.imgur.com/OvDt2f1.png', +} diff --git a/websites/G/Grok/conversation.ts b/websites/G/Grok/conversation.ts new file mode 100644 index 000000000000..ff36bbd42699 --- /dev/null +++ b/websites/G/Grok/conversation.ts @@ -0,0 +1,212 @@ +export interface ConversationInfo { + id: string | null + title: string | null + lastPrompt: string | null + isGenerating: boolean +} + +interface ConversationCache { + id: string + title: string | null + lastPrompt: string | null + fetchedAt: number +} + +const FETCH_INTERVAL = 12_000 +let cache: ConversationCache | null = null +let inflight: Promise | null = null + +function collapse(text: string): string { + return text.replace(/\s+/g, ' ').trim() +} + +function truncate(text: string, max: number): string { + const value = collapse(text) + if (value.length <= max) + return value + return `${value.slice(0, Math.max(0, max - 1))}…` +} + +export function getConversationId(pathname: string): string | null { + const match = pathname.match(/^\/(?:a\/)?(?:c|chat)\/([^/?#]+)/) + const id = match?.[1] + if (!id || id === 'c' || id === 'chat') + return null + try { + return decodeURIComponent(id) + } + catch { + return id + } +} + +function titleFromDocument(): string | null { + const raw = document.title + .replace(/\s*[—–|·-]\s*Grok\s*$/i, '') + .replace(/^Grok\s*[—–|·-]\s*/i, '') + .trim() + if (!raw || /^grok$/i.test(raw)) + return null + return raw +} + +function titleFromSidebar(id: string): string | null { + const selectors = [ + `a[href="/c/${CSS.escape(id)}"]`, + `a[href="/chat/${CSS.escape(id)}"]`, + ] + for (const selector of selectors) { + const text = document.querySelector(selector)?.textContent + if (text && collapse(text)) + return collapse(text) + } + + for (const link of document.querySelectorAll('a[href^="/c/"], a[href^="/chat/"]')) { + if (link.href.includes(id) && link.textContent) + return collapse(link.textContent) + } + return null +} + +function lastPromptFromDom(): string | null { + const bubbles = document.querySelectorAll( + '.message-bubble.user-message, .user-message .message-bubble, .user-message', + ) + for (let i = bubbles.length - 1; i >= 0; i--) { + const clone = bubbles[i]!.cloneNode(true) as HTMLElement + for (const noise of clone.querySelectorAll('button, svg, nav, time, [aria-hidden="true"]')) + noise.remove() + const text = collapse(clone.textContent ?? '') + if (text) + return text + } + return null +} + +export function isGenerating(): boolean { + if (document.querySelector('[id="model-select-trigger"]') && document.querySelector('.animate-gaussian')) + return true + if (document.querySelector('.animate-gaussian')) + return true + const abort = document.querySelector( + '[aria-label="Stop model response"], [aria-label*="Stop model"], [aria-label*="Stop generating"]', + ) + return !!abort +} + +async function fetchConversationFallback(id: string): Promise { + if (cache?.id === id && Date.now() - cache.fetchedAt < FETCH_INTERVAL) + return + if (inflight) + return inflight + + inflight = (async () => { + const next: ConversationCache = { + id, + title: cache?.id === id ? cache.title : null, + lastPrompt: cache?.id === id ? cache.lastPrompt : null, + fetchedAt: Date.now(), + } + + try { + const detail = await fetch( + `https://grok.com/rest/app-chat/conversations_v2/${encodeURIComponent(id)}`, + { credentials: 'include', headers: { Accept: 'application/json' } }, + ) + if (detail.ok) { + const payload = await detail.json() as { + conversation?: { title?: string } + } + if (payload.conversation?.title) + next.title = collapse(payload.conversation.title) + } + } + catch { + // Title API is optional; DOM title is used when this fails. + } + + try { + const nodesResponse = await fetch( + `https://grok.com/rest/app-chat/conversations/${encodeURIComponent(id)}/response-node`, + { credentials: 'include', headers: { Accept: 'application/json' } }, + ) + if (nodesResponse.ok) { + const nodesPayload = await nodesResponse.json() as { + responseNodes?: Array<{ responseId?: string, sender?: string }> + } + const responseIds = (nodesPayload.responseNodes ?? []) + .map(node => node.responseId) + .filter((value): value is string => !!value) + .slice(-12) + + if (responseIds.length) { + const loaded = await fetch( + `https://grok.com/rest/app-chat/conversations/${encodeURIComponent(id)}/load-responses`, + { + method: 'POST', + credentials: 'include', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ responseIds }), + }, + ) + if (loaded.ok) { + const loadedPayload = await loaded.json() as { + responses?: Array<{ sender?: string, message?: string }> + } + const human = (loadedPayload.responses ?? []) + .filter(response => /^(?:human|user)$/i.test(response.sender ?? '')) + .at(-1) + if (human?.message) + next.lastPrompt = collapse(human.message) + } + } + } + } + catch { + // Prompt API is optional; DOM messages are used when this fails. + } + + cache = next + })().finally(() => { + inflight = null + }) + + return inflight +} + +export async function getConversationInfo(pathname: string): Promise { + const id = getConversationId(pathname) + if (!id) { + cache = null + return { + id: null, + title: titleFromDocument(), + lastPrompt: lastPromptFromDom(), + isGenerating: isGenerating(), + } + } + + const title = titleFromSidebar(id) ?? titleFromDocument() + const lastPrompt = lastPromptFromDom() + + if (!title || !lastPrompt) + await fetchConversationFallback(id) + + return { + id, + title: title ?? (cache?.id === id ? cache.title : null), + lastPrompt: lastPrompt ?? (cache?.id === id ? cache.lastPrompt : null), + isGenerating: isGenerating(), + } +} + +export function formatPrompt(prompt: string): string { + return truncate(prompt, 96) +} + +export function formatTitle(title: string): string { + return truncate(title, 128) +} diff --git a/websites/G/Grok/functions/fetchResponsesMetadata.ts b/websites/G/Grok/functions/fetchResponsesMetadata.ts deleted file mode 100644 index a8b1915163ab..000000000000 --- a/websites/G/Grok/functions/fetchResponsesMetadata.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type { LoadResponses, ResponseNode } from '../types.js' -import { isEqual } from 'lodash' -import pLimit from 'p-limit' - -const limit = pLimit(1) - -export const responsesMetadata: { - url: string - data: { - responseNode?: ResponseNode - loadResponses?: LoadResponses - } - request: { - responseNode?: { - lastTimestamp: number - rateLimited: boolean - } - loadResponses?: { - lastTimestamp: number - rateLimited: boolean - } - } -} = { - url: document.location.href, - data: {}, - request: {}, -} - -export async function fetchResponsesMetadata(id: string): Promise { - await limit(async () => { - if ( - responsesMetadata.request.responseNode - && responsesMetadata.request.responseNode.lastTimestamp + 10000 > Date.now() - ) { - return - } - - if ( - responsesMetadata.request.responseNode?.rateLimited - && responsesMetadata.request.responseNode.lastTimestamp + 60000 > Date.now() - ) { - return - } - - const responseNodeResponse = await fetch( - `https://grok.com/rest/app-chat/conversations/${id}/response-node`, - ) - const responseNodeLastRequestTimestamp = Date.now() - - if (!responseNodeResponse.ok) { - if (responseNodeResponse.status === 429) { - responsesMetadata.request.responseNode = { - lastTimestamp: responseNodeLastRequestTimestamp, - rateLimited: true, - } - } - else { - responsesMetadata.request.responseNode = { - lastTimestamp: responseNodeLastRequestTimestamp, - rateLimited: false, - } - } - return - } - responsesMetadata.request.responseNode = { - lastTimestamp: responseNodeLastRequestTimestamp, - rateLimited: false, - } - const responseNode: ResponseNode = await responseNodeResponse.json() - - if ( - responsesMetadata.url === document.location.href - && responsesMetadata?.data.responseNode - && isEqual( - responsesMetadata.data.responseNode.responseNodes.map( - node => node.responseId, - ), - responseNode.responseNodes.map(node => node.responseId), - ) - ) { - return - } - - responsesMetadata.url = document.location.href - responsesMetadata.data.responseNode = responseNode - - if ( - responsesMetadata.request.loadResponses - && responsesMetadata.request.loadResponses.lastTimestamp + 10000 > Date.now() - ) { - return - } - - if ( - responsesMetadata.request.loadResponses?.rateLimited - && responsesMetadata.request.loadResponses.lastTimestamp + 60000 > Date.now() - ) { - return - } - - const loadResponsesResponse = await fetch( - `https://grok.com/rest/app-chat/conversations/${id}/load-responses`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - responseIds: responseNode.responseNodes.map( - node => node.responseId, - ), - }), - }, - ) - const loadResponsesLastRequestTimestamp = Date.now() - - if (!loadResponsesResponse.ok) { - if (loadResponsesResponse.status === 429) { - responsesMetadata.request.loadResponses = { - lastTimestamp: loadResponsesLastRequestTimestamp, - rateLimited: true, - } - } - else { - responsesMetadata.request.loadResponses = { - lastTimestamp: loadResponsesLastRequestTimestamp, - rateLimited: false, - } - } - return - } - responsesMetadata.request.loadResponses = { - lastTimestamp: loadResponsesLastRequestTimestamp, - rateLimited: false, - } - const loadResponses: LoadResponses = await loadResponsesResponse.json() - - responsesMetadata.url = document.location.href - responsesMetadata.data.loadResponses = loadResponses - }) -} - -export function clearResponsesMetadata(): void { - responsesMetadata.url = document.location.href - responsesMetadata.data = {} - responsesMetadata.request = {} -} diff --git a/websites/G/Grok/metadata.json b/websites/G/Grok/metadata.json index a1a707340098..e00dd7c21056 100644 --- a/websites/G/Grok/metadata.json +++ b/websites/G/Grok/metadata.json @@ -5,6 +5,12 @@ "id": "1180088441459978261", "name": "kohnoselami" }, + "contributors": [ + { + "name": "caffeinepx", + "id": "1217564834825109604" + } + ], "service": "Grok", "description": { "en": "Grok is a generative artificial intelligence chatbot developed by xAI. Based on the large language model (LLM) of the same name, it was launched in 2023 as an initiative by Elon Musk. The chatbot is advertised as having a \"sense of humor\" and direct access to X, formerly known as Twitter.", @@ -12,10 +18,10 @@ }, "url": "grok.com", "regExp": "^https?[:][/][/]([a-z0-9-]+[.])*grok[.]com[/]", - "version": "1.1.2", - "logo": "https://cdn.rcd.gg/PreMiD/websites/G/Grok/assets/logo.png", + "version": "1.2.0", + "logo": "https://i.imgur.com/KygGP1w.png", "thumbnail": "https://cdn.rcd.gg/PreMiD/websites/G/Grok/assets/thumbnail.png", - "color": "#1D1E20", + "color": "#050505", "category": "other", "tags": [ "ai", @@ -34,6 +40,12 @@ "title": "Show chat title", "icon": "fa-solid fa-message-quote", "value": true + }, + { + "id": "showLastPrompt", + "title": "Show last prompt", + "icon": "fa-solid fa-reply", + "value": true } ] } diff --git a/websites/G/Grok/modes.ts b/websites/G/Grok/modes.ts new file mode 100644 index 000000000000..f352963ce24a --- /dev/null +++ b/websites/G/Grok/modes.ts @@ -0,0 +1,52 @@ +import { ActivityAssets } from './assets.js' + +export interface GrokMode { + id: 'fast' | 'auto' | 'expert' | 'heavy' | 'build' | 'voice' + title: string +} + +const MODE_TITLES: Record = { + fast: 'Fast', + auto: 'Auto', + expert: 'Expert', + heavy: 'Heavy', + build: 'Build', + voice: 'Voice', +} + +export const MODE_ASSETS: Record = { + fast: ActivityAssets.Fast, + auto: ActivityAssets.Auto, + expert: ActivityAssets.Expert, + heavy: ActivityAssets.Heavy, + build: ActivityAssets.Build, + voice: ActivityAssets.Voice, +} + +function matchMode(text: string): GrokMode | null { + const value = text.toLowerCase() + const ids: GrokMode['id'][] = ['expert', 'heavy', 'build', 'voice', 'fast', 'auto'] + for (const id of ids) { + if (new RegExp(`\\b${id}\\b`, 'i').test(value)) + return { id, title: MODE_TITLES[id] } + } + return null +} + +export function getGrokMode(pathname: string): GrokMode | null { + if (pathname.startsWith('/voice') || document.querySelector('[class*="voice-bar"]')) + return { id: 'voice', title: MODE_TITLES.voice } + + const trigger = document.querySelector('#model-select-trigger') + if (trigger) { + const titled = trigger.querySelector('.truncate, .font-semibold')?.textContent ?? '' + const fromTitle = matchMode(titled) + if (fromTitle) + return fromTitle + const fromTrigger = matchMode(trigger.textContent ?? '') + if (fromTrigger) + return fromTrigger + } + + return null +} diff --git a/websites/G/Grok/package-lock.json b/websites/G/Grok/package-lock.json deleted file mode 100644 index 4bca87a1d6ff..000000000000 --- a/websites/G/Grok/package-lock.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "name": "Grok", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "franc-min": "^6.2.0", - "lodash": "^4.18.1", - "p-limit": "^7.3.0" - }, - "devDependencies": { - "@types/lodash": "^4.17.24" - } - }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/franc-min": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/franc-min/-/franc-min-6.2.0.tgz", - "integrity": "sha512-1uDIEUSlUZgvJa2AKYR/dmJC66v/PvGQ9mWfI9nOr/kPpMFyvswK0gPXOwpYJYiYD008PpHLkGfG58SPjQJFxw==", - "license": "MIT", - "dependencies": { - "trigram-utils": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, - "node_modules/n-gram": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/n-gram/-/n-gram-2.0.2.tgz", - "integrity": "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/p-limit": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", - "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.2.1" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/trigram-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/trigram-utils/-/trigram-utils-2.0.1.tgz", - "integrity": "sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ==", - "license": "MIT", - "dependencies": { - "collapse-white-space": "^2.0.0", - "n-gram": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/websites/G/Grok/package.json b/websites/G/Grok/package.json deleted file mode 100644 index 228a6825f54b..000000000000 --- a/websites/G/Grok/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "type": "module", - "dependencies": { - "franc-min": "^6.2.0", - "lodash": "^4.18.1", - "p-limit": "^7.3.0" - }, - "devDependencies": { - "@types/lodash": "^4.17.24" - } -} diff --git a/websites/G/Grok/presence.ts b/websites/G/Grok/presence.ts index 0af66a72d7cb..c6dc15fb6ada 100644 --- a/websites/G/Grok/presence.ts +++ b/websites/G/Grok/presence.ts @@ -1,94 +1,142 @@ -import { franc } from 'franc-min' +import { ActivityType } from 'premid' +import { ActivityAssets } from './assets.js' import { - clearResponsesMetadata, - fetchResponsesMetadata, - responsesMetadata, -} from './functions/fetchResponsesMetadata.js' + formatPrompt, + formatTitle, + getConversationInfo, +} from './conversation.js' +import { getGrokMode, MODE_ASSETS } from './modes.js' const presence = new Presence({ clientId: '1350152994993209536', }) + +const sessionTimestamp = Math.floor(Date.now() / 1000) +const chatTimestamps = new Map() +let previousChatId: string | null = null + async function getStrings() { return presence.getStrings({ talkingWithAI: 'grok.talkingWithAI', - aiResponding: 'grok.aiResponding', - conversationStats: 'grok.conversationStats', startNewConversation: 'grok.startNewConversation', thinkingOfPrompt: 'grok.thinkingOfPrompt', + replyingTo: 'grok.replyingTo', + creatingImages: 'grok.creatingImages', + viewingFiles: 'grok.viewingFiles', + browsingProjects: 'grok.browsingProjects', + readingSharedChat: 'grok.readingSharedChat', + browsing: 'general.browsing', }) } -const browsingTimestamp = Math.floor(Date.now() / 1000) -let oldLang: string | null = null -let strings: Awaited> -enum ActivityAssets { - Logo = 'https://cdn.rcd.gg/PreMiD/websites/G/Grok/assets/logo.png', - Talking = 'https://cdn.rcd.gg/PreMiD/websites/G/Grok/assets/0.png', +function applyMode(presenceData: PresenceData, pathname: string): void { + const mode = getGrokMode(pathname) + if (!mode) + return + presenceData.smallImageKey = MODE_ASSETS[mode.id] + presenceData.smallImageText = mode.title +} + +function timestampFor(chatId: string | null, useChatTimer: boolean): number { + if (!chatId || !useChatTimer) + return sessionTimestamp + + let started = chatTimestamps.get(chatId) + if (!started) { + started = Math.floor(Date.now() / 1000) + chatTimestamps.set(chatId, started) + } + return started } presence.on('UpdateData', async () => { - const [lang, showTitle] = await Promise.all([ - presence.getSetting('lang').catch(() => 'en'), - presence.getSetting('showTitle'), + const [showTitle, showLastPrompt] = await Promise.all([ + presence.getSetting('showTitle').catch(() => true), + presence.getSetting('showLastPrompt').catch(() => true), ]) + const strings = await getStrings() + const { pathname } = document.location + const conversation = await getConversationInfo(pathname) + const useChatTimer = !!(conversation.id && (showTitle || showLastPrompt)) - if (oldLang !== lang) { - oldLang = lang - strings = await getStrings() + if (conversation.id !== previousChatId) { + previousChatId = conversation.id + if (conversation.id && !chatTimestamps.has(conversation.id)) + chatTimestamps.set(conversation.id, Math.floor(Date.now() / 1000)) } - const { pathname } = document.location const presenceData: PresenceData = { + type: ActivityType.Playing, largeImageKey: ActivityAssets.Logo, - startTimestamp: browsingTimestamp, + startTimestamp: timestampFor(conversation.id, useChatTimer), } - const conversationId = pathname.match( - /\/chat\/([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12})/, - )?.[1] + applyMode(presenceData, pathname) - if (conversationId) { - await fetchResponsesMetadata(conversationId) + const showDetails = showTitle || showLastPrompt - if (responsesMetadata.data.loadResponses) { - const isTalking - = document.getElementsByClassName('animate-gaussian').length > 0 + if (!showDetails) { + presence.setActivity(presenceData) + return + } - let wordCount = 0 - for (const response of responsesMetadata.data.loadResponses.responses) { - wordCount += Array.from( - new Intl.Segmenter(franc(response.message), { - granularity: 'word', - }).segment(response.message), - ).length + switch (true) { + case pathname.startsWith('/imagine'): + case pathname.startsWith('/images'): { + presenceData.details = strings.creatingImages + break + } + case pathname.startsWith('/files'): + case pathname.startsWith('/library'): { + presenceData.details = strings.viewingFiles + break + } + case pathname.startsWith('/project'): + case pathname.startsWith('/workspace'): { + presenceData.details = strings.browsingProjects + break + } + case pathname.startsWith('/share'): + case pathname.startsWith('/s/'): { + presenceData.details = showTitle + ? (conversation.title ? formatTitle(conversation.title) : strings.readingSharedChat) + : strings.readingSharedChat + if (showLastPrompt && conversation.lastPrompt) { + presenceData.state = strings.replyingTo.replace( + '{0}', + formatPrompt(conversation.lastPrompt), + ) + } + break + } + case !!conversation.id: + case pathname.startsWith('/voice'): { + if (showTitle) { + presenceData.details = conversation.title + ? formatTitle(conversation.title) + : strings.talkingWithAI + } + else if (showLastPrompt) { + presenceData.details = strings.talkingWithAI } - presenceData.details = showTitle - ? document.title.slice(0, -7) - : strings.talkingWithAI - presenceData.state = isTalking - ? strings.aiResponding - : strings.conversationStats - .replace( - '{0}', - `${ - responsesMetadata.data.loadResponses.responses.filter( - response => response.sender === 'human', - ).length - }`, - ) - .replace('{1}', `${wordCount}`) - presenceData.smallImageKey = isTalking ? ActivityAssets.Talking : null + if (showLastPrompt && conversation.lastPrompt) { + presenceData.state = strings.replyingTo.replace( + '{0}', + formatPrompt(conversation.lastPrompt), + ) + } + break } - else { + case pathname === '/' || pathname === '/chat' || pathname === '/c': { presenceData.details = strings.startNewConversation presenceData.state = strings.thinkingOfPrompt + break + } + default: { + presenceData.details = strings.browsing + break } - } - else { - clearResponsesMetadata() - presenceData.details = strings.startNewConversation - presenceData.state = strings.thinkingOfPrompt } presence.setActivity(presenceData) diff --git a/websites/G/Grok/types.ts b/websites/G/Grok/types.ts deleted file mode 100644 index de4dcbffe5c2..000000000000 --- a/websites/G/Grok/types.ts +++ /dev/null @@ -1,67 +0,0 @@ -export interface ResponseNode { - responseNodes: Array<{ - responseId: string - sender: string - parentResponseId?: string - }> - inflightResponses: Array -} - -export interface LoadResponses { - responses: Array<{ - responseId: string - message: string - sender: string - createTime: string - manual: boolean - partial: boolean - shared: boolean - query: string - queryType: string - webSearchResults: Array<{ - url: string - title: string - preview: string - searchEngineText: string - description: string - siteName: string - metadataTitle: string - creator: string - image: string - favicon: string - citationId: string - }> - xpostIds: Array - xposts: Array - generatedImageUrls: Array - imageAttachments: Array - fileAttachments: Array - cardAttachmentsJson: Array - fileUris: Array - fileAttachmentsMetadata: Array - isControl: boolean - steps: Array<{ - text: Array - tags: Array - webSearchResults: Array<{ - url: string - title: string - preview: string - searchEngineText: string - description: string - siteName: string - metadataTitle: string - creator: string - image: string - favicon: string - citationId: string - }> - xpostIds: Array - xposts: Array - }> - mediaTypes: Array - webpageUrls: Array - parentResponseId?: string - error?: string - }> -}