More work on CC display

This commit is contained in:
N-Pex 2025-09-09 11:34:48 +02:00
parent 27b27876c0
commit a27864e3d2
7 changed files with 45 additions and 20 deletions

View file

@ -500,8 +500,6 @@
"metadata_info_compressed": "Compressing the image automatically excludes its metadata.", "metadata_info_compressed": "Compressing the image automatically excludes its metadata.",
"metadata_info_original": "Sharing the original automatically includes its metadata.", "metadata_info_original": "Sharing the original automatically includes its metadata.",
"exif_data": "Exif Data", "exif_data": "Exif Data",
"content_credentials": "Content Credentials",
"content_credentials_info": "Source or history information is available for this media to be verified.",
"learn_more": "Learn more", "learn_more": "Learn more",
"ai_used": "Photo modified with AI", "ai_used": "Photo modified with AI",
"screenshot": "Screenshot. ", "screenshot": "Screenshot. ",
@ -517,6 +515,8 @@
"cc_location": "Location" "cc_location": "Location"
}, },
"cc": { "cc": {
"content_credentials": "Content Credentials",
"content_credentials_info": "Source or history information is available for this media to be verified.",
"metadata-stripped": "Image has been compressed and stripped of metadata.\n\nWe think this image has additional file information. If you want to learn more, ask the sender to share the original." "metadata-stripped": "Image has been compressed and stripped of metadata.\n\nWe think this image has additional file information. If you want to learn more, ask the sender to share the original."
} }
} }

View file

