Skip to content

Commit

Permalink
Show YT's AI chat summary in a 'pinned'-style box if it is present
Browse files Browse the repository at this point in the history
  • Loading branch information
FlaminSarge committed Nov 2, 2024
1 parent 836aca3 commit fc7dc6e
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 7 deletions.
68 changes: 68 additions & 0 deletions src/components/ChatSummary.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts">
import { slide, fade } from 'svelte/transition';
import MessageRun from './MessageRuns.svelte';
import Tooltip from './common/Tooltip.svelte';
import Icon from 'smelte/src/components/Icon';
import { Theme } from '../ts/chat-constants';
import { createEventDispatcher } from 'svelte';
export let summary: Ytc.ParsedSummary;
let dismissed = false;
let shorten = false;
const classes = 'rounded inline-flex flex-col overflow-visible ' +
'bg-secondary-900 p-2 w-full text-white z-10 shadow';
const onShorten = () => { shorten = !shorten; };
$: if (summary) {
dismissed = false;
shorten = false;
}
const dispatch = createEventDispatcher();
$: dismissed, shorten, dispatch('resize');
</script>

{#if !dismissed}
<div
class={classes}
transition:fade={{ duration: 250 }}
>
<div class="flex flex-row items-center cursor-pointer" on:click={onShorten}>
<div class="font-medium tracking-wide text-white flex-1">
<span class="mr-1 inline-block" style="transform: translateY(3px);">
<Icon small>
{#if shorten}
expand_more
{:else}
expand_less
{/if}
</Icon>
</span>
{#each summary.header as run}
{#if run.type === 'text'}
<span class="align-middle">{run.text}</span>
{/if}
{/each}
</div>
<div class="flex-none self-end" style="transform: translateY(3px);">
<Tooltip offsetY={0} small>
<Icon
slot="activator"
class="cursor-pointer text-lg"
on:click={() => { dismissed = true; }}
>
close
</Icon>
Dismiss
</Tooltip>
</div>
</div>
{#if !shorten && !dismissed}
<div class="mt-1 whitespace-pre-line" transition:slide|local={{ duration: 300 }}>
<MessageRun runs={summary.summary} forceDark forceTLColor={Theme.DARK}/>
</div>
{/if}
</div>
{/if}
20 changes: 16 additions & 4 deletions src/components/Hyperchat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import WelcomeMessage from './WelcomeMessage.svelte';
import Message from './Message.svelte';
import PinnedMessage from './PinnedMessage.svelte';
import ChatSummary from './ChatSummary.svelte';
import PaidMessage from './PaidMessage.svelte';
import MembershipItem from './MembershipItem.svelte';
import ReportBanDialog from './ReportBanDialog.svelte';
Expand Down Expand Up @@ -53,6 +54,7 @@
let messageActions: (Chat.MessageAction | Welcome)[] = [];
const messageKeys = new Set<string>();
let pinned: Ytc.ParsedPinned | null;
let summary: Ytc.ParsedSummary | null;
let div: HTMLElement;
let isAtBottom = true;
let truncateInterval: number;
Expand Down Expand Up @@ -183,6 +185,9 @@
case 'delete':
onDelete(action.deletion);
break;
case 'summary':
summary = action;
break;
case 'pin':
pinned = action;
break;
Expand Down Expand Up @@ -393,11 +398,18 @@
</div>
{/each}
</div>
{#if pinned}
{#if summary || pinned}
<div class="absolute top-0 w-full" bind:this={topBar}>
<div class="mx-1.5 mt-1.5">
<PinnedMessage pinned={pinned} on:resize={topBarResized} />
</div>
{#if summary}
<div class="mx-1.5 mt-1.5">
<ChatSummary summary={summary} on:resize={topBarResized} />
</div>
{/if}
{#if pinned}
<div class="mx-1.5 mt-1.5">
<PinnedMessage pinned={pinned} on:resize={topBarResized} />
</div>
{/if}
</div>
{/if}
{#if !isAtBottom}
Expand Down
26 changes: 24 additions & 2 deletions src/ts/chat-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ const parseMessageRuns = (runs?: Ytc.MessageRun[]): Ytc.ParsedRun[] => {
return parsedRuns;
};

const parseChatSummary = (renderer: Ytc.AddChatItem, isEphemeral: boolean | undefined, bannerTimeoutMs: number | undefined): Ytc.ParsedSummary | undefined => {
const baseRenderer = renderer.liveChatBannerChatSummaryRenderer!;
const runs = parseMessageRuns(renderer.liveChatBannerChatSummaryRenderer?.chatSummary.runs);
if (runs[2].type === 'text') {
runs[2].text = '(' + runs[2].text + ')';
}
const item: Ytc.ParsedSummary = {
type: 'summary',
header: runs.slice(0, 3),
summary: runs.slice(4),
icon: baseRenderer.icon && {
iconType: baseRenderer.icon?.iconType.toLowerCase(),
},
id: baseRenderer.liveChatSummaryId,
showtime: isEphemeral ? (bannerTimeoutMs ?? 0) / 1000 : 0,
};
return item;
}

const parseAddChatItemAction = (action: Ytc.AddChatItemAction, isReplay = false, liveTimeoutOrReplayMs = 0): Ytc.ParsedMessage | undefined => {
const actionItem = action.item;
const renderer = actionItem.liveChatTextMessageRenderer ??
Expand Down Expand Up @@ -183,8 +202,11 @@ const parseMessageDeletedAction = (action: Ytc.MessageDeletedAction): Ytc.Parsed
};
};

const parsePinnedMessageAction = (action: Ytc.AddPinnedAction): Ytc.ParsedPinned | undefined => {
const parseBannerAction = (action: Ytc.AddPinnedAction): Ytc.ParsedPinned | Ytc.ParsedSummary | undefined => {
const baseRenderer = action.bannerRenderer.liveChatBannerRenderer;
if (baseRenderer.contents.liveChatBannerChatSummaryRenderer) {
return parseChatSummary(baseRenderer.contents, action.bannerProperties?.isEphemeral, action.bannerProperties?.bannerTimeoutMs);
}
const parsedContents = parseAddChatItemAction(
{ item: baseRenderer.contents }, true
);
Expand Down Expand Up @@ -229,7 +251,7 @@ const processCommonAction = (
if (action.addChatItemAction) {
return parseAddChatItemAction(action.addChatItemAction, isReplay, liveTimeoutOrReplayMs);
} else if (action.addBannerToLiveChatCommand) {
return parsePinnedMessageAction(action.addBannerToLiveChatCommand);
return parseBannerAction(action.addBannerToLiveChatCommand);
} else if (action.removeBannerForLiveChatCommand) {
return { type: 'unpin' } as const;
} else if (action.addLiveChatTickerItemAction) {
Expand Down
29 changes: 28 additions & 1 deletion src/ts/typings/ytc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ declare namespace Ytc {
};
};
};
bannerProperties?: {
isEphemeral: boolean;
bannerTimeoutMs: number;
}
}

interface AddTickerAction {
Expand Down Expand Up @@ -227,6 +231,15 @@ declare namespace Ytc {
};
}

interface ChatSummaryRenderer {
liveChatSummaryId: string;
chatSummary: RunsObj;
icon?: {
/** Unlocalized string */
iconType: string;
};
}

interface PlaceholderRenderer { // No idea what the purpose of this is
id: string;
timestampUsec: IntString;
Expand All @@ -248,6 +261,8 @@ declare namespace Ytc {
liveChatSponsorshipsGiftPurchaseAnnouncementRenderer?: MembershipGiftPurchaseRenderer;
/** Membership gift redemption */
liveChatSponsorshipsGiftRedemptionAnnouncementRenderer?: TextMessageRenderer;
/** AI Chat Summary */
liveChatBannerChatSummaryRenderer?: ChatSummaryRenderer;
/** ??? */
liveChatPlaceholderItemRenderer?: PlaceholderRenderer;
}
Expand Down Expand Up @@ -365,13 +380,25 @@ declare namespace Ytc {
};
}

interface ParsedSummary {
type: 'summary';
header: ParsedRun[];
summary: ParsedRun[];
id: string;
icon?: {
/** Unlocalized string */
iconType: string;
};
showtime: number;
}

interface ParsedTicker extends ParsedMessage {
type: 'ticker';
tickerDuration: number;
detailText?: string;
}

type ParsedMisc = ParsedPinned | { type: 'unpin'};
type ParsedMisc = ParsedPinned | ParsedSummary | { type: 'unpin' };

type ParsedTimedItem = ParsedMessage | ParsedTicker;

Expand Down

0 comments on commit fc7dc6e

Please sign in to comment.