Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion DSL/Resql/users/POST/get-chat-by-id.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ csa_title_config AS (
AND id IN (SELECT max(id) FROM configuration WHERE key = 'is_csa_title_visible' GROUP BY key)
AND NOT deleted
),
chat_history_comments AS (
SELECT
comment,
chat_id,
created,
author_display_name
FROM chat_history_comments
where id in (SELECT MAX(id) AS maxId FROM chat_history_comments GROUP BY chat_id)
),
chatById AS(
SELECT
base_id,
Expand Down Expand Up @@ -72,8 +81,12 @@ SELECT c.base_id AS id,
ELSE ''
END) AS csa_title,
m.content AS last_message,
m.updated AS last_message_timestamp
m.updated AS last_message_timestamp,
s.comment,
s.created as comment_added_date,
s.author_display_name as comment_author
FROM chatById AS c
JOIN message AS m ON c.base_id = m.chat_base_id
LEFT JOIN chat_history_comments AS s ON s.chat_id = m.chat_base_id
ORDER BY m.updated DESC
LIMIT 1;
8 changes: 4 additions & 4 deletions GUI/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion GUI/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@buerokratt-ria/header": "^0.1.47",
"@buerokratt-ria/menu": "^0.2.10",
"@buerokratt-ria/styles": "^0.0.1",
"@buerokratt-ria/common-gui-components": "^0.0.42",
"@buerokratt-ria/common-gui-components": "^0.0.43",
"@fontsource/roboto": "^4.5.8",
"@formkit/auto-animate": "^1.0.0-beta.5",
"@radix-ui/react-accessible-icon": "^1.0.1",
Expand Down
60 changes: 49 additions & 11 deletions GUI/src/components/HistoricalChat/Markdownify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,57 @@ interface MarkdownifyProps {
message: string | undefined;
}

const LinkPreview: React.FC<{ href: string; children: React.ReactNode }> = ({ href, children }) => {
const isValidImageUrl = (s: string): boolean => {
try {
const u = new URL(s);
if (!/^(https?|data):$/i.test(u.protocol)) return false;
if (s.startsWith('data:image/')) {
return /^data:image\/(png|jpe?g|gif|webp|svg\+xml|bmp);(base64,|charset=utf-8;)/i.test(s);
}
const path = u.pathname.toLowerCase();
const search = u.search.toLowerCase();
if (/\.(png|jpe?g|gif|webp|svg|ico|bmp|tiff?|avif|heic|heif|apng)([?#]|$)/i.test(path)) {
return true;
}
if (/\/(images?|img|photos?|pictures?|media|uploads?|thumb|avatar)\//i.test(path)) {
return true;
}
return /[?&](format|type|image|img|photo)=(png|jpe?g|gif|webp|svg|ico)/i.test(search);
} catch {
return false;
}
};

const LinkPreview: React.FC<{
href: string;
children: React.ReactNode;
}> = ({ href, children }) => {
const [hasError, setHasError] = useState(false);
const basicAuthPattern = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\/[^@]+@/;

if (basicAuthPattern.test(href)) {
return null;
}

return !hasError ? (

if (!isValidImageUrl(href)) {
return (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
);
}

return hasError ? (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
) : (
<img
src={href}
alt={typeof children === 'string' ? children : 'Preview'}
style={{ maxWidth: '100%', height: 'auto', borderRadius: '20px' }}
onError={() => setHasError(true)}
/>
) : (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
);
};

Expand All @@ -41,16 +73,22 @@ function formatMessage(message?: string): string {
.replaceAll(/\\?\$v\w*/g, '')
.replaceAll(/\\?\$g\w*/g, '');

return filteredMessage
.replaceAll(/&#x([0-9A-Fa-f]+);/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))
const dataImagePattern = /((?:^|\s))(data:image\/[a-z0-9+]+;[^)\s]+)/gi;
const finalMessage = filteredMessage.replaceAll(
dataImagePattern,
(_, prefix, dataUrl) => `${prefix}[image](${dataUrl})`
);

return finalMessage
.replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))
.replaceAll('&amp;', '&')
.replaceAll('&gt;', '>')
.replaceAll('&lt;', '<')
.replaceAll('&quot;', '"')
.replaceAll('&#39;', "'")
.replaceAll('&apos;', "'")
.replaceAll(/(^|\n)(\d{4})\.\s/g, (match, prefix, year) => {
const remainingText = filteredMessage.substring(filteredMessage.indexOf(match) + match.length);
const remainingText = finalMessage.substring(finalMessage.indexOf(match) + match.length);
const sentenceEnd = remainingText.indexOf('\n\n');
if (sentenceEnd !== -1) {
const currentSentence = remainingText.substring(0, sentenceEnd);
Expand All @@ -60,7 +98,7 @@ function formatMessage(message?: string): string {
}
return `${prefix}${year}\\. `;
})
.replaceAll(/(?<=\n)\d+\.\s/g, hasSpecialFormat(filteredMessage) ? '\n\n$&' : '$&')
.replaceAll(/(?<=\n)\d+\.\s/g, hasSpecialFormat(finalMessage) ? '\n\n$&' : '$&')
.replaceAll(/^(\s+)/g, (match) => match.replaceAll(' ', '&nbsp;'));
}

Expand Down
Loading