@ -1,11 +1,11 @@
<template> <template>
<div v-if="props.flags"> <div v-if="props.flags">
<div class="detail-title"> <div class="detail-title">
{{ t("file_mode.content_credentials") }} {{ t("cc.content_credentials") }}
<v-icon>$vuetify.icons.ic_cr</v-icon> <v-icon>$vuetify.icons.ic_cr</v-icon>
</div> </div>
<div class="detail-subtitle"> <div class="detail-subtitle" v-if="hasC2PA">
{{ t("file_mode.content_credentials_info") }} {{ t("cc.content_credentials_info") }}
</div> </div>
<div class="cc-detail-info" v-if="infoText !== undefined"> <div class="cc-detail-info" v-if="infoText !== undefined">
@ -29,8 +29,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { ProofHintFlags } from "../../models/proof"; import { Proof, ProofHintFlags } from "../../models/proof";
import { ref, Ref, watch } from "vue"; import { computed, ref, Ref, watch } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import CCProperty from "./CCProperty.vue"; import CCProperty from "./CCProperty.vue";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
@ -40,6 +40,7 @@ dayjs.extend(relativeTime);
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
proof?: Proof;
flags?: ProofHintFlags; flags?: ProofHintFlags;
}>(); }>();
@ -47,6 +48,10 @@ const infoText: Ref<string | undefined> = ref(undefined);
const creationDate: Ref<string | undefined> = ref(undefined); const creationDate: Ref<string | undefined> = ref(undefined);
const valid: Ref<boolean> = ref(false); const valid: Ref<boolean> = ref(false);
const hasC2PA = computed(() => {
return props.proof?.integrity?.c2pa !== undefined;
});
watch( watch(
props, props,
() => { () => {

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="cc-summary" v-if="props.flags.length > 0 && infoText.length > 0"> <div class="cc-summary" v-if="flags.length > 0 && infoText.length > 0">
<v-icon class="intervention-icon">{{ <v-icon class="intervention-icon">{{
showCheck ? "$vuetify.icons.ic_intervention_check" : "$vuetify.icons.ic_intervention" showCheck ? "$vuetify.icons.ic_intervention_check" : "$vuetify.icons.ic_intervention"
}}</v-icon }}</v-icon
@ -11,18 +11,30 @@
import { computed } from "vue"; import { computed } from "vue";
import { ProofHintFlags } from "../../models/proof"; import { ProofHintFlags } from "../../models/proof";
const props = defineProps<{ const { multiple, flags } = defineProps<{
multiple: boolean;
flags: ProofHintFlags[]; flags: ProofHintFlags[];
}>(); }>();
const showCheck = computed(() => { const showCheck = computed(() => {
return props.flags.some((f) => f?.valid); if (!multiple && flags.length == 1) {
return flags[0].generatorSource === "c2pa";
} else if (multiple) {
return flags.some((f) => f.generatorSource === "c2pa")
}
return false;
}); });
const infoText = computed(() => { const infoText = computed(() => {
if (props.flags.some((f) => f.generator === "ai")) { if (!multiple && flags.length == 1) {
if (flags[0].generator === "ai") {
return "<b>This image is generated by AI.</b> Take a closer look at the file details.";
} else if (flags[0].generator === "screenshot") {
return "<b>This is a screenshot.</b> Take a closer look at the file details.";
}
} else if (flags.some((f) => f.generator === "ai")) {
return "<b>Contains AI generated media.</b> Take a closer look at the file details for each."; return "<b>Contains AI generated media.</b> Take a closer look at the file details for each.";
} else if (props.flags.some((f) => f.generator === "screenshot")) { } else if (flags.some((f) => f.generator === "screenshot")) {
return "<b>Contains screenshots.</b> Take a closer look at the file details for each."; return "<b>Contains screenshots.</b> Take a closer look at the file details for each.";
} }
return "TODO - Content Credentials Info"; return "TODO - Content Credentials Info";

View file

@ -28,7 +28,7 @@
</div> </div>
</div> </div>
<C2PAInfo class="attachment-info__detail-box" v-if="hasC2PA" :flags="attachment.proofHintFlags" /> <C2PAInfo class="attachment-info__detail-box" v-if="showC2PAInfo" :proof="attachment.proof" :flags="attachment.proofHintFlags" />
<EXIFInfo class="attachment-info__detail-box" v-if="hasExif" :exif="attachment.proof?.integrity?.exif" /> <EXIFInfo class="attachment-info__detail-box" v-if="hasExif" :exif="attachment.proof?.integrity?.exif" />
</div> </div>
</template> </template>
@ -49,8 +49,8 @@ const { attachment } = defineProps<{
//console.error("ATTACHMENT", attachment.proof); //console.error("ATTACHMENT", attachment.proof);
const hasC2PA = computed(() => { const showC2PAInfo = computed(() => {
return attachment.proof?.integrity?.c2pa !== undefined; return attachment.proof?.integrity?.c2pa !== undefined || attachment.proofHintFlags !== undefined;
}); });
const hasExif = computed(() => { const hasExif = computed(() => {

View file

@ -5,9 +5,9 @@
<v-progress-circular indeterminate class="mb-0"></v-progress-circular> <v-progress-circular indeterminate class="mb-0"></v-progress-circular>
</div> </div>
</div> </div>
<div v-else-if="metaStripped" class="cc-detail-info white-space-pre">{{ t("cc.metadata-stripped") }}</div>
<div v-else> <div v-else>
<C2PAInfo class="attachment-info__detail-box" v-if="hasC2PA" :flags="attachment?.proofHintFlags" /> <div v-if="metaStripped" class="cc-detail-info white-space-pre">{{ t("cc.metadata-stripped") }}</div>
<C2PAInfo class="attachment-info__detail-box" :proof="attachment?.proof" :flags="attachment?.proofHintFlags" />
<EXIFInfo class="attachment-info__detail-box" v-if="hasExif" :exif="attachment?.proof?.integrity?.exif" /> <EXIFInfo class="attachment-info__detail-box" v-if="hasExif" :exif="attachment?.proof?.integrity?.exif" />
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
<template> <template>
<component :is="rootComponent" ref="root" v-bind="{ ...$props, ...$attrs }" v-if="showMultiview"> <component :is="rootComponent" ref="root" v-bind="{ ...$props, ...$attrs }" v-if="showMultiview">
<div class="bubble"> <div class="bubble">
<div class="bubble-inset" v-if="showCCSummary"><CCSummary :flags="proofHintFlags" /></div> <div class="bubble-inset" v-if="showCCSummary"><CCSummary :multiple="items.length > 1" :flags="proofHintFlags" /></div>
<div class="original-message bubble-inset" v-if="inReplyToText"> <div class="original-message bubble-inset" v-if="inReplyToText">
<div class="original-message-sender">{{ inReplyToSender }}</div> <div class="original-message-sender">{{ inReplyToSender }}</div>
<div class="original-message-text" v-html="linkify($$sanitize(inReplyToText))" /> <div class="original-message-text" v-html="linkify($$sanitize(inReplyToText))" />
@ -158,7 +158,7 @@ const showMultiview = computed((): boolean => {
}); });
const showCCSummary = computed(() => { const showCCSummary = computed(() => {
return items.value?.some((i) => i.proofHintFlags !== undefined && i.proofHintFlags.valid); return items.value?.some((i) => i.proofHintFlags !== undefined);
}); });
const proofHintFlags = computed(() => { const proofHintFlags = computed(() => {

View file

@ -138,7 +138,7 @@ const ruleAiGenerated = (): FlagMatchRule[] => {
]; ];
}; };
const aiHintFlags = (): FlagMatchRule[] => { const ruleAiMeta = (): FlagMatchRule[] => {
const knownAIServices = [ const knownAIServices = [
"ChatGPT", "ChatGPT",
"OpenAI-API", "OpenAI-API",
@ -292,9 +292,17 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined
if (matchFlag(ruleScreenshotMeta(), proof).result) { if (matchFlag(ruleScreenshotMeta(), proof).result) {
generator = "screenshot"; generator = "screenshot";
generatorSource = "metadata"; generatorSource = "metadata";
} else if (matchFlag(ruleAiMeta(), proof).result) {
generator = "ai";
generatorSource = "metadata";
} }
} }
// Do we have any data? Else, return "undefined", we don't just want to send an object with all defaults.
if (source.length === 0 && dateCreated.length === 0 && generator === "unknown") {
return undefined;
}
const flags: ProofHintFlags = { const flags: ProofHintFlags = {
valid: valid, valid: valid,
device: source && source.length == 1 ? source[0].value : undefined, device: source && source.length == 1 ? source[0].value : undefined,